import { nanoid } from "nanoid"; import { getNodeSpec } from "../constants"; import { CanvasNodeType, type CanvasConnection, type CanvasNodeData, type CanvasNodeMetadata, type ViewportTransform } from "../types"; export type CanvasAgentOp = | { type: "add_node"; id?: string; nodeType?: CanvasNodeType; title?: string; position?: { x: number; y: number }; x?: number; y?: number; width?: number; height?: number; metadata?: CanvasNodeMetadata } | { type: "update_node"; id: string; patch?: Partial; metadata?: CanvasNodeMetadata } | { type: "delete_node"; id?: string; ids?: string[] } | { type: "connect_nodes"; id?: string; fromNodeId: string; toNodeId: string } | { type: "set_viewport"; viewport: ViewportTransform } | { type: "select_nodes"; ids: string[] } | { type: "run_generation"; nodeId: string; mode?: "text" | "image" | "video" | "audio"; prompt?: string }; export type CanvasAgentSnapshot = { projectId: string; title: string; nodes: CanvasNodeData[]; connections: CanvasConnection[]; selectedNodeIds: string[]; viewport: ViewportTransform; }; export function summarizeCanvasAgentOps(ops: CanvasAgentOp[]) { const counts = ops.reduce>((acc, op) => { acc[op.type] = (acc[op.type] || 0) + 1; return acc; }, {}); return Object.entries(counts) .map(([type, count]) => `${opLabel(type)} ${count}`) .join(","); } export function applyCanvasAgentOps(snapshot: CanvasAgentSnapshot, ops: CanvasAgentOp[]) { let nodes = snapshot.nodes; let connections = snapshot.connections; let selectedNodeIds = snapshot.selectedNodeIds; let viewport = snapshot.viewport; ops.forEach((op, index) => { if (op.type === "add_node") { const nodeType = op.nodeType || CanvasNodeType.Text; const spec = getNodeSpec(nodeType); const node: CanvasNodeData = { id: op.id || `${nodeType}-${Date.now()}-${index}`, type: nodeType, title: op.title || spec.title, position: op.position || { x: op.x ?? index * 36, y: op.y ?? index * 36 }, width: op.width || spec.width, height: op.height || spec.height, metadata: { ...spec.metadata, ...op.metadata }, }; nodes = [...nodes, node]; selectedNodeIds = [node.id]; } if (op.type === "update_node") { nodes = nodes.map((node) => (node.id === op.id ? { ...node, ...op.patch, metadata: { ...node.metadata, ...op.patch?.metadata, ...op.metadata } } : node)); } if (op.type === "delete_node") { const ids = new Set(op.ids || (op.id ? [op.id] : [])); nodes = nodes.filter((node) => !ids.has(node.id)); connections = connections.filter((conn) => !ids.has(conn.fromNodeId) && !ids.has(conn.toNodeId)); selectedNodeIds = selectedNodeIds.filter((id) => !ids.has(id)); } if (op.type === "connect_nodes") { const exists = connections.some((conn) => conn.fromNodeId === op.fromNodeId && conn.toNodeId === op.toNodeId); const hasNodes = nodes.some((node) => node.id === op.fromNodeId) && nodes.some((node) => node.id === op.toNodeId); if (!exists && hasNodes) connections = [...connections, { id: op.id || nanoid(), fromNodeId: op.fromNodeId, toNodeId: op.toNodeId }]; } if (op.type === "set_viewport") viewport = op.viewport; if (op.type === "select_nodes") selectedNodeIds = op.ids.filter((id) => nodes.some((node) => node.id === id)); }); return { ...snapshot, nodes, connections, selectedNodeIds, viewport }; } function opLabel(type: string) { if (type === "add_node") return "新增节点"; if (type === "update_node") return "更新节点"; if (type === "delete_node") return "删除节点"; if (type === "connect_nodes") return "连接"; if (type === "set_viewport") return "调整视图"; if (type === "select_nodes") return "选择节点"; if (type === "run_generation") return "触发生成"; return type; }