feat(web): add Agent Studio workspace UI
This commit is contained in:
@@ -0,0 +1,76 @@
|
||||
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>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user