Files
verse-quote-app/app/root.tsx
T

175 lines
5.0 KiB
TypeScript

import { configureTracking } from "@agent-native/core/client/analytics";
import { appPath } from "@agent-native/core/client/api-path";
import { useDbSync } from "@agent-native/core/client/hooks";
import {
AppProviders,
createAgentNativeQueryClient,
} from "@agent-native/core/client/hooks";
import { getLocaleInitScript, useT } from "@agent-native/core/client/i18n";
import {
CommandMenu,
useCommandMenuShortcut,
} from "@agent-native/core/client/navigation";
import { getThemeInitScript } from "@agent-native/core/client/ui";
import { IconHierarchy2, IconSun, IconMoon } from "@tabler/icons-react";
import { useQueryClient } from "@tanstack/react-query";
import { useTheme } from "next-themes";
import { useCallback, useState } from "react";
import {
Links,
Meta,
Outlet,
Scripts,
ScrollRestoration,
useNavigate,
} from "react-router";
import type { LinksFunction } from "react-router";
import { Layout as AppLayout } from "@/components/layout/Layout";
import { AppToolkitProvider } from "@/components/ui/toolkit-provider";
import { useNavigationState } from "@/hooks/use-navigation-state";
import { APP_TITLE } from "@/lib/app-config";
import { TAB_ID } from "@/lib/tab-id";
import changelog from "../CHANGELOG.md?raw";
import { i18nCatalog } from "./i18n";
import stylesheet from "./global.css?url";
configureTracking({
getDefaultProps: (_name, properties) => ({
...properties,
app: "app",
template: "chat",
}),
});
export const links: LinksFunction = () => [
{ rel: "stylesheet", href: stylesheet },
];
const THEME_INIT_SCRIPT = getThemeInitScript();
const LOCALE_INIT_SCRIPT = getLocaleInitScript();
export function Layout({ children }: { children: React.ReactNode }) {
return (
<html lang="en" suppressHydrationWarning>
<head>
<meta charSet="utf-8" />
<meta
name="viewport"
content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no"
/>
<script
suppressHydrationWarning
dangerouslySetInnerHTML={{ __html: THEME_INIT_SCRIPT }}
/>
<script
data-agent-native-locale-init
suppressHydrationWarning
dangerouslySetInnerHTML={{ __html: LOCALE_INIT_SCRIPT }}
/>
<link rel="manifest" href={appPath("/manifest.json")} />
<meta name="theme-color" content="#18181B" />
<meta name="mobile-web-app-capable" content="yes" />
<meta
name="apple-mobile-web-app-status-bar-style"
content="black-translucent"
/>
<meta name="apple-mobile-web-app-title" content={APP_TITLE} />
<link rel="icon" type="image/svg+xml" href={appPath("/favicon.svg")} />
<link rel="apple-touch-icon" href={appPath("/icon-180.svg")} />
<Meta />
<Links />
</head>
<body>
{children}
<ScrollRestoration />
<Scripts />
</body>
</html>
);
}
function DbSyncSetup() {
const qc = useQueryClient();
useNavigationState();
useDbSync({
queryClient: qc,
ignoreSource: TAB_ID,
});
return null;
}
function ThemeToggleItem() {
const { resolvedTheme, setTheme } = useTheme();
const t = useT();
const isDark = resolvedTheme === "dark";
return (
<CommandMenu.Item
onSelect={() => setTheme(isDark ? "light" : "dark")}
keywords={["theme", "dark", "light", "mode"]}
>
{isDark ? <IconSun size={16} /> : <IconMoon size={16} />}
{t("root.toggleTheme")}
</CommandMenu.Item>
);
}
function AppContent() {
const [cmdkOpen, setCmdkOpen] = useState(false);
const navigate = useNavigate();
const t = useT();
useCommandMenuShortcut(useCallback(() => setCmdkOpen(true), []));
return (
<>
<CommandMenu
open={cmdkOpen}
onOpenChange={setCmdkOpen}
changelog={changelog}
changelogKey="chat"
>
<CommandMenu.Group heading={t("root.commandActions")}>
<CommandMenu.Item onSelect={() => {}}>
{t("root.commandSearch")}
</CommandMenu.Item>
<CommandMenu.Item
onSelect={() => navigate("/agent")}
keywords={[
"agent",
"context",
"files",
"connections",
"jobs",
"access",
]}
>
<IconHierarchy2 size={16} />
{t("settings.openAgentSettings")}
</CommandMenu.Item>
</CommandMenu.Group>
<CommandMenu.Group heading={t("root.commandAppearance")}>
<ThemeToggleItem />
</CommandMenu.Group>
</CommandMenu>
<AppLayout>
<Outlet />
</AppLayout>
</>
);
}
export default function Root() {
const [queryClient] = useState(() => createAgentNativeQueryClient());
return (
<AppToolkitProvider>
<AppProviders queryClient={queryClient} i18n={{ catalog: i18nCatalog }}>
<DbSyncSetup />
<AppContent />
</AppProviders>
</AppToolkitProvider>
);
}
export { ErrorBoundary } from "@agent-native/core/client/ui";