import { configureTracking } from "@agent-native/core/client/analytics";
import { appPath } from "@agent-native/core/client/api-path";
import {
AppProviders,
createAgentNativeQueryClient,
useDbSync,
} 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 { Layout as AppLayout } from "@agent-native/dispatch/components";
import { IconHierarchy2, IconSun, IconMoon } from "@tabler/icons-react";
import { useQueryClient } from "@tanstack/react-query";
import { useTheme } from "next-themes";
import { useCallback, useEffect, useRef, useState } from "react";
import {
Links,
Meta,
Outlet,
Scripts,
ScrollRestoration,
useNavigate,
} from "react-router";
import type { LinksFunction } from "react-router";
import { Toaster } from "sonner";
import { AppToolkitProvider } from "@/components/ui/toolkit-provider";
import { useNavigationState } from "@/hooks/use-navigation-state";
import changelog from "../CHANGELOG.md?raw";
import { dispatchExtensions } from "./dispatch-extensions";
import { i18nCatalog } from "./i18n";
import stylesheet from "./global.css?url";
configureTracking({
getDefaultProps: (_name, properties) => ({
...properties,
app: "agent-native-dispatch",
}),
});
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 (
{children}
);
}
const TAB_ID = Math.random().toString(36).slice(2, 10);
function DbSyncSetup() {
const qc = useQueryClient();
useNavigationState(dispatchExtensions);
useDbSync({
queryClient: qc,
queryKeys: [
"list-dispatch-overview",
"list-destinations",
"list-linked-identities",
"list-dispatch-approvals",
"list-dispatch-audit",
"list-dispatch-usage-metrics",
"list-agent-thread-sources",
"search-agent-threads",
"get-agent-thread-debug",
"list-mcp-app-access",
"get-dispatch-settings",
"list-connected-agents",
"list-vault-secrets",
"list-vault-grants",
"list-vault-requests",
"list-vault-audit",
"list-workspace-resources",
"list-workspace-resource-grants",
"list-workspace-apps",
"list-integrations-catalog",
"list-workspace-connections",
...(dispatchExtensions.queryKeys ?? []),
],
ignoreSource: TAB_ID,
});
useThreadDeepLink();
return null;
}
/**
* Reads ?thread= from the URL on mount and opens that thread in the
* full-page chat route.
*/
function useThreadDeepLink() {
const navigate = useNavigate();
const handled = useRef(false);
useEffect(() => {
if (handled.current) return;
const params = new URLSearchParams(window.location.search);
const threadId = params.get("thread");
if (!threadId) return;
handled.current = true;
params.delete("thread");
navigate(
{
pathname: "/chat",
search: params.toString() ? `?${params.toString()}` : "",
hash: window.location.hash,
},
{
replace: true,
state: {
dispatchThread: {
id: `${Date.now()}-${threadId}`,
threadId,
},
},
},
);
}, [navigate]);
}
function ThemeToggleItem() {
const { resolvedTheme, setTheme } = useTheme();
const t = useT();
const isDark = resolvedTheme === "dark";
return (
setTheme(isDark ? "light" : "dark")}
keywords={["theme", "dark", "light", "mode"]}
>
{isDark ? : }
{t("root.toggleTheme")}
);
}
function AppContent() {
const [cmdkOpen, setCmdkOpen] = useState(false);
const t = useT();
const navigate = useNavigate();
useCommandMenuShortcut(useCallback(() => setCmdkOpen(true), []));
return (
<>
navigate("/agent")}>
{t("root.openAgent")}
{}}>
{t("root.commandSearch")}
>
);
}
export default function Root() {
const [queryClient] = useState(() => createAgentNativeQueryClient());
return (
}
i18n={{ catalog: i18nCatalog }}
>
);
}
export { ErrorBoundary } from "@agent-native/core/client/ui";