Initial commit from agent-native create

This commit is contained in:
agent-native
2026-07-25 17:10:38 +00:00
commit ffea2aeff7
120 changed files with 6804 additions and 0 deletions
+60
View File
@@ -0,0 +1,60 @@
import { AgentToggleButton } from "@agent-native/core/client/agent-chat";
import { useT } from "@agent-native/core/client/i18n";
import {
useHeaderTitle,
useHeaderActions,
} from "@agent-native/toolkit/app-shell";
import { IconMenu2 } from "@tabler/icons-react";
import { useLocation } from "react-router";
import { APP_TITLE } from "@/lib/app-config";
const pageTitleKeys: Record<string, string> = {
"/": "navigation.chat",
"/observability": "navigation.observability",
"/agent": "settings.agentTitle",
"/settings": "navigation.settings",
};
function resolveTitle(pathname: string, t: (key: string) => string): string {
if (pageTitleKeys[pathname]) return t(pageTitleKeys[pathname]);
if (pathname.startsWith("/extensions")) return t("navigation.extensions");
return APP_TITLE;
}
interface HeaderProps {
onOpenMobileSidebar?: () => void;
}
export function Header({ onOpenMobileSidebar }: HeaderProps) {
const location = useLocation();
const t = useT();
const title = useHeaderTitle();
const actions = useHeaderActions();
return (
<header className="flex h-12 items-center gap-3 border-b border-border bg-background px-4 lg:px-6 shrink-0">
{onOpenMobileSidebar && (
<button
type="button"
onClick={onOpenMobileSidebar}
aria-label={t("navigation.openNavigation")}
className="flex h-9 w-9 cursor-pointer items-center justify-center rounded-md text-muted-foreground hover:text-foreground hover:bg-accent md:hidden"
>
<IconMenu2 className="h-4 w-4" />
</button>
)}
<div className="flex items-center gap-3 flex-1 min-w-0">
{title ?? (
<h1 className="text-lg font-semibold tracking-tight truncate">
{resolveTitle(location.pathname, t)}
</h1>
)}
</div>
<div className="flex items-center gap-2 shrink-0">
{actions}
<AgentToggleButton />
</div>
</header>
);
}
+188
View File
@@ -0,0 +1,188 @@
import {
AgentSidebar,
focusAgentChat,
isAgentChatHomeHandoffActive,
navigateWithAgentChatViewTransition,
useAgentChatHomeHandoff,
useAgentChatHomeHandoffLinks,
} from "@agent-native/core/client/agent-chat";
import { useT } from "@agent-native/core/client/i18n";
import { HeaderActionsProvider } from "@agent-native/toolkit/app-shell";
import { IconMenu2 } from "@tabler/icons-react";
import { useState, useEffect } from "react";
import { useLocation, useNavigate } from "react-router";
import { Button } from "@/components/ui/button";
import {
Sheet,
SheetContent,
SheetDescription,
SheetTitle,
} from "@/components/ui/sheet";
import { APP_TITLE } from "@/lib/app-config";
import { TAB_ID } from "@/lib/tab-id";
import { Header } from "./Header";
import { Sidebar } from "./Sidebar";
interface LayoutProps {
children: React.ReactNode;
}
const SIDEBAR_COLLAPSE_KEY = "chat.sidebar.collapsed";
/**
* Routes whose page renders its own toolbar. Layout still wraps these with the
* left Sidebar and agent surfaces but skips the global Header so they don't
* double-stack chrome.
*/
function routeOwnsToolbar(pathname: string): boolean {
return (
pathname === "/" ||
pathname.startsWith("/chat/") ||
pathname === "/database" ||
pathname.startsWith("/extensions")
);
}
export function Layout({ children }: LayoutProps) {
const location = useLocation();
const navigate = useNavigate();
const t = useT();
const [mobileSidebarOpen, setMobileSidebarOpen] = useState(false);
const [sidebarCollapsed, setSidebarCollapsed] = useState(true);
const isChatRoute =
location.pathname === "/" || location.pathname.startsWith("/chat/");
const chatHomeHandoffActive = useAgentChatHomeHandoff({
storageKey: "chat",
activePath: location.pathname,
enabled: !isChatRoute,
});
const chatHomeHandoffPending = isAgentChatHomeHandoffActive("chat");
useAgentChatHomeHandoffLinks({
storageKey: "chat",
isChatPath: (pathname) => pathname === "/" || pathname.startsWith("/chat/"),
requireActiveHandoff: true,
});
useEffect(() => {
setMobileSidebarOpen(false);
}, [location.pathname]);
useEffect(() => {
const closeMobileSidebar = () => setMobileSidebarOpen(false);
window.addEventListener("agent-chat:open-thread", closeMobileSidebar);
return () => {
window.removeEventListener("agent-chat:open-thread", closeMobileSidebar);
};
}, []);
useEffect(() => {
try {
const stored = window.localStorage.getItem(SIDEBAR_COLLAPSE_KEY);
if (stored !== null) setSidebarCollapsed(stored === "1");
} catch {
// Ignore storage access errors; the default collapsed state still works.
}
}, []);
useEffect(() => {
try {
window.localStorage.setItem(
SIDEBAR_COLLAPSE_KEY,
sidebarCollapsed ? "1" : "0",
);
} catch {
// Ignore storage access errors.
}
}, [sidebarCollapsed]);
const ownsToolbar = routeOwnsToolbar(location.pathname);
function openAskAgentFullscreen() {
focusAgentChat();
navigateWithAgentChatViewTransition(navigate, "/");
}
const contentFrame = (
<div className="flex h-full min-w-0 flex-1 flex-col overflow-hidden">
{isChatRoute ? (
<div className="flex h-12 shrink-0 items-center gap-3 border-b border-border bg-card px-3 md:hidden">
<Button
type="button"
variant="ghost"
size="icon"
onClick={() => setMobileSidebarOpen(true)}
aria-label={t("navigation.openNavigation")}
>
<IconMenu2 className="size-4" />
</Button>
<span className="truncate text-sm font-semibold">{APP_TITLE}</span>
</div>
) : ownsToolbar ? (
<div className="flex h-12 shrink-0 items-center border-b border-border px-4 md:hidden">
<button
type="button"
onClick={() => setMobileSidebarOpen(true)}
aria-label={t("navigation.openNavigation")}
className="flex h-9 w-9 cursor-pointer items-center justify-center rounded-md text-muted-foreground hover:bg-accent hover:text-foreground"
>
<IconMenu2 className="h-4 w-4" />
</button>
</div>
) : (
<Header onOpenMobileSidebar={() => setMobileSidebarOpen(true)} />
)}
<main className="agent-native-app-main min-w-0 flex-1 overflow-y-auto overscroll-contain">
{children}
</main>
</div>
);
return (
<HeaderActionsProvider>
<div className="agent-layout-shell flex h-screen w-full overflow-hidden bg-background text-foreground">
<div className="agent-layout-left-drawer hidden md:block">
<Sidebar
collapsed={sidebarCollapsed}
onCollapsedChange={setSidebarCollapsed}
/>
</div>
<Sheet open={mobileSidebarOpen} onOpenChange={setMobileSidebarOpen}>
<SheetContent side="left" className="p-0 w-[260px]">
<SheetTitle className="sr-only">
{t("navigation.navigation")}
</SheetTitle>
<SheetDescription className="sr-only">
{t("navigation.navigationDescription")}
</SheetDescription>
<Sidebar collapsed={false} collapsible={false} />
</SheetContent>
</Sheet>
{isChatRoute ? (
<div className="agent-layout-main-surface flex min-w-0 flex-1 overflow-hidden">
{contentFrame}
</div>
) : (
<AgentSidebar
position="right"
chatViewTransition
chatViewTransitionHandoff={chatHomeHandoffPending}
storageKey="chat"
browserTabId={TAB_ID}
openOnChatRunning={chatHomeHandoffActive}
onFullscreenRequest={openAskAgentFullscreen}
emptyStateText={t("chat.inspectEmptyState")}
agentPageHref="/agent"
suggestions={[
t("chat.inspectSuggestionCapabilities"),
t("chat.inspectSuggestionHello"),
t("chat.inspectSuggestionAction"),
]}
>
{contentFrame}
</AgentSidebar>
)}
</div>
</HeaderActionsProvider>
);
}
+505
View File
@@ -0,0 +1,505 @@
import {
navigateWithAgentChatViewTransition,
useChatThreads,
type ChatThreadSummary,
} from "@agent-native/core/client/agent-chat";
import { appPath } from "@agent-native/core/client/api-path";
import { LanguagePicker, useT } from "@agent-native/core/client/i18n";
import { openCommandMenu } from "@agent-native/core/client/navigation";
import { OrgSwitcher } from "@agent-native/core/client/org";
import { FeedbackButton } from "@agent-native/core/client/ui";
import { SidebarFooterActions } from "@agent-native/toolkit/app-shell";
import {
ChatHistoryRail,
type ChatHistoryItem,
} from "@agent-native/toolkit/chat-history";
import {
IconLayoutSidebarLeftCollapse,
IconLayoutSidebarLeftExpand,
IconMessageCircle,
IconSearch,
IconSettings,
} from "@tabler/icons-react";
import { useEffect, useMemo } from "react";
import { Link, useLocation, useNavigate } from "react-router";
import { toast } from "sonner";
import {
Tooltip,
TooltipContent,
TooltipTrigger,
} from "@/components/ui/tooltip";
import { APP_TITLE } from "@/lib/app-config";
import { cn } from "@/lib/utils";
const navItems = [
{
icon: IconMessageCircle,
labelKey: "navigation.chat",
href: "/",
view: "chat",
},
];
const bottomNavItems = [
{
icon: IconSettings,
labelKey: "navigation.settings",
href: "/settings",
view: "settings",
},
];
const CHAT_STORAGE_KEY = "chat";
const CHAT_ACTIVE_THREAD_KEY = `agent-chat-active-thread:${CHAT_STORAGE_KEY}`;
interface SidebarProps {
collapsed?: boolean;
collapsible?: boolean;
onCollapsedChange?: (collapsed: boolean) => void;
}
function formatThreadAge(updatedAt: number) {
const diffMs = Math.max(0, Date.now() - updatedAt);
const minutes = Math.floor(diffMs / 60_000);
if (minutes < 1) return "now";
if (minutes < 60) return `${minutes}m`;
const hours = Math.floor(minutes / 60);
if (hours < 24) return `${hours}h`;
const days = Math.floor(hours / 24);
if (days < 7) return `${days}d`;
return new Date(updatedAt).toLocaleDateString([], {
month: "short",
day: "numeric",
});
}
function threadTitle(thread: ChatThreadSummary) {
return thread.title || thread.preview || "Untitled chat";
}
function threadUpdatedAt(thread: ChatThreadSummary) {
return Number.isFinite(thread.updatedAt)
? thread.updatedAt
: Number.isFinite(thread.createdAt)
? thread.createdAt
: 0;
}
function compareThreads(a: ChatThreadSummary, b: ChatThreadSummary) {
const aPinned = a.pinnedAt ?? 0;
const bPinned = b.pinnedAt ?? 0;
if (aPinned || bPinned) return bPinned - aPinned;
return threadUpdatedAt(b) - threadUpdatedAt(a);
}
function persistedActiveThreadId() {
try {
return localStorage.getItem(CHAT_ACTIVE_THREAD_KEY);
} catch {
return null;
}
}
function persistActiveThreadId(threadId: string) {
try {
localStorage.setItem(CHAT_ACTIVE_THREAD_KEY, threadId);
} catch {}
}
function threadIdFromPath(pathname: string) {
const match = pathname.match(/^\/chat\/([^/]+)/);
if (!match) return null;
try {
const value = decodeURIComponent(match[1]).trim();
return value || null;
} catch {
return null;
}
}
function chatThreadPath(threadId: string) {
return `/chat/${encodeURIComponent(threadId)}`;
}
function ChatThreadsSection({ open }: { open: boolean }) {
const navigate = useNavigate();
const location = useLocation();
const t = useT();
const {
threads,
activeThreadId,
createThread,
switchThread,
pinThread,
archiveThread,
renameThread,
refreshThreads,
} = useChatThreads(undefined, CHAT_STORAGE_KEY, undefined, {
autoCreate: false,
restoreActiveThread: false,
});
const visibleThreads = useMemo(
() =>
threads
.filter((thread) => thread.messageCount > 0 && !thread.archivedAt)
.sort(compareThreads)
.slice(0, 15),
[threads],
);
const displayedActiveThreadId =
threadIdFromPath(location.pathname) ??
(location.pathname === "/" ? null : activeThreadId);
const chatItems = useMemo<ChatHistoryItem[]>(
() =>
visibleThreads.map((thread) => ({
id: thread.id,
title: threadTitle(thread),
titleText: threadTitle(thread),
timestamp:
thread.id === displayedActiveThreadId
? undefined
: formatThreadAge(threadUpdatedAt(thread)),
pinned: Boolean(thread.pinnedAt),
})),
[displayedActiveThreadId, visibleThreads],
);
useEffect(() => {
const refresh = () => refreshThreads();
const handleRunning = (event: Event) => {
const detail = (event as CustomEvent).detail as
| { isRunning?: unknown }
| undefined;
if (typeof detail?.isRunning === "boolean") refreshThreads();
};
window.addEventListener("agent-chat:threads-updated", refresh);
window.addEventListener("agentNative.chatRunning", handleRunning);
window.addEventListener("focus", refresh);
return () => {
window.removeEventListener("agent-chat:threads-updated", refresh);
window.removeEventListener("agentNative.chatRunning", handleRunning);
window.removeEventListener("focus", refresh);
};
}, [refreshThreads]);
function openThread(threadId: string, options?: { isNew?: boolean }) {
switchThread(threadId);
persistActiveThreadId(threadId);
navigateWithAgentChatViewTransition(
navigate,
options?.isNew ? "/" : chatThreadPath(threadId),
);
window.requestAnimationFrame(() => {
window.dispatchEvent(
new CustomEvent("agent-chat:open-thread", {
detail: { threadId, newThread: options?.isNew === true },
}),
);
});
}
async function handleNewChat() {
const threadId = await createThread();
if (threadId) openThread(threadId, { isNew: true });
}
async function handleArchiveThread(threadId: string) {
const wasActive =
threadId === activeThreadId || threadId === persistedActiveThreadId();
const archived = await archiveThread(threadId);
if (!archived) {
toast.error(t("chat.archiveFailed"));
return;
}
if (wasActive) {
await handleNewChat();
}
}
function handleRenameThread(threadId: string, title: string) {
void renameThread(threadId, title).then((renamed) => {
if (!renamed) toast.error(t("chat.renameFailed"));
});
}
return (
<div
className="an-chat-history-rail__collapse"
data-state={open ? "open" : "closed"}
aria-hidden={!open}
>
<div className="ms-4">
<ChatHistoryRail
items={chatItems}
activeId={displayedActiveThreadId}
onSelect={(threadId) => openThread(threadId)}
onNewChat={() => void handleNewChat()}
railLabels={{
newChat: t("chat.newChat"),
showMore: t("chat.chats"),
showLess: t("chat.chats"),
}}
renameMaxLength={160}
onTogglePin={(threadId) => {
const thread = visibleThreads.find((item) => item.id === threadId);
if (thread) void pinThread(threadId, !thread.pinnedAt);
}}
onRename={handleRenameThread}
onDelete={(threadId) => void handleArchiveThread(threadId)}
labels={{
options: (item) =>
t("chat.optionsFor", { title: item.titleText ?? "" }),
renameInput: (item) =>
t("chat.renameThread", { title: item.titleText ?? "" }),
rename: t("chat.renameChat"),
pin: t("chat.pinChat"),
unpin: t("chat.unpinChat"),
delete: t("chat.archiveChat"),
}}
className="min-w-0"
/>
</div>
</div>
);
}
export function Sidebar({
collapsed = false,
collapsible = true,
onCollapsedChange,
}: SidebarProps) {
const location = useLocation();
const navigate = useNavigate();
const t = useT();
const isChatRoute =
location.pathname === "/" || location.pathname.startsWith("/chat/");
const ToggleIcon = collapsed
? IconLayoutSidebarLeftExpand
: IconLayoutSidebarLeftCollapse;
const navClass = ({ isActive }: { isActive: boolean }) =>
cn(
"flex items-center text-sm transition-colors",
collapsed
? "relative h-10 w-full justify-center rounded-none px-0"
: "h-9 rounded-md gap-3 px-3",
isActive
? collapsed
? "bg-sidebar-accent text-sidebar-accent-foreground"
: "bg-sidebar-accent text-sidebar-accent-foreground"
: collapsed
? "text-sidebar-foreground/70 hover:bg-sidebar-accent/55 hover:text-sidebar-accent-foreground"
: "text-sidebar-foreground hover:bg-sidebar-accent/65 hover:text-sidebar-accent-foreground",
);
const collapseButton = collapsible ? (
<Tooltip>
<TooltipTrigger asChild>
<button
type="button"
onClick={() => onCollapsedChange?.(!collapsed)}
className={cn(
"flex shrink-0 items-center justify-center rounded-md text-sidebar-foreground/65 transition-colors hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
collapsed ? "size-8" : "size-7",
)}
aria-label={
collapsed
? t("navigation.expandSidebar")
: t("navigation.collapseSidebar")
}
>
<ToggleIcon className="size-4" />
</button>
</TooltipTrigger>
<TooltipContent side="right">
{collapsed
? t("navigation.expandSidebar")
: t("navigation.collapseSidebar")}
</TooltipContent>
</Tooltip>
) : null;
const searchButton = (
<Tooltip>
<TooltipTrigger asChild>
<button
type="button"
onClick={openCommandMenu}
aria-label={t("root.commandSearch")}
className="flex size-8 items-center justify-center rounded-md text-sidebar-foreground/65 transition-colors hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
>
<IconSearch className="size-4" />
</button>
</TooltipTrigger>
<TooltipContent side="right">{t("root.commandSearch")}</TooltipContent>
</Tooltip>
);
const translateButton = (
<LanguagePicker variant="ghost-icon" label={t("settings.languageLabel")} />
);
const feedbackButton = (
<FeedbackButton
variant={collapsed ? "icon" : "sidebar"}
side="right"
className={collapsed ? "h-8 w-8" : "min-w-0"}
/>
);
return (
<aside
data-collapsed={collapsed ? "true" : "false"}
className={cn(
"flex h-full min-w-0 shrink-0 flex-col overflow-hidden border-e border-sidebar-border bg-sidebar text-sidebar-foreground transition-[width] duration-200 ease-out",
collapsed ? "w-12" : "w-60",
)}
>
<div
className={cn(
"flex shrink-0 items-center border-b border-sidebar-border",
collapsed ? "h-12 justify-center px-0" : "h-14 px-3",
)}
>
<Link
to="/"
className={cn(
"flex min-w-0 items-center rounded outline-none focus-visible:ring-2 focus-visible:ring-ring",
collapsed ? "size-7 justify-center" : "flex-1 gap-3",
)}
aria-label={collapsed ? APP_TITLE : undefined}
>
<img
src={appPath("/agent-native-icon-light.svg")}
alt=""
aria-hidden="true"
className="block h-4 w-auto shrink-0 dark:hidden"
/>
<img
src={appPath("/agent-native-icon-dark.svg")}
alt=""
aria-hidden="true"
className="hidden h-4 w-auto shrink-0 dark:block"
/>
<div className={cn("min-w-0", collapsed && "sr-only")}>
<p className="truncate text-sm font-semibold text-sidebar-accent-foreground">
{APP_TITLE}
</p>
</div>
</Link>
</div>
<nav
className={cn(
"flex-1 overflow-y-auto",
collapsed ? "px-0 py-2" : "px-2 py-3",
)}
>
<div className={cn("grid", collapsed ? "gap-0" : "gap-1")}>
{navItems.map((item) => {
const Icon = item.icon;
const isActive =
item.href === "/"
? isChatRoute
: location.pathname.startsWith(item.href);
const link = (
<Link
to={item.href}
onClick={(event) => {
if (
item.href === "/" &&
!isChatRoute &&
!event.metaKey &&
!event.ctrlKey &&
!event.shiftKey &&
!event.altKey
) {
event.preventDefault();
navigateWithAgentChatViewTransition(navigate, "/");
}
}}
className={navClass({ isActive })}
aria-current={isActive ? "page" : undefined}
aria-label={collapsed ? t(item.labelKey) : undefined}
>
<Icon className="size-4 shrink-0" />
<span className={collapsed ? "sr-only" : "truncate"}>
{t(item.labelKey)}
</span>
</Link>
);
return (
<div key={item.href}>
{collapsed ? (
<Tooltip>
<TooltipTrigger asChild>{link}</TooltipTrigger>
<TooltipContent side="right">
{t(item.labelKey)}
</TooltipContent>
</Tooltip>
) : (
link
)}
{!collapsed && item.view === "chat" ? (
<ChatThreadsSection open={isChatRoute} />
) : null}
</div>
);
})}
</div>
</nav>
<div className={cn("mt-auto shrink-0", collapsed && "py-2")}>
<nav
className={cn(
"grid",
collapsed ? "gap-0 px-1 py-1" : "gap-1 px-2 py-1",
)}
>
{bottomNavItems.map((item) => {
const Icon = item.icon;
const isActive = location.pathname.startsWith(item.href);
const link = (
<Link
to={item.href}
className={navClass({ isActive })}
aria-current={isActive ? "page" : undefined}
aria-label={collapsed ? t(item.labelKey) : undefined}
>
<Icon className="size-4 shrink-0" />
<span className={collapsed ? "sr-only" : "truncate"}>
{t(item.labelKey)}
</span>
</Link>
);
return collapsed ? (
<Tooltip key={item.href}>
<TooltipTrigger asChild>{link}</TooltipTrigger>
<TooltipContent side="right">{t(item.labelKey)}</TooltipContent>
</Tooltip>
) : (
<div key={item.href}>{link}</div>
);
})}
</nav>
<div className={cn(collapsed ? "px-1 py-1" : "px-3 py-2")}>
<OrgSwitcher
reserveSpace
className={
collapsed
? "h-8 justify-center px-0 [&>span]:sr-only [&>svg:last-child]:hidden"
: undefined
}
/>
</div>
<SidebarFooterActions
collapsed={collapsed}
feedback={feedbackButton}
translate={translateButton}
search={searchButton}
collapse={collapseButton}
/>
</div>
</aside>
);
}
+1
View File
@@ -0,0 +1 @@
export * from "@agent-native/toolkit/ui/button";
+1
View File
@@ -0,0 +1 @@
export * from "@agent-native/toolkit/ui/card";
+1
View File
@@ -0,0 +1 @@
export * from "@agent-native/toolkit/ui/dropdown-menu";
+1
View File
@@ -0,0 +1 @@
export * from "@agent-native/toolkit/ui/input";
+1
View File
@@ -0,0 +1 @@
export * from "@agent-native/toolkit/ui/label";
+1
View File
@@ -0,0 +1 @@
export * from "@agent-native/toolkit/ui/sheet";
+17
View File
@@ -0,0 +1,17 @@
import { ToolkitProvider, type ToolkitComponents } from "@agent-native/toolkit";
import type { ReactNode } from "react";
import { Button } from "@/components/ui/button";
import { designSystem } from "@/design-system";
const components: ToolkitComponents = {
Button: Button as ToolkitComponents["Button"],
};
export function AppToolkitProvider({ children }: { children: ReactNode }) {
return (
<ToolkitProvider components={components} designSystem={designSystem}>
{children}
</ToolkitProvider>
);
}
+1
View File
@@ -0,0 +1 @@
export * from "@agent-native/toolkit/ui/tooltip";
+3
View File
@@ -0,0 +1,3 @@
import { defineDesignSystem } from "@agent-native/toolkit/design-system";
export const designSystem = defineDesignSystem({});
+19
View File
@@ -0,0 +1,19 @@
// Import only the tiny URL-helper module, not the full client barrel.
// This keeps the ~650-700 KB gzip chat stack off the static import closure
// of the client entry point so it can't block the first page parse.
import { appBasePath } from "@agent-native/core/client/api-path";
import { hydrateRoot } from "react-dom/client";
import { HydratedRouter } from "react-router/dom";
const basePath = appBasePath();
const pathname = window.location.pathname;
const routerBasePath =
basePath && (pathname === basePath || pathname.startsWith(`${basePath}/`))
? basePath
: "";
const context = (
window as Window & { __reactRouterContext?: { basename?: string } }
).__reactRouterContext;
if (context) context.basename = routerBasePath;
hydrateRoot(document, <HydratedRouter />);
+10
View File
@@ -0,0 +1,10 @@
import {
createDocumentRequestHandler,
streamTimeout,
} from "@agent-native/core/server/entry-server";
import { ServerRouter } from "react-router";
const handleDocumentRequest = createDocumentRequestHandler(ServerRouter);
export { streamTimeout };
export default handleDocumentRequest;
+93
View File
@@ -0,0 +1,93 @@
/* Self-hosted via @fontsource-variable/inter — no render-blocking Google Fonts request */
@import "@fontsource-variable/inter";
@import "tailwindcss";
@import "@agent-native/core/styles/agent-native.css";
@import "@agent-native/toolkit/styles.css";
@source "./**/*.{ts,tsx}";
:root {
--background: 0 0% 100%;
--foreground: 0 0% 10%;
--card: 0 0% 100%;
--card-foreground: 0 0% 10%;
--popover: 0 0% 100%;
--popover-foreground: 0 0% 10%;
--primary: 0 0% 15%;
--primary-foreground: 0 0% 100%;
--secondary: 0 0% 95%;
--secondary-foreground: 0 0% 15%;
--muted: 0 0% 95%;
--muted-foreground: 0 0% 45%;
--accent: 0 0% 95%;
--accent-foreground: 0 0% 15%;
--destructive: 0 91% 71%;
--destructive-foreground: 0 0% 98%;
--border: 0 0% 90%;
--input: 0 0% 90%;
--ring: 0 0% 40%;
--radius: 0.5rem;
--sidebar-background: 0 0% 97%;
--sidebar-foreground: 0 0% 45%;
--sidebar-primary: 0 0% 15%;
--sidebar-primary-foreground: 0 0% 100%;
--sidebar-accent: 0 0% 95%;
--sidebar-accent-foreground: 0 0% 15%;
--sidebar-border: 0 0% 90%;
--sidebar-ring: 0 0% 40%;
}
.dark {
--background: 0 0% 13%;
--foreground: 0 0% 90%;
--card: 0 0% 15%;
--card-foreground: 0 0% 90%;
--popover: 0 0% 15%;
--popover-foreground: 0 0% 90%;
--primary: 0 0% 75%;
--primary-foreground: 0 0% 10%;
--secondary: 0 0% 18%;
--secondary-foreground: 0 0% 90%;
--muted: 0 0% 16%;
--muted-foreground: 0 0% 60%;
--accent: 0 0% 18%;
--accent-foreground: 0 0% 90%;
--destructive: 0 84% 60%;
--destructive-foreground: 0 0% 98%;
--border: 0 0% 24%;
--input: 0 0% 24%;
--ring: 0 0% 60%;
--radius: 0.5rem;
--sidebar-background: 0 0% 10%;
--sidebar-foreground: 0 0% 60%;
--sidebar-primary: 0 0% 75%;
--sidebar-primary-foreground: 0 0% 10%;
--sidebar-accent: 0 0% 16%;
--sidebar-accent-foreground: 0 0% 90%;
--sidebar-border: 0 0% 20%;
--sidebar-ring: 0 0% 60%;
}
@layer base {
body {
@apply bg-background text-foreground;
font-family: "Inter Variable", "Inter", sans-serif;
font-feature-settings: "cv02", "cv03", "cv04", "cv11";
}
}
::-webkit-scrollbar {
width: 5px;
height: 5px;
}
::-webkit-scrollbar-track {
background: transparent;
}
::-webkit-scrollbar-thumb {
background: hsl(var(--border));
border-radius: 3px;
}
::-webkit-scrollbar-thumb:hover {
background: hsl(var(--muted-foreground) / 0.4);
}
+93
View File
@@ -0,0 +1,93 @@
import { appBasePath, appPath } from "@agent-native/core/client/api-path";
import { useAgentRouteState } from "@agent-native/core/client/navigation";
import { TAB_ID } from "@/lib/tab-id";
export interface NavigationState {
view: string;
path?: string;
threadId?: string;
}
export function useNavigationState() {
useAgentRouteState<NavigationState>({
browserTabId: TAB_ID,
requestSource: TAB_ID,
getNavigationState: ({ pathname }) => {
const threadId = threadIdFromPath(pathname);
return {
view: viewForPath(pathname),
path: appPath(pathname),
...(threadId ? { threadId } : {}),
};
},
getCommandPath: (command) =>
routerPath(command.path || pathForCommand(command)),
});
}
function threadIdFromPath(pathname: string): string | null {
const match = pathname.match(/^\/chat\/([^/]+)/);
if (!match) return null;
try {
const value = decodeURIComponent(match[1]).trim();
return value || null;
} catch {
return null;
}
}
function viewForPath(pathname: string): string {
if (isChatPath(pathname)) return "chat";
if (pathname.startsWith("/database")) return "database";
if (pathname.startsWith("/extensions")) return "extensions";
if (pathname.startsWith("/observability")) return "observability";
if (pathname.startsWith("/agent")) return "agent";
if (pathname.startsWith("/team")) return "settings";
return "chat";
}
function pathForView(view?: string): string {
switch (view) {
case "chat":
case "home":
case "ask":
return "/";
case "database":
return "/database";
case "extensions":
return "/extensions";
case "observability":
return "/observability";
case "agent":
return "/agent";
case "settings":
return "/settings";
case "team":
return "/settings#organization";
default:
return "/";
}
}
function pathForCommand(command: any): string {
const path = pathForView(command?.view);
if (path !== "/") return path;
const threadId =
typeof command?.threadId === "string" ? command.threadId.trim() : "";
return threadId ? `/chat/${encodeURIComponent(threadId)}` : "/";
}
function routerPath(path: string): string {
const basePath = appBasePath();
if (!basePath) return path;
if (path === basePath) return "/";
if (path.startsWith(`${basePath}/`)) {
return path.slice(basePath.length) || "/";
}
return path;
}
function isChatPath(pathname: string): boolean {
return pathname === "/" || pathname.startsWith("/chat/");
}
+556
View File
@@ -0,0 +1,556 @@
import { type LocaleCode } from "@agent-native/core/client/i18n";
import zhTW from "./i18n/zh-TW";
const enUS = {
root: {
commandActions: "Actions",
commandSearch: "Search",
commandAppearance: "Appearance",
toggleTheme: "Toggle theme",
},
navigation: {
chat: "Chat",
observability: "Observability",
database: "Database",
team: "Team",
settings: "Settings",
extensions: "Extensions",
navigation: "Navigation",
navigationDescription: "App navigation links",
openNavigation: "Open navigation",
expandSidebar: "Expand sidebar",
collapseSidebar: "Collapse sidebar",
},
pages: {
observabilityTitle: "Agent Observability",
observabilityPageTitle: "Observability",
databaseTitle: "Database",
teamTitle: "Team",
teamCreateOrgDescription:
"Set up a team to share this app with your colleagues.",
},
chat: {
emptyState: "Ask anything, then customize the app when you need more.",
composerPlaceholder: "Message the agent...",
heroTitle: "How can I help?",
heroDescription:
"Chat about anything. Add actions, components, pages, jobs, or your own agent backend when you want this app to do more.",
suggestionCapabilities: "What can you do?",
suggestionCustomize: "Help me customize this chat app",
suggestionActions: "Show me the actions and pages I can add",
inspectEmptyState: "Ask the agent to inspect or change this app.",
inspectSuggestionCapabilities: "What can you do here?",
inspectSuggestionHello: "Call hello for Builder",
inspectSuggestionAction: "Add a new action and show it in the UI",
chats: "Chats",
newChat: "New chat",
renameChat: "Rename chat",
pinChat: "Pin chat",
unpinChat: "Unpin chat",
archiveChat: "Archive chat",
archiveFailed: "Could not archive chat.",
renameFailed: "Could not rename chat.",
renameThread: "Rename {{title}}",
optionsFor: "Chat options for {{title}}",
},
};
type Messages = typeof enUS;
type PartialMessages = { [K in keyof Messages]?: Partial<Messages[K]> };
function mergeMessages(overrides: PartialMessages): Messages {
return {
root: { ...enUS.root, ...overrides.root },
navigation: { ...enUS.navigation, ...overrides.navigation },
pages: { ...enUS.pages, ...overrides.pages },
chat: { ...enUS.chat, ...overrides.chat },
};
}
export const messagesByLocale = {
"en-US": enUS,
"zh-TW": mergeMessages(zhTW),
"zh-CN": mergeMessages({
root: {
commandActions: "操作",
commandSearch: "搜索",
commandAppearance: "外观",
toggleTheme: "切换主题",
},
navigation: {
chat: "聊天",
observability: "可观测性",
database: "数据库",
team: "团队",
settings: "设置",
extensions: "扩展",
navigation: "导航",
navigationDescription: "应用导航链接",
openNavigation: "打开导航",
expandSidebar: "展开侧边栏",
collapseSidebar: "收起侧边栏",
},
pages: {
observabilityTitle: "代理可观测性",
observabilityPageTitle: "可观测性",
databaseTitle: "数据库",
teamTitle: "团队",
teamCreateOrgDescription: "设置团队,与同事共享此应用。",
},
chat: {
emptyState: "随便提问,需要更多功能时再自定义应用。",
composerPlaceholder: "给代理发消息...",
heroTitle: "我能帮你什么?",
heroDescription:
"可以聊任何内容。需要此应用做更多事时,可添加操作、组件、页面、任务或自己的代理后端。",
suggestionCapabilities: "你能做什么?",
suggestionCustomize: "帮我自定义这个聊天应用",
suggestionActions: "展示我可以添加的操作和页面",
inspectEmptyState: "让代理检查或修改此应用。",
inspectSuggestionCapabilities: "你在这里能做什么?",
inspectSuggestionHello: "为 Builder 调用 hello",
inspectSuggestionAction: "添加新操作并显示在 UI 中",
chats: "聊天",
newChat: "新聊天",
renameChat: "重命名聊天",
pinChat: "置顶聊天",
unpinChat: "取消置顶聊天",
archiveChat: "归档聊天",
archiveFailed: "无法归档聊天。",
renameFailed: "无法重命名聊天。",
renameThread: "重命名 {{title}}",
optionsFor: "{{title}} 的聊天选项",
},
}),
"es-ES": mergeMessages({
root: {
commandActions: "Acciones",
commandSearch: "Buscar",
commandAppearance: "Apariencia",
toggleTheme: "Cambiar tema",
},
navigation: {
chat: "Chat",
observability: "Observabilidad",
database: "Base de datos",
team: "Equipo",
settings: "Ajustes",
extensions: "Extensiones",
navigation: "Navegación",
navigationDescription: "Enlaces de navegación de la app",
openNavigation: "Abrir navegación",
expandSidebar: "Expandir barra lateral",
collapseSidebar: "Contraer barra lateral",
},
pages: {
observabilityTitle: "Observabilidad del agente",
observabilityPageTitle: "Observabilidad",
databaseTitle: "Base de datos",
teamTitle: "Equipo",
teamCreateOrgDescription:
"Configura un equipo para compartir esta app con tus compañeros.",
},
chat: {
emptyState:
"Pregunta cualquier cosa y personaliza la app cuando necesites más.",
composerPlaceholder: "Mensaje al agente...",
heroTitle: "¿En qué puedo ayudar?",
heroDescription:
"Chatea sobre cualquier cosa. Añade acciones, componentes, páginas, trabajos o tu propio backend de agente cuando quieras más.",
suggestionCapabilities: "¿Qué puedes hacer?",
suggestionCustomize: "Ayúdame a personalizar esta app de chat",
suggestionActions: "Muéstrame las acciones y páginas que puedo añadir",
inspectEmptyState: "Pide al agente que inspeccione o cambie esta app.",
inspectSuggestionCapabilities: "¿Qué puedes hacer aquí?",
inspectSuggestionHello: "Llama a hello para Builder",
inspectSuggestionAction: "Añade una acción nueva y muéstrala en la UI",
chats: "Chats",
newChat: "Nuevo chat",
renameChat: "Renombrar chat",
pinChat: "Fijar chat",
unpinChat: "Desfijar chat",
archiveChat: "Archivar chat",
archiveFailed: "No se pudo archivar el chat.",
renameFailed: "No se pudo renombrar el chat.",
renameThread: "Renombrar {{title}}",
optionsFor: "Opciones de chat para {{title}}",
},
}),
"fr-FR": mergeMessages({
root: {
commandActions: "Actions",
commandSearch: "Rechercher",
commandAppearance: "Apparence",
toggleTheme: "Changer de thème",
},
navigation: {
chat: "Chat",
observability: "Observabilité",
database: "Base de données",
team: "Équipe",
settings: "Paramètres",
extensions: "Extensions",
navigation: "Navigation",
navigationDescription: "Liens de navigation de l'app",
openNavigation: "Ouvrir la navigation",
expandSidebar: "Développer la barre latérale",
collapseSidebar: "Réduire la barre latérale",
},
pages: {
observabilityTitle: "Observabilité de lagent",
observabilityPageTitle: "Observabilité",
databaseTitle: "Base de données",
teamTitle: "Équipe",
teamCreateOrgDescription:
"Configurez une équipe pour partager cette app avec vos collègues.",
},
chat: {
emptyState:
"Demandez n'importe quoi, puis personnalisez l'app au besoin.",
composerPlaceholder: "Message à l'agent...",
heroTitle: "Comment puis-je aider ?",
heroDescription:
"Discutez de tout. Ajoutez actions, composants, pages, jobs ou votre propre backend d'agent lorsque vous voulez aller plus loin.",
suggestionCapabilities: "Que peux-tu faire ?",
suggestionCustomize: "Aide-moi à personnaliser cette app de chat",
suggestionActions: "Montre-moi les actions et pages que je peux ajouter",
inspectEmptyState:
"Demandez à l'agent d'inspecter ou modifier cette app.",
inspectSuggestionCapabilities: "Que peux-tu faire ici ?",
inspectSuggestionHello: "Appelle hello pour Builder",
inspectSuggestionAction: "Ajoute une action et montre-la dans l'UI",
chats: "Chats",
newChat: "Nouveau chat",
renameChat: "Renommer le chat",
pinChat: "Épingler le chat",
unpinChat: "Désépingler le chat",
archiveChat: "Archiver le chat",
archiveFailed: "Impossible d'archiver le chat.",
renameFailed: "Impossible de renommer le chat.",
renameThread: "Renommer {{title}}",
optionsFor: "Options du chat pour {{title}}",
},
}),
"de-DE": mergeMessages({
root: {
commandActions: "Aktionen",
commandSearch: "Suchen",
commandAppearance: "Darstellung",
toggleTheme: "Theme wechseln",
},
navigation: {
chat: "Chat",
observability: "Observability",
database: "Datenbank",
team: "Team",
settings: "Einstellungen",
extensions: "Erweiterungen",
navigation: "Navigation",
navigationDescription: "App-Navigationslinks",
openNavigation: "Navigation öffnen",
expandSidebar: "Seitenleiste erweitern",
collapseSidebar: "Seitenleiste einklappen",
},
pages: {
observabilityTitle: "Agent-Beobachtbarkeit",
observabilityPageTitle: "Beobachtbarkeit",
databaseTitle: "Datenbank",
teamTitle: "Team",
teamCreateOrgDescription:
"Richte ein Team ein, um diese App mit deinen Kollegen zu teilen.",
},
chat: {
emptyState: "Frag alles und passe die App an, wenn du mehr brauchst.",
composerPlaceholder: "Nachricht an den Agenten...",
heroTitle: "Wie kann ich helfen?",
heroDescription:
"Chatte über alles. Füge Aktionen, Komponenten, Seiten, Jobs oder dein eigenes Agent-Backend hinzu, wenn die App mehr tun soll.",
suggestionCapabilities: "Was kannst du tun?",
suggestionCustomize: "Hilf mir, diese Chat-App anzupassen",
suggestionActions:
"Zeige mir Aktionen und Seiten, die ich hinzufügen kann",
inspectEmptyState:
"Bitte den Agenten, diese App zu prüfen oder zu ändern.",
inspectSuggestionCapabilities: "Was kannst du hier tun?",
inspectSuggestionHello: "Rufe hello für Builder auf",
inspectSuggestionAction:
"Füge eine neue Aktion hinzu und zeige sie in der UI",
chats: "Chats",
newChat: "Neuer Chat",
renameChat: "Chat umbenennen",
pinChat: "Chat anheften",
unpinChat: "Chat lösen",
archiveChat: "Chat archivieren",
archiveFailed: "Chat konnte nicht archiviert werden.",
renameFailed: "Chat konnte nicht umbenannt werden.",
renameThread: "{{title}} umbenennen",
optionsFor: "Chatoptionen für {{title}}",
},
}),
"ja-JP": mergeMessages({
root: {
commandActions: "操作",
commandSearch: "検索",
commandAppearance: "外観",
toggleTheme: "テーマを切り替え",
},
navigation: {
chat: "チャット",
observability: "可観測性",
database: "データベース",
team: "チーム",
settings: "設定",
extensions: "拡張機能",
navigation: "ナビゲーション",
navigationDescription: "アプリのナビゲーションリンク",
openNavigation: "ナビゲーションを開く",
expandSidebar: "サイドバーを展開",
collapseSidebar: "サイドバーを折りたたむ",
},
pages: {
observabilityTitle: "エージェントの可観測性",
observabilityPageTitle: "可観測性",
databaseTitle: "データベース",
teamTitle: "チーム",
teamCreateOrgDescription:
"同僚とこのアプリを共有するためのチームを設定します。",
},
chat: {
emptyState: "何でも質問し、必要になったらアプリをカスタマイズできます。",
composerPlaceholder: "エージェントにメッセージ...",
heroTitle: "どうお手伝いできますか?",
heroDescription:
"何でもチャットできます。必要に応じてアクション、コンポーネント、ページ、ジョブ、独自のエージェントバックエンドを追加できます。",
suggestionCapabilities: "何ができますか?",
suggestionCustomize: "このチャットアプリをカスタマイズして",
suggestionActions: "追加できるアクションとページを見せて",
inspectEmptyState: "エージェントにこのアプリの確認や変更を依頼します。",
inspectSuggestionCapabilities: "ここで何ができますか?",
inspectSuggestionHello: "Builder に hello を呼び出す",
inspectSuggestionAction: "新しいアクションを追加して UI に表示",
chats: "チャット",
newChat: "新しいチャット",
renameChat: "チャット名を変更",
pinChat: "チャットを固定",
unpinChat: "固定を解除",
archiveChat: "チャットをアーカイブ",
archiveFailed: "チャットをアーカイブできませんでした。",
renameFailed: "チャット名を変更できませんでした。",
renameThread: "{{title}} の名前を変更",
optionsFor: "{{title}} のチャットオプション",
},
}),
"ko-KR": mergeMessages({
root: {
commandActions: "작업",
commandSearch: "검색",
commandAppearance: "모양",
toggleTheme: "테마 전환",
},
navigation: {
chat: "채팅",
observability: "관측성",
database: "데이터베이스",
team: "팀",
settings: "설정",
extensions: "확장",
navigation: "탐색",
navigationDescription: "앱 탐색 링크",
openNavigation: "탐색 열기",
expandSidebar: "사이드바 펼치기",
collapseSidebar: "사이드바 접기",
},
pages: {
observabilityTitle: "에이전트 관찰 가능성",
observabilityPageTitle: "관찰 가능성",
databaseTitle: "데이터베이스",
teamTitle: "팀",
teamCreateOrgDescription: "동료와 이 앱을 공유할 팀을 설정하세요.",
},
chat: {
emptyState: "무엇이든 물어보고, 더 필요할 때 앱을 사용자 지정하세요.",
composerPlaceholder: "에이전트에게 메시지...",
heroTitle: "무엇을 도와드릴까요?",
heroDescription:
"무엇이든 채팅하세요. 앱이 더 많은 일을 하게 하고 싶을 때 작업, 컴포넌트, 페이지, 작업 또는 자체 에이전트 백엔드를 추가하세요.",
suggestionCapabilities: "무엇을 할 수 있나요?",
suggestionCustomize: "이 채팅 앱을 사용자 지정하게 도와줘",
suggestionActions: "추가할 수 있는 작업과 페이지를 보여줘",
inspectEmptyState:
"에이전트에게 이 앱을 검사하거나 변경하도록 요청하세요.",
inspectSuggestionCapabilities: "여기서 무엇을 할 수 있나요?",
inspectSuggestionHello: "Builder용 hello 호출",
inspectSuggestionAction: "새 작업을 추가하고 UI에 보여줘",
chats: "채팅",
newChat: "새 채팅",
renameChat: "채팅 이름 변경",
pinChat: "채팅 고정",
unpinChat: "채팅 고정 해제",
archiveChat: "채팅 보관",
archiveFailed: "채팅을 보관할 수 없습니다.",
renameFailed: "채팅 이름을 변경할 수 없습니다.",
renameThread: "{{title}} 이름 변경",
optionsFor: "{{title}} 채팅 옵션",
},
}),
"pt-BR": mergeMessages({
root: {
commandActions: "Ações",
commandSearch: "Buscar",
commandAppearance: "Aparência",
toggleTheme: "Alternar tema",
},
navigation: {
chat: "Chat",
observability: "Observabilidade",
database: "Banco de dados",
team: "Equipe",
settings: "Configurações",
extensions: "Extensões",
navigation: "Navegação",
navigationDescription: "Links de navegação do app",
openNavigation: "Abrir navegação",
expandSidebar: "Expandir barra lateral",
collapseSidebar: "Recolher barra lateral",
},
pages: {
observabilityTitle: "Observabilidade do agente",
observabilityPageTitle: "Observabilidade",
databaseTitle: "Banco de dados",
teamTitle: "Equipe",
teamCreateOrgDescription:
"Configure uma equipe para compartilhar este app com seus colegas.",
},
chat: {
emptyState:
"Pergunte qualquer coisa e personalize o app quando precisar.",
composerPlaceholder: "Mensagem para o agente...",
heroTitle: "Como posso ajudar?",
heroDescription:
"Converse sobre qualquer coisa. Adicione ações, componentes, páginas, jobs ou seu próprio backend de agente quando quiser que o app faça mais.",
suggestionCapabilities: "O que você pode fazer?",
suggestionCustomize: "Ajude-me a personalizar este app de chat",
suggestionActions: "Mostre as ações e páginas que posso adicionar",
inspectEmptyState: "Peça ao agente para inspecionar ou alterar este app.",
inspectSuggestionCapabilities: "O que você pode fazer aqui?",
inspectSuggestionHello: "Chame hello para Builder",
inspectSuggestionAction: "Adicione uma nova ação e mostre na UI",
chats: "Chats",
newChat: "Novo chat",
renameChat: "Renomear chat",
pinChat: "Fixar chat",
unpinChat: "Desafixar chat",
archiveChat: "Arquivar chat",
archiveFailed: "Não foi possível arquivar o chat.",
renameFailed: "Não foi possível renomear o chat.",
renameThread: "Renomear {{title}}",
optionsFor: "Opções de chat para {{title}}",
},
}),
"hi-IN": mergeMessages({
root: {
commandActions: "क्रियाएं",
commandSearch: "खोजें",
commandAppearance: "रूप",
toggleTheme: "थीम बदलें",
},
navigation: {
chat: "चैट",
observability: "ऑब्ज़र्वेबिलिटी",
database: "डेटाबेस",
team: "टीम",
settings: "सेटिंग्स",
extensions: "एक्सटेंशन",
navigation: "नेविगेशन",
navigationDescription: "ऐप नेविगेशन लिंक",
openNavigation: "नेविगेशन खोलें",
expandSidebar: "साइडबार फैलाएं",
collapseSidebar: "साइडबार समेटें",
},
pages: {
observabilityTitle: "एजेंट ऑब्जर्वेबिलिटी",
observabilityPageTitle: "ऑब्जर्वेबिलिटी",
databaseTitle: "डेटाबेस",
teamTitle: "टीम",
teamCreateOrgDescription:
"इस ऐप को अपने सहयोगियों के साथ साझा करने के लिए टीम सेट करें।",
},
chat: {
emptyState: "कुछ भी पूछें, फिर जरूरत होने पर ऐप कस्टमाइज़ करें।",
composerPlaceholder: "एजेंट को संदेश...",
heroTitle: "मैं कैसे मदद कर सकता हूं?",
heroDescription:
"किसी भी चीज़ पर चैट करें। जब ऐप से और काम कराना हो तो actions, components, pages, jobs या अपना agent backend जोड़ें।",
suggestionCapabilities: "आप क्या कर सकते हैं?",
suggestionCustomize: "इस chat app को कस्टमाइज़ करने में मदद करें",
suggestionActions: "जो actions और pages जोड़ सकता हूं वे दिखाएं",
inspectEmptyState: "एजेंट से इस ऐप को inspect या change करने को कहें।",
inspectSuggestionCapabilities: "आप यहां क्या कर सकते हैं?",
inspectSuggestionHello: "Builder के लिए hello call करें",
inspectSuggestionAction: "नई action जोड़ें और UI में दिखाएं",
chats: "चैट",
newChat: "नई चैट",
renameChat: "चैट का नाम बदलें",
pinChat: "चैट पिन करें",
unpinChat: "चैट अनपिन करें",
archiveChat: "चैट आर्काइव करें",
archiveFailed: "चैट आर्काइव नहीं कर सके।",
renameFailed: "चैट का नाम नहीं बदल सके।",
renameThread: "{{title}} का नाम बदलें",
optionsFor: "{{title}} के लिए चैट विकल्प",
},
}),
"ar-SA": mergeMessages({
root: {
commandActions: "الإجراءات",
commandSearch: "بحث",
commandAppearance: "المظهر",
toggleTheme: "تبديل السمة",
},
navigation: {
chat: "المحادثة",
observability: "المراقبة",
database: "قاعدة البيانات",
team: "الفريق",
settings: "الإعدادات",
extensions: "الإضافات",
navigation: "التنقل",
navigationDescription: "روابط تنقل التطبيق",
openNavigation: "فتح التنقل",
expandSidebar: "توسيع الشريط الجانبي",
collapseSidebar: "طي الشريط الجانبي",
},
pages: {
observabilityTitle: "قابلية ملاحظة الوكيل",
observabilityPageTitle: "قابلية الملاحظة",
databaseTitle: "قاعدة البيانات",
teamTitle: "الفريق",
teamCreateOrgDescription: "أعد فريقا لمشاركة هذا التطبيق مع زملائك.",
},
chat: {
emptyState: "اسأل أي شيء ثم خصص التطبيق عندما تحتاج المزيد.",
composerPlaceholder: "راسل الوكيل...",
heroTitle: "كيف يمكنني المساعدة؟",
heroDescription:
"تحدث عن أي شيء. أضف إجراءات أو مكونات أو صفحات أو مهام أو خلفية وكيل خاصة عندما تريد أن يفعل التطبيق المزيد.",
suggestionCapabilities: "ماذا يمكنك أن تفعل؟",
suggestionCustomize: "ساعدني في تخصيص تطبيق الدردشة هذا",
suggestionActions: "أرني الإجراءات والصفحات التي يمكنني إضافتها",
inspectEmptyState: "اطلب من الوكيل فحص هذا التطبيق أو تغييره.",
inspectSuggestionCapabilities: "ماذا يمكنك أن تفعل هنا؟",
inspectSuggestionHello: "استدع hello لـ Builder",
inspectSuggestionAction: "أضف إجراءً جديدًا واعرضه في الواجهة",
chats: "المحادثات",
newChat: "محادثة جديدة",
renameChat: "إعادة تسمية المحادثة",
pinChat: "تثبيت المحادثة",
unpinChat: "إلغاء تثبيت المحادثة",
archiveChat: "أرشفة المحادثة",
archiveFailed: "تعذرت أرشفة المحادثة.",
renameFailed: "تعذرت إعادة تسمية المحادثة.",
renameThread: "إعادة تسمية {{title}}",
optionsFor: "خيارات المحادثة لـ {{title}}",
},
}),
} satisfies Record<LocaleCode, Messages>;
+68
View File
@@ -0,0 +1,68 @@
const messages = {
settings: {
title: "الإعدادات",
description: "تفضيلات اللغة ومساحة العمل لهذا التطبيق.",
languageTitle: "اللغة",
languageDescription: "اختر لغة الواجهة. يتم حفظ هذا التفضيل في حسابك.",
languageLabel: "لغة الواجهة",
workspaceTitle: "مساحة العمل",
workspaceDescription:
"إدارة أعضاء الفريق ووصول المؤسسة وتفضيلات مساحة العمل المشتركة.",
openTeamSettings: "فتح إعدادات الفريق",
openResourceSettings: "فتح إعدادات الموارد",
agentTitle: "إدارة الوكيل",
agentDescription:
"أدر نموذج الوكيل ومفاتيح API والأتمتة والصوت وعناصر التحكم الأخرى.",
openAgentSettings: "إدارة الوكيل",
},
chat: {
archiveChat: "أرشفة المحادثة",
archiveFailed: "فشلت الأرشفة",
chats: "المحادثات",
composerPlaceholder: "اسأل الوكيل...",
emptyState: "اسألني أي شيء",
heroDescription: "اطلب من الوكيل فحص هذا التطبيق أو شرحه أو تغييره.",
heroTitle: "كيف يمكنني المساعدة؟",
inspectEmptyState: "اسألني أي شيء عن هذا التطبيق",
inspectSuggestionAction: "إظهار الإجراءات المتاحة",
inspectSuggestionCapabilities: "ما الذي يمكن لهذا التطبيق فعله؟",
inspectSuggestionHello: "ساعدني على البدء",
newChat: "محادثة جديدة",
optionsFor: "خيارات لـ",
pinChat: "تثبيت المحادثة",
renameChat: "إعادة تسمية المحادثة",
renameFailed: "فشلت إعادة التسمية",
renameThread: "إعادة تسمية السلسلة",
suggestionActions: "أرني الإجراءات المتاحة",
suggestionCapabilities: "ما الذي يمكن لهذا التطبيق فعله؟",
suggestionCustomize: "ساعدني في تخصيص هذا التطبيق",
unpinChat: "إلغاء تثبيت المحادثة",
},
navigation: {
chat: "المحادثة",
collapseSidebar: "طي الشريط الجانبي",
database: "قاعدة البيانات",
expandSidebar: "توسيع الشريط الجانبي",
extensions: "الإضافات",
navigation: "التنقل",
navigationDescription: "التنقل الرئيسي",
observability: "قابلية المراقبة",
openNavigation: "فتح التنقل",
settings: "الإعدادات",
team: "الفريق",
},
pages: {
databaseTitle: "قاعدة البيانات",
observabilityPageTitle: "قابلية ملاحظة الوكيل",
teamTitle: "الفريق",
teamCreateOrgDescription: "أنشئ مؤسسة لدعوة الزملاء ومشاركة هذا التطبيق.",
},
root: {
commandActions: "الإجراءات",
commandAppearance: "المظهر",
commandSearch: "بحث",
toggleTheme: "تبديل السمة",
},
};
export default messages;
+71
View File
@@ -0,0 +1,71 @@
const messages = {
settings: {
title: "Einstellungen",
description: "Sprach- und Arbeitsbereichseinstellungen für diese App.",
languageTitle: "Sprache",
languageDescription:
"Wähle die Sprache der Oberfläche. Diese Einstellung wird in deinem Konto gespeichert.",
languageLabel: "Oberflächensprache",
workspaceTitle: "Arbeitsbereich",
workspaceDescription:
"Verwalte Teammitglieder, Organisationszugriff und gemeinsame Arbeitsbereichseinstellungen.",
openTeamSettings: "Teameinstellungen öffnen",
openResourceSettings: "Ressourceneinstellungen öffnen",
agentTitle: "Agent verwalten",
agentDescription:
"Verwalte das Modell, die API-Schlüssel, Automatisierungen, Sprache und weitere Steuerungen des Agents.",
openAgentSettings: "Agent verwalten",
},
chat: {
archiveChat: "Chat archivieren",
archiveFailed: "Archivieren fehlgeschlagen",
chats: "Chats",
composerPlaceholder: "Frag den Agenten...",
emptyState: "Frag mich alles",
heroDescription:
"Bitte den Agenten, diese App zu prüfen, zu erklären oder zu ändern.",
heroTitle: "Wie kann ich helfen?",
inspectEmptyState: "Frag mich alles über diese App",
inspectSuggestionAction: "Verfügbare Aktionen anzeigen",
inspectSuggestionCapabilities: "Was kann diese App?",
inspectSuggestionHello: "Hilf mir beim Einstieg",
newChat: "Neuer Chat",
optionsFor: "Optionen für",
pinChat: "Chat anheften",
renameChat: "Chat umbenennen",
renameFailed: "Umbenennen fehlgeschlagen",
renameThread: "Thread umbenennen",
suggestionActions: "Zeig mir die verfügbaren Aktionen",
suggestionCapabilities: "Was kann diese App?",
suggestionCustomize: "Hilf mir, diese App anzupassen",
unpinChat: "Chat lösen",
},
navigation: {
chat: "Chat",
collapseSidebar: "Seitenleiste einklappen",
database: "Datenbank",
expandSidebar: "Seitenleiste ausklappen",
extensions: "Erweiterungen",
navigation: "Navigation",
navigationDescription: "Hauptnavigation",
observability: "Beobachtbarkeit",
openNavigation: "Navigation öffnen",
settings: "Einstellungen",
team: "Team",
},
pages: {
databaseTitle: "Datenbank",
observabilityPageTitle: "Agent-Beobachtbarkeit",
teamTitle: "Team",
teamCreateOrgDescription:
"Erstelle eine Organisation, um Teammitglieder einzuladen und diese App zu teilen.",
},
root: {
commandActions: "Aktionen",
commandAppearance: "Darstellung",
commandSearch: "Suchen",
toggleTheme: "Design wechseln",
},
};
export default messages;
+70
View File
@@ -0,0 +1,70 @@
const messages = {
settings: {
title: "Settings",
description: "Language and workspace preferences for this app.",
languageTitle: "Language",
languageDescription:
"Choose the interface language. This preference is saved for your account.",
languageLabel: "Interface language",
workspaceTitle: "Workspace",
workspaceDescription:
"Manage team members, organization access, and shared workspace preferences.",
openTeamSettings: "Open team settings",
openResourceSettings: "Open resource settings",
agentTitle: "Manage agent",
agentDescription:
"Manage the agent's model, API keys, automations, voice, and other controls.",
openAgentSettings: "Manage agent",
},
chat: {
archiveChat: "Archive Chat",
archiveFailed: "Archive Failed",
chats: "Chats",
composerPlaceholder: "Ask the agent...",
emptyState: "Ask me anything",
heroDescription: "Ask the agent to inspect, explain, or change this app.",
heroTitle: "How can I help?",
inspectEmptyState: "Ask me anything about this app",
inspectSuggestionAction: "Show available actions",
inspectSuggestionCapabilities: "What can this app do?",
inspectSuggestionHello: "Help me get started",
newChat: "New Chat",
optionsFor: "Options For",
pinChat: "Pin Chat",
renameChat: "Rename Chat",
renameFailed: "Rename Failed",
renameThread: "Rename Thread",
suggestionActions: "Show me the available actions",
suggestionCapabilities: "What can this app do?",
suggestionCustomize: "Help me customize this app",
unpinChat: "Unpin Chat",
},
navigation: {
chat: "Chat",
collapseSidebar: "Collapse Sidebar",
database: "Database",
expandSidebar: "Expand Sidebar",
extensions: "Extensions",
navigation: "Navigation",
navigationDescription: "Main navigation",
observability: "Observability",
openNavigation: "Open navigation",
settings: "Settings",
team: "Team",
},
pages: {
databaseTitle: "Database",
observabilityPageTitle: "Agent Observability",
teamTitle: "Team",
teamCreateOrgDescription:
"Create an organization to invite teammates and share this app.",
},
root: {
commandActions: "Actions",
commandAppearance: "Appearance",
commandSearch: "Search",
toggleTheme: "Toggle theme",
},
};
export default messages;
+71
View File
@@ -0,0 +1,71 @@
const messages = {
settings: {
title: "Ajustes",
description: "Preferencias de idioma y espacio de trabajo para esta app.",
languageTitle: "Idioma",
languageDescription:
"Elige el idioma de la interfaz. Esta preferencia se guarda en tu cuenta.",
languageLabel: "Idioma de la interfaz",
workspaceTitle: "Espacio de trabajo",
workspaceDescription:
"Gestiona miembros del equipo, acceso de la organización y preferencias compartidas.",
openTeamSettings: "Abrir ajustes del equipo",
openResourceSettings: "Abrir ajustes de recursos",
agentTitle: "Gestionar agente",
agentDescription:
"Gestiona el modelo del agente, claves API, automatizaciones, voz y otros controles.",
openAgentSettings: "Gestionar agente",
},
chat: {
archiveChat: "Archivar chat",
archiveFailed: "No se pudo archivar",
chats: "Chats",
composerPlaceholder: "Pregunta al agente...",
emptyState: "Pregúntame lo que quieras",
heroDescription:
"Pide al agente que inspeccione, explique o cambie esta app.",
heroTitle: "¿Cómo puedo ayudarte?",
inspectEmptyState: "Pregúntame cualquier cosa sobre esta app",
inspectSuggestionAction: "Mostrar acciones disponibles",
inspectSuggestionCapabilities: "¿Qué puede hacer esta app?",
inspectSuggestionHello: "Ayúdame a empezar",
newChat: "Nuevo chat",
optionsFor: "Opciones para",
pinChat: "Fijar chat",
renameChat: "Renombrar chat",
renameFailed: "No se pudo renombrar",
renameThread: "Renombrar hilo",
suggestionActions: "Muéstrame las acciones disponibles",
suggestionCapabilities: "¿Qué puede hacer esta app?",
suggestionCustomize: "Ayúdame a personalizar esta app",
unpinChat: "Desfijar chat",
},
navigation: {
chat: "Chat",
collapseSidebar: "Contraer barra lateral",
database: "Base de datos",
expandSidebar: "Expandir barra lateral",
extensions: "Extensiones",
navigation: "Navegación",
navigationDescription: "Navegación principal",
observability: "Observabilidad",
openNavigation: "Abrir navegación",
settings: "Ajustes",
team: "Equipo",
},
pages: {
databaseTitle: "Base de datos",
observabilityPageTitle: "Observabilidad del agente",
teamTitle: "Equipo",
teamCreateOrgDescription:
"Crea una organización para invitar compañeros y compartir esta app.",
},
root: {
commandActions: "Acciones",
commandAppearance: "Apariencia",
commandSearch: "Buscar",
toggleTheme: "Cambiar tema",
},
};
export default messages;
+71
View File
@@ -0,0 +1,71 @@
const messages = {
settings: {
title: "Paramètres",
description: "Préférences de langue et despace de travail pour cette app.",
languageTitle: "Langue",
languageDescription:
"Choisissez la langue de linterface. Cette préférence est enregistrée dans votre compte.",
languageLabel: "Langue de linterface",
workspaceTitle: "Espace de travail",
workspaceDescription:
"Gérez les membres, laccès de lorganisation et les préférences partagées.",
openTeamSettings: "Ouvrir les paramètres d’équipe",
openResourceSettings: "Ouvrir les paramètres des ressources",
agentTitle: "Gérer lagent",
agentDescription:
"Gérez le modèle de lagent, les clés API, les automatisations, la voix et les autres contrôles.",
openAgentSettings: "Gérer lagent",
},
chat: {
archiveChat: "Archiver le chat",
archiveFailed: "Échec de larchivage",
chats: "Discussions",
composerPlaceholder: "Demandez à lagent...",
emptyState: "Posez-moi une question",
heroDescription:
"Demandez à lagent dinspecter, dexpliquer ou de modifier cette app.",
heroTitle: "Comment puis-je aider ?",
inspectEmptyState: "Posez-moi une question sur cette app",
inspectSuggestionAction: "Afficher les actions disponibles",
inspectSuggestionCapabilities: "Que peut faire cette app ?",
inspectSuggestionHello: "Aidez-moi à démarrer",
newChat: "Nouveau chat",
optionsFor: "Options pour",
pinChat: "Épingler le chat",
renameChat: "Renommer le chat",
renameFailed: "Échec du renommage",
renameThread: "Renommer le fil",
suggestionActions: "Montrez-moi les actions disponibles",
suggestionCapabilities: "Que peut faire cette app ?",
suggestionCustomize: "Aidez-moi à personnaliser cette app",
unpinChat: "Désépingler le chat",
},
navigation: {
chat: "Chat",
collapseSidebar: "Réduire la barre latérale",
database: "Base de données",
expandSidebar: "Développer la barre latérale",
extensions: "Rallonges",
navigation: "Navigation",
navigationDescription: "Navigation principale",
observability: "Observabilité",
openNavigation: "Ouvrir la navigation",
settings: "Paramètres",
team: "Équipe",
},
pages: {
databaseTitle: "Base de données",
observabilityPageTitle: "Observabilité de l'agent",
teamTitle: "Équipe",
teamCreateOrgDescription:
"Créez une organisation pour inviter des coéquipiers et partager cette app.",
},
root: {
commandActions: "Opérations",
commandAppearance: "Apparence",
commandSearch: "Rechercher",
toggleTheme: "Changer de thème",
},
};
export default messages;
+69
View File
@@ -0,0 +1,69 @@
const messages = {
settings: {
title: "सेटिंग्स",
description: "इस ऐप के लिए भाषा और कार्यस्थान प्राथमिकताएं।",
languageTitle: "भाषा",
languageDescription: "इंटरफ़ेस भाषा चुनें। यह पसंद आपके खाते में सहेजी जाती है।",
languageLabel: "इंटरफ़ेस भाषा",
workspaceTitle: "कार्यस्थान",
workspaceDescription:
"टीम सदस्यों, संगठन पहुंच और साझा कार्यस्थान प्राथमिकताओं को प्रबंधित करें।",
openTeamSettings: "टीम सेटिंग्स खोलें",
openResourceSettings: "संसाधन सेटिंग्स खोलें",
agentTitle: "एजेंट प्रबंधित करें",
agentDescription:
"एजेंट के मॉडल, API कुंजियों, ऑटोमेशन, आवाज़ और अन्य नियंत्रणों को प्रबंधित करें।",
openAgentSettings: "एजेंट प्रबंधित करें",
},
chat: {
archiveChat: "चैट आर्काइव करें",
archiveFailed: "आर्काइव विफल",
chats: "चैट",
composerPlaceholder: "एजेंट से पूछें...",
emptyState: "मुझसे कुछ भी पूछें",
heroDescription: "एजेंट से इस ऐप की जांच, व्याख्या या बदलाव करने को कहें।",
heroTitle: "मैं कैसे मदद कर सकता हूं?",
inspectEmptyState: "इस ऐप के बारे में मुझसे कुछ भी पूछें",
inspectSuggestionAction: "उपलब्ध क्रियाएं दिखाएं",
inspectSuggestionCapabilities: "यह ऐप क्या कर सकता है?",
inspectSuggestionHello: "शुरू करने में मेरी मदद करें",
newChat: "नई चैट",
optionsFor: "इसके लिए विकल्प",
pinChat: "चैट पिन करें",
renameChat: "चैट का नाम बदलें",
renameFailed: "नाम बदलना विफल",
renameThread: "थ्रेड का नाम बदलें",
suggestionActions: "मुझे उपलब्ध क्रियाएं दिखाएं",
suggestionCapabilities: "यह ऐप क्या कर सकता है?",
suggestionCustomize: "इस ऐप को कस्टमाइज करने में मेरी मदद करें",
unpinChat: "चैट अनपिन करें",
},
navigation: {
chat: "चैट",
collapseSidebar: "साइडबार संक्षिप्त करें",
database: "डेटाबेस",
expandSidebar: "साइडबार विस्तृत करें",
extensions: "एक्सटेंशन",
navigation: "नेविगेशन",
navigationDescription: "मुख्य नेविगेशन",
observability: "अवलोकनक्षमता",
openNavigation: "नेविगेशन खोलें",
settings: "सेटिंग्स",
team: "टीम",
},
pages: {
databaseTitle: "डेटाबेस",
observabilityPageTitle: "एजेंट ऑब्ज़र्वेबिलिटी",
teamTitle: "टीम",
teamCreateOrgDescription:
"टीम के सदस्यों को आमंत्रित करने और यह ऐप साझा करने के लिए संगठन बनाएं।",
},
root: {
commandActions: "कार्रवाइयाँ",
commandAppearance: "दिखावट",
commandSearch: "खोजें",
toggleTheme: "थीम बदलें",
},
};
export default messages;
+34
View File
@@ -0,0 +1,34 @@
import { type AgentNativeI18nCatalog } from "@agent-native/core/client/i18n";
import enUS from "./en-US";
export const i18nCatalog = {
sourceLocale: "en-US",
messages: enUS,
loadMessages: async (locale) => {
switch (locale) {
case "zh-CN":
return (await import("./zh-CN")).default;
case "zh-TW":
return (await import("./zh-TW")).default;
case "es-ES":
return (await import("./es-ES")).default;
case "fr-FR":
return (await import("./fr-FR")).default;
case "de-DE":
return (await import("./de-DE")).default;
case "ja-JP":
return (await import("./ja-JP")).default;
case "ko-KR":
return (await import("./ko-KR")).default;
case "pt-BR":
return (await import("./pt-BR")).default;
case "hi-IN":
return (await import("./hi-IN")).default;
case "ar-SA":
return (await import("./ar-SA")).default;
default:
return null;
}
},
} satisfies AgentNativeI18nCatalog;
+70
View File
@@ -0,0 +1,70 @@
const messages = {
settings: {
title: "設定",
description: "このアプリの言語とワークスペース設定。",
languageTitle: "言語",
languageDescription:
"インターフェース言語を選択します。この設定はアカウントに保存されます。",
languageLabel: "インターフェース言語",
workspaceTitle: "ワークスペース",
workspaceDescription:
"チームメンバー、組織アクセス、共有ワークスペース設定を管理します。",
openTeamSettings: "チーム設定を開く",
openResourceSettings: "リソース設定を開く",
agentTitle: "エージェントを管理",
agentDescription:
"エージェントのモデル、API キー、自動化、音声などを管理します。",
openAgentSettings: "エージェントを管理",
},
chat: {
archiveChat: "チャットをアーカイブ",
archiveFailed: "アーカイブに失敗しました",
chats: "チャット",
composerPlaceholder: "エージェントに質問...",
emptyState: "何でも聞いてください",
heroDescription: "このアプリの確認、説明、変更をエージェントに依頼します。",
heroTitle: "どのようにお手伝いできますか?",
inspectEmptyState: "このアプリについて何でも聞いてください",
inspectSuggestionAction: "利用可能なアクションを表示",
inspectSuggestionCapabilities: "このアプリで何ができますか?",
inspectSuggestionHello: "使い始めを手伝って",
newChat: "新しいチャット",
optionsFor: "オプション:",
pinChat: "チャットをピン留め",
renameChat: "チャット名を変更",
renameFailed: "名前の変更に失敗しました",
renameThread: "スレッド名を変更",
suggestionActions: "利用可能なアクションを表示して",
suggestionCapabilities: "このアプリで何ができますか?",
suggestionCustomize: "このアプリのカスタマイズを手伝って",
unpinChat: "チャットのピン留めを解除",
},
navigation: {
chat: "チャット",
collapseSidebar: "サイドバーを折りたたむ",
database: "データベース",
expandSidebar: "サイドバーを展開",
extensions: "拡張機能",
navigation: "ナビゲーション",
navigationDescription: "メインナビゲーション",
observability: "可観測性",
openNavigation: "ナビゲーションを開く",
settings: "設定",
team: "チーム",
},
pages: {
databaseTitle: "データベース",
observabilityPageTitle: "エージェント可観測性",
teamTitle: "チーム",
teamCreateOrgDescription:
"チームメイトを招待してこのアプリを共有するための組織を作成します。",
},
root: {
commandActions: "操作",
commandAppearance: "外観",
commandSearch: "検索",
toggleTheme: "テーマを切り替え",
},
};
export default messages;
+70
View File
@@ -0,0 +1,70 @@
const messages = {
settings: {
title: "설정",
description: "이 앱의 언어 및 워크스페이스 환경설정입니다.",
languageTitle: "언어",
languageDescription:
"인터페이스 언어를 선택하세요. 이 기본 설정은 계정에 저장됩니다.",
languageLabel: "인터페이스 언어",
workspaceTitle: "워크스페이스",
workspaceDescription:
"팀원, 조직 접근 권한, 공유 워크스페이스 환경설정을 관리합니다.",
openTeamSettings: "팀 설정 열기",
openResourceSettings: "리소스 설정 열기",
agentTitle: "에이전트 관리",
agentDescription:
"에이전트의 모델, API 키, 자동화, 음성 및 기타 제어를 관리합니다.",
openAgentSettings: "에이전트 관리",
},
chat: {
archiveChat: "채팅 보관",
archiveFailed: "보관 실패",
chats: "채팅",
composerPlaceholder: "에이전트에게 물어보세요...",
emptyState: "무엇이든 물어보세요",
heroDescription:
"에이전트에게 이 앱을 살펴보고 설명하거나 변경하도록 요청하세요.",
heroTitle: "무엇을 도와드릴까요?",
inspectEmptyState: "이 앱에 대해 무엇이든 물어보세요",
inspectSuggestionAction: "사용 가능한 작업 보기",
inspectSuggestionCapabilities: "이 앱은 무엇을 할 수 있나요?",
inspectSuggestionHello: "시작할 수 있게 도와주세요",
newChat: "새 채팅",
optionsFor: "옵션 대상",
pinChat: "채팅 고정",
renameChat: "채팅 이름 바꾸기",
renameFailed: "이름 변경 실패",
renameThread: "스레드 이름 바꾸기",
suggestionActions: "사용 가능한 작업을 보여줘",
suggestionCapabilities: "이 앱은 무엇을 할 수 있나요?",
suggestionCustomize: "이 앱을 맞춤 설정하도록 도와줘",
unpinChat: "채팅 고정 해제",
},
navigation: {
chat: "채팅",
collapseSidebar: "사이드바 접기",
database: "데이터베이스",
expandSidebar: "사이드바 펼치기",
extensions: "확장 프로그램",
navigation: "탐색",
navigationDescription: "기본 탐색",
observability: "관찰성",
openNavigation: "탐색 열기",
settings: "설정",
team: "팀",
},
pages: {
databaseTitle: "데이터베이스",
observabilityPageTitle: "에이전트 관찰성",
teamTitle: "팀",
teamCreateOrgDescription: "팀원을 초대하고 이 앱을 공유할 조직을 만드세요.",
},
root: {
commandActions: "작업",
commandAppearance: "화면 표시",
commandSearch: "검색",
toggleTheme: "테마 전환",
},
};
export default messages;
+71
View File
@@ -0,0 +1,71 @@
const messages = {
settings: {
title: "Configurações",
description: "Preferências de idioma e espaço de trabalho deste app.",
languageTitle: "Idioma",
languageDescription:
"Escolha o idioma da interface. Essa preferência é salva na sua conta.",
languageLabel: "Idioma da interface",
workspaceTitle: "Espaço de trabalho",
workspaceDescription:
"Gerencie membros da equipe, acesso da organização e preferências compartilhadas.",
openTeamSettings: "Abrir configurações da equipe",
openResourceSettings: "Abrir configurações de recursos",
agentTitle: "Gerenciar agente",
agentDescription:
"Gerencie o modelo do agente, chaves de API, automações, voz e outros controles.",
openAgentSettings: "Gerenciar agente",
},
chat: {
archiveChat: "Arquivar chat",
archiveFailed: "Falha ao arquivar",
chats: "Chats",
composerPlaceholder: "Pergunte ao agente...",
emptyState: "Pergunte-me qualquer coisa",
heroDescription:
"Peça ao agente para inspecionar, explicar ou alterar este app.",
heroTitle: "Como posso ajudar?",
inspectEmptyState: "Pergunte-me qualquer coisa sobre este app",
inspectSuggestionAction: "Mostrar ações disponíveis",
inspectSuggestionCapabilities: "O que este app pode fazer?",
inspectSuggestionHello: "Ajude-me a começar",
newChat: "Novo chat",
optionsFor: "Opções para",
pinChat: "Fixar chat",
renameChat: "Renomear chat",
renameFailed: "Falha ao renomear",
renameThread: "Renomear conversa",
suggestionActions: "Mostre-me as ações disponíveis",
suggestionCapabilities: "O que este app pode fazer?",
suggestionCustomize: "Ajude-me a personalizar este app",
unpinChat: "Desafixar chat",
},
navigation: {
chat: "Chat",
collapseSidebar: "Recolher barra lateral",
database: "Banco de dados",
expandSidebar: "Expandir barra lateral",
extensions: "Extensões",
navigation: "Navegação",
navigationDescription: "Navegação principal",
observability: "Observabilidade",
openNavigation: "Abrir navegação",
settings: "Configurações",
team: "Equipe",
},
pages: {
databaseTitle: "Banco de dados",
observabilityPageTitle: "Observabilidade do agente",
teamTitle: "Equipe",
teamCreateOrgDescription:
"Crie uma organização para convidar colegas e compartilhar este app.",
},
root: {
commandActions: "Ações",
commandAppearance: "Aparência",
commandSearch: "Pesquisar",
toggleTheme: "Alternar tema",
},
};
export default messages;
+66
View File
@@ -0,0 +1,66 @@
const messages = {
settings: {
title: "设置",
description: "此应用的语言和工作区偏好设置。",
languageTitle: "语言",
languageDescription: "选择界面语言。此偏好会保存到你的账户。",
languageLabel: "界面语言",
workspaceTitle: "工作区",
workspaceDescription: "管理团队成员、组织访问权限和共享工作区偏好。",
openTeamSettings: "打开团队设置",
openResourceSettings: "打开资源设置",
agentTitle: "管理代理",
agentDescription: "管理代理的模型、API 密钥、自动化、语音和其他控制项。",
openAgentSettings: "管理代理",
},
chat: {
archiveChat: "归档聊天",
archiveFailed: "归档失败",
chats: "聊天",
composerPlaceholder: "询问代理...",
emptyState: "问我任何问题",
heroDescription: "让代理检查、解释或更改此应用。",
heroTitle: "我能帮你什么?",
inspectEmptyState: "询问我有关此应用的任何问题",
inspectSuggestionAction: "显示可用操作",
inspectSuggestionCapabilities: "这个应用能做什么?",
inspectSuggestionHello: "帮我开始使用",
newChat: "新建聊天",
optionsFor: "选项:",
pinChat: "置顶聊天",
renameChat: "重命名聊天",
renameFailed: "重命名失败",
renameThread: "重命名对话",
suggestionActions: "显示可用操作",
suggestionCapabilities: "这个应用能做什么?",
suggestionCustomize: "帮我自定义此应用",
unpinChat: "取消置顶聊天",
},
navigation: {
chat: "聊天",
collapseSidebar: "收起侧边栏",
database: "数据库",
expandSidebar: "展开侧边栏",
extensions: "扩展",
navigation: "导航",
navigationDescription: "主导航",
observability: "可观测性",
openNavigation: "打开导航",
settings: "设置",
team: "团队",
},
pages: {
databaseTitle: "数据库",
observabilityPageTitle: "代理可观测性",
teamTitle: "团队",
teamCreateOrgDescription: "创建组织以邀请队友并共享此应用。",
},
root: {
commandActions: "操作",
commandAppearance: "外观",
commandSearch: "搜索",
toggleTheme: "切换主题",
},
};
export default messages;
+66
View File
@@ -0,0 +1,66 @@
const messages = {
settings: {
title: "設定",
description: "此應用的語言和工作區偏好設定。",
languageTitle: "語言",
languageDescription: "選取介面語言。此偏好會儲存到你的帳戶。",
languageLabel: "介面語言",
workspaceTitle: "工作區",
workspaceDescription: "管理團隊成員、組織存取權限和共用工作區偏好。",
openTeamSettings: "開啟團隊設定",
openResourceSettings: "開啟資源設定",
agentTitle: "管理代理",
agentDescription: "管理代理的模型、API 金鑰、自動化、語音和其他控制項。",
openAgentSettings: "管理代理",
},
chat: {
archiveChat: "封存聊天",
archiveFailed: "封存失敗",
chats: "聊天",
composerPlaceholder: "詢問代理...",
emptyState: "問我任何問題",
heroDescription: "讓代理檢查、解釋或更改此應用。",
heroTitle: "我能幫你什麼?",
inspectEmptyState: "詢問我有關此應用的任何問題",
inspectSuggestionAction: "顯示可用操作",
inspectSuggestionCapabilities: "這個應用能做什麼?",
inspectSuggestionHello: "幫我開始使用",
newChat: "新建聊天",
optionsFor: "選項:",
pinChat: "置頂聊天",
renameChat: "重新命名聊天",
renameFailed: "重新命名失敗",
renameThread: "重新命名對話",
suggestionActions: "顯示可用操作",
suggestionCapabilities: "這個應用能做什麼?",
suggestionCustomize: "幫我自訂此應用",
unpinChat: "取消置頂聊天",
},
navigation: {
chat: "聊天",
collapseSidebar: "收起側邊欄",
database: "資料庫",
expandSidebar: "展開側邊欄",
extensions: "擴充功能",
navigation: "導覽",
navigationDescription: "主導覽",
observability: "可觀測性",
openNavigation: "開啟導覽",
settings: "設定",
team: "團隊",
},
pages: {
databaseTitle: "資料庫",
observabilityPageTitle: "代理可觀測性",
teamTitle: "團隊",
teamCreateOrgDescription: "建立組織以邀請隊友並共用此應用。",
},
root: {
commandActions: "操作",
commandAppearance: "外觀",
commandSearch: "搜尋",
toggleTheme: "切換主題",
},
};
export default messages;
+49
View File
@@ -0,0 +1,49 @@
import type { ComponentType } from "react";
export type AgentPageProps = {
appName?: string;
};
type AgentClientModule = {
AgentChatSurface: ComponentType<{
mode?: "panel" | "page";
className?: string;
showHeader?: boolean;
showTabBar?: boolean;
}>;
AgentTabsPage?: ComponentType<AgentPageProps>;
};
const legacyAgentPages = new WeakMap<
AgentClientModule,
ComponentType<AgentPageProps>
>();
/**
* Keep the chat scaffold runnable when its template and core package are
* briefly out of sync during a release. Older core versions do not export
* AgentTabsPage, but they do expose the page-level chat surface.
*/
export function resolveAgentPageComponent(
client: AgentClientModule,
): ComponentType<AgentPageProps> {
if (typeof client.AgentTabsPage === "function") {
return client.AgentTabsPage;
}
const existing = legacyAgentPages.get(client);
if (existing) return existing;
const legacyAgentPage = function LegacyAgentPage() {
return (
<client.AgentChatSurface
mode="page"
className="h-full"
showHeader={false}
showTabBar={false}
/>
);
};
legacyAgentPages.set(client, legacyAgentPage);
return legacyAgentPage;
}
+11
View File
@@ -0,0 +1,11 @@
const rawAppName = "app";
const rawAppTitle = "App";
const APP_NAME_PLACEHOLDER = "{" + "{APP_NAME}}";
const APP_TITLE_PLACEHOLDER = "{" + "{APP_TITLE}}";
export const APP_NAME =
rawAppName === APP_NAME_PLACEHOLDER ? "chat" : rawAppName;
export const APP_TITLE =
rawAppTitle === APP_TITLE_PLACEHOLDER ? "Chat" : rawAppTitle;
+1
View File
@@ -0,0 +1 @@
export const TAB_ID = Math.random().toString(36).slice(2, 10);
+1
View File
@@ -0,0 +1 @@
export { cn } from "@agent-native/toolkit/utils";
+174
View File
@@ -0,0 +1,174 @@
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";
+4
View File
@@ -0,0 +1,4 @@
import { type RouteConfig } from "@react-router/dev/routes";
import { flatRoutes } from "@react-router/fs-routes";
export default flatRoutes() satisfies RouteConfig;
+94
View File
@@ -0,0 +1,94 @@
import {
AgentChatSurface,
markAgentChatHomeHandoff,
} from "@agent-native/core/client/agent-chat";
import { useT } from "@agent-native/core/client/i18n";
import { useEffect } from "react";
import { useNavigate, useParams } from "react-router";
import { APP_TITLE } from "@/lib/app-config";
import { TAB_ID } from "@/lib/tab-id";
const SEO_TITLE = `${APP_TITLE} - Open Source AI app starter with actions`;
const SEO_DESCRIPTION =
"Open Source starter for agent-native apps with durable chat, shared actions, UI state, tools, and a backend your agent can extend.";
export function meta() {
return [
{ title: SEO_TITLE },
{
name: "description",
content: SEO_DESCRIPTION,
},
{ property: "og:title", content: SEO_TITLE },
{ property: "og:description", content: SEO_DESCRIPTION },
{ name: "twitter:card", content: "summary" },
{ name: "twitter:title", content: SEO_TITLE },
{ name: "twitter:description", content: SEO_DESCRIPTION },
];
}
function chatThreadPath(threadId: string | null) {
return threadId ? `/chat/${encodeURIComponent(threadId)}` : "/";
}
export default function ChatRoute() {
const { threadId } = useParams();
const navigate = useNavigate();
const t = useT();
const threadUrlSync = threadId
? {
routeThreadId: threadId,
getPath: chatThreadPath,
navigate,
}
: undefined;
useEffect(() => {
function handleChatRunning(event: Event) {
const detail = (event as CustomEvent).detail;
if (detail?.isRunning === true) markAgentChatHomeHandoff("chat");
}
window.addEventListener("agentNative.chatRunning", handleChatRunning);
return () =>
window.removeEventListener("agentNative.chatRunning", handleChatRunning);
}, []);
return (
<div className="flex h-full min-h-0 flex-col bg-background">
<AgentChatSurface
mode="page"
chatViewTransition
className="h-full"
defaultMode="chat"
storageKey="chat"
threadUrlSync={threadUrlSync}
browserTabId={TAB_ID}
showHeader={false}
showTabBar={false}
dynamicSuggestions={false}
suggestions={[
t("chat.suggestionCapabilities"),
t("chat.suggestionCustomize"),
t("chat.suggestionActions"),
]}
emptyStateText={t("chat.emptyState")}
emptyStateDisplay="hidden"
centerComposerWhenEmpty
composerLayoutVariant="hero"
composerPlaceholder={t("chat.composerPlaceholder")}
composerSlot={
<div className="mx-auto mb-5 max-w-xl px-4 text-center">
<h1 className="text-2xl font-semibold tracking-normal text-foreground sm:text-3xl">
{t("chat.heroTitle")}
</h1>
<p className="mt-2 text-sm leading-6 text-muted-foreground">
{t("chat.heroDescription")}
</p>
</div>
}
/>
</div>
);
}
+24
View File
@@ -0,0 +1,24 @@
import {
AgentChatSurface,
AgentTabsPage,
} from "@agent-native/core/client/agent-chat";
import { useT } from "@agent-native/core/client/i18n";
import { useSetPageTitle } from "@agent-native/toolkit/app-shell";
import { resolveAgentPageComponent } from "@/lib/agent-page";
import { APP_TITLE } from "@/lib/app-config";
export function meta() {
return [{ title: `Agent - ${APP_TITLE}` }];
}
export default function AgentRoute() {
const t = useT();
useSetPageTitle(t("settings.agentTitle"));
const AgentPage = resolveAgentPageComponent({
AgentChatSurface,
AgentTabsPage,
});
return <AgentPage appName={APP_TITLE} />;
}
+1
View File
@@ -0,0 +1 @@
export { default, meta } from "./_index";
+17
View File
@@ -0,0 +1,17 @@
import { DbAdminPage } from "@agent-native/core/client/db-admin";
import { useT } from "@agent-native/core/client/i18n";
import { useSetPageTitle } from "@agent-native/toolkit/app-shell";
export function meta() {
return [{ title: "Database" }];
}
export default function DatabasePage() {
const t = useT();
useSetPageTitle(t("pages.databaseTitle"));
return (
<div className="h-full">
<DbAdminPage />
</div>
);
}
+2
View File
@@ -0,0 +1,2 @@
export * from "./extensions.$id";
export { default } from "./extensions.$id";
+11
View File
@@ -0,0 +1,11 @@
import { ExtensionViewerPage } from "@agent-native/core/client/extensions";
import { APP_TITLE } from "@/lib/app-config";
export function meta() {
return [{ title: `Extension — ${APP_TITLE}` }];
}
export default function ExtensionViewerRoute() {
return <ExtensionViewerPage />;
}
+11
View File
@@ -0,0 +1,11 @@
import { Navigate } from "react-router";
import { APP_TITLE } from "@/lib/app-config";
export function meta() {
return [{ title: `Extensions — ${APP_TITLE}` }];
}
export default function ExtensionsRoute() {
return <Navigate to="/settings#extensions" replace />;
}
+5
View File
@@ -0,0 +1,5 @@
import { Outlet } from "react-router";
export default function ExtensionsLayout() {
return <Outlet />;
}
+19
View File
@@ -0,0 +1,19 @@
import { useT } from "@agent-native/core/client/i18n";
import { ObservabilityDashboard } from "@agent-native/core/client/observability";
import { useSetPageTitle } from "@agent-native/toolkit/app-shell";
import enUS from "@/i18n/en-US";
export function meta() {
return [{ title: enUS.pages.observabilityPageTitle }];
}
export default function ObservabilityPage() {
const t = useT();
useSetPageTitle(t("pages.observabilityPageTitle"));
return (
<div className="p-6">
<ObservabilityDashboard />
</div>
);
}
+89
View File
@@ -0,0 +1,89 @@
import { ChangelogSettingsCard } from "@agent-native/core/client/changelog";
import { LanguagePicker, useT } from "@agent-native/core/client/i18n";
import { TeamPage } from "@agent-native/core/client/org";
import {
AccountSettingsCard,
SettingsTabsPage,
useAgentSettingsTabs,
type SettingsSearchEntry,
} from "@agent-native/core/client/settings";
import { useSetPageTitle } from "@agent-native/toolkit/app-shell";
import { useMemo } from "react";
import {
Card,
CardContent,
CardDescription,
CardHeader,
CardTitle,
} from "@/components/ui/card";
import { Label } from "@/components/ui/label";
import { APP_TITLE } from "@/lib/app-config";
import changelog from "../../CHANGELOG.md?raw";
export function meta() {
return [{ title: `Settings - ${APP_TITLE}` }];
}
export default function SettingsRoute() {
const t = useT();
const agentSettingsTabs = useAgentSettingsTabs();
useSetPageTitle(t("settings.title"));
const generalSearchEntries = useMemo<SettingsSearchEntry[]>(
() => [
{
id: "chat-language",
label: t("settings.languageTitle"),
keywords: "language locale translation i18n",
hash: "language",
},
],
[t],
);
return (
<SettingsTabsPage
account={<AccountSettingsCard />}
teamLabel={t("navigation.team")}
extraTabs={agentSettingsTabs}
generalSearchEntries={generalSearchEntries}
general={
<div className="mx-auto w-full max-w-2xl space-y-6">
<p className="text-sm leading-6 text-muted-foreground">
{t("settings.description")}
</p>
<Card id="language" className="scroll-mt-16">
<CardHeader>
<CardTitle className="text-base">
{t("settings.languageTitle")}
</CardTitle>
<CardDescription>
{t("settings.languageDescription")}
</CardDescription>
</CardHeader>
<CardContent className="max-w-xs space-y-1.5">
<Label>{t("settings.languageLabel")}</Label>
<LanguagePicker label={t("settings.languageLabel")} />
</CardContent>
</Card>
</div>
}
team={
<div className="mx-auto w-full max-w-3xl">
<TeamPage
showTitle={false}
createOrgDescription={t("pages.teamCreateOrgDescription")}
/>
</div>
}
whatsNew={
<div className="mx-auto w-full max-w-2xl">
<ChangelogSettingsCard markdown={changelog} />
</div>
}
/>
);
}
+11
View File
@@ -0,0 +1,11 @@
import { Navigate } from "react-router";
import { APP_TITLE } from "@/lib/app-config";
export function meta() {
return [{ title: `Team — ${APP_TITLE}` }];
}
export default function TeamRoute() {
return <Navigate to="/settings#organization" replace />;
}
+6
View File
@@ -0,0 +1,6 @@
/// <reference types="vite/client" />
declare module "react-dom/server.browser" {
export * from "react-dom/server";
export { default } from "react-dom/server";
}