- Add stocks table (server/db) with list/get/add/update/delete actions, scoped per user, backing both the chat agent and the /stocks UI via useActionQuery/useActionMutation - Add mobile-first /stocks page (shadcn table/cards, watchlist, search) and wire it into the sidebar, navigation state, and view-screen context - Install the platform's Claude Agent SDK harness (server/plugins, server/routes/api/claude-agent) so the app agent runs on the platform's own Claude subscription instead of the Builder.io gateway; fix the vendored turn.post.ts route to resolve the session and wrap it in runWithRequestContext, since custom /api/ routes aren't auto-wrapped in request context the way /_agent-native/actions/* are - Add shadcn table/badge/dialog/select components (Tabler icons, not lucide)
97 lines
2.6 KiB
TypeScript
97 lines
2.6 KiB
TypeScript
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("/stocks")) return "stocks";
|
|
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 "stocks":
|
|
return "/stocks";
|
|
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/");
|
|
}
|