Files
agent/apps/web/src/App.tsx
T

77 lines
3.0 KiB
TypeScript

import { AssistantRuntimeProvider } from "@assistant-ui/react";
import { useEffect, useState, useSyncExternalStore } from "react";
import { useHarnessRuntime, type HarnessRuntimeStore } from "./assistant/use-harness-runtime.js";
import { Thread } from "./components/chat/Thread.js";
import { AppShell } from "./components/layout/AppShell.js";
import { SkillsPanel } from "./components/skills/SkillsPanel.js";
import { EventTimeline } from "./components/timeline/EventTimeline.js";
import { HarnessClientStore } from "./store/harness-client-store.js";
type AppStore = HarnessRuntimeStore & {
start(): Promise<void>;
getModelConfig(): Promise<{ modelConfig: { configured: boolean; modelId?: string; supportsImages?: boolean } }>;
};
export type AppProps = {
store?: AppStore;
autoStart?: boolean;
modelConfigured?: boolean;
};
export function App({ store: suppliedStore, autoStart = true, modelConfigured: suppliedModelConfigured }: AppProps) {
const [ownedStore] = useState(() => new HarnessClientStore());
const store = suppliedStore ?? ownedStore;
const snapshot = useSyncExternalStore(
(subscriber) => store.subscribe(subscriber),
() => store.getSnapshot(),
() => store.getSnapshot(),
);
const [model, setModel] = useState({
configured: suppliedModelConfigured ?? false,
modelId: suppliedModelConfigured === true ? "Configured model" : "No model",
supportsImages: false,
});
const runtime = useHarnessRuntime(store, { isSendDisabled: !model.configured });
const busy = Object.values(snapshot.runs).some((run) => run.status === "running");
const approvalPending = Object.values(snapshot.approvals).some((approval) => approval.status === "pending");
useEffect(() => {
if (!autoStart) return undefined;
let active = true;
void store.start().then(async () => {
const response = await store.getModelConfig();
if (active) setModel({
configured: response.modelConfig.configured,
modelId: response.modelConfig.modelId ?? "No model",
supportsImages: response.modelConfig.supportsImages ?? false,
});
}).catch(() => undefined);
return () => { active = false; };
}, [autoStart, store]);
return (
<AssistantRuntimeProvider runtime={runtime}>
<AppShell
topBar={{
...(snapshot.sessionId === undefined ? {} : { sessionId: snapshot.sessionId }),
connection: snapshot.connection,
modelLabel: model.modelId,
supportsImages: model.supportsImages,
onOpenSettings: () => { window.dispatchEvent(new CustomEvent("agent-studio:open-settings")); },
onReset: () => window.location.reload(),
}}
skills={<SkillsPanel skills={snapshot.skills} />}
chat={<Thread
runtime={runtime}
busy={busy}
modelConfigured={model.configured}
connection={snapshot.connection}
error={snapshot.error}
approvalPending={approvalPending}
/>}
timeline={<EventTimeline events={snapshot.timeline} />}
/>
</AssistantRuntimeProvider>
);
}