From 45314b7be6dd503f94ef596df58b730a1584d35c Mon Sep 17 00:00:00 2001 From: liailing1026 <1815388873@qq.com> Date: Wed, 21 Jan 2026 15:18:15 +0800 Subject: [PATCH] =?UTF-8?q?feat:=E4=BB=BB=E5=8A=A1=E6=89=A7=E8=A1=8C?= =?UTF-8?q?=E7=BB=93=E6=9E=9C=E6=80=A7=E8=83=BD=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../PlanEngine/AgentSelectModify.py | 3 +- .../PlanEngine/basePlan_Generator.py | 86 ++- backend/server.py | 42 +- frontend/src/api/index.ts | 202 ++++-- frontend/src/layout/components/Main/Task.vue | 98 ++- .../Main/TaskTemplate/TaskResult/index.vue | 193 +++++- .../Main/TaskTemplate/TaskSyllabus/Bg.vue | 10 +- .../TaskSyllabus/Branch/PlanModification.vue | 598 +++++++++++------- .../Main/TaskTemplate/TaskSyllabus/index.vue | 58 ++ 9 files changed, 952 insertions(+), 338 deletions(-) diff --git a/backend/AgentCoord/PlanEngine/AgentSelectModify.py b/backend/AgentCoord/PlanEngine/AgentSelectModify.py index 4eb1c28..a9b30f5 100644 --- a/backend/AgentCoord/PlanEngine/AgentSelectModify.py +++ b/backend/AgentCoord/PlanEngine/AgentSelectModify.py @@ -137,5 +137,6 @@ def AgentSelectModify_init(stepTask, General_Goal, Agent_Board): def AgentSelectModify_addAspect(aspectList, Agent_Board): - scoreTable = agentAbilityScoring(Agent_Board, aspectList) + newAspect = aspectList[-1] + scoreTable = agentAbilityScoring(Agent_Board, [newAspect]) return scoreTable diff --git a/backend/AgentCoord/PlanEngine/basePlan_Generator.py b/backend/AgentCoord/PlanEngine/basePlan_Generator.py index 347ace2..25c55ac 100644 --- a/backend/AgentCoord/PlanEngine/basePlan_Generator.py +++ b/backend/AgentCoord/PlanEngine/basePlan_Generator.py @@ -1,55 +1,53 @@ from AgentCoord.PlanEngine.planOutline_Generator import generate_PlanOutline -from AgentCoord.PlanEngine.AgentSelection_Generator import ( - generate_AgentSelection, -) -from AgentCoord.PlanEngine.taskProcess_Generator import generate_TaskProcess -import AgentCoord.util as util +# from AgentCoord.PlanEngine.AgentSelection_Generator import ( +# generate_AgentSelection, +# ) def generate_basePlan( General_Goal, Agent_Board, AgentProfile_Dict, InitialObject_List ): - basePlan = { - "Initial Input Object": InitialObject_List, - "Collaboration Process": [], - } + """ + 优化模式:生成大纲 + 智能体选择,但不生成任务流程 + 优化用户体验: + 1. 快速生成大纲和分配智能体 + 2. 用户可以看到完整的大纲和智能体图标 + 3. TaskProcess由前端通过 fillStepTask API 异步填充 + + """ + # 参数保留以保持接口兼容性 + _ = AgentProfile_Dict PlanOutline = generate_PlanOutline( - InitialObject_List=[], General_Goal=General_Goal + InitialObject_List=InitialObject_List, General_Goal=General_Goal ) + + basePlan = { + "General Goal": General_Goal, + "Initial Input Object": InitialObject_List, + "Collaboration Process": [] + } + for stepItem in PlanOutline: - Current_Task = { - "TaskName": stepItem["StepName"], - "InputObject_List": stepItem["InputObject_List"], - "OutputObject": stepItem["OutputObject"], - "TaskContent": stepItem["TaskContent"], + # # 为每个步骤分配智能体 + # Current_Task = { + # "TaskName": stepItem["StepName"], + # "InputObject_List": stepItem["InputObject_List"], + # "OutputObject": stepItem["OutputObject"], + # "TaskContent": stepItem["TaskContent"], + # } + # AgentSelection = generate_AgentSelection( + # General_Goal=General_Goal, + # Current_Task=Current_Task, + # Agent_Board=Agent_Board, + # ) + + # 添加智能体选择,但不添加任务流程 + stepItem["AgentSelection"] = [] + stepItem["TaskProcess"] = [] # 空数组,由前端异步填充 + stepItem["Collaboration_Brief_frontEnd"] = { + "template": "", + "data": {} } - AgentSelection = generate_AgentSelection( - General_Goal=General_Goal, - Current_Task=Current_Task, - Agent_Board=Agent_Board, - ) - Current_Task_Description = { - "TaskName": stepItem["StepName"], - "AgentInvolved": [ - {"Name": name, "Profile": AgentProfile_Dict[name]} - for name in AgentSelection - ], - "InputObject_List": stepItem["InputObject_List"], - "OutputObject": stepItem["OutputObject"], - "CurrentTaskDescription": util.generate_template_sentence_for_CollaborationBrief( - stepItem["InputObject_List"], - stepItem["OutputObject"], - AgentSelection, - stepItem["TaskContent"], - ), - } - TaskProcess = generate_TaskProcess( - General_Goal=General_Goal, - Current_Task_Description=Current_Task_Description, - ) - # add the generated AgentSelection and TaskProcess to the stepItem - stepItem["AgentSelection"] = AgentSelection - stepItem["TaskProcess"] = TaskProcess basePlan["Collaboration Process"].append(stepItem) - basePlan["General Goal"] = General_Goal - return basePlan + + return basePlan \ No newline at end of file diff --git a/backend/server.py b/backend/server.py index 8af354a..8cd9417 100644 --- a/backend/server.py +++ b/backend/server.py @@ -1,7 +1,8 @@ -from flask import Flask, request, jsonify +from flask import Flask, request, jsonify, Response, stream_with_context import json from DataProcess import Add_Collaboration_Brief_FrontEnd from AgentCoord.RehearsalEngine_V2.ExecutePlan import executePlan +from AgentCoord.RehearsalEngine_V2.ExecutePlan_Optimized import executePlan_streaming from AgentCoord.PlanEngine.basePlan_Generator import generate_basePlan from AgentCoord.PlanEngine.fill_stepTask import fill_stepTask from AgentCoord.PlanEngine.fill_stepTask_TaskProcess import ( @@ -257,6 +258,45 @@ def Handle_executePlan(): return response +@app.route("/executePlanOptimized", methods=["post"]) +def Handle_executePlanOptimized(): + """ + 优化版流式执行计划(阶段1+2:步骤级流式 + 动作级智能并行) + + 返回 SSE 流,每完成一个动作就返回结果 + - 无依赖关系的动作并行执行 + - 有依赖关系的动作串行执行 + + 前端使用 EventSource 接收 + """ + incoming_data = request.get_json() + + def generate(): + try: + for chunk in executePlan_streaming( + plan=incoming_data["plan"], + num_StepToRun=incoming_data.get("num_StepToRun"), + RehearsalLog=incoming_data.get("RehearsalLog", []), + AgentProfile_Dict=AgentProfile_Dict, + ): + yield chunk + except Exception as e: + error_event = json.dumps({ + "type": "error", + "message": str(e) + }, ensure_ascii=False) + yield f"data: {error_event}\n\n" + + return Response( + stream_with_context(generate()), + mimetype="text/event-stream", + headers={ + "Cache-Control": "no-cache", + "X-Accel-Buffering": "no", + } + ) + + @app.route("/_saveRequestCashe", methods=["post"]) def Handle_saveRequestCashe(): with open( diff --git a/frontend/src/api/index.ts b/frontend/src/api/index.ts index f2e573f..65ca112 100644 --- a/frontend/src/api/index.ts +++ b/frontend/src/api/index.ts @@ -34,6 +34,47 @@ export type IExecuteRawResponse = { ActionHistory: ActionHistory[] } +/** + * SSE 流式事件类型 + */ +export type StreamingEvent = + | { + type: 'step_start' + step_index: number + total_steps: number + step_name: string + task_description?: string + } + | { + type: 'action_complete' + step_index: number + step_name: string + action_index: number + total_actions: number + completed_actions: number + action_result: ActionHistory + batch_info?: { + batch_index: number + batch_size: number + is_parallel: boolean + } + } + | { + type: 'step_complete' + step_index: number + step_name: string + step_log_node: any + object_log_node: any + } + | { + type: 'execution_complete' + total_steps: number + } + | { + type: 'error' + message: string + } + export interface IFillAgentSelectionRequest { goal: string stepTask: IApiStepTask @@ -99,7 +140,95 @@ class Api { }) } - // 分支任务大纲 + /** + * 优化版流式执行计划(阶段1+2:步骤级流式 + 动作级智能并行) + * 无依赖关系的动作并行执行,有依赖关系的动作串行执行 + */ + executePlanOptimized = ( + plan: IRawPlanResponse, + onMessage: (event: StreamingEvent) => void, + onError?: (error: Error) => void, + onComplete?: () => void, + ) => { + const data = { + RehearsalLog: [], + num_StepToRun: null, + plan: { + 'Initial Input Object': plan['Initial Input Object'], + 'General Goal': plan['General Goal'], + 'Collaboration Process': plan['Collaboration Process']?.map((step) => ({ + StepName: step.StepName, + TaskContent: step.TaskContent, + InputObject_List: step.InputObject_List, + OutputObject: step.OutputObject, + AgentSelection: step.AgentSelection, + Collaboration_Brief_frontEnd: step.Collaboration_Brief_frontEnd, + TaskProcess: step.TaskProcess.map((action) => ({ + ActionType: action.ActionType, + AgentName: action.AgentName, + Description: action.Description, + ID: action.ID, + ImportantInput: action.ImportantInput, + })), + })), + }, + } + + fetch('/api/executePlanOptimized', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify(data), + }) + .then(async (response) => { + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`) + } + + const reader = response.body?.getReader() + const decoder = new TextDecoder() + + if (!reader) { + throw new Error('Response body is null') + } + + let buffer = '' + + while (true) { + const { done, value } = await reader.read() + + if (done) { + onComplete?.() + break + } + + buffer += decoder.decode(value, { stream: true }) + + const lines = buffer.split('\n') + buffer = lines.pop() || '' + + for (const line of lines) { + if (line.startsWith('data: ')) { + const data = line.slice(6) + try { + const event = JSON.parse(data) + onMessage(event) + } catch (e) { + console.error('Failed to parse SSE data:', e) + } + } + } + } + }) + .catch((error) => { + onError?.(error) + }) + } + + /** + * 分支任务大纲 + */ branchPlanOutline = (data: { branch_Number: number Modification_Requirement: string @@ -122,7 +251,9 @@ class Api { }) } - // 分支任务流程 + /** + * 分支任务流程 + */ branchTaskProcess = (data: { branch_Number: number Modification_Requirement: string @@ -146,7 +277,6 @@ class Api { } fillStepTask = async (data: { goal: string; stepTask: any }): Promise => { - // 后端返回格式:包含 Collaboration_Brief_FrontEnd(大写 FrontEnd) const response = await request< { 'General Goal': string @@ -179,14 +309,11 @@ class Api { }, }) - // 数据转换:后端的 Collaboration_Brief_FrontEnd → 前端的 Collaboration_Brief_frontEnd - const vec2Hsl = (color: number[]): string => { const [h, s, l] = color return `hsl(${h}, ${s}%, ${l}%)` } - // 转换 brief.data:后端格式 { "0": { text, color: [h,s,l] } } → 前端格式 { "0": { text, style: { background } } } const briefData: Record }> = {} if (response.Collaboration_Brief_FrontEnd?.data) { for (const [key, value] of Object.entries(response.Collaboration_Brief_FrontEnd.data)) { @@ -199,7 +326,9 @@ class Api { } } - // 构建前端格式的 IRawStepTask + /** + * 构建前端格式的 IRawStepTask + */ return { StepName: response.StepName || '', TaskContent: response.TaskContent || '', @@ -219,7 +348,6 @@ class Api { stepTask: IApiStepTask agents: string[] }): Promise => { - // 后端返回格式: IRawStepTask const response = await request< { 'General Goal': string @@ -264,16 +392,11 @@ class Api { }, }) - // 数据转换:后端格式 (IRawStepTask) → 前端格式 (IApiStepTask) - // 注意:此转换逻辑与Mock API完全一致 - - // 1. 转换颜色格式:[h, s, l] → "hsl(h, s%, l%)" const vec2Hsl = (color: number[]): string => { const [h, s, l] = color return `hsl(${h}, ${s}%, ${l}%)` } - // 2. 转换 brief.data: { "0": { text, color: [h,s,l] } } → { "0": { text, style: { background } } } const briefData: Record = {} if (response.Collaboration_Brief_FrontEnd?.data) { for (const [key, value] of Object.entries(response.Collaboration_Brief_FrontEnd.data)) { @@ -286,7 +409,6 @@ class Api { } } - // 3. 转换 process: { ID, ActionType, AgentName, Description, ImportantInput } → { id, type, agent, description, inputs } const process = (response.TaskProcess || []).map((action) => ({ id: action.ID, type: action.ActionType, @@ -295,7 +417,6 @@ class Api { inputs: action.ImportantInput, })) - // 4. 构建前端格式的 IApiStepTask return { name: response.StepName || '', content: response.TaskContent || '', @@ -310,12 +431,13 @@ class Api { } } - // 为每个智能体评分 + /** + * 为每个智能体评分 + */ agentSelectModifyInit = async (data: { goal: string stepTask: any }): Promise>> => { - // 后端返回:维度 -> agent -> { Reason, Score } const response = await request< { 'General Goal': string @@ -336,11 +458,9 @@ class Api { }, }) - // 数据转换:后端格式 (维度->agent) → 前端格式 (agent->维度) const transformedData: Record> = {} for (const [aspect, agents] of Object.entries(response)) { - // aspect: 维度名称, agents: { agentName: { Reason, Score } } for (const [agentName, scoreInfo] of Object.entries(agents)) { if (!transformedData[agentName]) { transformedData[agentName] = {} @@ -355,15 +475,15 @@ class Api { return transformedData } - // 添加新的评估维度 - // 定义返回类型(与 Mock 数据格式一致) + /** + * 添加新的评估维度 + */ agentSelectModifyAddAspect = async (data: { aspectList: string[] }): Promise<{ aspectName: string agentScores: Record }> => { - // 后端返回:维度 -> agent -> { Reason, Score } const response = await request< { aspectList: string[] @@ -377,13 +497,14 @@ class Api { }, }) - // 获取新添加的维度(最后一个) + /** + * 获取新添加的维度 + */ const newAspect = data.aspectList[data.aspectList.length - 1] if (!newAspect) { throw new Error('aspectList is empty') } - // 提取该维度的数据:维度 -> agent -> { Reason, Score } → agent -> { score, reason } const newAspectAgents = response[newAspect] const agentScores: Record = {} @@ -401,23 +522,18 @@ class Api { agentScores, } } - - // ==================== Mock API(开发阶段使用)==================== - // Mock API 使用与真实 API 相同的数据转换逻辑,确保将来切换无缝 - - // Mock: 为每个智能体评分 + /** + * ==================== Mock API(开发阶段使用)==================== + *为每个智能体评分 + */ mockAgentSelectModifyInit = async (): Promise< Record> > => { - // 调用Mock后端数据(维度 -> agent -> { Reason, Score }) const response: BackendAgentScoreResponse = await mockBackendAgentSelectModifyInit() - // 数据转换:后端格式 (维度->agent) → 前端格式 (agent->维度) - // 注意:此转换逻辑与真实API完全一致 const transformedData: Record> = {} for (const [aspect, agents] of Object.entries(response)) { - // aspect: 维度名称, agents: { agentName: { Reason, Score } } for (const [agentName, scoreInfo] of Object.entries(agents)) { if (!transformedData[agentName]) { transformedData[agentName] = {} @@ -432,26 +548,21 @@ class Api { return transformedData } - // Mock: 添加新的评估维度 mockAgentSelectModifyAddAspect = async (data: { aspectList: string[] }): Promise<{ aspectName: string agentScores: Record }> => { - // 调用Mock后端数据(维度 -> agent -> { Reason, Score }) const response: BackendAgentScoreResponse = await mockBackendAgentSelectModifyAddAspect( data.aspectList, ) - // 获取新添加的维度(最后一个) const newAspect = data.aspectList[data.aspectList.length - 1] if (!newAspect) { throw new Error('aspectList is empty') } - // 提取该维度的数据:维度 -> agent -> { Reason, Score } → agent -> { score, reason } - // 注意:此转换逻辑与真实API完全一致 const newAspectAgents = response[newAspect] const agentScores: Record = {} @@ -470,29 +581,22 @@ class Api { } } - // Mock: 填充智能体任务流程 mockFillStepTaskTaskProcess = async (data: { goal: string stepTask: IApiStepTask agents: string[] }): Promise => { - // 调用Mock后端数据(后端原始格式) const response: RawAgentTaskProcessResponse = await mockBackendFillAgentTaskProcess( data.goal, data.stepTask, data.agents, ) - // 数据转换:后端格式 → 前端格式 - // 注意:此转换逻辑与真实API完全一致 - - // 1. 转换颜色格式:[h, s, l] → "hsl(h, s%, l%)" const vec2Hsl = (color: number[]): string => { const [h, s, l] = color return `hsl(${h}, ${s}%, ${l}%)` } - // 2. 转换 brief.data: { "0": { text, color: [h,s,l] } } → { "0": { text, style: { background } } } const briefData: Record = {} if (response.Collaboration_Brief_frontEnd?.data) { for (const [key, value] of Object.entries(response.Collaboration_Brief_frontEnd.data)) { @@ -505,7 +609,6 @@ class Api { } } - // 3. 转换 process: { ID, ActionType, AgentName, Description, ImportantInput } → { id, type, agent, description, inputs } const process = (response.TaskProcess || []).map((action) => ({ id: action.ID, type: action.ActionType, @@ -514,7 +617,6 @@ class Api { inputs: action.ImportantInput, })) - // 4. 构建前端格式的 IApiStepTask return { name: response.StepName || '', content: response.TaskContent || '', @@ -529,7 +631,6 @@ class Api { } } - // Mock: 分支任务大纲 mockBranchPlanOutline = async (data: { branch_Number: number Modification_Requirement: string @@ -538,7 +639,6 @@ class Api { initialInputs: string[] goal: string }): Promise => { - // 直接调用 Mock API(已经返回 IRawPlanResponse 格式) const response = await mockBranchPlanOutlineAPI({ branch_Number: data.branch_Number, Modification_Requirement: data.Modification_Requirement, @@ -551,9 +651,7 @@ class Api { return response } - // Mock: 填充任务流程 mockFillStepTask = async (data: { goal: string; stepTask: any }): Promise => { - // 直接调用 Mock API(已经返回 IRawStepTask 格式) const response = await mockFillStepTaskAPI({ General_Goal: data.goal, stepTask: data.stepTask, @@ -562,7 +660,6 @@ class Api { return response } - // Mock: 分支任务流程 mockBranchTaskProcess = async (data: { branch_Number: number Modification_Requirement: string @@ -571,7 +668,6 @@ class Api { stepTaskExisting: any goal: string }): Promise => { - // 直接调用 Mock API(已经返回 BranchAction[][] 格式,与后端完全一致) const response = await mockBranchTaskProcessAPI({ branch_Number: data.branch_Number, Modification_Requirement: data.Modification_Requirement, diff --git a/frontend/src/layout/components/Main/Task.vue b/frontend/src/layout/components/Main/Task.vue index e2985d1..8e85a7f 100644 --- a/frontend/src/layout/components/Main/Task.vue +++ b/frontend/src/layout/components/Main/Task.vue @@ -84,6 +84,9 @@ function resetTextareaHeight() { } async function handleSearch() { + // 用于标记大纲是否成功加载 + let outlineLoaded = false + try { triggerOnFocus.value = false if (!searchValue.value) { @@ -93,17 +96,102 @@ async function handleSearch() { emit('search-start') agentsStore.resetAgent() agentsStore.setAgentRawPlan({ loading: true }) - const data = await api.generateBasePlan({ + + // 获取大纲 + const outlineData = await api.generateBasePlan({ goal: searchValue.value, inputs: [] }) - data['Collaboration Process'] = changeBriefs(data['Collaboration Process']) - agentsStore.setAgentRawPlan({ data }) + // 处理简报数据格式 + outlineData['Collaboration Process'] = changeBriefs(outlineData['Collaboration Process']) + + // 立即显示大纲 + agentsStore.setAgentRawPlan({ data: outlineData, loading: false }) + outlineLoaded = true emit('search', searchValue.value) + + // 并行填充所有步骤的详情 + const steps = outlineData['Collaboration Process'] || [] + + // 带重试的填充函数 + const fillStepWithRetry = async (step: any, retryCount = 0): Promise => { + const maxRetries = 2 // 最多重试2次 + + try { + if (!step.StepName) { + console.warn('步骤缺少 StepName,跳过填充详情') + return + } + + // 使用现有的 fillStepTask API 填充每个步骤的详情 + const detailedStep = await api.fillStepTask({ + goal: searchValue.value, + stepTask: { + StepName: step.StepName, + TaskContent: step.TaskContent, + InputObject_List: step.InputObject_List, + OutputObject: step.OutputObject + } + }) + + // 更新该步骤的详情到 store + updateStepDetail(step.StepName, detailedStep) + } catch (error) { + console.error( + `填充步骤 ${step.StepName} 详情失败 (尝试 ${retryCount + 1}/${maxRetries + 1}):`, + error + ) + + // 如果未达到最大重试次数,延迟后重试 + if (retryCount < maxRetries) { + console.log(`正在重试步骤 ${step.StepName}...`) + // 延迟1秒后重试,避免立即重试导致同样的问题 + await new Promise(resolve => setTimeout(resolve, 1000)) + return fillStepWithRetry(step, retryCount + 1) + } else { + console.error(`步骤 ${step.StepName} 在 ${maxRetries + 1} 次尝试后仍然失败`) + } + } + } + + // // 为每个步骤并行填充详情(选人+过程) + // const fillPromises = steps.map(step => fillStepWithRetry(step)) + // // 等待所有步骤填充完成(包括重试) + // await Promise.all(fillPromises) + + // 串行填充所有步骤的详情(避免字段混乱) + for (const step of steps) { + await fillStepWithRetry(step) + } } finally { triggerOnFocus.value = true - agentsStore.setAgentRawPlan({ loading: false }) + // 如果大纲加载失败,确保关闭loading + if (!outlineLoaded) { + agentsStore.setAgentRawPlan({ loading: false }) + } + } +} + +// 辅助函数:更新单个步骤的详情 +function updateStepDetail(stepId: string, detailedStep: any) { + const planData = agentsStore.agentRawPlan.data + if (!planData) return + + const collaborationProcess = planData['Collaboration Process'] + if (!collaborationProcess) return + + const index = collaborationProcess.findIndex((s: any) => s.StepName === stepId) + if (index !== -1 && collaborationProcess[index]) { + // 保持响应式更新 - 使用 Vue 的响应式系统 + Object.assign(collaborationProcess[index], { + AgentSelection: detailedStep.AgentSelection || [], + TaskProcess: detailedStep.TaskProcess || [], + Collaboration_Brief_frontEnd: detailedStep.Collaboration_Brief_frontEnd || { + template: '', + data: {} + } + }) } } @@ -215,8 +303,6 @@ onMounted(() => { :deep(.el-autocomplete .el-textarea .el-textarea__inner) { overflow-y: auto !important; min-height: 56px !important; - // overflow-y: hidden; - // background-color: black; } } diff --git a/frontend/src/layout/components/Main/TaskTemplate/TaskResult/index.vue b/frontend/src/layout/components/Main/TaskTemplate/TaskResult/index.vue index 3e52cfd..df0f751 100644 --- a/frontend/src/layout/components/Main/TaskTemplate/TaskResult/index.vue +++ b/frontend/src/layout/components/Main/TaskTemplate/TaskResult/index.vue @@ -8,7 +8,7 @@ import { getActionTypeDisplay, getAgentMapIcon } from '@/layout/components/confi import { type ConnectArg, Jsplumb } from '@/layout/components/Main/TaskTemplate/utils.ts' import variables from '@/styles/variables.module.scss' import { type IRawStepTask, useAgentsStore } from '@/stores' -import api from '@/api' +import api, { type StreamingEvent } from '@/api' import ProcessCard from '../TaskProcess/ProcessCard.vue' import ExecutePlan from './ExecutePlan.vue' @@ -182,14 +182,148 @@ function createInternalLine(id?: string) { } const loading = ref(false) +const executionProgress = ref({ + currentStep: 0, + totalSteps: 0, + currentAction: 0, + totalActions: 0, + currentStepName: '', + message: '正在执行...' +}) async function handleRun() { + // 清空之前的执行结果 + agentsStore.setExecutePlan([]) + const tempResults: any[] = [] + try { loading.value = true - const d = await api.executePlan(agentsStore.agentRawPlan.data!) - agentsStore.setExecutePlan(d) + + // 使用优化版流式API(阶段1+2:步骤级流式 + 动作级智能并行) + api.executePlanOptimized( + agentsStore.agentRawPlan.data!, + // onMessage: 处理每个事件 + (event: StreamingEvent) => { + switch (event.type) { + case 'step_start': + // 步骤开始 + executionProgress.value = { + currentStep: event.step_index + 1, + totalSteps: event.total_steps, + currentAction: 0, + totalActions: 0, + currentStepName: event.step_name, + message: `正在执行步骤 ${event.step_index + 1}/${event.total_steps}: ${ + event.step_name + }` + } + console.log( + `📋 步骤 ${event.step_index + 1}/${event.total_steps} 开始: ${event.step_name}` + ) + break + + case 'action_complete': + // 动作完成 + const parallelInfo = event.batch_info?.is_parallel + ? ` [批次 ${event.batch_info!.batch_index + 1}, 并行 ${ + event.batch_info!.batch_size + } 个]` + : '' + + executionProgress.value = { + ...executionProgress.value, + currentAction: event.completed_actions, + totalActions: event.total_actions, + message: `步骤 ${event.step_index + 1}/${executionProgress.value.totalSteps}: ${ + event.step_name + } - 动作 ${event.completed_actions}/${event.total_actions} 完成${parallelInfo}` + } + + console.log( + `✅ 动作 ${event.completed_actions}/${event.total_actions} 完成${parallelInfo}: ${event.action_result.ActionType} by ${event.action_result.AgentName}` + ) + + // 实时更新到 store(找到对应的步骤并添加 ActionHistory) + const step = collaborationProcess.value.find(s => s.StepName === event.step_name) + if (step) { + const stepLogNode = tempResults.find( + r => r.NodeId === event.step_name && r.LogNodeType === 'step' + ) + if (!stepLogNode) { + // 创建步骤日志节点 + const newStepLog = { + LogNodeType: 'step', + NodeId: event.step_name, + InputName_List: step.InputObject_List || [], + OutputName: step.OutputObject || '', + chatLog: [], + inputObject_Record: [], + ActionHistory: [event.action_result] + } + tempResults.push(newStepLog) + } else { + // 追加动作结果 + stepLogNode.ActionHistory.push(event.action_result) + } + + // 更新 store + agentsStore.setExecutePlan([...tempResults]) + } + break + + case 'step_complete': + // 步骤完成 + console.log(`🎯 步骤完成: ${event.step_name}`) + + // 更新步骤日志节点 + const existingStepLog = tempResults.find( + r => r.NodeId === event.step_name && r.LogNodeType === 'step' + ) + if (existingStepLog) { + existingStepLog.ActionHistory = event.step_log_node.ActionHistory + } else { + tempResults.push(event.step_log_node) + } + + // 添加对象日志节点 + tempResults.push(event.object_log_node) + + // 更新 store + agentsStore.setExecutePlan([...tempResults]) + break + + case 'execution_complete': + // 执行完成 + executionProgress.value.message = `执行完成!共 ${event.total_steps} 个步骤` + console.log(`🎉 执行完成,共 ${event.total_steps} 个步骤`) + + // 确保所有结果都保存到 store + agentsStore.setExecutePlan([...tempResults]) + break + + case 'error': + // 错误 + console.error('❌ 执行错误:', event.message) + executionProgress.value.message = `执行错误: ${event.message}` + break + } + }, + // onError: 处理错误 + (error: Error) => { + console.error('❌ 流式执行错误:', error) + executionProgress.value.message = `执行失败: ${error.message}` + }, + // onComplete: 执行完成 + () => { + console.log('✅ 流式执行完成') + loading.value = false + } + ) + } catch (error) { + console.error('执行失败:', error) + executionProgress.value.message = '执行失败,请重试' } finally { - loading.value = false + // loading 会在 onComplete 中设置为 false } } @@ -381,6 +515,15 @@ defineExpose({ id="task-results" :class="{ 'is-running': agentsStore.executePlan.length > 0 }" > + +
+ + {{ executionProgress.message }} + + {{ executionProgress.currentStep }}/{{ executionProgress.totalSteps }} + +
+
执行结果 @@ -606,6 +749,48 @@ defineExpose({ diff --git a/frontend/src/layout/components/Main/TaskTemplate/TaskSyllabus/index.vue b/frontend/src/layout/components/Main/TaskTemplate/TaskSyllabus/index.vue index acd3435..ec228e5 100644 --- a/frontend/src/layout/components/Main/TaskTemplate/TaskSyllabus/index.vue +++ b/frontend/src/layout/components/Main/TaskTemplate/TaskSyllabus/index.vue @@ -5,6 +5,7 @@ import { type ConnectArg, Jsplumb } from '@/layout/components/Main/TaskTemplate/ import { type IRawStepTask, useAgentsStore } from '@/stores' import { computed, ref, nextTick, watch, onMounted } from 'vue' import { AnchorLocations } from '@jsplumb/browser-ui' +import { Loading } from '@element-plus/icons-vue' import MultiLineTooltip from '@/components/MultiLineTooltip/index.vue' import Bg from './Bg.vue' import BranchButton from './components/BranchButton.vue' @@ -37,6 +38,22 @@ const collaborationProcess = computed(() => { return agentsStore.agentRawPlan.data?.['Collaboration Process'] ?? [] }) +// 检测是否正在填充详情(有步骤但没有 AgentSelection) +const isFillingDetails = computed(() => { + const process = agentsStore.agentRawPlan.data?.['Collaboration Process'] || [] + return process.length > 0 && process.some(step => !step.AgentSelection || step.AgentSelection.length === 0) +}) + +// 计算填充进度 +const completedSteps = computed(() => { + const process = agentsStore.agentRawPlan.data?.['Collaboration Process'] || [] + return process.filter(step => step.AgentSelection && step.AgentSelection.length > 0).length +}) + +const totalSteps = computed(() => { + return agentsStore.agentRawPlan.data?.['Collaboration Process']?.length || 0 +}) + // 编辑状态管理 const editingTaskId = ref(null) const editingContent = ref('') @@ -258,6 +275,13 @@ defineExpose({ 任务大纲
+ +
+ + 正在生成任务协作流程... + {{ completedSteps }}/{{ totalSteps }} +
+