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