perf: lighten routes and improve ai runtime events

This commit is contained in:
2026-06-21 21:01:38 +08:00
parent 0c52bf0641
commit 9647a227da
13 changed files with 130 additions and 74 deletions
@@ -139,6 +139,9 @@ const getStepLabel = (step: ReasoningStep) => {
if (step.status === 'running') { if (step.status === 'running') {
return '执行中'; return '执行中';
} }
if (step.status === 'failed') {
return '执行失败';
}
return '已完成'; return '已完成';
} }
@@ -169,6 +172,9 @@ const getStepColor = (step: ReasoningStep) => {
if (step.status === 'running') { if (step.status === 'running') {
return 'text-blue-500'; return 'text-blue-500';
} }
if (step.status === 'failed') {
return 'text-red-500';
}
return 'text-emerald-500'; return 'text-emerald-500';
} }
@@ -83,8 +83,8 @@ export interface ReasoningStep {
target?: Record<string, any>; target?: Record<string, any>;
}; };
output?: any; output?: any;
/** 步骤状态:running 执行中,completed 已完成 */ /** 步骤状态:running 执行中,completed 已完成failed 执行失败 */
status?: 'completed' | 'running'; status?: 'completed' | 'failed' | 'running';
timestamp?: string; timestamp?: string;
} }
@@ -214,6 +214,18 @@ export function useEventHandler(config: EventHandlerConfig) {
}; };
}; };
const getStreamErrorMessage = (event: StreamEvent) =>
event.error_message ||
event.message ||
event.error ||
event.content ||
'执行失败';
const isSuccessEvent = (event: StreamEvent) =>
!event.error_message &&
!event.error &&
(!event.status || event.status === 'success' || event.status === 'completed');
/** 处理流式事件 */ /** 处理流式事件 */
const handleStreamEvent = (event: StreamEvent, msgId: string) => { const handleStreamEvent = (event: StreamEvent, msgId: string) => {
switch (event.type) { switch (event.type) {
@@ -357,18 +369,19 @@ export function useEventHandler(config: EventHandlerConfig) {
} }
case 'error': { case 'error': {
const errorMessage = getStreamErrorMessage(event);
currentSteps.value.push({ currentSteps.value.push({
type: 'error', type: 'error',
content: event.message || event.content || '执行失败', content: errorMessage,
node_id: event.node_id, node_id: event.node_id,
node_type: event.node_type, node_type: event.node_type,
...buildReasoningMeta(event), ...buildReasoningMeta(event),
status: 'completed', status: 'failed',
timestamp: new Date().toISOString(), timestamp: new Date().toISOString(),
}); });
updateAssistantMessage(msgId, { updateAssistantMessage(msgId, {
status: 'failed', status: 'failed',
error_message: event.message || event.content || '执行失败', error_message: errorMessage,
reasoning_steps: [...currentSteps.value], reasoning_steps: [...currentSteps.value],
}); });
running.value = false; running.value = false;
@@ -535,25 +548,30 @@ export function useEventHandler(config: EventHandlerConfig) {
event.node_type, event.node_type,
event.node_label, event.node_label,
); );
const nodeSucceeded = isSuccessEvent(event);
const nodeStatus = nodeSucceeded ? 'completed' : 'failed';
const nodeContent = nodeSucceeded
? completeLabel
: `${completeLabel || event.node_id || '节点'}${getStreamErrorMessage(event)}`;
if (existingIndex === -1) { if (existingIndex === -1) {
currentSteps.value.push({ currentSteps.value.push({
type: 'node_complete', type: 'node_complete',
content: completeLabel, content: nodeContent,
node_id: event.node_id, node_id: event.node_id,
node_type: event.node_type, node_type: event.node_type,
...buildReasoningMeta(event), ...buildReasoningMeta(event),
output: event.outputs, output: event.outputs,
status: 'completed', status: nodeStatus,
timestamp: new Date().toISOString(), timestamp: new Date().toISOString(),
}); });
} else { } else {
currentSteps.value[existingIndex] = { currentSteps.value[existingIndex] = {
...currentSteps.value[existingIndex]!, ...currentSteps.value[existingIndex]!,
type: 'node_complete', type: 'node_complete',
content: completeLabel, content: nodeContent,
...buildReasoningMeta(event), ...buildReasoningMeta(event),
output: event.outputs, output: event.outputs,
status: 'completed', status: nodeStatus,
}; };
} }
updateAssistantMessage(msgId, { updateAssistantMessage(msgId, {
@@ -165,18 +165,12 @@ export class ZqTableApi<T extends Record<string, any> = any> {
// 核心数据加载逻辑 // 核心数据加载逻辑
async reload(params: Record<string, any> = {}) { async reload(params: Record<string, any> = {}) {
console.log('[ZqTable] reload called, state:', this.state?.gridOptions);
const { proxyConfig } = this.state?.gridOptions || {}; const { proxyConfig } = this.state?.gridOptions || {};
if (!proxyConfig?.ajax?.query) { if (!proxyConfig?.ajax?.query) {
console.warn(
'[ZqTable] No ajax.query configured, proxyConfig:',
proxyConfig,
);
this.setLoading(false); this.setLoading(false);
return; return;
} }
console.log('[ZqTable] Starting data load...');
this.setLoading(true); this.setLoading(true);
try { try {
// 1. 获取表单数据(带超时保护) // 1. 获取表单数据(带超时保护)
-3
View File
@@ -9,7 +9,6 @@ import { preferences } from '@vben/preferences';
import { getAllMenusApi } from '#/api/core/menu'; import { getAllMenusApi } from '#/api/core/menu';
import { BasicLayout, IFrameView } from '#/layouts'; import { BasicLayout, IFrameView } from '#/layouts';
import { $t } from '#/locales';
import { useAppContextStore } from '#/store/app-context'; import { useAppContextStore } from '#/store/app-context';
import { isLightMenuName } from './light-menu'; import { isLightMenuName } from './light-menu';
@@ -194,8 +193,6 @@ async function generateAccess(options: GenerateMenuAndRoutesOptions) {
'../views/ai-platform/workflow/editor/index.vue', '../views/ai-platform/workflow/editor/index.vue',
'../views/ai-platform/workflow/index.vue', '../views/ai-platform/workflow/index.vue',
'../views/ai-platform/workflow-runs/index.vue', '../views/ai-platform/workflow-runs/index.vue',
'../views/dashboard/analytics/index.vue',
'../views/dashboard/workspace/index.vue',
]); ]);
const layoutMap: ComponentRecordType = { const layoutMap: ComponentRecordType = {
@@ -1,8 +1,5 @@
export const LIGHT_MENU_NAMES = new Set([ export const LIGHT_MENU_NAMES = new Set([
'ControlCenter', 'ControlCenter',
'Dashboard',
'DashboardAnalytics',
'DashboardWorkspace',
'AIAgent', 'AIAgent',
'AIKnowledgeDetail', 'AIKnowledgeDetail',
'AIModelConfig', 'AIModelConfig',
@@ -26,7 +23,6 @@ export const LIGHT_MENU_NAMES = new Set([
const LIGHT_ROUTE_PATH_PREFIXES = [ const LIGHT_ROUTE_PATH_PREFIXES = [
'/control-center', '/control-center',
'/dashboard',
'/ai-platform/agent', '/ai-platform/agent',
'/ai-platform/knowledge', '/ai-platform/knowledge',
'/ai-platform/knowledge-base', '/ai-platform/knowledge-base',
+54 -9
View File
@@ -11,20 +11,32 @@ type WindowWithIdleCallback = Window & {
) => IdleCallbackHandle; ) => IdleCallbackHandle;
}; };
type NavigatorWithConnection = Navigator & {
connection?: {
effectiveType?: string;
saveData?: boolean;
};
};
let prefetched = false; let prefetched = false;
const criticalPrefetchTasks = [ const corePrefetchTasks = [
() => import('#/views/_core/agent-chat/index.vue'), () => import('#/views/_core/user/index.vue'),
() => import('#/views/ai-platform/workflow-runs/index.vue'), () => import('#/views/_core/menu/index.vue'),
() => import('#/components/ai-chat-panel/AiChatPanel.vue'), () => import('#/views/_core/role/index.vue'),
() => import('#/views/ai-platform/workflow-runs/modules/detail-dialog.vue'),
]; ];
const deferredPrefetchTasks = [ const aiInteractivePrefetchTasks = [
() => import('#/views/_core/agent-chat/index.vue'),
() => import('#/components/ai-chat-panel/AiChatPanel.vue'),
];
const aiManagementPrefetchTasks = [
() => import('#/views/ai-platform/agent/index.vue'), () => import('#/views/ai-platform/agent/index.vue'),
() => import('#/views/ai-platform/workflow/index.vue'), () => import('#/views/ai-platform/workflow/index.vue'),
() => import('#/views/ai-platform/model/index.vue'), () => import('#/views/ai-platform/model/index.vue'),
() => import('#/views/ai-platform/knowledge/index.vue'), () => import('#/views/ai-platform/knowledge/index.vue'),
() => import('#/views/ai-platform/workflow-runs/index.vue'),
]; ];
function scheduleIdle(callback: () => void) { function scheduleIdle(callback: () => void) {
@@ -38,6 +50,28 @@ function scheduleIdle(callback: () => void) {
window.setTimeout(callback, 1200); window.setTimeout(callback, 1200);
} }
function canPrefetch() {
if (typeof document !== 'undefined' && document.visibilityState !== 'visible') {
return false;
}
const connection = (navigator as NavigatorWithConnection).connection;
if (connection?.saveData) {
return false;
}
return !/(^|-)2g$/.test(connection?.effectiveType || '');
}
async function runTasksSequentially(tasks: Array<() => Promise<unknown>>) {
for (const task of tasks) {
if (!canPrefetch()) {
return;
}
await task().catch(() => undefined);
}
}
function prefetchAiPlatformPages() { function prefetchAiPlatformPages() {
if (prefetched || typeof window === 'undefined') { if (prefetched || typeof window === 'undefined') {
return; return;
@@ -45,11 +79,22 @@ function prefetchAiPlatformPages() {
prefetched = true; prefetched = true;
window.setTimeout(() => { window.setTimeout(() => {
void Promise.allSettled(criticalPrefetchTasks.map((task) => task()));
}, 300);
scheduleIdle(() => { scheduleIdle(() => {
void Promise.allSettled(deferredPrefetchTasks.map((task) => task())); void runTasksSequentially(corePrefetchTasks);
}); });
}, 800);
window.setTimeout(() => {
scheduleIdle(() => {
void runTasksSequentially(aiInteractivePrefetchTasks);
});
}, 4000);
window.setTimeout(() => {
scheduleIdle(() => {
void runTasksSequentially(aiManagementPrefetchTasks);
});
}, 9000);
} }
export { prefetchAiPlatformPages }; export { prefetchAiPlatformPages };
@@ -32,6 +32,24 @@ const coreRoutes: RouteRecordRaw[] = [
redirect: preferences.app.defaultHomePath, redirect: preferences.app.defaultHomePath,
children: [], children: [],
}, },
{
meta: { hideInMenu: true, title: '控制中心' },
name: 'DashboardLegacyRedirect',
path: '/dashboard',
redirect: '/page-render/main_home',
},
{
meta: { hideInMenu: true, title: '控制中心' },
name: 'DashboardAnalyticsLegacyRedirect',
path: '/dashboard/analytics',
redirect: '/page-render/main_home',
},
{
meta: { hideInMenu: true, title: '控制中心' },
name: 'DashboardWorkspaceLegacyRedirect',
path: '/dashboard/workspace',
redirect: '/page-render/main_home',
},
{ {
component: AuthPageLayout, component: AuthPageLayout,
meta: { meta: {
@@ -4,7 +4,6 @@ import { mergeRouteModules, traverseTreeValues } from '@vben/utils';
import { coreRoutes, fallbackNotFoundRoute } from './core'; import { coreRoutes, fallbackNotFoundRoute } from './core';
import aiPlatformRoutes from './modules/ai-platform'; import aiPlatformRoutes from './modules/ai-platform';
import dashboardRoutes from './modules/dashboard';
// 有需要可以自行打开注释,并创建文件夹 // 有需要可以自行打开注释,并创建文件夹
// const externalRouteFiles = import.meta.glob('./external/**/*.ts', { eager: true }); // const externalRouteFiles = import.meta.glob('./external/**/*.ts', { eager: true });
@@ -13,7 +12,6 @@ import dashboardRoutes from './modules/dashboard';
/** 动态路由 */ /** 动态路由 */
const dynamicRoutes: RouteRecordRaw[] = mergeRouteModules({ const dynamicRoutes: RouteRecordRaw[] = mergeRouteModules({
'./modules/ai-platform.ts': { default: aiPlatformRoutes }, './modules/ai-platform.ts': { default: aiPlatformRoutes },
'./modules/dashboard.ts': { default: dashboardRoutes },
}); });
/** 外部路由列表,访问这些页面可以不需要Layout,可能用于内嵌在别的系统(不会显示在菜单中) */ /** 外部路由列表,访问这些页面可以不需要Layout,可能用于内嵌在别的系统(不会显示在菜单中) */
@@ -2,7 +2,7 @@ import type { RouteRecordRaw } from 'vue-router';
const routes: RouteRecordRaw[] = [ const routes: RouteRecordRaw[] = [
{ {
meta: { hideInMenu: true }, meta: { hideInMenu: true, title: '控制中心' },
name: 'ControlCenterLegacyRedirect', name: 'ControlCenterLegacyRedirect',
path: '/control-center', path: '/control-center',
redirect: '/page-render/main_home', redirect: '/page-render/main_home',
@@ -17,13 +17,13 @@ const routes: RouteRecordRaw[] = [
component: () => import('#/views/_core/page-render/index.vue'), component: () => import('#/views/_core/page-render/index.vue'),
}, },
{ {
meta: { hideInMenu: true }, meta: { hideInMenu: true, title: '知识库' },
name: 'AIKnowledgeLegacyRedirect', name: 'AIKnowledgeLegacyRedirect',
path: '/ai-platform/knowledge', path: '/ai-platform/knowledge',
redirect: '/ai-platform/knowledge-base', redirect: '/ai-platform/knowledge-base',
}, },
{ {
meta: { hideInMenu: true }, meta: { hideInMenu: true, title: '知识库' },
name: 'SubAppAIKnowledgeLegacyRedirect', name: 'SubAppAIKnowledgeLegacyRedirect',
path: '/app/:appCode/ai-platform/knowledge', path: '/app/:appCode/ai-platform/knowledge',
redirect: (to) => `/app/${to.params.appCode}/ai-platform/knowledge-base`, redirect: (to) => `/app/${to.params.appCode}/ai-platform/knowledge-base`,
@@ -65,13 +65,13 @@ const routes: RouteRecordRaw[] = [
component: () => import('#/views/ai-platform/model/index.vue'), component: () => import('#/views/ai-platform/model/index.vue'),
}, },
{ {
meta: { hideInMenu: true }, meta: { hideInMenu: true, title: '知识库详情' },
name: 'AIKnowledgeLegacyDetailRedirect', name: 'AIKnowledgeLegacyDetailRedirect',
path: '/ai-platform/knowledge/detail/:id', path: '/ai-platform/knowledge/detail/:id',
redirect: (to) => `/ai-platform/knowledge-base/detail/${to.params.id}`, redirect: (to) => `/ai-platform/knowledge-base/detail/${to.params.id}`,
}, },
{ {
meta: { hideInMenu: true }, meta: { hideInMenu: true, title: '知识库详情' },
name: 'SubAppAIKnowledgeLegacyDetailRedirect', name: 'SubAppAIKnowledgeLegacyDetailRedirect',
path: '/app/:appCode/ai-platform/knowledge/detail/:id', path: '/app/:appCode/ai-platform/knowledge/detail/:id',
redirect: (to) => redirect: (to) =>
@@ -98,6 +98,11 @@ const getStreamErrorMessage = (
event?.content || event?.content ||
$t('ai-platform.workflow.editor.chatPanel.execFailed'); $t('ai-platform.workflow.editor.chatPanel.execFailed');
const isSuccessEvent = (event: WorkflowStreamEvent) =>
!event.error_message &&
!event.error &&
(!event.status || event.status === 'success' || event.status === 'completed');
// 发送消息 // 发送消息
const handleSend = (text: string) => { const handleSend = (text: string) => {
if (!text || running.value) return; if (!text || running.value) return;
@@ -448,15 +453,20 @@ const handleStreamEvent = (event: WorkflowStreamEvent, msgId: string) => {
event.node_type, event.node_type,
event.node_label, event.node_label,
); );
const nodeSucceeded = isSuccessEvent(event);
const nodeStatus = nodeSucceeded ? 'completed' : 'failed';
const nodeContent = nodeSucceeded
? nodeLabel
: `${nodeLabel || event.node_id || '节点'}${getStreamErrorMessage(event)}`;
if (existingIndex === -1) { if (existingIndex === -1) {
// 如果没有找到对应的 start,添加新的 complete 步骤 // 如果没有找到对应的 start,添加新的 complete 步骤
currentSteps.value.push({ currentSteps.value.push({
type: 'node_complete', type: 'node_complete',
content: nodeLabel, content: nodeContent,
node_id: event.node_id, node_id: event.node_id,
node_type: event.node_type, node_type: event.node_type,
output: event.outputs, output: event.outputs,
status: 'completed', status: nodeStatus,
timestamp: new Date().toISOString(), timestamp: new Date().toISOString(),
}); });
} else { } else {
@@ -464,9 +474,9 @@ const handleStreamEvent = (event: WorkflowStreamEvent, msgId: string) => {
currentSteps.value[existingIndex] = { currentSteps.value[existingIndex] = {
...currentSteps.value[existingIndex], ...currentSteps.value[existingIndex],
type: 'node_complete', type: 'node_complete',
content: nodeLabel, content: nodeContent,
output: event.outputs, output: event.outputs,
status: 'completed', status: nodeStatus,
}; };
} }
updateAssistantMessage(msgId, { updateAssistantMessage(msgId, {
@@ -296,7 +296,10 @@ const handleRun = () => {
// 更新节点完成状态(包含输出数据) // 更新节点完成状态(包含输出数据)
const state = nodeStates.value.get(event.node_id || ''); const state = nodeStates.value.get(event.node_id || '');
if (state) { if (state) {
state.status = event.status === 'success' ? 'success' : 'failed'; state.status =
event.status === 'success' || event.status === 'completed'
? 'success'
: 'failed';
state.elapsed_time = event.elapsed_time; state.elapsed_time = event.elapsed_time;
state.tokens_used = event.tokens_used; state.tokens_used = event.tokens_used;
state.error_message = event.error_message; state.error_message = event.error_message;
@@ -151,10 +151,6 @@ watch(
); );
if (orphanedChildren.length > 0) { if (orphanedChildren.length > 0) {
console.log(
'[Loop Delete] Cascade deleting orphaned children:',
orphanedChildren.map((n) => n.id),
);
nextTick(() => { nextTick(() => {
removeNodes(orphanedChildren.map((n) => n.id)); removeNodes(orphanedChildren.map((n) => n.id));
}); });
@@ -483,10 +479,6 @@ const handleChildNodeDrag = (changes: any[]) => {
// 查找被删除节点的子节点(在节点被删除前,子节点的 parentNode 仍然指向它) // 查找被删除节点的子节点(在节点被删除前,子节点的 parentNode 仍然指向它)
const childNodes = nodes.value.filter((n) => n.parentNode === change.id); const childNodes = nodes.value.filter((n) => n.parentNode === change.id);
if (childNodes.length > 0) { if (childNodes.length > 0) {
console.log(
'[Loop Delete] Found child nodes to delete:',
childNodes.map((n) => n.id),
);
for (const child of childNodes) { for (const child of childNodes) {
childNodesToDelete.push(child.id); childNodesToDelete.push(child.id);
} }
@@ -1095,26 +1087,6 @@ const onDrop = (event: DragEvent) => {
position.y < nodeY + nodeHeight; position.y < nodeY + nodeHeight;
const isInside = isInsideX && isInsideY; const isInside = isInsideX && isInsideY;
console.log('[Loop Drop Check]', {
dropPosition: position,
loopNode: {
id: node.id,
x: nodeX,
y: nodeY,
width: nodeWidth,
height: nodeHeight,
},
bounds: {
left: nodeX,
right: nodeX + nodeWidth,
top: nodeY + loopHeaderHeight,
bottom: nodeY + nodeHeight,
},
isInsideX,
isInsideY,
isInside,
});
if (isInside) { if (isInside) {
parentNodeId = node.id; parentNodeId = node.id;
// 计算相对于父节点的位置(子节点位置是相对于父节点左上角的) // 计算相对于父节点的位置(子节点位置是相对于父节点左上角的)
@@ -1126,7 +1098,6 @@ const onDrop = (event: DragEvent) => {
x: Math.min(Math.max(20, relX), nodeWidth - 280), // 限制在容器宽度内 x: Math.min(Math.max(20, relX), nodeWidth - 280), // 限制在容器宽度内
y: Math.min(Math.max(loopHeaderHeight, relY), nodeHeight - 50), // 限制在容器高度内 y: Math.min(Math.max(loopHeaderHeight, relY), nodeHeight - 50), // 限制在容器高度内
}; };
console.log('[Loop Child Position]', { relativePosition, relX, relY });
break; break;
} }
} }