mirror of
https://github.com/basketikun/infinite-canvas.git
synced 2026-08-05 17:04:27 +08:00
87 lines
4.1 KiB
TypeScript
87 lines
4.1 KiB
TypeScript
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<CanvasNodeData>; 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<Record<string, number>>((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;
|
||
}
|