|
@@ -30,6 +30,7 @@ export type SubTraceEntry = { id: string; mission?: string };
|
|
|
const EMPTY_GOALS: Goal[] = [];
|
|
const EMPTY_GOALS: Goal[] = [];
|
|
|
|
|
|
|
|
export const FlowChart: FC<FlowChartProps> = ({ goals, msgGroups = {}, onNodeClick, onSubTraceClick }) => {
|
|
export const FlowChart: FC<FlowChartProps> = ({ goals, msgGroups = {}, onNodeClick, onSubTraceClick }) => {
|
|
|
|
|
+ console.log("%c [ msgGroups ]-33", "font-size:13px; background:pink; color:#bf2c9f;", msgGroups);
|
|
|
goals = goals.filter((g) => !g.parent_id);
|
|
goals = goals.filter((g) => !g.parent_id);
|
|
|
|
|
|
|
|
console.log("%c [ FlowChart-goals ]-33", "font-size:13px; background:pink; color:#bf2c9f;", goals);
|
|
console.log("%c [ FlowChart-goals ]-33", "font-size:13px; background:pink; color:#bf2c9f;", goals);
|
|
@@ -55,15 +56,11 @@ export const FlowChart: FC<FlowChartProps> = ({ goals, msgGroups = {}, onNodeCli
|
|
|
const [selectedSubTrace, setSelectedSubTrace] = useState<{ parentGoal: Goal; entry: SubTraceEntry } | null>(null);
|
|
const [selectedSubTrace, setSelectedSubTrace] = useState<{ parentGoal: Goal; entry: SubTraceEntry } | null>(null);
|
|
|
|
|
|
|
|
// Use useFlowChartData for subTrace to handle WebSocket connection and data processing
|
|
// Use useFlowChartData for subTrace to handle WebSocket connection and data processing
|
|
|
- const {
|
|
|
|
|
- messages: subTraceMessages,
|
|
|
|
|
- msgGroups: subTraceMsgGroups,
|
|
|
|
|
- goals: subTraceGoals,
|
|
|
|
|
- } = useFlowChartData(selectedSubTrace?.entry.id || null, EMPTY_GOALS);
|
|
|
|
|
- console.log("%c [ subTraceGoals ]-57", "font-size:13px; background:pink; color:#bf2c9f;", subTraceGoals);
|
|
|
|
|
- console.log("%c [ subTraceMsgGroups ]-57", "font-size:13px; background:pink; color:#bf2c9f;", subTraceMsgGroups);
|
|
|
|
|
- console.log("%c [ subTraceMessages ]-56", "font-size:13px; background:pink; color:#bf2c9f;", subTraceMessages);
|
|
|
|
|
-
|
|
|
|
|
|
|
+ const { messages: subTraceMessages, msgGroups: subMsgGroups } = useFlowChartData(
|
|
|
|
|
+ selectedSubTrace?.entry.id || null,
|
|
|
|
|
+ EMPTY_GOALS,
|
|
|
|
|
+ );
|
|
|
|
|
+ console.log("%c [ subMsgGroups ]-60", "font-size:13px; background:pink; color:#bf2c9f;", subMsgGroups);
|
|
|
const [panOffset, setPanOffset] = useState({ x: 0, y: 0 });
|
|
const [panOffset, setPanOffset] = useState({ x: 0, y: 0 });
|
|
|
const [isPanning, setIsPanning] = useState(false);
|
|
const [isPanning, setIsPanning] = useState(false);
|
|
|
const [zoom, setZoom] = useState(1);
|
|
const [zoom, setZoom] = useState(1);
|
|
@@ -220,6 +217,8 @@ export const FlowChart: FC<FlowChartProps> = ({ goals, msgGroups = {}, onNodeCli
|
|
|
// 节点点击:记录选中状态并回传对应边
|
|
// 节点点击:记录选中状态并回传对应边
|
|
|
const handleNodeClick = useCallback(
|
|
const handleNodeClick = useCallback(
|
|
|
(node: LayoutNode) => {
|
|
(node: LayoutNode) => {
|
|
|
|
|
+ if (node.data.sub_goals && node.data.sub_goals.length > 0) return;
|
|
|
|
|
+
|
|
|
setSelectedNodeId(node.data.id);
|
|
setSelectedNodeId(node.data.id);
|
|
|
const nearestLink = mainLinks.find((link) => link.target.data.id === node.data.id);
|
|
const nearestLink = mainLinks.find((link) => link.target.data.id === node.data.id);
|
|
|
const edge: EdgeType | undefined = nearestLink
|
|
const edge: EdgeType | undefined = nearestLink
|
|
@@ -354,7 +353,14 @@ export const FlowChart: FC<FlowChartProps> = ({ goals, msgGroups = {}, onNodeCli
|
|
|
if (!layoutData)
|
|
if (!layoutData)
|
|
|
return [] as Array<{
|
|
return [] as Array<{
|
|
|
parentId: string;
|
|
parentId: string;
|
|
|
- nodes: Array<{ x: number; y: number; fullText: string; shortText: string }>;
|
|
|
|
|
|
|
+ nodes: Array<{
|
|
|
|
|
+ x: number;
|
|
|
|
|
+ y: number;
|
|
|
|
|
+ fullText: string;
|
|
|
|
|
+ shortText: string;
|
|
|
|
|
+ data: Goal;
|
|
|
|
|
+ mainGoal: Goal;
|
|
|
|
|
+ }>;
|
|
|
paths: Array<{ d: string }>;
|
|
paths: Array<{ d: string }>;
|
|
|
}>;
|
|
}>;
|
|
|
|
|
|
|
@@ -385,11 +391,18 @@ export const FlowChart: FC<FlowChartProps> = ({ goals, msgGroups = {}, onNodeCli
|
|
|
|
|
|
|
|
const overlays: Array<{
|
|
const overlays: Array<{
|
|
|
parentId: string;
|
|
parentId: string;
|
|
|
- nodes: Array<{ x: number; y: number; fullText: string; shortText: string }>;
|
|
|
|
|
|
|
+ nodes: Array<{
|
|
|
|
|
+ x: number;
|
|
|
|
|
+ y: number;
|
|
|
|
|
+ fullText: string;
|
|
|
|
|
+ shortText: string;
|
|
|
|
|
+ data: Goal;
|
|
|
|
|
+ mainGoal: Goal;
|
|
|
|
|
+ }>;
|
|
|
paths: Array<{ d: string }>;
|
|
paths: Array<{ d: string }>;
|
|
|
}> = [];
|
|
}> = [];
|
|
|
|
|
|
|
|
- displayGoals.forEach((goal) => {
|
|
|
|
|
|
|
+ displayGoals.forEach((goal, index) => {
|
|
|
const anchorNode = nodeMap.get(goal.id);
|
|
const anchorNode = nodeMap.get(goal.id);
|
|
|
if (!anchorNode) return;
|
|
if (!anchorNode) return;
|
|
|
|
|
|
|
@@ -413,13 +426,44 @@ export const FlowChart: FC<FlowChartProps> = ({ goals, msgGroups = {}, onNodeCli
|
|
|
y: centerY + Math.sin(angle) * radius,
|
|
y: centerY + Math.sin(angle) * radius,
|
|
|
fullText,
|
|
fullText,
|
|
|
shortText: truncateMiddle(fullText, 10),
|
|
shortText: truncateMiddle(fullText, 10),
|
|
|
|
|
+ data: subGoal,
|
|
|
|
|
+ mainGoal: goal,
|
|
|
};
|
|
};
|
|
|
});
|
|
});
|
|
|
|
|
|
|
|
const nodeHalfH = 5;
|
|
const nodeHalfH = 5;
|
|
|
- const paths = nodes.map((node) => ({
|
|
|
|
|
- d: buildSegment(anchorX, anchorY + nodeHalfH, node.x, node.y),
|
|
|
|
|
- }));
|
|
|
|
|
|
|
+ const paths: Array<{ d: string }> = [];
|
|
|
|
|
+
|
|
|
|
|
+ if (nodes.length > 0) {
|
|
|
|
|
+ // Connect Parent to first node
|
|
|
|
|
+ paths.push({
|
|
|
|
|
+ d: buildSegment(anchorX, anchorY + nodeHalfH, nodes[0].x, nodes[0].y),
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ // Connect subsequent nodes serially
|
|
|
|
|
+ for (let i = 0; i < nodes.length - 1; i++) {
|
|
|
|
|
+ const source = nodes[i];
|
|
|
|
|
+ const target = nodes[i + 1];
|
|
|
|
|
+ paths.push({
|
|
|
|
|
+ d: buildSegment(source.x, source.y, target.x, target.y),
|
|
|
|
|
+ });
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // Connect last node to the next main node
|
|
|
|
|
+ if (index < displayGoals.length - 1) {
|
|
|
|
|
+ const nextGoal = displayGoals[index + 1];
|
|
|
|
|
+ const nextNode = nodeMap.get(nextGoal.id);
|
|
|
|
|
+ if (nextNode) {
|
|
|
|
|
+ const lastNode = nodes[nodes.length - 1];
|
|
|
|
|
+ // Connect to the left side of the next node (approximate width 140, half 70)
|
|
|
|
|
+ const targetX = nextNode.x - 70;
|
|
|
|
|
+ const targetY = nextNode.y;
|
|
|
|
|
+ paths.push({
|
|
|
|
|
+ d: buildSegment(lastNode.x, lastNode.y, targetX, targetY),
|
|
|
|
|
+ });
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
overlays.push({ parentId: goal.id, nodes, paths });
|
|
overlays.push({ parentId: goal.id, nodes, paths });
|
|
|
});
|
|
});
|
|
@@ -672,10 +716,9 @@ export const FlowChart: FC<FlowChartProps> = ({ goals, msgGroups = {}, onNodeCli
|
|
|
key={`subgoals-path-${overlay.parentId}-${idx}`}
|
|
key={`subgoals-path-${overlay.parentId}-${idx}`}
|
|
|
d={p.d}
|
|
d={p.d}
|
|
|
fill="none"
|
|
fill="none"
|
|
|
- stroke="#7aa0d6"
|
|
|
|
|
|
|
+ stroke="#00c853"
|
|
|
strokeWidth={1.5}
|
|
strokeWidth={1.5}
|
|
|
- strokeDasharray="6,6"
|
|
|
|
|
- markerEnd="url(#arrow-default)"
|
|
|
|
|
|
|
+ markerEnd="url(#arrow-success)"
|
|
|
opacity={0.7}
|
|
opacity={0.7}
|
|
|
strokeLinecap="round"
|
|
strokeLinecap="round"
|
|
|
/>
|
|
/>
|
|
@@ -686,6 +729,14 @@ export const FlowChart: FC<FlowChartProps> = ({ goals, msgGroups = {}, onNodeCli
|
|
|
<g
|
|
<g
|
|
|
key={`subgoals-node-${overlay.parentId}-${idx}`}
|
|
key={`subgoals-node-${overlay.parentId}-${idx}`}
|
|
|
transform={`translate(${node.x},${node.y})`}
|
|
transform={`translate(${node.x},${node.y})`}
|
|
|
|
|
+ onClick={(e) => {
|
|
|
|
|
+ e.stopPropagation();
|
|
|
|
|
+ const entries = normalizeSubTraceEntries(node.data);
|
|
|
|
|
+ if (entries.length > 0) {
|
|
|
|
|
+ handleSubTraceClick(node.mainGoal, entries[0]);
|
|
|
|
|
+ }
|
|
|
|
|
+ }}
|
|
|
|
|
+ style={{ cursor: "pointer" }}
|
|
|
>
|
|
>
|
|
|
<Tooltip content={node.fullText}>
|
|
<Tooltip content={node.fullText}>
|
|
|
<text
|
|
<text
|