| import { Inter } from "next/font/google";
|
| import "./globals.css";
|
| import { ThemeProvider } from "@/shared/components/ThemeProvider";
|
| import { NextIntlClientProvider } from "next-intl";
|
| import { getMessages, getLocale } from "next-intl/server";
|
| import { RTL_LOCALES } from "@/i18n/config";
|
| import { normalizeComplianceEventTypes } from "@/i18n/request";
|
| import { getSettings } from "@/lib/db/settings";
|
| import type { Viewport } from "next";
|
| import { PwaRegister } from "@/shared/components/PwaRegister";
|
|
|
| const inter = Inter({
|
| subsets: ["latin"],
|
| variable: "--font-inter",
|
| });
|
|
|
| export const viewport: Viewport = {
|
| themeColor: "#0b0f1a",
|
| viewportFit: "cover",
|
| };
|
|
|
| export async function generateMetadata() {
|
| const settings = await getSettings();
|
| const instanceName = settings?.instanceName || "OmniRoute";
|
| const customFaviconUrl = settings?.customFaviconUrl || settings?.customFaviconBase64;
|
|
|
| return {
|
| title: `${instanceName} — AI Gateway for Multi-Provider LLMs`,
|
| description:
|
| "OmniRoute is an AI gateway for multi-provider LLMs. One endpoint for all your AI providers.",
|
| manifest: "/manifest.webmanifest",
|
| applicationName: instanceName,
|
| appleWebApp: {
|
| capable: true,
|
| title: instanceName,
|
| statusBarStyle: "black-translucent",
|
| },
|
| other: {
|
| "mobile-web-app-capable": "yes",
|
| },
|
| icons: {
|
| icon: customFaviconUrl
|
| ? "/api/settings/favicon"
|
| : [
|
| { url: "/favicon.ico", sizes: "any" },
|
| { url: "/favicon.svg", type: "image/svg+xml" },
|
| { url: "/icon-512.png", type: "image/png", sizes: "512x512" },
|
| ],
|
| apple: [{ url: "/apple-touch-icon.png", sizes: "180x180", type: "image/png" }],
|
| },
|
| };
|
| }
|
|
|
| export default async function RootLayout({ children }) {
|
| const locale = await getLocale();
|
| const messages = normalizeComplianceEventTypes((await getMessages()) as Record<string, unknown>);
|
| const isRtl = RTL_LOCALES.includes(locale as (typeof RTL_LOCALES)[number]);
|
|
|
| return (
|
| <html lang={locale} dir={isRtl ? "rtl" : "ltr"} suppressHydrationWarning>
|
| <head>
|
| <link rel="preconnect" href="https://fonts.googleapis.com" />
|
| <link rel="preconnect" href="https://fonts.gstatic.com" crossOrigin="anonymous" />
|
| {/* eslint-disable-next-line @next/next/no-page-custom-font */}
|
| <link
|
| href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:opsz,wght,FILL,GRAD@20..48,100..700,0..1,-50..200&display=swap"
|
| rel="stylesheet"
|
| />
|
| <script
|
| dangerouslySetInnerHTML={{
|
| __html: `
|
| try {
|
| const stored = localStorage.getItem('theme');
|
| const parsed = stored ? JSON.parse(stored) : null;
|
| const theme = parsed?.state?.theme || 'system';
|
| if (theme === 'dark' || (theme === 'system' && window.matchMedia('(prefers-color-scheme: dark)').matches)) {
|
| document.documentElement.classList.add('dark');
|
| } else {
|
| document.documentElement.classList.remove('dark');
|
| }
|
| } catch (e) {}
|
| `,
|
| }}
|
| />
|
| </head>
|
| <body className={`${inter.variable} font-sans antialiased`} suppressHydrationWarning>
|
| <a
|
| href="#main-content"
|
| className="sr-only focus:not-sr-only focus:absolute focus:top-2 focus:left-2 focus:z-50 focus:px-4 focus:py-2 focus:bg-[#6366f1] focus:text-white focus:rounded-lg focus:text-sm focus:font-semibold focus:shadow-lg"
|
| >
|
| Skip to content
|
| </a>
|
| <NextIntlClientProvider locale={locale} messages={messages}>
|
| <PwaRegister />
|
| <ThemeProvider>{children}</ThemeProvider>
|
| </NextIntlClientProvider>
|
| </body>
|
| </html>
|
| );
|
| }
|
|
|