perf: 优化智能体与工作流幕布渲染性能
- 分阶段加载智能体配置并按需缓存 MCP 工具 - 合并画布状态更新与节点尺寸监听,启用大图可视区域渲染和静态连线 - 隔离 Tinyflow Store 实例并补充数据同步与回归测试
This commit is contained in:
@@ -6,7 +6,7 @@ import type {
|
||||
AgentValidationIssue,
|
||||
} from './types';
|
||||
|
||||
import { computed, onMounted, ref } from 'vue';
|
||||
import { computed, onActivated, onDeactivated, onMounted, ref } from 'vue';
|
||||
import { useRoute, useRouter } from 'vue-router';
|
||||
|
||||
import { ElMessage, ElMessageBox } from 'element-plus';
|
||||
@@ -23,6 +23,8 @@ import {
|
||||
import {
|
||||
getAgentDetail,
|
||||
getAgentModels,
|
||||
getMcpPage,
|
||||
getMcpTools,
|
||||
getPublishedKnowledgeList,
|
||||
saveAgent,
|
||||
submitAgentOfflineApproval,
|
||||
@@ -35,6 +37,7 @@ import AgentStudioCanvas from './components/agent-studio/AgentStudioCanvas.vue';
|
||||
import AgentCommandBar from './components/AgentCommandBar.vue';
|
||||
import AgentInspectorPanel from './components/AgentInspectorPanel.vue';
|
||||
import { useAgentDesignerState } from './composables/useAgentDesignerState';
|
||||
import { createMcpToolLoader } from './mcpToolLoader';
|
||||
|
||||
const route = useRoute();
|
||||
const router = useRouter();
|
||||
@@ -56,10 +59,13 @@ const {
|
||||
validate,
|
||||
} = useAgentDesignerState();
|
||||
|
||||
const pageLoading = ref(false);
|
||||
const pageLoading = ref(true);
|
||||
const contentReady = ref(false);
|
||||
const canvasActive = ref(true);
|
||||
const saveLoading = ref(false);
|
||||
const offlineLoading = ref(false);
|
||||
const publishLoading = ref(false);
|
||||
const mcpToolsLoading = ref<Record<string, boolean>>({});
|
||||
const issues = ref<AgentValidationIssue[]>([]);
|
||||
const categories = ref<AgentOption[]>([]);
|
||||
const models = ref<AgentOption[]>([]);
|
||||
@@ -67,6 +73,10 @@ const knowledges = ref<AgentOption[]>([]);
|
||||
const workflows = ref<AgentOption[]>([]);
|
||||
const pluginTools = ref<AgentOption[]>([]);
|
||||
const mcps = ref<AgentOption[]>([]);
|
||||
const fetchMcpToolResource = createMcpToolLoader(async (id) => {
|
||||
const res = await getMcpTools(id);
|
||||
return res.errorCode === 0 ? res.data : undefined;
|
||||
});
|
||||
|
||||
const isNew = computed(() => String(route.params.id || '') === 'new');
|
||||
const publishText = computed(() => {
|
||||
@@ -116,14 +126,23 @@ const offlineDisabled = computed(() => {
|
||||
});
|
||||
|
||||
onMounted(async () => {
|
||||
pageLoading.value = true;
|
||||
void loadDeferredOptions();
|
||||
try {
|
||||
await Promise.all([loadOptions(), loadAgent()]);
|
||||
await Promise.all([loadCriticalOptions(), loadAgent()]);
|
||||
} finally {
|
||||
contentReady.value = true;
|
||||
pageLoading.value = false;
|
||||
}
|
||||
});
|
||||
|
||||
onActivated(() => {
|
||||
canvasActive.value = true;
|
||||
});
|
||||
|
||||
onDeactivated(() => {
|
||||
canvasActive.value = false;
|
||||
});
|
||||
|
||||
async function loadAgent() {
|
||||
if (isNew.value) {
|
||||
reset();
|
||||
@@ -191,13 +210,33 @@ function syncNavTitle(title: string, options: { force?: boolean } = {}) {
|
||||
});
|
||||
}
|
||||
|
||||
async function loadOptions() {
|
||||
const [categoryRes, modelRes, knowledgeRes, workflowRes, pluginRes, mcpRes] =
|
||||
await Promise.all([
|
||||
api.get('/api/v1/agentCategory/visibleList', {
|
||||
params: { sortKey: 'sortNo', sortType: 'asc' },
|
||||
}),
|
||||
getAgentModels(),
|
||||
async function loadCriticalOptions() {
|
||||
const [categoryResult, modelResult] = await Promise.allSettled([
|
||||
api.get('/api/v1/agentCategory/visibleList', {
|
||||
params: { sortKey: 'sortNo', sortType: 'asc' },
|
||||
}),
|
||||
getAgentModels(),
|
||||
]);
|
||||
|
||||
if (categoryResult.status === 'fulfilled') {
|
||||
categories.value = (categoryResult.value.data || []).map((item: any) => ({
|
||||
label: item.categoryName || item.name,
|
||||
value: String(item.id),
|
||||
raw: item,
|
||||
}));
|
||||
}
|
||||
if (modelResult.status === 'fulfilled') {
|
||||
models.value = (modelResult.value.data || []).map((item: any) => ({
|
||||
label: item.title || item.name,
|
||||
value: String(item.id),
|
||||
raw: item,
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
async function loadDeferredOptions() {
|
||||
const [knowledgeResult, workflowResult, pluginResult, mcpResult] =
|
||||
await Promise.allSettled([
|
||||
getPublishedKnowledgeList(),
|
||||
api.get('/api/v1/workflow/page', {
|
||||
params: { pageNumber: 1, pageSize: 200 },
|
||||
@@ -205,37 +244,37 @@ async function loadOptions() {
|
||||
api.get('/api/v1/plugin/pageByCategory', {
|
||||
params: { pageNumber: 1, pageSize: 200, category: 0 },
|
||||
}),
|
||||
api.get('/api/v1/mcp/pageTools', {
|
||||
params: { pageNumber: 1, pageSize: 200, status: 1 },
|
||||
}),
|
||||
getMcpPage(),
|
||||
]);
|
||||
|
||||
categories.value = (categoryRes.data || []).map((item: any) => ({
|
||||
label: item.categoryName || item.name,
|
||||
value: String(item.id),
|
||||
raw: item,
|
||||
}));
|
||||
models.value = (modelRes.data || []).map((item: any) => ({
|
||||
label: item.title || item.name,
|
||||
value: String(item.id),
|
||||
raw: item,
|
||||
}));
|
||||
knowledges.value = (knowledgeRes.data || []).map((item: any) => ({
|
||||
label: item.title || item.name,
|
||||
value: String(item.id),
|
||||
raw: item,
|
||||
}));
|
||||
workflows.value = (
|
||||
(workflowRes.data?.records || workflowRes.data || []) as any[]
|
||||
).map((item) => ({
|
||||
label: item.title || item.name,
|
||||
value: String(item.id),
|
||||
raw: item,
|
||||
}));
|
||||
pluginTools.value = flattenPluginTools(
|
||||
pluginRes.data?.records || pluginRes.data || [],
|
||||
);
|
||||
mcps.value = mapMcpOptions(mcpRes.data?.records || mcpRes.data || []);
|
||||
if (knowledgeResult.status === 'fulfilled') {
|
||||
knowledges.value = (knowledgeResult.value.data || []).map((item: any) => ({
|
||||
label: item.title || item.name,
|
||||
value: String(item.id),
|
||||
raw: item,
|
||||
}));
|
||||
}
|
||||
if (workflowResult.status === 'fulfilled') {
|
||||
workflows.value = (
|
||||
(workflowResult.value.data?.records ||
|
||||
workflowResult.value.data ||
|
||||
[]) as any[]
|
||||
).map((item) => ({
|
||||
label: item.title || item.name,
|
||||
value: String(item.id),
|
||||
raw: item,
|
||||
}));
|
||||
}
|
||||
if (pluginResult.status === 'fulfilled') {
|
||||
pluginTools.value = flattenPluginTools(
|
||||
pluginResult.value.data?.records || pluginResult.value.data || [],
|
||||
);
|
||||
}
|
||||
if (mcpResult.status === 'fulfilled') {
|
||||
mcps.value = mapMcpOptions(
|
||||
mcpResult.value.data?.records || mcpResult.value.data || [],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function flattenPluginTools(list: any[]): AgentOption[] {
|
||||
@@ -269,6 +308,56 @@ function mapMcpOptions(list: any[]): AgentOption[] {
|
||||
}));
|
||||
}
|
||||
|
||||
async function loadMcpToolsForOption(id: number | string) {
|
||||
const key = String(id || '').trim();
|
||||
if (!key || mcpToolsLoading.value[key]) return;
|
||||
|
||||
mcpToolsLoading.value = {
|
||||
...mcpToolsLoading.value,
|
||||
[key]: true,
|
||||
};
|
||||
try {
|
||||
const resource = await fetchMcpToolResource(key);
|
||||
if (!resource) return;
|
||||
|
||||
const currentOption = mcps.value.find((item) => String(item.value) === key);
|
||||
const mergedResource = {
|
||||
...currentOption?.raw,
|
||||
...resource,
|
||||
tools: Array.isArray(resource.tools) ? resource.tools : [],
|
||||
};
|
||||
if (currentOption) {
|
||||
mcps.value = mcps.value.map((item) =>
|
||||
String(item.value) === key
|
||||
? {
|
||||
...item,
|
||||
label:
|
||||
resource.title || resource.name || currentOption.label || 'MCP',
|
||||
raw: mergedResource,
|
||||
}
|
||||
: item,
|
||||
);
|
||||
}
|
||||
state.toolBindings.forEach((binding) => {
|
||||
if (
|
||||
String(binding.toolType || '').toUpperCase() === 'MCP' &&
|
||||
String(binding.targetId || '') === key
|
||||
) {
|
||||
binding.resourceSummary = {
|
||||
...binding.resourceSummary,
|
||||
...mergedResource,
|
||||
};
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('加载 MCP 工具失败', error);
|
||||
} finally {
|
||||
const nextLoading = { ...mcpToolsLoading.value };
|
||||
delete nextLoading[key];
|
||||
mcpToolsLoading.value = nextLoading;
|
||||
}
|
||||
}
|
||||
|
||||
async function handleAdd(kind: AgentCapabilityKind) {
|
||||
if (kind === 'knowledge') {
|
||||
addKnowledgeNode();
|
||||
@@ -417,43 +506,48 @@ function handleCloseTryout() {
|
||||
|
||||
<template>
|
||||
<div v-loading="pageLoading" class="agent-designer">
|
||||
<AgentStudioCanvas
|
||||
:state="state"
|
||||
:knowledge-options="knowledges"
|
||||
:mcp-options="mcps"
|
||||
:plugin-options="pluginTools"
|
||||
:selected-node-id="state.selectedNodeId"
|
||||
:workflow-options="workflows"
|
||||
@select="handleSelectNode"
|
||||
/>
|
||||
<AgentInspectorPanel
|
||||
:state="state"
|
||||
:models="models"
|
||||
:categories="categories"
|
||||
:knowledges="knowledges"
|
||||
:workflows="workflows"
|
||||
:plugin-tools="pluginTools"
|
||||
:mcps="mcps"
|
||||
:issues="issues"
|
||||
@change="markDirty"
|
||||
@remove-capability="removeSelectedCapability"
|
||||
@close-tryout="handleCloseTryout"
|
||||
@select-issue="handleSelectIssue"
|
||||
/>
|
||||
<AgentCommandBar
|
||||
:save-loading="saveLoading"
|
||||
:publish-loading="publishLoading"
|
||||
:publish-disabled="publishDisabled"
|
||||
:publish-text="publishText"
|
||||
:offline-disabled="offlineDisabled"
|
||||
:offline-loading="offlineLoading"
|
||||
:offline-visible="offlineVisible"
|
||||
@add="handleAdd"
|
||||
@save="handleSave()"
|
||||
@offline="handleOffline"
|
||||
@publish="handlePublish"
|
||||
@tryout="handleTryout"
|
||||
/>
|
||||
<template v-if="contentReady">
|
||||
<AgentStudioCanvas
|
||||
:active="canvasActive"
|
||||
:state="state"
|
||||
:knowledge-options="knowledges"
|
||||
:mcp-options="mcps"
|
||||
:plugin-options="pluginTools"
|
||||
:selected-node-id="state.selectedNodeId"
|
||||
:workflow-options="workflows"
|
||||
@select="handleSelectNode"
|
||||
/>
|
||||
<AgentInspectorPanel
|
||||
:state="state"
|
||||
:models="models"
|
||||
:categories="categories"
|
||||
:knowledges="knowledges"
|
||||
:workflows="workflows"
|
||||
:plugin-tools="pluginTools"
|
||||
:mcps="mcps"
|
||||
:mcp-tools-loading="mcpToolsLoading"
|
||||
:issues="issues"
|
||||
@change="markDirty"
|
||||
@remove-capability="removeSelectedCapability"
|
||||
@close-tryout="handleCloseTryout"
|
||||
@load-mcp-tools="loadMcpToolsForOption"
|
||||
@select-issue="handleSelectIssue"
|
||||
/>
|
||||
<AgentCommandBar
|
||||
:save-loading="saveLoading"
|
||||
:publish-loading="publishLoading"
|
||||
:publish-disabled="publishDisabled"
|
||||
:publish-text="publishText"
|
||||
:offline-disabled="offlineDisabled"
|
||||
:offline-loading="offlineLoading"
|
||||
:offline-visible="offlineVisible"
|
||||
@add="handleAdd"
|
||||
@save="handleSave()"
|
||||
@offline="handleOffline"
|
||||
@publish="handlePublish"
|
||||
@tryout="handleTryout"
|
||||
/>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user