KI-Konzil/frontend/app/store/council-store.ts
Claude 216fdd9589
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
2026-02-20 17:03:32 +00:00

125 lines
3.3 KiB
TypeScript

// Zustand store for canvas state and council run state
import { create } from "zustand";
import { Node, Edge, addEdge, applyNodeChanges, applyEdgeChanges, NodeChange, EdgeChange, Connection } from "@xyflow/react";
import { AgentNodeData, CouncilRun, LLMModel } from "@/app/types/council";
import { nanoid } from "nanoid";
interface CouncilStore {
// Canvas
nodes: Node<AgentNodeData>[];
edges: Edge[];
selectedNodeId: string | null;
councilName: string;
// Execution
activeRun: CouncilRun | null;
activeNodeId: string | null;
// Canvas actions
onNodesChange: (changes: NodeChange[]) => void;
onEdgesChange: (changes: EdgeChange[]) => void;
onConnect: (connection: Connection) => void;
addAgentNode: (position: { x: number; y: number }) => void;
updateNodeData: (nodeId: string, data: Partial<AgentNodeData>) => void;
selectNode: (nodeId: string | null) => void;
setCouncilName: (name: string) => void;
setNodes: (nodes: Node<AgentNodeData>[]) => void;
setEdges: (edges: Edge[]) => void;
// Run actions
setActiveRun: (run: CouncilRun | null) => void;
setActiveNodeId: (nodeId: string | null) => void;
markNodeActive: (nodeName: string) => void;
clearActiveNode: () => void;
}
function makeDefaultNodeData(label: string): AgentNodeData {
return {
label,
systemPrompt: "",
model: "claude-3-5-sonnet",
tools: { webSearch: false, pdfReader: false },
isActive: false,
};
}
export const useCouncilStore = create<CouncilStore>((set, get) => ({
nodes: [],
edges: [],
selectedNodeId: null,
councilName: "Mein Rat",
activeRun: null,
activeNodeId: null,
onNodesChange: (changes) =>
set((state) => ({
nodes: applyNodeChanges(changes, state.nodes) as Node<AgentNodeData>[],
})),
onEdgesChange: (changes) =>
set((state) => ({
edges: applyEdgeChanges(changes, state.edges),
})),
onConnect: (connection) =>
set((state) => ({
edges: addEdge(
{ ...connection, type: "default", data: { type: "linear" } },
state.edges
),
})),
addAgentNode: (position) => {
const id = nanoid();
const count = get().nodes.length + 1;
const newNode: Node<AgentNodeData> = {
id,
type: "agentNode",
position,
data: makeDefaultNodeData(`Agent ${count}`),
};
set((state) => ({ nodes: [...state.nodes, newNode] }));
},
updateNodeData: (nodeId, data) =>
set((state) => ({
nodes: state.nodes.map((n) =>
n.id === nodeId ? { ...n, data: { ...n.data, ...data } } : n
),
})),
selectNode: (nodeId) => set({ selectedNodeId: nodeId }),
setCouncilName: (name) => set({ councilName: name }),
setNodes: (nodes) => set({ nodes }),
setEdges: (edges) => set({ edges }),
setActiveRun: (run) => set({ activeRun: run }),
setActiveNodeId: (nodeId) => set({ activeNodeId: nodeId }),
markNodeActive: (nodeName) => {
const node = get().nodes.find((n) => n.data.label === nodeName);
if (node) {
set((state) => ({
activeNodeId: node.id,
nodes: state.nodes.map((n) => ({
...n,
data: { ...n.data, isActive: n.id === node.id },
})),
}));
}
},
clearActiveNode: () =>
set((state) => ({
activeNodeId: null,
nodes: state.nodes.map((n) => ({
...n,
data: { ...n.data, isActive: false },
})),
})),
}));