import type { ActorCard, BehaviorDriverCard, NarrativeCard, PurposeCard, SubStepKey, } from './types'; export interface LayoutNode { id: string; level: number; x: number; y: number; width: number; height: number; variant: 'agent' | 'purpose' | 'actor' | 'behavior' | 'driver' | 'narrative'; title: string; detail: string; children: string[]; parent?: string; } export interface LayoutEdge { id: string; fromId: string; toId: string; pair: 'agent-purpose' | 'purpose-actor' | 'actor-behavior' | 'behavior-driver' | 'driver-narrative'; } export interface FlowLayout { nodes: LayoutNode[]; edges: LayoutEdge[]; width: number; height: number; } export interface BuildLayoutOptions { nodeWidth?: number; nodeHeight?: number; levelGap?: number; nodeGap?: number; paddingX?: number; paddingY?: number; rootTitle?: string; rootDetail?: string; } export const CURRENT_PAIR_BY_STAGE: Record = { purpose: 'agent-purpose', actor: 'purpose-actor', behavior: 'actor-behavior', narrative: 'driver-narrative', }; export const STATUS_TITLE: Record = { purpose: '正在生成目的中', actor: '正在生成行为体中', behavior: '正在生成预期行为和驱动因素中', narrative: '正在生成叙事中', }; export const EDGE_COLOR: Record = { 'agent-purpose': { dark: '#a78bfa', light: '#fb923c' }, 'purpose-actor': { dark: '#22d3ee', light: '#ef4444' }, 'actor-behavior': { dark: '#34d399', light: '#10b981' }, 'behavior-driver': { dark: '#5eead4', light: '#0d9488' }, 'driver-narrative': { dark: '#fbbf24', light: '#f59e0b' }, }; export const EDGE_LABEL: Record = { 'agent-purpose': '智能体→目的', 'purpose-actor': '目的→行为体', 'actor-behavior': '行为体→预期行为', 'behavior-driver': '预期行为→驱动因素', 'driver-narrative': '驱动因素→叙事', }; export function buildEdgePath(from: LayoutNode, to: LayoutNode): string { const x1 = from.x + from.width / 2; const y1 = from.y + from.height; const x2 = to.x + to.width / 2; const y2 = to.y; const my = (y1 + y2) / 2; return `M ${x1} ${y1} C ${x1} ${my}, ${x2} ${my}, ${x2} ${y2}`; } export function buildLayout( purposes: PurposeCard[], actors: ActorCard[], behaviors: BehaviorDriverCard[], narratives: NarrativeCard[], options: BuildLayoutOptions = {}, ): FlowLayout { const NODE_WIDTH = options.nodeWidth ?? 200; const NODE_HEIGHT = options.nodeHeight ?? 84; const LEVEL_GAP = options.levelGap ?? 130; const NODE_GAP = options.nodeGap ?? 28; const PADDING_X = options.paddingX ?? 48; const PADDING_Y = options.paddingY ?? 40; const rootTitle = options.rootTitle?.trim() || '智能体'; const rootDetail = options.rootDetail?.trim() || '协同推演 6 步法生成内容'; const nodes: LayoutNode[] = []; const nodeMap = new Map(); const push = (node: LayoutNode) => { nodes.push(node); nodeMap.set(node.id, node); }; const agent: LayoutNode = { id: 'agent-root', level: 0, x: 0, y: 0, width: NODE_WIDTH, height: NODE_HEIGHT, variant: 'agent', title: rootTitle, detail: rootDetail, children: purposes.map((p) => p.id), }; push(agent); purposes.forEach((p) => { push({ id: p.id, level: 1, x: 0, y: 0, width: NODE_WIDTH, height: NODE_HEIGHT, variant: 'purpose', title: p.title, detail: p.reason, children: actors.filter((a) => a.purposeId === p.id).map((a) => a.id), parent: 'agent-root', }); }); actors.forEach((a) => { push({ id: a.id, level: 2, x: 0, y: 0, width: NODE_WIDTH, height: NODE_HEIGHT, variant: 'actor', title: a.title, detail: a.content, children: behaviors.filter((b) => b.actorId === a.id).map((b) => `${b.id}-behavior`), parent: a.purposeId, }); }); behaviors.forEach((b) => { const behaviorId = `${b.id}-behavior`; const driverId = `${b.id}-driver`; push({ id: behaviorId, level: 3, x: 0, y: 0, width: NODE_WIDTH, height: NODE_HEIGHT, variant: 'behavior', title: '预期行为', detail: b.expectedBehavior, children: [driverId], parent: b.actorId, }); push({ id: driverId, level: 4, x: 0, y: 0, width: NODE_WIDTH, height: NODE_HEIGHT, variant: 'driver', title: '驱动因素', detail: b.driver, children: narratives.filter((n) => n.driverId === b.id).map((n) => n.id), parent: behaviorId, }); }); narratives.forEach((n) => { push({ id: n.id, level: 5, x: 0, y: 0, width: NODE_WIDTH, height: NODE_HEIGHT, variant: 'narrative', title: n.title, detail: n.reason, children: [], parent: `${n.driverId}-driver`, }); }); const leafCounter = { v: 0 }; const place = (node: LayoutNode) => { if (node.children.length === 0) { node.x = PADDING_X + leafCounter.v * (NODE_WIDTH + NODE_GAP); leafCounter.v += 1; return; } node.children.forEach((cid) => { const child = nodeMap.get(cid); if (child) place(child); }); const xs = node.children.map((cid) => nodeMap.get(cid)?.x ?? 0).filter((v) => v >= 0); if (xs.length > 0) { node.x = (Math.min(...xs) + Math.max(...xs)) / 2; } else { node.x = PADDING_X; } }; place(agent); nodes.forEach((node) => { node.y = PADDING_Y + node.level * (NODE_HEIGHT + LEVEL_GAP); }); const edges: LayoutEdge[] = []; nodes.forEach((node) => { node.children.forEach((cid) => { const child = nodeMap.get(cid); if (!child) return; let pair: LayoutEdge['pair']; if (node.variant === 'agent') pair = 'agent-purpose'; else if (node.variant === 'purpose') pair = 'purpose-actor'; else if (node.variant === 'actor') pair = 'actor-behavior'; else if (node.variant === 'behavior') pair = 'behavior-driver'; else pair = 'driver-narrative'; edges.push({ id: `${node.id}->${cid}`, fromId: node.id, toId: cid, pair }); }); }); const allLevels = Math.max(...nodes.map((n) => n.level)); const maxX = Math.max(...nodes.map((n) => n.x + n.width)); const totalWidth = maxX + PADDING_X; const totalHeight = PADDING_Y * 2 + (allLevels + 1) * NODE_HEIGHT + allLevels * LEVEL_GAP; return { nodes, edges, width: totalWidth, height: totalHeight }; } export const VARIANT_ABBREV: Record = { agent: 'AI', purpose: 'md', actor: 'xwt', behavior: 'yqxw', driver: 'qdys', narrative: 'xs', }; export const VARIANT_LABEL: Record = { agent: '智能体', purpose: '目的', actor: '行为体', behavior: '预期行为', driver: '驱动因素', narrative: '叙事', };