import { useRef, useState } from "react"; import { Button } from "@douyinfe/semi-ui-19"; import type { FlowNode, FlowRun, Workflow } from "./flowTypes"; import { flowStatus } from "./flowTypes"; export type NodeType = { type: string; label: string; accepts: string[]; produces: string; }; export function FlowCanvas({ graph, types, selected, onSelect, onChange, run, }: { graph: Workflow; types: NodeType[]; selected: string | null; onSelect: (id: string) => void; onChange?: (graph: Workflow) => void; run?: FlowRun | null; }) { const [zoom, setZoom] = useState(0.8); const viewport = useRef(null); const portNames: Record = { context: "输入范围", template: "模板", candidates: "候选表达式", results: "回测结果", evaluation: "评估报告", summary: "研究汇总", iteration: "迭代产物", }; const drag = useRef<{ id: string; x: number; y: number; left: number; top: number; } | null>(null); const width = Math.max(1120, ...graph.nodes.map((n) => n.x + 260)); const height = Math.max(520, ...graph.nodes.map((n) => n.y + 180)); function move(node: FlowNode, x: number, y: number) { onChange?.({ ...graph, nodes: graph.nodes.map((n) => n.id === node.id ? { ...n, x: Math.max(0, Math.min(10000, x)), y: Math.max(0, Math.min(10000, y)), } : n, ), }); } return (
{onChange ? "拖动节点排列 · 选中后可用方向键微调" : "节点颜色与状态显示当前轮次"}
{Math.round(zoom * 100)}%
{graph.edges.map((e, index) => { const a = graph.nodes.find((n) => n.id === e.source), b = graph.nodes.find((n) => n.id === e.target); if (!a || !b) return null; const x = a.x + 200, y = a.y + 50, tx = b.x, ty = b.y + 50; const sibling = graph.edges .filter((edge) => edge.source === e.source) .indexOf(e); const routed = Boolean(e.branch) || tx < x || tx - x > 130; const lane = tx < x ? a.y + 130 + sibling * 14 : Math.max(10, Math.min(a.y, b.y) - 30 - sibling * 14); const path = routed ? `M${x} ${y} H${x + 18} V${lane} H${tx - 18} V${ty} H${tx}` : `M${x} ${y} C${x + 30} ${y}, ${tx - 30} ${ty}, ${tx} ${ty}`; const upstream = run?.steps.find( (step) => step.node_id === e.source && step.round === run.round, ); const inactive = upstream && (upstream.status === "skipped" || (e.branch && upstream.output.verdict !== e.branch)); return ( {e.branch && ( {e.branch} )} ); })} {graph.nodes.map((node) => { const type = types.find((t) => t.type === node.type); const step = run?.steps.find( (s) => s.node_id === node.id && s.round === run.round, ); return ( ); })}
); }