79 lines
2.6 KiB
TypeScript
79 lines
2.6 KiB
TypeScript
import type { Metadata, Viewport } from "next";
|
|
import { NuqsAdapter } from "nuqs/adapters/next/app";
|
|
import { ThemeProvider, ThemedToaster } from "@/providers/ThemeProvider";
|
|
import { AuthRedirectInstaller } from "./components/AuthRedirectInstaller";
|
|
import { THEME_STORAGE_KEY } from "@/lib/theme";
|
|
import "katex/dist/katex.min.css";
|
|
import "@mescius/spread-sheets/styles/gc.spread.sheets.excel2013white.css";
|
|
import localFont from "next/font/local";
|
|
import "./globals.css";
|
|
|
|
export const dynamic = "force-dynamic";
|
|
|
|
export async function generateMetadata(): Promise<Metadata> {
|
|
const { getSystemConfig, brandingVersion } = await import(
|
|
"@/lib/server/systemConfig"
|
|
);
|
|
const config = getSystemConfig();
|
|
const faviconVersion = brandingVersion("favicon");
|
|
return {
|
|
title: `${config.branding.wordmark} WebUI`,
|
|
description:
|
|
"Web UI for EvoScientist — a self-evolving AI scientist built on DeepAgents/LangGraph.",
|
|
icons:
|
|
faviconVersion > 0
|
|
? { icon: `/api/system/branding/asset/favicon?v=${faviconVersion}` }
|
|
: undefined,
|
|
};
|
|
}
|
|
|
|
export const viewport: Viewport = {
|
|
themeColor: [
|
|
{ media: "(prefers-color-scheme: light)", color: "#fafafa" },
|
|
{ media: "(prefers-color-scheme: dark)", color: "#09090b" },
|
|
],
|
|
colorScheme: "light dark",
|
|
};
|
|
|
|
// Self-hosted Inter variable font; exposes --font-inter, consumed by globals.css.
|
|
// No CJK glyphs — Chinese text falls through to PingFang/YaHei in the stack.
|
|
const inter = localFont({
|
|
src: "./fonts/InterVariable.woff2",
|
|
variable: "--font-inter",
|
|
display: "swap",
|
|
});
|
|
|
|
// Runs before paint so the right theme class is on <html> immediately — no flash
|
|
// of the wrong theme. Mirrors ThemeProvider's resolution (default: follow the
|
|
// system); ThemeProvider takes over once React mounts. Kept inline + minimal.
|
|
const themeScript = `(function(){var k=${JSON.stringify(
|
|
THEME_STORAGE_KEY
|
|
)};var t="system";try{t=localStorage.getItem(k)||"system";}catch(_){}var d=t==="dark"||(t!=="light"&&window.matchMedia("(prefers-color-scheme: dark)").matches);var e=document.documentElement;e.classList.toggle("dark",d);e.style.colorScheme=d?"dark":"light";})();`;
|
|
|
|
export default function RootLayout({
|
|
children,
|
|
}: {
|
|
children: React.ReactNode;
|
|
}) {
|
|
return (
|
|
<html
|
|
lang="en"
|
|
suppressHydrationWarning
|
|
>
|
|
<body
|
|
suppressHydrationWarning
|
|
className={inter.variable}
|
|
>
|
|
<script dangerouslySetInnerHTML={{ __html: themeScript }} />
|
|
<NuqsAdapter>
|
|
<ThemeProvider>
|
|
<AuthRedirectInstaller />
|
|
{children}
|
|
<ThemedToaster />
|
|
</ThemeProvider>
|
|
</NuqsAdapter>
|
|
</body>
|
|
</html>
|
|
);
|
|
}
|