New /analytics route: token usage dashboard with aggregate summary, per-session breakdown, context window stats, and per-category token distribution. Data served from existing agent_sessions + tool_cost_stats. New /results route: browsable archive of orchestrator flow runs and arena battles. Two-tab layout (Analysis Runs / Arena Battles) using existing API endpoints (no new backend). Sidebar gains Results (ScrollText icon) and Token Analytics (BarChart3 icon) nav buttons above Settings.
127 lines
4.6 KiB
TypeScript
127 lines
4.6 KiB
TypeScript
import { useEffect, useState } from 'react';
|
|
import { BrowserRouter, Routes, Route, useParams } from 'react-router-dom';
|
|
import { api } from '@/api/client';
|
|
import { ProjectSidebar } from '@/components/ProjectSidebar';
|
|
import { RightRail } from '@/components/RightRail';
|
|
import { Home } from '@/pages/Home';
|
|
import { Project } from '@/pages/Project';
|
|
import { Session } from '@/pages/Session';
|
|
import { Settings } from '@/pages/Settings';
|
|
import { Analytics } from '@/pages/Analytics';
|
|
import { Results } from '@/pages/Results';
|
|
import { Toaster } from '@/components/ui/sonner';
|
|
import { useUserEvents } from '@/hooks/useUserEvents';
|
|
import { useCoderUserEvents } from '@/hooks/useCoderUserEvents';
|
|
import { useTheme } from '@/lib/theme';
|
|
import { SidebarDrawerProvider, useSidebarDrawer } from '@/hooks/useSidebarDrawer';
|
|
import { RightRailDrawerProvider, useRightRailDrawer } from '@/hooks/useRightRailDrawer';
|
|
import { useViewport } from '@/hooks/useViewport';
|
|
import { ThemeFx } from '@/components/fx/ThemeFx';
|
|
import { FlowLauncherDialog } from '@/components/FlowLauncherDialog';
|
|
import { ArenaLauncherDialog } from '@/components/ArenaLauncherDialog';
|
|
|
|
function SessionRightRail() {
|
|
const { id } = useParams<{ id: string }>();
|
|
if (!id) return null;
|
|
return <RightRailForSession sessionId={id} />;
|
|
}
|
|
|
|
function RightRailForSession({ sessionId }: { sessionId: string }) {
|
|
const [projectId, setProjectId] = useState<string | null>(null);
|
|
useEffect(() => {
|
|
api.sessions
|
|
.get(sessionId)
|
|
.then((s) => setProjectId(s.project_id))
|
|
.catch((err) => console.warn('RightRail: failed to fetch session', err));
|
|
}, [sessionId]);
|
|
if (!projectId) return null;
|
|
// v1.6.2: rendered on all viewports. On mobile, RightRail itself renders as
|
|
// a right-side drawer toggled by the header's FolderTree button (via
|
|
// useRightRailDrawer). On desktop, it renders inline as before with its
|
|
// own internal open/close state.
|
|
return <RightRail projectId={projectId} sessionId={sessionId} />;
|
|
}
|
|
|
|
function MobileBackdrop() {
|
|
const { open, setOpen } = useSidebarDrawer();
|
|
const { isMobile } = useViewport();
|
|
if (!isMobile || !open) return null;
|
|
return (
|
|
<div
|
|
className="fixed inset-0 z-30 bg-black/40 md:hidden"
|
|
onClick={() => setOpen(false)}
|
|
aria-hidden="true"
|
|
/>
|
|
);
|
|
}
|
|
|
|
function MobileRightRailBackdrop() {
|
|
const { open, setOpen } = useRightRailDrawer();
|
|
const { isMobile } = useViewport();
|
|
if (!isMobile || !open) return null;
|
|
return (
|
|
<div
|
|
className="fixed inset-0 z-30 bg-black/40 md:hidden"
|
|
onClick={() => setOpen(false)}
|
|
aria-hidden="true"
|
|
/>
|
|
);
|
|
}
|
|
|
|
function AppShell() {
|
|
// themes-v1: useTheme() owns the matchMedia subscription for system mode
|
|
// and reconciles cache with /api/settings on mount. Mounted first so the
|
|
// theme class on <html> is correct before any child renders.
|
|
useTheme();
|
|
useUserEvents();
|
|
useCoderUserEvents();
|
|
// v1.10.8c: h-dvh (dynamic viewport) instead of h-screen (100vh) so the
|
|
// root height excludes the iOS URL-bar overlay area. Without this, every
|
|
// descendant — including the terminal pane — measures itself against a
|
|
// height that extends behind the URL bar, and xterm allocates extra rows
|
|
// that scroll out of reach on iPhone.
|
|
//
|
|
// ThemeFx: canvas-based animated backgrounds (Classic rain, Override neon
|
|
// field) rendered at z-0 behind the content wrapper. ThemeFx also manages
|
|
// the bc-anim-on gate class on <html>. Content wrapper at z-10 ensures all
|
|
// UI sits above any fixed canvas regardless of theme.
|
|
return (
|
|
<>
|
|
<ThemeFx />
|
|
<div className="h-dvh flex bg-background text-foreground relative z-10">
|
|
<ProjectSidebar />
|
|
<MobileBackdrop />
|
|
<main className="flex-1 flex flex-col min-w-0">
|
|
<Routes>
|
|
<Route path="/" element={<Home />} />
|
|
<Route path="/project/:id" element={<Project />} />
|
|
<Route path="/session/:id" element={<Session />} />
|
|
<Route path="/settings" element={<Settings />} />
|
|
<Route path="/analytics" element={<Analytics />} />
|
|
<Route path="/results" element={<Results />} />
|
|
</Routes>
|
|
</main>
|
|
<MobileRightRailBackdrop />
|
|
<Routes>
|
|
<Route path="/session/:id" element={<SessionRightRail />} />
|
|
</Routes>
|
|
<Toaster position="bottom-right" />
|
|
<FlowLauncherDialog />
|
|
<ArenaLauncherDialog />
|
|
</div>
|
|
</>
|
|
);
|
|
}
|
|
|
|
export default function App() {
|
|
return (
|
|
<BrowserRouter>
|
|
<SidebarDrawerProvider>
|
|
<RightRailDrawerProvider>
|
|
<AppShell />
|
|
</RightRailDrawerProvider>
|
|
</SidebarDrawerProvider>
|
|
</BrowserRouter>
|
|
);
|
|
}
|