diff --git a/.gitignore b/.gitignore index a76bbf7..2cfafb5 100644 --- a/.gitignore +++ b/.gitignore @@ -23,6 +23,9 @@ coverage ./web/package-lock.json .VSCodeCounter **/backend-mock/data +/.tmp/ +/ai-agent-admin-dist-*.tgz +/ai-agent-admin-web-dist*.tgz # local env files ./web/.env.local diff --git a/backend-fastapi/ai_platform/api/agent_api.py b/backend-fastapi/ai_platform/api/agent_api.py index 9365617..9859b9f 100644 --- a/backend-fastapi/ai_platform/api/agent_api.py +++ b/backend-fastapi/ai_platform/api/agent_api.py @@ -422,8 +422,23 @@ async def publish_agent(agent_id: str, db: AsyncSession = Depends(get_db)): # 验证必要配置 if agent.mode == "autonomous": - if not agent.model_id: - raise HTTPException(status_code=400, detail="请先配置模型") + if agent.model_id: + model_result = await db.execute( + select(LLMModel).where( + LLMModel.id == agent.model_id, + LLMModel.is_deleted == False, + ) + ) + model = model_result.scalar_one_or_none() + if not model: + raise HTTPException(status_code=400, detail="模型不存在") + if not model.is_active: + raise HTTPException(status_code=400, detail="模型已禁用") + elif not await _resolve_default_chat_model_id(db): + raise HTTPException( + status_code=400, + detail="请先在模型配置中启用一个 chat 模型,或为智能体指定模型", + ) elif agent.mode == "dialog_flow": if not agent.workflow_id: raise HTTPException(status_code=400, detail="对话流模式需要配置工作流") diff --git a/backend-fastapi/ai_platform/services/agent_service.py b/backend-fastapi/ai_platform/services/agent_service.py index dd3fa2e..46e1562 100644 --- a/backend-fastapi/ai_platform/services/agent_service.py +++ b/backend-fastapi/ai_platform/services/agent_service.py @@ -582,6 +582,9 @@ class AgentService: 'type': 'complete', 'elapsed_time': elapsed_time, 'tokens_used': total_tokens, + 'total_tokens': total_tokens, + 'conversation_id': str(conversation.id), + 'message_id': str(assistant_msg.id), } except Exception as e: diff --git a/web/apps/web-ele/src/components/ChatBox/MessageBubble.vue b/web/apps/web-ele/src/components/ChatBox/MessageBubble.vue index b0cc209..947dd66 100644 --- a/web/apps/web-ele/src/components/ChatBox/MessageBubble.vue +++ b/web/apps/web-ele/src/components/ChatBox/MessageBubble.vue @@ -150,6 +150,14 @@ const getStepLabel = (step: ReasoningStep) => { tool_result: '工具结果', knowledge_retrieval: '知识库检索', annotation_reply: '标注回复', + parallel_start: '并行中', + parallel_complete: '并行完成', + waiting_input: '等待输入', + error: '失败', + loop_iteration_start: '循环中', + loop_iteration_complete: '循环完成', + loop_iteration_error: '循环失败', + loop_complete: '循环完成', }; return labels[step.type] || step.type; }; @@ -172,6 +180,14 @@ const getStepColor = (step: ReasoningStep) => { tool_result: 'text-teal-500', knowledge_retrieval: 'text-cyan-500', annotation_reply: 'text-emerald-500', + parallel_start: 'text-indigo-500', + parallel_complete: 'text-emerald-500', + waiting_input: 'text-amber-500', + error: 'text-red-500', + loop_iteration_start: 'text-blue-500', + loop_iteration_complete: 'text-emerald-500', + loop_iteration_error: 'text-red-500', + loop_complete: 'text-emerald-500', }; return colors[step.type] || 'text-gray-500'; }; @@ -301,7 +317,9 @@ const formatVoiceDuration = (seconds: number) => { {{ getStepLabel(step) }} - {{ step.content }} + + {{ step.content }} + diff --git a/web/apps/web-ele/src/components/ChatBox/types.ts b/web/apps/web-ele/src/components/ChatBox/types.ts index 065e34d..f462b5a 100644 --- a/web/apps/web-ele/src/components/ChatBox/types.ts +++ b/web/apps/web-ele/src/components/ChatBox/types.ts @@ -41,13 +41,21 @@ export interface ReasoningStep { type: | 'action' | 'annotation_reply' + | 'error' | 'knowledge_retrieval' + | 'loop_complete' + | 'loop_iteration_complete' + | 'loop_iteration_error' + | 'loop_iteration_start' | 'node_complete' | 'node_start' | 'observation' + | 'parallel_complete' + | 'parallel_start' | 'thought' | 'tool_call' - | 'tool_result'; + | 'tool_result' + | 'waiting_input'; content: string; tool?: string; params?: Record; diff --git a/web/apps/web-ele/src/components/ai-chat-panel/composables/useEventHandler.ts b/web/apps/web-ele/src/components/ai-chat-panel/composables/useEventHandler.ts index 4865e6e..fb9b08c 100644 --- a/web/apps/web-ele/src/components/ai-chat-panel/composables/useEventHandler.ts +++ b/web/apps/web-ele/src/components/ai-chat-panel/composables/useEventHandler.ts @@ -164,6 +164,12 @@ export function useEventHandler(config: EventHandlerConfig) { return fallbackLabel || nodeType || ''; }; + const getItemCount = (value: any): number => { + if (Array.isArray(value)) return value.length; + if (value && typeof value === 'object') return Object.keys(value).length; + return 0; + }; + /** 处理流式事件 */ const handleStreamEvent = (event: StreamEvent, msgId: string) => { switch (event.type) { @@ -272,9 +278,18 @@ export function useEventHandler(config: EventHandlerConfig) { } case 'error': { + currentSteps.value.push({ + type: 'error', + content: event.message || event.content || '执行失败', + node_id: event.node_id, + node_type: event.node_type, + status: 'completed', + timestamp: new Date().toISOString(), + }); updateAssistantMessage(msgId, { status: 'failed', error_message: event.message || event.content || '执行失败', + reasoning_steps: [...currentSteps.value], }); running.value = false; break; @@ -322,6 +337,19 @@ export function useEventHandler(config: EventHandlerConfig) { outputs[outputVar] = loopEvent.loop_results; } + currentSteps.value.push({ + type: 'loop_complete', + content: '循环执行完成', + node_id: loopEvent.node_id, + node_type: 'loop', + output: outputs, + status: 'completed', + timestamp: new Date().toISOString(), + }); + updateAssistantMessage(msgId, { + reasoning_steps: [...currentSteps.value], + }); + if (props.enableNodeEvents) { emit('node-complete', { node_id: loopEvent.node_id, @@ -336,6 +364,19 @@ export function useEventHandler(config: EventHandlerConfig) { } case 'loop_iteration_complete': { + currentSteps.value.push({ + type: 'loop_iteration_complete', + content: `循环第 ${event.iteration || '-'} 次完成`, + node_id: event.node_id, + node_type: 'loop', + output: event.output, + status: 'completed', + timestamp: new Date().toISOString(), + }); + updateAssistantMessage(msgId, { + reasoning_steps: [...currentSteps.value], + }); + if (props.enableNodeEvents) { emit('loop-iteration', { node_id: event.node_id, @@ -349,6 +390,18 @@ export function useEventHandler(config: EventHandlerConfig) { } case 'loop_iteration_error': { + currentSteps.value.push({ + type: 'loop_iteration_error', + content: `循环第 ${event.iteration || '-'} 次失败:${event.error || '执行失败'}`, + node_id: event.node_id, + node_type: 'loop', + status: 'completed', + timestamp: new Date().toISOString(), + }); + updateAssistantMessage(msgId, { + reasoning_steps: [...currentSteps.value], + }); + if (props.enableNodeEvents) { emit('loop-iteration', { node_id: event.node_id, @@ -362,6 +415,19 @@ export function useEventHandler(config: EventHandlerConfig) { } case 'loop_iteration_start': { + currentSteps.value.push({ + type: 'loop_iteration_start', + content: `循环第 ${event.iteration || '-'} 次开始`, + node_id: event.node_id, + node_type: 'loop', + params: { item: event.item, total: event.total }, + status: 'running', + timestamp: new Date().toISOString(), + }); + updateAssistantMessage(msgId, { + reasoning_steps: [...currentSteps.value], + }); + if (props.enableNodeEvents) { emit('loop-iteration', { node_id: event.node_id, @@ -423,6 +489,60 @@ export function useEventHandler(config: EventHandlerConfig) { break; } + case 'parallel_complete': { + const existingIndex = currentSteps.value.findIndex( + (s) => s.node_id === event.node_id && s.type === 'parallel_start', + ); + const branchResults = event.branch_results || {}; + const content = `并行分支完成:${getItemCount(branchResults)} 个分支`; + if (existingIndex === -1) { + currentSteps.value.push({ + type: 'parallel_complete', + content, + node_id: event.node_id, + node_type: 'parallel', + output: { + branch_results: branchResults, + total_tokens: event.total_tokens, + }, + status: 'completed', + timestamp: new Date().toISOString(), + }); + } else { + currentSteps.value[existingIndex] = { + ...currentSteps.value[existingIndex]!, + type: 'parallel_complete', + content, + output: { + branch_results: branchResults, + total_tokens: event.total_tokens, + }, + status: 'completed', + }; + } + updateAssistantMessage(msgId, { + reasoning_steps: [...currentSteps.value], + }); + break; + } + + case 'parallel_start': { + const branches = event.branches || []; + currentSteps.value.push({ + type: 'parallel_start', + content: `并行分支开始:${getItemCount(branches)} 个分支`, + node_id: event.node_id, + node_type: 'parallel', + params: { branches }, + status: 'running', + timestamp: new Date().toISOString(), + }); + updateAssistantMessage(msgId, { + reasoning_steps: [...currentSteps.value], + }); + break; + } + case 'node_event': { const nodeEvent = event.event; if (nodeEvent?.type === 'message') { @@ -493,12 +613,28 @@ export function useEventHandler(config: EventHandlerConfig) { waitingConfig.value = event.waiting_config || event.config; const configData = event.waiting_config || (event as any).config; + currentSteps.value.push({ + type: 'waiting_input', + content: + configData?.title || + configData?.question || + event.node_label || + '等待用户输入', + node_id: event.node_id, + node_type: event.node_type, + params: configData, + status: 'running', + timestamp: new Date().toISOString(), + }); if (configData?.type === 'design_preview') { handleDesignPreview(event, msgId, configData); } else { handleDialogFlowInteraction(msgId, configData); } + updateAssistantMessage(msgId, { + reasoning_steps: [...currentSteps.value], + }); running.value = false; break; diff --git a/web/apps/web-ele/src/router/access.ts b/web/apps/web-ele/src/router/access.ts index 2e662ce..7b67b91 100644 --- a/web/apps/web-ele/src/router/access.ts +++ b/web/apps/web-ele/src/router/access.ts @@ -205,13 +205,6 @@ async function generateAccess(options: GenerateMenuAndRoutesOptions) { return await generateAccessible(preferences.app.accessMode, { ...options, fetchMenuListAsync: async () => { - const { ElMessage } = - await import('element-plus/es/components/message/index'); - ElMessage({ - duration: 1500, - message: `${$t('common.loadingMenu')}...`, - }); - const appContextStore = useAppContextStore(); const menus = appContextStore.appCode ? await getAllMenusApi(appContextStore.appCode)