KI-Konzil/frontend/app/store/council-store.ts
Claude 001649a364
Implement Phase 4: tools, God Mode, and missing features
Backend:
- Add Tavily web search tool wrapper (tools/web_search.py)
- Add PDF reader + ChromaDB vector store tool (tools/pdf_reader.py)
- Bind tools to LLM calls via .bind_tools() in dynamic_graph_builder
- Implement God Mode using LangGraph interrupt_before + MemorySaver
- Add approve/reject/modify API endpoints for God Mode
- Add PDF upload endpoint with ingestion pipeline
- Add persistent run history (CouncilRun model + run_service + API)
- Add Alembic migration for council_runs table
- Enhance WebSocket to emit run_paused and run_resumed events
- Add tests for tools, God Mode, and run history

Frontend:
- Add God Mode approval UI (GodModePanel component)
- Add Auto-Pilot / God Mode toggle in Konferenzzimmer
- Add functional PDF upload handler
- Add Conditional Edge editor (EdgeSettingsPanel component)
- Add edge click selection in ArchitectCanvas
- Update Zustand store with edge selection and update actions
- Update types for God Mode, execution modes, and WS events
- Update API client with God Mode, PDF upload, and blueprint run endpoints
- Update WebSocket hook for paused/resumed events
- Add Vitest config and frontend tests (store, parser, types, API)

https://claude.ai/code/session_017U6idFgaqnYTXzPxA7mxMv
2026-02-21 10:53:12 +00:00

145 lines
4.1 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, EdgeType } from "@/app/types/council";
import { nanoid } from "nanoid";
interface CouncilStore {
// Canvas
nodes: Node<AgentNodeData>[];
edges: Edge[];
selectedNodeId: string | null;
selectedEdgeId: 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;
selectEdge: (edgeId: string | null) => void;
updateEdgeData: (edgeId: string, type: EdgeType, condition?: string) => 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,
selectedEdgeId: 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, selectedEdgeId: null }),
selectEdge: (edgeId) => set({ selectedEdgeId: edgeId, selectedNodeId: null }),
updateEdgeData: (edgeId, type, condition) =>
set((state) => ({
edges: state.edges.map((e) =>
e.id === edgeId
? {
...e,
type: type === "conditional" ? "conditionalEdge" : "default",
data: { ...e.data, type, condition: condition ?? "" },
label: type === "conditional" ? (condition || "?") : undefined,
animated: type === "conditional",
}
: e
),
})),
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 },
})),
})),
}));