Implement Phase 2: Next.js + React Flow frontend MVP
- Scaffold Next.js 15 app with TypeScript, Tailwind, App Router - Install @xyflow/react, Zustand, Lucide icons, nanoid - Define council types (AgentNodeData, CouncilBlueprint, WSMessage, etc.) - Implement Zustand store for canvas and run state - Build custom AgentNode component (label, system prompt, model badge, tool chips, active pulse) - Build ConditionalEdge component (dashed indigo line with condition label) - Build NodeSidebar (drag-and-drop + click to add agents) - Build NodeSettingsPanel (name, system prompt, model selector, tool toggles) - Build ArchitectCanvas (React Flow canvas with drop zone, minimap, controls) - Build blueprint parser (React Flow JSON ↔ CouncilBlueprint JSON) - Build API client for FastAPI backend (CRUD + run endpoints) - Build useCouncilWebSocket hook for live agent status via WebSocket - Build Tab A: Rat-Architekt (canvas builder with save/export toolbar) - Build Tab B: Konferenzzimmer (execution view with live diagram + result panel) - Add NavTabs navigation with CouncilOS branding - All TypeScript checks passing https://claude.ai/code/session_01EkbecUVn7esdxLCXxVVRDX
This commit is contained in:
parent
06aec41a8a
commit
216fdd9589
30 changed files with 8237 additions and 0 deletions
74
frontend/app/hooks/useCouncilWebSocket.ts
Normal file
74
frontend/app/hooks/useCouncilWebSocket.ts
Normal file
|
|
@ -0,0 +1,74 @@
|
|||
"use client";
|
||||
|
||||
import { useEffect, useRef, useCallback } from "react";
|
||||
import { WSMessage } from "@/app/types/council";
|
||||
import { wsUrl } from "@/app/utils/api-client";
|
||||
import { useCouncilStore } from "@/app/store/council-store";
|
||||
|
||||
interface Options {
|
||||
run_id: string | null;
|
||||
onComplete?: (result: string) => void;
|
||||
onError?: (error: string) => void;
|
||||
}
|
||||
|
||||
// WebSocket hook for live agent status updates during a council run
|
||||
export function useCouncilWebSocket({ run_id, onComplete, onError }: Options) {
|
||||
const ws = useRef<WebSocket | null>(null);
|
||||
const markNodeActive = useCouncilStore((s) => s.markNodeActive);
|
||||
const clearActiveNode = useCouncilStore((s) => s.clearActiveNode);
|
||||
const setActiveRun = useCouncilStore((s) => s.setActiveRun);
|
||||
|
||||
const disconnect = useCallback(() => {
|
||||
if (ws.current) {
|
||||
ws.current.close();
|
||||
ws.current = null;
|
||||
}
|
||||
clearActiveNode();
|
||||
}, [clearActiveNode]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!run_id) return;
|
||||
|
||||
const socket = new WebSocket(wsUrl(run_id));
|
||||
ws.current = socket;
|
||||
|
||||
socket.onmessage = (event: MessageEvent<string>) => {
|
||||
let msg: WSMessage;
|
||||
try {
|
||||
msg = JSON.parse(event.data) as WSMessage;
|
||||
} catch {
|
||||
return;
|
||||
}
|
||||
|
||||
switch (msg.type) {
|
||||
case "node_enter":
|
||||
if (msg.node_name) markNodeActive(msg.node_name);
|
||||
break;
|
||||
case "node_exit":
|
||||
clearActiveNode();
|
||||
break;
|
||||
case "run_complete":
|
||||
clearActiveNode();
|
||||
setActiveRun(null);
|
||||
if (msg.result) onComplete?.(msg.result);
|
||||
disconnect();
|
||||
break;
|
||||
case "run_error":
|
||||
clearActiveNode();
|
||||
setActiveRun(null);
|
||||
if (msg.error) onError?.(msg.error);
|
||||
disconnect();
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
socket.onerror = () => {
|
||||
onError?.("WebSocket-Verbindungsfehler");
|
||||
disconnect();
|
||||
};
|
||||
|
||||
return () => disconnect();
|
||||
}, [run_id]); // eslint-disable-line react-hooks/exhaustive-deps
|
||||
|
||||
return { disconnect };
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue