Arena is a new pane kind for competitive AI evaluation. A Battle runs the same prompt against 2-6 Contestants across two concurrent lanes: local lane (llama-swap models, serial) and cloud lane (parallel). Added to all three registries: @boocode/contracts WsFrameSchema, server InferenceFrame, and web WsFrame. Backend (apps/coder): - arena-runner: battle scheduler, lane classifier, benchmark, results writer, resume, user winner override - arena-analyzer: two-stage digest→judge analysis on DEFAULT_MODEL - arena-decisions: status transitions and resume logic (unit-tested) - arena-analyzer-helpers: pure helper functions (unit-tested) - arena-model-call: model call utility for analysis - arena routes: create/get/list/stop/analyze/cross-examine/winner/diff - schema: battles, contestants, cross_examinations tables (idempotent) - remove old /api/arena* routes and tasks.arena_id column Frontend (apps/web): - ArenaLauncherDialog: battle type, prompt, contestant selection - ArenaPane: live roster, streaming output, analysis, cross-exam - DiffView: unified diff with line-by-line color for coding contests - Winner override per-row dropdown (Trophy icon) - battle_updated WS handler for live winner/analysis updates - arena pane kind in Workspace, ChatTabBar, useSidebar Cross-app: - ArenaState and ArenaContestantShape/WsFrame types (contracts) - battle_* frames in WsFrameSchema, InferenceFrame, and web WsFrame - manifest.json written per battle results folder - /Arena added to .gitignore
123 lines
4.4 KiB
TypeScript
123 lines
4.4 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 { 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 />} />
|
|
</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>
|
|
);
|
|
}
|