fix(studio): 载入编排防御兜底,避免损坏图导致黑屏

点击「我的编排」里某条若其 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 <noreply@anthropic.com>
This commit is contained in:
Blizzard
2026-06-13 16:28:21 +08:00
parent 4bf614a07c
commit e1cac0eb69
@@ -106,12 +106,30 @@ export function StudioView({ onRun, phase, identity }: { onRun: (dsl: TaskDsl) =
); );
const loadGraph = useCallback( const loadGraph = useCallback(
(g: { nodes: Node[]; edges: Edge[] }) => { (g: { nodes?: Node[]; edges?: Edge[] }) => {
setNodes((g.nodes ?? []).map((n) => ({ ...n, data: { ...n.data, status: "idle" as NodeStatus } }))); // 防御:旧/损坏数据可能缺 position/type/data —— 全部兜底,避免 React Flow 崩成黑屏。
setEdges(g.edges ?? []); const safeNodes: Node[] = (g.nodes ?? [])
.filter((n) => n && n.id)
.map((n, i) => {
const d = (n.data ?? {}) as { kind?: string; label?: string; config?: Record<string, unknown> };
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); setSelId(null);
setIssues(null); setIssues(null);
for (const n of g.nodes ?? []) { for (const n of safeNodes) {
const m = /^n(\d+)$/.exec(n.id); const m = /^n(\d+)$/.exec(n.id);
if (m) seq = Math.max(seq, Number(m[1])); if (m) seq = Math.max(seq, Number(m[1]));
} }