267 lines
6.9 KiB
TypeScript
267 lines
6.9 KiB
TypeScript
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<SubStepKey, LayoutEdge['pair'] | null> = {
|
|
purpose: 'agent-purpose',
|
|
actor: 'purpose-actor',
|
|
behavior: 'actor-behavior',
|
|
narrative: 'driver-narrative',
|
|
};
|
|
|
|
export const STATUS_TITLE: Record<SubStepKey, string> = {
|
|
purpose: '正在生成目的中',
|
|
actor: '正在生成行为体中',
|
|
behavior: '正在生成预期行为和驱动因素中',
|
|
narrative: '正在生成叙事中',
|
|
};
|
|
|
|
export const EDGE_COLOR: Record<LayoutEdge['pair'], { dark: string; light: string }> = {
|
|
'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<LayoutEdge['pair'], string> = {
|
|
'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<string, LayoutNode>();
|
|
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<LayoutNode['variant'], string> = {
|
|
agent: 'AI',
|
|
purpose: 'md',
|
|
actor: 'xwt',
|
|
behavior: 'yqxw',
|
|
driver: 'qdys',
|
|
narrative: 'xs',
|
|
};
|
|
|
|
export const VARIANT_LABEL: Record<LayoutNode['variant'], string> = {
|
|
agent: '智能体',
|
|
purpose: '目的',
|
|
actor: '行为体',
|
|
behavior: '预期行为',
|
|
driver: '驱动因素',
|
|
narrative: '叙事',
|
|
};
|