From e1cac0eb696f2b4c5ac6727e7139b3c995105193 Mon Sep 17 00:00:00 2001 From: Blizzard Date: Sat, 13 Jun 2026 16:28:21 +0800 Subject: [PATCH] =?UTF-8?q?fix(studio):=20=E8=BD=BD=E5=85=A5=E7=BC=96?= =?UTF-8?q?=E6=8E=92=E9=98=B2=E5=BE=A1=E5=85=9C=E5=BA=95=EF=BC=8C=E9=81=BF?= =?UTF-8?q?=E5=85=8D=E6=8D=9F=E5=9D=8F=E5=9B=BE=E5=AF=BC=E8=87=B4=E9=BB=91?= =?UTF-8?q?=E5=B1=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 点击「我的编排」里某条若其 graph 缺 position/type/data(旧数据/外部写入), React Flow 缺 position 会直接崩成黑屏。loadGraph 改为对每个节点兜底: 补 type=typed、缺失或非法 position 给网格默认坐标、data.kind 不识别则回退 output、 补 label/config/status;边过滤掉两端不存在或缺 source/target 的,自动补 id。 验证(Preview):故意存一条节点全缺字段的「损坏测试」→ 载入渲染为 2 节点·1 连线、 渲染器正常(不再黑屏);正常「尽调问答 Agent」4 节点·3 连线照常载入。tsc+vite 通过;重建 .app。 Co-Authored-By: Claude Opus 4.8 --- .../frontend/src/studio/StudioView.tsx | 26 ++++++++++++++++--- 1 file changed, 22 insertions(+), 4 deletions(-) diff --git a/sundynix-desktop/frontend/src/studio/StudioView.tsx b/sundynix-desktop/frontend/src/studio/StudioView.tsx index 7ed791b..f7d5131 100644 --- a/sundynix-desktop/frontend/src/studio/StudioView.tsx +++ b/sundynix-desktop/frontend/src/studio/StudioView.tsx @@ -106,12 +106,30 @@ export function StudioView({ onRun, phase, identity }: { onRun: (dsl: TaskDsl) = ); const loadGraph = useCallback( - (g: { nodes: Node[]; edges: Edge[] }) => { - setNodes((g.nodes ?? []).map((n) => ({ ...n, data: { ...n.data, status: "idle" as NodeStatus } }))); - setEdges(g.edges ?? []); + (g: { nodes?: Node[]; edges?: Edge[] }) => { + // 防御:旧/损坏数据可能缺 position/type/data —— 全部兜底,避免 React Flow 崩成黑屏。 + const safeNodes: Node[] = (g.nodes ?? []) + .filter((n) => n && n.id) + .map((n, i) => { + const d = (n.data ?? {}) as { kind?: string; label?: string; config?: Record }; + const kind = d.kind && NODE_KINDS[d.kind] ? d.kind : "output"; + const pos = n.position && typeof n.position.x === "number" && typeof n.position.y === "number" ? n.position : { x: 100 + (i % 4) * 220, y: 80 + Math.floor(i / 4) * 120 }; + return { + id: n.id, + type: "typed", + position: pos, + data: { kind, label: d.label || NODE_KINDS[kind].label, config: d.config ?? {}, status: "idle" as NodeStatus }, + }; + }); + const ids = new Set(safeNodes.map((n) => n.id)); + const safeEdges: Edge[] = (g.edges ?? []) + .filter((e) => e && e.source && e.target && ids.has(e.source) && ids.has(e.target)) + .map((e, i) => ({ ...e, id: e.id || `e${i}` })); + setNodes(safeNodes); + setEdges(safeEdges); setSelId(null); setIssues(null); - for (const n of g.nodes ?? []) { + for (const n of safeNodes) { const m = /^n(\d+)$/.exec(n.id); if (m) seq = Math.max(seq, Number(m[1])); }