fix: 稳定 AgentRun 树状视图刷新

This commit is contained in:
root
2026-07-16 18:07:25 +02:00
parent f63ec3beb1
commit 9f45087b91
@@ -1,5 +1,5 @@
<script setup lang="ts">
import { computed, nextTick } from "vue";
import { computed, nextTick, onMounted, ref, watch } from "vue";
import dagre from "@dagrejs/dagre";
import { Handle, MarkerType, Position, VueFlow, useVueFlow, type Edge, type Node } from "@vue-flow/core";
import { Focus, Minus, Plus } from "lucide-vue-next";
@@ -32,23 +32,35 @@ const emit = defineEmits<{
const flowId = "agentrun-mind-map";
const { fitView, zoomIn, zoomOut } = useVueFlow({ id: flowId });
const runById = computed(() => new Map(props.runs.map((run) => [run.id, run])));
const layoutCache = new Map<string, Map<string, { x: number; y: number }>>();
const initialFitDone = ref(false);
const graph = computed(() => {
const layout = new dagre.graphlib.Graph().setDefaultEdgeLabel(() => ({}));
layout.setGraph({ rankdir: "LR", ranksep: 74, nodesep: 24, edgesep: 12, marginx: 32, marginy: 32 });
for (const row of props.rows) layout.setNode(row.id, { width: nodeWidth(row), height: 70 });
for (const row of props.rows) if (row.parentId && layout.hasNode(row.parentId)) layout.setEdge(row.parentId, row.id);
dagre.layout(layout);
// 状态和消息更新只刷新节点内容,不重新计算位置。
const layoutKey = props.rows.map((row) => `${row.id}|${row.parentId ?? ""}|${row.kind}|${row.childCount}|${row.expanded}`).join("\n");
let positions = layoutCache.get(layoutKey);
if (!positions) {
const layout = new dagre.graphlib.Graph().setDefaultEdgeLabel(() => ({}));
layout.setGraph({ rankdir: "LR", ranksep: 74, nodesep: 24, edgesep: 12, marginx: 32, marginy: 32 });
for (const row of props.rows) layout.setNode(row.id, { width: nodeWidth(row), height: 70 });
for (const row of props.rows) if (row.parentId && layout.hasNode(row.parentId)) layout.setEdge(row.parentId, row.id);
dagre.layout(layout);
positions = new Map(props.rows.map((row) => {
const position = layout.node(row.id);
return [row.id, { x: position.x - position.width / 2, y: position.y - position.height / 2 }];
}));
layoutCache.set(layoutKey, positions);
if (layoutCache.size > 8) layoutCache.delete(layoutCache.keys().next().value!);
}
const nodes: Node<MindMapNodeData>[] = props.rows.map((row) => {
const position = layout.node(row.id);
const position = positions.get(row.id) ?? { x: 0, y: 0 };
const run = row.runId ? runById.value.get(row.runId) : undefined;
const title = row.kind === "run" && run?.title ? run.title : row.label;
return {
id: row.id,
type: "observer",
position: { x: position.x - position.width / 2, y: position.y - position.height / 2 },
position,
sourcePosition: Position.Right,
targetPosition: Position.Left,
selectable: false,
@@ -57,7 +69,7 @@ const graph = computed(() => {
};
});
const edges: Edge[] = props.rows
.filter((row) => row.parentId && layout.hasNode(row.parentId))
.filter((row) => row.parentId && positions.has(row.parentId))
.map((row) => ({
id: `${row.parentId}->${row.id}`,
source: row.parentId!,
@@ -89,6 +101,15 @@ async function fit(): Promise<void> {
await nextTick();
await fitView({ padding: 0.14, duration: 220, maxZoom: 1 });
}
async function fitInitial(): Promise<void> {
if (initialFitDone.value || !props.rows.length) return;
await fit();
initialFitDone.value = true;
}
watch(() => graph.value.nodes.length, () => { void fitInitial(); }, { flush: "post" });
onMounted(() => { void fitInitial(); });
</script>
<template>
@@ -103,7 +124,7 @@ async function fit(): Promise<void> {
:nodes-draggable="false"
:nodes-connectable="false"
:elements-selectable="false"
fit-view-on-init
:fit-view-on-init="false"
class="agent-mind-map-flow"
aria-label="AgentRun 思维导图"
>