From 2140cfaf92fb2d78f2f80f95bb6a9dab3e6a39f7 Mon Sep 17 00:00:00 2001 From: liailing1026 <1815388873@qq.com> Date: Wed, 25 Feb 2026 10:55:51 +0800 Subject: [PATCH] =?UTF-8?q?feat:1.=E6=95=B0=E6=8D=AE=E5=BA=93=E5=AD=98?= =?UTF-8?q?=E5=82=A8=E5=8A=9F=E8=83=BD=E6=B7=BB=E5=8A=A0=EF=BC=88=E5=88=9D?= =?UTF-8?q?=E7=89=88=EF=BC=892.=E5=90=8E=E7=AB=AFREST=20API=E7=89=88?= =?UTF-8?q?=E6=9C=AC=E4=BB=A3=E7=A0=81=E6=B8=85=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/AgentCoord/LLMAPI/LLMAPI.py | 12 +- .../PlanEngine/AgentSelection_Generator.py | 3 +- .../RehearsalEngine_V2/Action/baseAction.py | 1 - .../RehearsalEngine_V2/ExecutePlan.py | 11 - .../ExecutePlan_Optimized.py | 43 +- .../dynamic_execution_manager.py | 23 +- .../RehearsalEngine_V2/execution_state.py | 17 - .../RehearsalEngine_V2/generation_state.py | 8 - backend/AgentCoord/util/converter.py | 1 + backend/db/__init__.py | 25 + backend/db/crud.py | 404 +++++ backend/db/database.py | 95 ++ backend/db/init_db.py | 22 + backend/db/models.py | 104 ++ backend/db/schema.sql | 70 + backend/server.py | 1386 ++++++++++++----- frontend/src/api/index.ts | 727 ++++----- frontend/src/layout/components/Main/Task.vue | 239 ++- .../Main/TaskTemplate/AgentRepo/index.vue | 1 + .../Main/TaskTemplate/HistoryList/index.vue | 376 +++++ .../TaskProcess/components/PlanTask.vue | 823 +++++----- .../mock/AgentTaskProcessBackendMock.ts | 183 --- .../Main/TaskTemplate/TaskResult/index.vue | 16 +- .../TaskSyllabus/Branch/PlanModification.vue | 1089 ++++++------- .../Branch/mock/branchTaskProcessMock.ts | 155 -- .../components/AgentAllocation.vue | 79 +- .../components/mock/AgentAddAspectMock.ts | 116 -- .../mock/AgentAssignmentBackendMock.ts | 192 --- .../components/mock/AgentAssignmentMock.ts | 314 ---- .../Main/TaskTemplate/TaskSyllabus/index.vue | 15 +- .../components/Main/TaskTemplate/index.vue | 7 + frontend/src/layout/components/Main/index.vue | 14 +- frontend/src/layout/index.vue | 26 +- frontend/src/stores/modules/agents.ts | 67 +- frontend/src/stores/modules/selection.ts | 229 ++- 35 files changed, 3912 insertions(+), 2981 deletions(-) create mode 100644 backend/db/__init__.py create mode 100644 backend/db/crud.py create mode 100644 backend/db/database.py create mode 100644 backend/db/init_db.py create mode 100644 backend/db/models.py create mode 100644 backend/db/schema.sql create mode 100644 frontend/src/layout/components/Main/TaskTemplate/HistoryList/index.vue delete mode 100644 frontend/src/layout/components/Main/TaskTemplate/TaskProcess/components/mock/AgentTaskProcessBackendMock.ts delete mode 100644 frontend/src/layout/components/Main/TaskTemplate/TaskSyllabus/Branch/mock/branchTaskProcessMock.ts delete mode 100644 frontend/src/layout/components/Main/TaskTemplate/TaskSyllabus/components/mock/AgentAddAspectMock.ts delete mode 100644 frontend/src/layout/components/Main/TaskTemplate/TaskSyllabus/components/mock/AgentAssignmentBackendMock.ts delete mode 100644 frontend/src/layout/components/Main/TaskTemplate/TaskSyllabus/components/mock/AgentAssignmentMock.ts diff --git a/backend/AgentCoord/LLMAPI/LLMAPI.py b/backend/AgentCoord/LLMAPI/LLMAPI.py index 85dcafe..96f8fbe 100644 --- a/backend/AgentCoord/LLMAPI/LLMAPI.py +++ b/backend/AgentCoord/LLMAPI/LLMAPI.py @@ -49,7 +49,6 @@ def LLM_Completion( messages: list[dict], stream: bool = True, useGroq: bool = True,model_config: dict = None ) -> str: if model_config: - print_colored(f"Using model config: {model_config}", "blue") return _call_with_custom_config(messages,stream,model_config) if not useGroq or not FAST_DESIGN_MODE: force_gpt4 = True @@ -125,7 +124,7 @@ def _call_with_custom_config(messages: list[dict], stream: bool, model_config: d #print(colored(full_reply_content, "blue", "on_white"), end="") return full_reply_content except Exception as e: - print_colored(f"Custom API error for model {api_model} :{str(e)}","red") + print_colored(f"[API Error] Custom API error: {str(e)}", "red") raise @@ -166,11 +165,11 @@ async def _achat_completion_stream_custom(messages:list[dict], temp_async_client except httpx.RemoteProtocolError as e: if attempt < max_retries - 1: wait_time = (attempt + 1) *2 - print_colored(f"⚠️ Stream connection interrupted (attempt {attempt+1}/{max_retries}). Retrying in {wait_time}s...", text_color="yellow") + print_colored(f"[API Warn] Stream connection interrupted, retrying in {wait_time}s...", "yellow") await asyncio.sleep(wait_time) continue except Exception as e: - print_colored(f"Custom API stream error for model {api_model} :{str(e)}","red") + print_colored(f"[API Error] Custom API stream error: {str(e)}", "red") raise @@ -181,7 +180,6 @@ async def _achat_completion_stream_groq(messages: list[dict]) -> str: max_attempts = 5 for attempt in range(max_attempts): - print("Attempt to use Groq (Fase Design Mode):") try: response = await groq_client.chat.completions.create( messages=messages, @@ -363,7 +361,7 @@ async def _achat_completion_stream(messages: list[dict]) -> str: return full_reply_content except Exception as e: - print_colored(f"OpenAI API error in _achat_completion_stream: {str(e)}", "red") + print_colored(f"[API Error] OpenAI API stream error: {str(e)}", "red") raise @@ -383,7 +381,7 @@ def _chat_completion(messages: list[dict]) -> str: return content except Exception as e: - print_colored(f"OpenAI API error in _chat_completion: {str(e)}", "red") + print_colored(f"[API Error] OpenAI API error: {str(e)}", "red") raise diff --git a/backend/AgentCoord/PlanEngine/AgentSelection_Generator.py b/backend/AgentCoord/PlanEngine/AgentSelection_Generator.py index 537af82..2606d27 100644 --- a/backend/AgentCoord/PlanEngine/AgentSelection_Generator.py +++ b/backend/AgentCoord/PlanEngine/AgentSelection_Generator.py @@ -77,7 +77,6 @@ def generate_AbilityRequirement(General_Goal, Current_Task): ), }, ] - print(messages[1]["content"]) return read_LLM_Completion(messages)["AbilityRequirement"] @@ -106,6 +105,7 @@ def generate_AgentSelection(General_Goal, Current_Task, Agent_Board): while True: candidate = read_LLM_Completion(messages)["AgentSelectionPlan"] + # 添加调试打印 if len(candidate) > MAX_TEAM_SIZE: teamSize = random.randint(2, MAX_TEAM_SIZE) candidate = candidate[0:teamSize] @@ -113,7 +113,6 @@ def generate_AgentSelection(General_Goal, Current_Task, Agent_Board): continue AgentSelectionPlan = sorted(candidate) AgentSelectionPlan_set = set(AgentSelectionPlan) - # Check if every item in AgentSelectionPlan is in agentboard if AgentSelectionPlan_set.issubset(agentboard_set): break # If all items are in agentboard, break the loop diff --git a/backend/AgentCoord/RehearsalEngine_V2/Action/baseAction.py b/backend/AgentCoord/RehearsalEngine_V2/Action/baseAction.py index a10e068..658a6de 100644 --- a/backend/AgentCoord/RehearsalEngine_V2/Action/baseAction.py +++ b/backend/AgentCoord/RehearsalEngine_V2/Action/baseAction.py @@ -85,7 +85,6 @@ class BaseAction(): # Handle missing agent profiles gracefully model_config = None if agentName not in AgentProfile_Dict: - print_colored(text=f"Warning: Agent '{agentName}' not found in AgentProfile_Dict. Using default profile.", text_color="yellow") agentProfile = f"AI Agent named {agentName}" else: # agentProfile = AgentProfile_Dict[agentName] diff --git a/backend/AgentCoord/RehearsalEngine_V2/ExecutePlan.py b/backend/AgentCoord/RehearsalEngine_V2/ExecutePlan.py index 13a8409..8c7ff6c 100644 --- a/backend/AgentCoord/RehearsalEngine_V2/ExecutePlan.py +++ b/backend/AgentCoord/RehearsalEngine_V2/ExecutePlan.py @@ -83,7 +83,6 @@ def executePlan(plan, num_StepToRun, RehearsalLog, AgentProfile_Dict): } # start the group chat - util.print_colored(TaskDescription, text_color="green") ActionHistory = [] action_count = 0 total_actions = len(TaskProcess) @@ -93,9 +92,6 @@ def executePlan(plan, num_StepToRun, RehearsalLog, AgentProfile_Dict): actionType = ActionInfo["ActionType"] agentName = ActionInfo["AgentName"] - # 添加进度日志 - util.print_colored(f"🔄 Executing action {action_count}/{total_actions}: {actionType} by {agentName}", text_color="yellow") - if actionType in Action.customAction_Dict: currentAction = Action.customAction_Dict[actionType]( info=ActionInfo, @@ -126,11 +122,4 @@ def executePlan(plan, num_StepToRun, RehearsalLog, AgentProfile_Dict): stepLogNode["ActionHistory"] = ActionHistory # Return Output - print( - colored( - "$Run " + str(StepRun_count) + "step$", - color="black", - on_color="on_white", - ) - ) return RehearsalLog diff --git a/backend/AgentCoord/RehearsalEngine_V2/ExecutePlan_Optimized.py b/backend/AgentCoord/RehearsalEngine_V2/ExecutePlan_Optimized.py index a5ff460..424b542 100644 --- a/backend/AgentCoord/RehearsalEngine_V2/ExecutePlan_Optimized.py +++ b/backend/AgentCoord/RehearsalEngine_V2/ExecutePlan_Optimized.py @@ -107,7 +107,6 @@ def get_parallel_batches(TaskProcess: List[Dict], dependency_map: Dict[int, List # 避免死循环 remaining = [i for i in range(len(TaskProcess)) if i not in completed] if remaining: - print(colored(f"警告: 检测到循环依赖,强制串行执行: {remaining}", "yellow")) ready_to_run = remaining[:1] else: break @@ -188,7 +187,8 @@ async def execute_step_async_streaming( KeyObjects: Dict, step_index: int, total_steps: int, - execution_id: str = None + execution_id: str = None, + RehearsalLog: List = None # 用于追加日志到历史记录 ) -> Generator[Dict, None, None]: """ 异步执行单个步骤,支持流式返回 @@ -276,8 +276,9 @@ async def execute_step_async_streaming( total_actions = len(TaskProcess) completed_actions = 0 + # 步骤开始日志 util.print_colored( - f"📋 步骤 {step_index + 1}/{total_steps}: {StepName} ({total_actions} 个动作, 分 {len(batches)} 批并行执行)", + f"📋 步骤 {step_index + 1}/{total_steps}: {StepName} ({total_actions} 个动作, 分 {len(batches)} 批执行)", text_color="cyan" ) @@ -286,11 +287,11 @@ async def execute_step_async_streaming( # 在每个批次执行前检查暂停状态 should_continue = await execution_state_manager.async_check_pause(execution_id) if not should_continue: - util.print_colored("🛑 用户请求停止执行", "red") return batch_size = len(batch_indices) + # 批次执行日志 if batch_size > 1: util.print_colored( f"🚦 批次 {batch_index + 1}/{len(batches)}: 并行执行 {batch_size} 个动作", @@ -298,7 +299,7 @@ async def execute_step_async_streaming( ) else: util.print_colored( - f"🔄 动作 {completed_actions + 1}/{total_actions}: 串行执行", + f"🔄 批次 {batch_index + 1}/{len(batches)}: 串行执行", text_color="yellow" ) @@ -353,12 +354,21 @@ async def execute_step_async_streaming( objectLogNode["content"] = KeyObjects[OutputName] stepLogNode["ActionHistory"] = ActionHistory + # 收集该步骤使用的 agent(去重) + assigned_agents_in_step = list(set(Agent_List)) if Agent_List else [] + + # 追加到 RehearsalLog(因为 RehearsalLog 是可变对象,会反映到原列表) + if RehearsalLog is not None: + RehearsalLog.append(stepLogNode) + RehearsalLog.append(objectLogNode) + yield { "type": "step_complete", "step_index": step_index, "step_name": StepName, "step_log_node": stepLogNode, "object_log_node": objectLogNode, + "assigned_agents": {StepName: assigned_agents_in_step}, # 该步骤使用的 agent } @@ -394,8 +404,6 @@ def executePlan_streaming_dynamic( execution_state_manager.start_execution(execution_id, general_goal) - print(colored(f"⏸️ 执行状态管理器已启动,支持暂停/恢复,execution_id={execution_id}", "green")) - # 准备执行 KeyObjects = existingKeyObjects.copy() if existingKeyObjects else {} finishedStep_index = -1 @@ -406,9 +414,6 @@ def executePlan_streaming_dynamic( if logNode["LogNodeType"] == "object": KeyObjects[logNode["NodeId"]] = logNode["content"] - if existingKeyObjects: - print(colored(f"📦 使用已存在的 KeyObjects: {list(existingKeyObjects.keys())}", "cyan")) - # 确定要运行的步骤范围 if num_StepToRun is None: run_to = len(plan["Collaboration Process"]) @@ -421,9 +426,6 @@ def executePlan_streaming_dynamic( if execution_id: # 初始化执行管理器,使用传入的execution_id actual_execution_id = dynamic_execution_manager.start_execution(general_goal, steps_to_run, execution_id) - print(colored(f"🚀 开始执行计划(动态模式),共 {len(steps_to_run)} 个步骤,执行ID: {actual_execution_id}", "cyan")) - else: - print(colored(f"🚀 开始执行计划(流式推送),共 {len(steps_to_run)} 个步骤", "cyan")) total_steps = len(steps_to_run) @@ -443,7 +445,7 @@ def executePlan_streaming_dynamic( # 检查暂停状态 should_continue = await execution_state_manager.async_check_pause(execution_id) if not should_continue: - print(colored("🛑 用户请求停止执行", "red")) + util.print_colored("🛑 用户请求停止执行", "red") await queue.put({ "type": "error", "message": "执行已被用户停止" @@ -466,29 +468,24 @@ def executePlan_streaming_dynamic( # 如果没有步骤在队列中(queue_total_steps为0),立即退出 if queue_total_steps == 0: - print(colored(f"⚠️ 没有步骤在队列中,退出执行", "yellow")) break # 如果所有步骤都已完成,等待可能的新步骤 if completed_steps >= queue_total_steps: if empty_wait_count >= max_empty_wait_cycles: # 等待超时,退出执行 - print(colored(f"✅ 所有步骤执行完成,等待超时", "green")) break else: # 等待新步骤追加 - print(colored(f"⏳ 等待新步骤追加... ({empty_wait_count}/{max_empty_wait_cycles})", "cyan")) await asyncio.sleep(1) continue else: # 还有步骤未完成,继续尝试获取 - print(colored(f"⏳ 等待步骤就绪... ({completed_steps}/{queue_total_steps})", "cyan")) await asyncio.sleep(0.5) empty_wait_count = 0 # 重置等待计数 continue else: # 执行信息不存在,退出 - print(colored(f"⚠️ 执行信息不存在,退出执行", "yellow")) break # 重置等待计数 @@ -506,7 +503,8 @@ def executePlan_streaming_dynamic( KeyObjects, step_index, current_total_steps, # 使用动态更新的总步骤数 - execution_id + execution_id, + RehearsalLog # 传递 RehearsalLog 用于追加日志 ): if execution_state_manager.is_stopped(execution_id): await queue.put({ @@ -533,7 +531,7 @@ def executePlan_streaming_dynamic( for step_index, stepDescrip in enumerate(steps_to_run): should_continue = await execution_state_manager.async_check_pause(execution_id) if not should_continue: - print(colored("🛑 用户请求停止执行", "red")) + util.print_colored("🛑 用户请求停止执行", "red") await queue.put({ "type": "error", "message": "执行已被用户停止" @@ -547,7 +545,8 @@ def executePlan_streaming_dynamic( KeyObjects, step_index, total_steps, - execution_id + execution_id, + RehearsalLog # 传递 RehearsalLog 用于追加日志 ): if execution_state_manager.is_stopped(execution_id): await queue.put({ diff --git a/backend/AgentCoord/RehearsalEngine_V2/dynamic_execution_manager.py b/backend/AgentCoord/RehearsalEngine_V2/dynamic_execution_manager.py index 4f7ac5e..a298977 100644 --- a/backend/AgentCoord/RehearsalEngine_V2/dynamic_execution_manager.py +++ b/backend/AgentCoord/RehearsalEngine_V2/dynamic_execution_manager.py @@ -63,10 +63,7 @@ class DynamicExecutionManager: # 初始化待执行步骤索引 self._pending_steps[execution_id] = list(range(len(initial_steps))) - print(f"🚀 启动执行: {execution_id}") - print(f"📊 初始步骤数: {len(initial_steps)}") - print(f"📋 待执行步骤索引: {self._pending_steps[execution_id]}") - + print(f"[Execution] 启动执行: {execution_id}, 步骤数={len(initial_steps)}") return execution_id def add_steps(self, execution_id: str, new_steps: List[Dict]) -> int: @@ -82,7 +79,6 @@ class DynamicExecutionManager: """ with self._lock: if execution_id not in self._step_queues: - print(f"⚠️ 警告: 执行ID {execution_id} 不存在,无法追加步骤") return 0 current_count = len(self._step_queues[execution_id]) @@ -95,14 +91,9 @@ class DynamicExecutionManager: self._pending_steps[execution_id].extend(new_indices) # 更新总步骤数 - old_total = self._executions[execution_id]["total_steps"] self._executions[execution_id]["total_steps"] = len(self._step_queues[execution_id]) - new_total = self._executions[execution_id]["total_steps"] - - print(f"➕ 追加了 {len(new_steps)} 个步骤到 {execution_id}") - print(f"📊 步骤总数: {old_total} -> {new_total}") - print(f"📋 待执行步骤索引: {self._pending_steps[execution_id]}") + print(f"[Execution] 追加步骤: +{len(new_steps)}, 总计={self._executions[execution_id]['total_steps']}") return len(new_steps) def get_next_step(self, execution_id: str) -> Optional[Dict]: @@ -117,7 +108,6 @@ class DynamicExecutionManager: """ with self._lock: if execution_id not in self._pending_steps: - print(f"⚠️ 警告: 执行ID {execution_id} 不存在") return None # 获取第一个待执行步骤的索引 @@ -128,7 +118,6 @@ class DynamicExecutionManager: # 从队列中获取步骤 if step_index >= len(self._step_queues[execution_id]): - print(f"⚠️ 警告: 步骤索引 {step_index} 超出范围") return None step = self._step_queues[execution_id][step_index] @@ -137,9 +126,7 @@ class DynamicExecutionManager: self._executed_steps[execution_id].add(step_index) step_name = step.get("StepName", "未知") - print(f"🎯 获取下一个步骤: {step_name} (索引: {step_index})") - print(f"📋 剩余待执行步骤: {len(self._pending_steps[execution_id])}") - + print(f"[Execution] 获取步骤: {step_name} (索引: {step_index})") return step def mark_step_completed(self, execution_id: str): @@ -154,9 +141,7 @@ class DynamicExecutionManager: self._executions[execution_id]["completed_steps"] += 1 completed = self._executions[execution_id]["completed_steps"] total = self._executions[execution_id]["total_steps"] - print(f"📊 步骤完成进度: {completed}/{total}") - else: - print(f"⚠️ 警告: 执行ID {execution_id} 不存在") + print(f"[Execution] 步骤完成: {completed}/{total}") def get_execution_info(self, execution_id: str) -> Optional[Dict]: """ diff --git a/backend/AgentCoord/RehearsalEngine_V2/execution_state.py b/backend/AgentCoord/RehearsalEngine_V2/execution_state.py index 5459d89..ffa27c9 100644 --- a/backend/AgentCoord/RehearsalEngine_V2/execution_state.py +++ b/backend/AgentCoord/RehearsalEngine_V2/execution_state.py @@ -129,7 +129,6 @@ class ExecutionStateManager: state['goal'] = goal state['should_pause'] = False state['should_stop'] = False - print(f"🚀 [DEBUG] start_execution: execution_id={execution_id}, 状态设置为 RUNNING, goal={goal}") def pause_execution(self, execution_id: str) -> bool: """ @@ -143,19 +142,13 @@ class ExecutionStateManager: """ state = self._get_state(execution_id) if state is None: - # 打印当前所有活跃的 execution_id,帮助调试 - active_ids = list(self._states.keys()) - print(f"⚠️ [DEBUG] pause_execution: execution_id={execution_id} 不存在") - print(f" 当前活跃的 execution_id 列表: {active_ids}") return False with self._get_lock(execution_id): if state['status'] != ExecutionStatus.RUNNING: - print(f"⚠️ [DEBUG] pause_execution: execution_id={execution_id}, 当前状态是 {state['status']},无法暂停") return False state['status'] = ExecutionStatus.PAUSED state['should_pause'] = True - print(f"⏸️ [DEBUG] pause_execution: execution_id={execution_id}, 状态设置为PAUSED") return True def resume_execution(self, execution_id: str) -> bool: @@ -170,16 +163,13 @@ class ExecutionStateManager: """ state = self._get_state(execution_id) if state is None: - print(f"⚠️ [DEBUG] resume_execution: execution_id={execution_id} 不存在") return False with self._get_lock(execution_id): if state['status'] != ExecutionStatus.PAUSED: - print(f"⚠️ [DEBUG] resume_execution: 当前状态不是PAUSED,而是 {state['status']}") return False state['status'] = ExecutionStatus.RUNNING state['should_pause'] = False - print(f"▶️ [DEBUG] resume_execution: execution_id={execution_id}, 状态设置为RUNNING, should_pause=False") return True def stop_execution(self, execution_id: str) -> bool: @@ -194,17 +184,14 @@ class ExecutionStateManager: """ state = self._get_state(execution_id) if state is None: - print(f"⚠️ [DEBUG] stop_execution: execution_id={execution_id} 不存在") return False with self._get_lock(execution_id): if state['status'] in [ExecutionStatus.IDLE, ExecutionStatus.STOPPED]: - print(f"⚠️ [DEBUG] stop_execution: 当前状态是 {state['status']}, 无法停止") return False state['status'] = ExecutionStatus.STOPPED state['should_stop'] = True state['should_pause'] = False - print(f"🛑 [DEBUG] stop_execution: execution_id={execution_id}, 状态设置为STOPPED") return True def reset(self, execution_id: str): @@ -215,12 +202,10 @@ class ExecutionStateManager: state['goal'] = None state['should_pause'] = False state['should_stop'] = False - print(f"🔄 [DEBUG] reset: execution_id={execution_id}, 状态重置为IDLE") def cleanup(self, execution_id: str): """清理指定 execution_id 的所有状态""" self._cleanup_state(execution_id) - print(f"🧹 [DEBUG] cleanup: execution_id={execution_id} 的状态已清理") async def async_check_pause(self, execution_id: str): """ @@ -248,7 +233,6 @@ class ExecutionStateManager: # 检查停止标志 if should_stop: - print("🛑 [DEBUG] async_check_pause: execution_id={}, 检测到停止信号".format(execution_id)) return False # 检查暂停状态 @@ -262,7 +246,6 @@ class ExecutionStateManager: should_stop = state['should_stop'] if not should_pause: - print("▶️ [DEBUG] async_check_pause: execution_id={}, 从暂停中恢复!".format(execution_id)) continue if should_stop: return False diff --git a/backend/AgentCoord/RehearsalEngine_V2/generation_state.py b/backend/AgentCoord/RehearsalEngine_V2/generation_state.py index f1ef046..112aa7d 100644 --- a/backend/AgentCoord/RehearsalEngine_V2/generation_state.py +++ b/backend/AgentCoord/RehearsalEngine_V2/generation_state.py @@ -113,7 +113,6 @@ class GenerationStateManager: state['status'] = GenerationStatus.GENERATING state['goal'] = goal state['should_stop'] = False - print(f"🚀 [GenerationState] start_generation: generation_id={generation_id}, 状态设置为 GENERATING") def stop_generation(self, generation_id: str) -> bool: """ @@ -127,26 +126,21 @@ class GenerationStateManager: """ state = self._get_state(generation_id) if state is None: - print(f"⚠️ [GenerationState] stop_generation: generation_id={generation_id} 不存在") return True # 不存在也算停止成功 with self._get_lock(generation_id): if state['status'] == GenerationStatus.STOPPED: - print(f"✅ [GenerationState] stop_generation: generation_id={generation_id} 已经是 STOPPED 状态") return True # 已经停止也算成功 if state['status'] == GenerationStatus.COMPLETED: - print(f"✅ [GenerationState] stop_generation: generation_id={generation_id} 已经 COMPLETED,视为停止成功") return True # 已完成也视为停止成功 if state['status'] == GenerationStatus.IDLE: - print(f"⚠️ [GenerationState] stop_generation: generation_id={generation_id} 是 IDLE 状态,无需停止") return True # 空闲状态也视为无需停止 # 真正需要停止的情况 state['status'] = GenerationStatus.STOPPED state['should_stop'] = True - print(f"🛑 [GenerationState] stop_generation: generation_id={generation_id}, 状态设置为STOPPED") return True def complete_generation(self, generation_id: str): @@ -154,12 +148,10 @@ class GenerationStateManager: state = self._ensure_state(generation_id) with self._get_lock(generation_id): state['status'] = GenerationStatus.COMPLETED - print(f"✅ [GenerationState] complete_generation: generation_id={generation_id}") def cleanup(self, generation_id: str): """清理指定 generation_id 的所有状态""" self._cleanup_state(generation_id) - print(f"🧹 [GenerationState] cleanup: generation_id={generation_id} 的状态已清理") def should_stop(self, generation_id: str) -> bool: """检查是否应该停止""" diff --git a/backend/AgentCoord/util/converter.py b/backend/AgentCoord/util/converter.py index f9c96b6..9ed7b2c 100644 --- a/backend/AgentCoord/util/converter.py +++ b/backend/AgentCoord/util/converter.py @@ -94,6 +94,7 @@ def remove_render_spec(duty_spec): return duty_spec + def read_LLM_Completion(messages, useGroq=True): for _ in range(3): text = LLM_Completion(messages, useGroq=useGroq) diff --git a/backend/db/__init__.py b/backend/db/__init__.py new file mode 100644 index 0000000..b50209d --- /dev/null +++ b/backend/db/__init__.py @@ -0,0 +1,25 @@ +""" +AgentCoord 数据库模块 +提供 PostgreSQL 数据库连接、模型和 CRUD 操作 +基于 DATABASE_DESIGN.md 设计 +""" + +from .database import get_db, get_db_context, test_connection, engine, text +from .models import MultiAgentTask, UserAgent, TaskStatus +from .crud import MultiAgentTaskCRUD, UserAgentCRUD + +__all__ = [ + # 连接管理 + "get_db", + "get_db_context", + "test_connection", + "engine", + "text", + # 模型 + "MultiAgentTask", + "UserAgent", + "TaskStatus", + # CRUD + "MultiAgentTaskCRUD", + "UserAgentCRUD", +] diff --git a/backend/db/crud.py b/backend/db/crud.py new file mode 100644 index 0000000..3874b43 --- /dev/null +++ b/backend/db/crud.py @@ -0,0 +1,404 @@ +""" +数据库 CRUD 操作 +封装所有数据库操作方法 (基于 DATABASE_DESIGN.md) +""" + +import copy +import uuid +from datetime import datetime, timezone +from typing import List, Optional +from sqlalchemy.orm import Session + +from .models import MultiAgentTask, UserAgent + + +class MultiAgentTaskCRUD: + """多智能体任务 CRUD 操作""" + + @staticmethod + def create( + db: Session, + task_id: Optional[str] = None, # 可选,如果为 None 则自动生成 + user_id: str = "", + query: str = "", + agents_info: list = [], + task_outline: Optional[dict] = None, + assigned_agents: Optional[list] = None, + agent_scores: Optional[dict] = None, + result: Optional[str] = None, + ) -> MultiAgentTask: + """创建任务记录""" + task = MultiAgentTask( + task_id=task_id or str(uuid.uuid4()), # 如果没传则生成新的 + user_id=user_id, + query=query, + agents_info=agents_info, + task_outline=task_outline, + assigned_agents=assigned_agents, + agent_scores=agent_scores, + result=result, + ) + db.add(task) + db.commit() + db.refresh(task) + return task + + @staticmethod + def get_by_id(db: Session, task_id: str) -> Optional[MultiAgentTask]: + """根据任务 ID 获取记录""" + return db.query(MultiAgentTask).filter(MultiAgentTask.task_id == task_id).first() + + @staticmethod + def get_by_user_id( + db: Session, user_id: str, limit: int = 50, offset: int = 0 + ) -> List[MultiAgentTask]: + """根据用户 ID 获取任务记录""" + return ( + db.query(MultiAgentTask) + .filter(MultiAgentTask.user_id == user_id) + .order_by(MultiAgentTask.created_at.desc()) + .offset(offset) + .limit(limit) + .all() + ) + + @staticmethod + def get_recent( + db: Session, limit: int = 20, offset: int = 0 + ) -> List[MultiAgentTask]: + """获取最近的任务记录""" + return ( + db.query(MultiAgentTask) + .order_by(MultiAgentTask.created_at.desc()) + .offset(offset) + .limit(limit) + .all() + ) + + @staticmethod + def update_result( + db: Session, task_id: str, result: list + ) -> Optional[MultiAgentTask]: + """更新任务结果""" + task = db.query(MultiAgentTask).filter(MultiAgentTask.task_id == task_id).first() + if task: + task.result = result if result else [] + db.commit() + db.refresh(task) + return task + + @staticmethod + def update_task_outline( + db: Session, task_id: str, task_outline: dict + ) -> Optional[MultiAgentTask]: + """更新任务大纲""" + task = db.query(MultiAgentTask).filter(MultiAgentTask.task_id == task_id).first() + if task: + task.task_outline = task_outline + db.commit() + db.refresh(task) + return task + + @staticmethod + def update_assigned_agents( + db: Session, task_id: str, assigned_agents: dict + ) -> Optional[MultiAgentTask]: + """更新分配的智能体(步骤名 -> agent列表)""" + task = db.query(MultiAgentTask).filter(MultiAgentTask.task_id == task_id).first() + if task: + task.assigned_agents = assigned_agents + db.commit() + db.refresh(task) + return task + + @staticmethod + def update_agent_scores( + db: Session, task_id: str, agent_scores: dict + ) -> Optional[MultiAgentTask]: + """更新智能体评分(合并模式,追加新步骤的评分)""" + task = db.query(MultiAgentTask).filter(MultiAgentTask.task_id == task_id).first() + if task: + # 合并现有评分数据和新评分数据 + existing_scores = task.agent_scores or {} + merged_scores = {**existing_scores, **agent_scores} # 新数据覆盖/追加旧数据 + task.agent_scores = merged_scores + db.commit() + db.refresh(task) + return task + + @staticmethod + def update_status( + db: Session, task_id: str, status: str + ) -> Optional[MultiAgentTask]: + """更新任务状态""" + task = db.query(MultiAgentTask).filter(MultiAgentTask.task_id == task_id).first() + if task: + task.status = status + db.commit() + db.refresh(task) + return task + + @staticmethod + def increment_execution_count(db: Session, task_id: str) -> Optional[MultiAgentTask]: + """增加任务执行次数""" + task = db.query(MultiAgentTask).filter(MultiAgentTask.task_id == task_id).first() + if task: + task.execution_count = (task.execution_count or 0) + 1 + db.commit() + db.refresh(task) + return task + + @staticmethod + def update_generation_id( + db: Session, task_id: str, generation_id: str + ) -> Optional[MultiAgentTask]: + """更新生成 ID""" + task = db.query(MultiAgentTask).filter(MultiAgentTask.task_id == task_id).first() + if task: + task.generation_id = generation_id + db.commit() + db.refresh(task) + return task + + @staticmethod + def update_execution_id( + db: Session, task_id: str, execution_id: str + ) -> Optional[MultiAgentTask]: + """更新执行 ID""" + task = db.query(MultiAgentTask).filter(MultiAgentTask.task_id == task_id).first() + if task: + task.execution_id = execution_id + db.commit() + db.refresh(task) + return task + + @staticmethod + def update_rehearsal_log( + db: Session, task_id: str, rehearsal_log: list + ) -> Optional[MultiAgentTask]: + """更新排练日志""" + task = db.query(MultiAgentTask).filter(MultiAgentTask.task_id == task_id).first() + if task: + task.rehearsal_log = rehearsal_log if rehearsal_log else [] + db.commit() + db.refresh(task) + return task + + @staticmethod + def append_rehearsal_log( + db: Session, task_id: str, log_entry: dict + ) -> Optional[MultiAgentTask]: + """追加排练日志条目""" + task = db.query(MultiAgentTask).filter(MultiAgentTask.task_id == task_id).first() + if task: + current_log = task.rehearsal_log or [] + if isinstance(current_log, list): + current_log.append(log_entry) + else: + current_log = [log_entry] + task.rehearsal_log = current_log + db.commit() + db.refresh(task) + return task + + @staticmethod + def update_branches( + db: Session, task_id: str, branches + ) -> Optional[MultiAgentTask]: + """更新任务分支数据 + 支持两种格式: + - list: 旧格式,直接覆盖 + - dict: 新格式 { flow_branches: [...], task_process_branches: {...} } + 两个 key 独立保存,互不干扰。 + """ + import copy + task = db.query(MultiAgentTask).filter(MultiAgentTask.task_id == task_id).first() + if task: + if isinstance(branches, dict): + # 新格式:字典,独立保存两个 key,互不干扰 + # 使用深拷贝避免引用共享问题 + existing = copy.deepcopy(task.branches) if task.branches else {} + if isinstance(existing, dict): + # 如果只更新 flow_branches,保留已有的 task_process_branches + if 'flow_branches' in branches and 'task_process_branches' not in branches: + branches['task_process_branches'] = existing.get('task_process_branches', {}) + # 如果只更新 task_process_branches,保留已有的 flow_branches + if 'task_process_branches' in branches and 'flow_branches' not in branches: + branches['flow_branches'] = existing.get('flow_branches', []) + task.branches = branches + else: + # 旧格式:列表 + task.branches = branches if branches else [] + db.commit() + db.refresh(task) + return task + + @staticmethod + def get_branches(db: Session, task_id: str) -> Optional[list]: + """获取任务分支数据""" + task = db.query(MultiAgentTask).filter(MultiAgentTask.task_id == task_id).first() + if task: + return task.branches or [] + return [] + + @staticmethod + def get_by_status( + db: Session, status: str, limit: int = 50, offset: int = 0 + ) -> List[MultiAgentTask]: + """根据状态获取任务记录""" + return ( + db.query(MultiAgentTask) + .filter(MultiAgentTask.status == status) + .order_by(MultiAgentTask.created_at.desc()) + .offset(offset) + .limit(limit) + .all() + ) + + @staticmethod + def get_by_generation_id( + db: Session, generation_id: str + ) -> List[MultiAgentTask]: + """根据生成 ID 获取任务记录""" + return ( + db.query(MultiAgentTask) + .filter(MultiAgentTask.generation_id == generation_id) + .all() + ) + + @staticmethod + def get_by_execution_id( + db: Session, execution_id: str + ) -> List[MultiAgentTask]: + """根据执行 ID 获取任务记录""" + return ( + db.query(MultiAgentTask) + .filter(MultiAgentTask.execution_id == execution_id) + .all() + ) + + @staticmethod + def delete(db: Session, task_id: str) -> bool: + """删除任务记录""" + task = db.query(MultiAgentTask).filter(MultiAgentTask.task_id == task_id).first() + if task: + db.delete(task) + db.commit() + return True + return False + + +class UserAgentCRUD: + """用户智能体配置 CRUD 操作""" + + @staticmethod + def create( + db: Session, + user_id: str, + agent_name: str, + agent_config: dict, + ) -> UserAgent: + """创建用户智能体配置""" + agent = UserAgent( + id=str(uuid.uuid4()), + user_id=user_id, + agent_name=agent_name, + agent_config=agent_config, + ) + db.add(agent) + db.commit() + db.refresh(agent) + return agent + + @staticmethod + def get_by_id(db: Session, agent_id: str) -> Optional[UserAgent]: + """根据 ID 获取配置""" + return db.query(UserAgent).filter(UserAgent.id == agent_id).first() + + @staticmethod + def get_by_user_id( + db: Session, user_id: str, limit: int = 50 + ) -> List[UserAgent]: + """根据用户 ID 获取所有智能体配置""" + return ( + db.query(UserAgent) + .filter(UserAgent.user_id == user_id) + .order_by(UserAgent.created_at.desc()) + .limit(limit) + .all() + ) + + @staticmethod + def get_by_name( + db: Session, user_id: str, agent_name: str + ) -> List[UserAgent]: + """根据用户 ID 和智能体名称获取配置""" + return ( + db.query(UserAgent) + .filter( + UserAgent.user_id == user_id, + UserAgent.agent_name == agent_name, + ) + .all() + ) + + @staticmethod + def update_config( + db: Session, agent_id: str, agent_config: dict + ) -> Optional[UserAgent]: + """更新智能体配置""" + agent = db.query(UserAgent).filter(UserAgent.id == agent_id).first() + if agent: + agent.agent_config = agent_config + db.commit() + db.refresh(agent) + return agent + + @staticmethod + def delete(db: Session, agent_id: str) -> bool: + """删除智能体配置""" + agent = db.query(UserAgent).filter(UserAgent.id == agent_id).first() + if agent: + db.delete(agent) + db.commit() + return True + return False + + @staticmethod + def upsert( + db: Session, + user_id: str, + agent_name: str, + agent_config: dict, + ) -> UserAgent: + """更新或插入用户智能体配置(根据 user_id + agent_name 判断唯一性) + + 如果已存在相同 user_id 和 agent_name 的记录,则更新配置; + 否则创建新记录。 + """ + existing = ( + db.query(UserAgent) + .filter( + UserAgent.user_id == user_id, + UserAgent.agent_name == agent_name, + ) + .first() + ) + if existing: + # 更新现有记录 + existing.agent_config = agent_config + db.commit() + db.refresh(existing) + return existing + else: + # 创建新记录 + agent = UserAgent( + id=str(uuid.uuid4()), + user_id=user_id, + agent_name=agent_name, + agent_config=agent_config, + ) + db.add(agent) + db.commit() + db.refresh(agent) + return agent diff --git a/backend/db/database.py b/backend/db/database.py new file mode 100644 index 0000000..484b204 --- /dev/null +++ b/backend/db/database.py @@ -0,0 +1,95 @@ +""" +数据库连接管理模块 +使用 SQLAlchemy ORM,支持同步操作 +""" + +import os +import yaml +from typing import Generator +from contextlib import contextmanager +import json + +from sqlalchemy import create_engine, text +from sqlalchemy.orm import sessionmaker, declarative_base +from sqlalchemy.pool import QueuePool +from sqlalchemy.dialects.postgresql import dialect as pg_dialect + +# 读取配置 +yaml_file = os.path.join(os.getcwd(), "config", "config.yaml") +try: + with open(yaml_file, "r", encoding="utf-8") as file: + config = yaml.safe_load(file).get("database", {}) +except Exception: + config = {} + + +def get_database_url() -> str: + """获取数据库连接 URL""" + # 优先使用环境变量 + host = os.getenv("DB_HOST", config.get("host", "localhost")) + port = os.getenv("DB_PORT", config.get("port", "5432")) + user = os.getenv("DB_USER", config.get("username", "postgres")) + password = os.getenv("DB_PASSWORD", config.get("password", "")) + dbname = os.getenv("DB_NAME", config.get("name", "agentcoord")) + + return f"postgresql://{user}:{password}@{host}:{port}/{dbname}" + + +# 创建引擎 +DATABASE_URL = get_database_url() +engine = create_engine( + DATABASE_URL, + poolclass=QueuePool, + pool_size=config.get("pool_size", 10), + max_overflow=config.get("max_overflow", 20), + pool_pre_ping=True, + echo=False, + # JSONB 类型处理器配置 + json_serializer=lambda obj: json.dumps(obj, ensure_ascii=False), + json_deserializer=lambda s: json.loads(s) if isinstance(s, str) else s +) + +# 创建会话工厂 +SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine) + +# 基础类 +Base = declarative_base() + + +def get_db() -> Generator: + """ + 获取数据库会话 + 用法: for db in get_db(): ... + """ + db = SessionLocal() + try: + yield db + finally: + db.close() + + +@contextmanager +def get_db_context() -> Generator: + """ + 上下文管理器方式获取数据库会话 + 用法: with get_db_context() as db: ... + """ + db = SessionLocal() + try: + yield db + db.commit() + except Exception as e: + db.rollback() + raise + finally: + db.close() + + +def test_connection() -> bool: + """测试数据库连接""" + try: + with engine.connect() as conn: + conn.execute(text("SELECT 1")) + return True + except Exception as e: + return False diff --git a/backend/db/init_db.py b/backend/db/init_db.py new file mode 100644 index 0000000..9f150c3 --- /dev/null +++ b/backend/db/init_db.py @@ -0,0 +1,22 @@ +""" +数据库初始化脚本 +运行此脚本创建所有表结构 +基于 DATABASE_DESIGN.md 设计 +""" + +import sys +import os + +sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) + +from db.database import engine, Base +from db.models import MultiAgentTask, UserAgent + + +def init_database(): + """初始化数据库表结构""" + Base.metadata.create_all(bind=engine) + + +if __name__ == "__main__": + init_database() diff --git a/backend/db/models.py b/backend/db/models.py new file mode 100644 index 0000000..b65f87c --- /dev/null +++ b/backend/db/models.py @@ -0,0 +1,104 @@ +""" +SQLAlchemy ORM 数据模型 +对应数据库表结构 (基于 DATABASE_DESIGN.md) +""" + +import uuid +from datetime import datetime, timezone +from enum import Enum as PyEnum +from sqlalchemy import Column, String, Text, DateTime, Integer, Enum, Index, ForeignKey +from sqlalchemy.dialects.postgresql import JSONB +from sqlalchemy.orm import relationship + +from .database import Base + + +class TaskStatus(str, PyEnum): + """任务状态枚举""" + GENERATING = "generating" # 生成中 - TaskProcess 生成阶段 + EXECUTING = "executing" # 执行中 - 任务执行阶段 + STOPPED = "stopped" # 已停止 - 用户手动停止执行 + COMPLETED = "completed" # 已完成 - 任务正常完成 + + +def utc_now(): + """获取当前 UTC 时间""" + return datetime.now(timezone.utc) + + +class MultiAgentTask(Base): + """多智能体任务记录模型""" + __tablename__ = "multi_agent_tasks" + + task_id = Column(String(64), primary_key=True) + user_id = Column(String(64), nullable=False, index=True) + query = Column(Text, nullable=False) + agents_info = Column(JSONB, nullable=False) + task_outline = Column(JSONB) + assigned_agents = Column(JSONB) + agent_scores = Column(JSONB) + result = Column(JSONB) + status = Column( + Enum(TaskStatus, name="task_status_enum", create_type=False), + default=TaskStatus.GENERATING, + nullable=False + ) + execution_count = Column(Integer, default=0, nullable=False) + generation_id = Column(String(64)) + execution_id = Column(String(64)) + rehearsal_log = Column(JSONB) + branches = Column(JSONB) # 任务大纲探索分支数据 + created_at = Column(DateTime(timezone=True), default=utc_now) + updated_at = Column(DateTime(timezone=True), default=utc_now, onupdate=utc_now) + + __table_args__ = ( + Index("idx_multi_agent_tasks_status", "status"), + Index("idx_multi_agent_tasks_generation_id", "generation_id"), + Index("idx_multi_agent_tasks_execution_id", "execution_id"), + ) + + def to_dict(self) -> dict: + """转换为字典""" + return { + "task_id": self.task_id, + "user_id": self.user_id, + "query": self.query, + "agents_info": self.agents_info, + "task_outline": self.task_outline, + "assigned_agents": self.assigned_agents, + "agent_scores": self.agent_scores, + "result": self.result, + "status": self.status.value if self.status else None, + "execution_count": self.execution_count, + "generation_id": self.generation_id, + "execution_id": self.execution_id, + "rehearsal_log": self.rehearsal_log, + "branches": self.branches, + "created_at": self.created_at.isoformat() if self.created_at else None, + "updated_at": self.updated_at.isoformat() if self.updated_at else None, + } + + +class UserAgent(Base): + """用户保存的智能体配置模型 (可选表)""" + __tablename__ = "user_agents" + + id = Column(String(64), primary_key=True) + user_id = Column(String(64), nullable=False, index=True) + agent_name = Column(String(100), nullable=False) + agent_config = Column(JSONB, nullable=False) + created_at = Column(DateTime(timezone=True), default=utc_now) + + __table_args__ = ( + Index("idx_user_agents_user_created", "user_id", "created_at"), + ) + + def to_dict(self) -> dict: + """转换为字典""" + return { + "id": self.id, + "user_id": self.user_id, + "agent_name": self.agent_name, + "agent_config": self.agent_config, + "created_at": self.created_at.isoformat() if self.created_at else None, + } diff --git a/backend/db/schema.sql b/backend/db/schema.sql new file mode 100644 index 0000000..10ede25 --- /dev/null +++ b/backend/db/schema.sql @@ -0,0 +1,70 @@ +-- AgentCoord 数据库表结构 +-- 基于 DATABASE_DESIGN.md 设计 +-- 执行方式: psql -U postgres -d agentcoord -f schema.sql + +-- ============================================================================= +-- 表1: multi_agent_tasks (多智能体任务记录) +-- 状态枚举: pending/planning/generating/executing/completed/failed +-- ============================================================================= +CREATE TABLE IF NOT EXISTS multi_agent_tasks ( + task_id VARCHAR(64) PRIMARY KEY, + user_id VARCHAR(64) NOT NULL, + query TEXT NOT NULL, + agents_info JSONB NOT NULL, + task_outline JSONB, + assigned_agents JSONB, + agent_scores JSONB, + result JSONB, + status VARCHAR(20) DEFAULT 'pending', + execution_count INTEGER DEFAULT 0, + generation_id VARCHAR(64), + execution_id VARCHAR(64), + rehearsal_log JSONB, + branches JSONB, -- 任务大纲探索分支数据 + created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP +); + +-- 索引 +CREATE INDEX IF NOT EXISTS idx_multi_agent_tasks_user_id ON multi_agent_tasks(user_id); +CREATE INDEX IF NOT EXISTS idx_multi_agent_tasks_created_at ON multi_agent_tasks(created_at DESC); +CREATE INDEX IF NOT EXISTS idx_multi_agent_tasks_status ON multi_agent_tasks(status); +CREATE INDEX IF NOT EXISTS idx_multi_agent_tasks_generation_id ON multi_agent_tasks(generation_id); +CREATE INDEX IF NOT EXISTS idx_multi_agent_tasks_execution_id ON multi_agent_tasks(execution_id); + +-- ============================================================================= +-- 表2: user_agents (用户保存的智能体配置) - 可选表 +-- ============================================================================= +CREATE TABLE IF NOT EXISTS user_agents ( + id VARCHAR(64) PRIMARY KEY, + user_id VARCHAR(64) NOT NULL, + agent_name VARCHAR(100) NOT NULL, + agent_config JSONB NOT NULL, + created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP +); + +CREATE INDEX IF NOT EXISTS idx_user_agents_user_id ON user_agents(user_id); + +-- ============================================================================= +-- 更新时间触发器函数 +-- ============================================================================= +CREATE OR REPLACE FUNCTION update_updated_at_column() +RETURNS TRIGGER AS $$ +BEGIN + NEW.updated_at = CURRENT_TIMESTAMP; + RETURN NEW; +END; +$$ language 'plpgsql'; + +-- 为 multi_agent_tasks 表创建触发器 +CREATE TRIGGER update_multi_agent_tasks_updated_at + BEFORE UPDATE ON multi_agent_tasks + FOR EACH ROW + EXECUTE FUNCTION update_updated_at_column(); + +DO $$ +BEGIN + RAISE NOTICE '✅ PostgreSQL 数据库表结构创建完成!'; + RAISE NOTICE '表: multi_agent_tasks (多智能体任务记录)'; + RAISE NOTICE '表: user_agents (用户智能体配置)'; +END $$; diff --git a/backend/server.py b/backend/server.py index 9f71804..13ecb72 100644 --- a/backend/server.py +++ b/backend/server.py @@ -18,7 +18,18 @@ from AgentCoord.PlanEngine.AgentSelectModify import ( import os import yaml import argparse -from typing import List, Dict +import uuid +import copy +from typing import List, Dict, Optional + +# 数据库模块导入 +from db import ( + get_db_context, + MultiAgentTaskCRUD, + UserAgentCRUD, + TaskStatus, + text, +) # initialize global variables yaml_file = os.path.join(os.getcwd(), "config", "config.yaml") @@ -38,8 +49,6 @@ Request_Cache: dict[str, str] = {} app = Flask(__name__) app.config['SECRET_KEY'] = 'agentcoord-secret-key' socketio = SocketIO(app, cors_allowed_origins="*", async_mode='threading') -#socketio = SocketIO(app, cors_allowed_origins="*", async_mode='eventlet') - def truncate_rehearsal_log(RehearsalLog: List, restart_from_step_index: int) -> List: """ @@ -75,321 +84,35 @@ def truncate_rehearsal_log(RehearsalLog: List, restart_from_step_index: int) -> return truncated_log -@app.route("/fill_stepTask_TaskProcess", methods=["post"]) -def Handle_fill_stepTask_TaskProcess(): - incoming_data = request.get_json() - requestIdentifier = str( - ( - "/fill_stepTask_TaskProcess", - incoming_data["General Goal"], - incoming_data["stepTask_lackTaskProcess"], - ) - ) - - if USE_CACHE: - if requestIdentifier in Request_Cache: - return jsonify(Request_Cache[requestIdentifier]) - - filled_stepTask = fill_stepTask_TaskProcess( - General_Goal=incoming_data["General Goal"], - stepTask=incoming_data["stepTask_lackTaskProcess"], - AgentProfile_Dict=AgentProfile_Dict, - ) - filled_stepTask = Add_Collaboration_Brief_FrontEnd(filled_stepTask) - Request_Cache[requestIdentifier] = filled_stepTask - response = jsonify(filled_stepTask) - return response - - -@app.route("/agentSelectModify_init", methods=["post"]) -def Handle_agentSelectModify_init(): - incoming_data = request.get_json() - requestIdentifier = str( - ( - "/agentSelectModify_init", - incoming_data["General Goal"], - incoming_data["stepTask"], - ) - ) - - if USE_CACHE: - if requestIdentifier in Request_Cache: - return jsonify(Request_Cache[requestIdentifier]) - - scoreTable = AgentSelectModify_init( - stepTask=incoming_data["stepTask"], - General_Goal=incoming_data["General Goal"], - Agent_Board=AgentBoard, - ) - Request_Cache[requestIdentifier] = scoreTable - response = jsonify(scoreTable) - return response - - -@app.route("/agentSelectModify_addAspect", methods=["post"]) -def Handle_agentSelectModify_addAspect(): - incoming_data = request.get_json() - requestIdentifier = str( - ("/agentSelectModify_addAspect", incoming_data["aspectList"]) - ) - - if USE_CACHE: - if requestIdentifier in Request_Cache: - return jsonify(Request_Cache[requestIdentifier]) - - scoreTable = AgentSelectModify_addAspect( - aspectList=incoming_data["aspectList"], Agent_Board=AgentBoard - ) - Request_Cache[requestIdentifier] = scoreTable - response = jsonify(scoreTable) - return response - - -@app.route("/fill_stepTask", methods=["post"]) -def Handle_fill_stepTask(): - incoming_data = request.get_json() - requestIdentifier = str( - ( - "/fill_stepTask", - incoming_data["General Goal"], - incoming_data["stepTask"], - ) - ) - - if USE_CACHE: - if requestIdentifier in Request_Cache: - return jsonify(Request_Cache[requestIdentifier]) - - filled_stepTask = fill_stepTask( - General_Goal=incoming_data["General Goal"], - stepTask=incoming_data["stepTask"], - Agent_Board=AgentBoard, - AgentProfile_Dict=AgentProfile_Dict, - ) - filled_stepTask = Add_Collaboration_Brief_FrontEnd(filled_stepTask) - Request_Cache[requestIdentifier] = filled_stepTask - response = jsonify(filled_stepTask) - return response - - -@app.route("/branch_PlanOutline", methods=["post"]) -def Handle_branch_PlanOutline(): - incoming_data = request.get_json() - requestIdentifier = str( - ( - "/branch_PlanOutline", - incoming_data["branch_Number"], - incoming_data["Modification_Requirement"], - incoming_data["Existing_Steps"], - incoming_data["Baseline_Completion"], - incoming_data["Initial Input Object"], - incoming_data["General Goal"], - ) - ) - - if USE_CACHE: - if requestIdentifier in Request_Cache: - return jsonify(Request_Cache[requestIdentifier]) - - branchList = branch_PlanOutline( - branch_Number=incoming_data["branch_Number"], - Modification_Requirement=incoming_data["Modification_Requirement"], - Existing_Steps=incoming_data["Existing_Steps"], - Baseline_Completion=incoming_data["Baseline_Completion"], - InitialObject_List=incoming_data["Initial Input Object"], - General_Goal=incoming_data["General Goal"], - ) - branchList = Add_Collaboration_Brief_FrontEnd(branchList) - Request_Cache[requestIdentifier] = branchList - response = jsonify(branchList) - return response - - -@app.route("/branch_TaskProcess", methods=["post"]) -def Handle_branch_TaskProcess(): - incoming_data = request.get_json() - requestIdentifier = str( - ( - "/branch_TaskProcess", - incoming_data["branch_Number"], - incoming_data["Modification_Requirement"], - incoming_data["Existing_Steps"], - incoming_data["Baseline_Completion"], - incoming_data["stepTaskExisting"], - incoming_data["General Goal"], - ) - ) - - if USE_CACHE: - if requestIdentifier in Request_Cache: - return jsonify(Request_Cache[requestIdentifier]) - - branchList = branch_TaskProcess( - branch_Number=incoming_data["branch_Number"], - Modification_Requirement=incoming_data["Modification_Requirement"], - Existing_Steps=incoming_data["Existing_Steps"], - Baseline_Completion=incoming_data["Baseline_Completion"], - stepTaskExisting=incoming_data["stepTaskExisting"], - General_Goal=incoming_data["General Goal"], - AgentProfile_Dict=AgentProfile_Dict, - ) - Request_Cache[requestIdentifier] = branchList - response = jsonify(branchList) - return response - - -@app.route("/generate_basePlan", methods=["post"]) -def Handle_generate_basePlan(): - incoming_data = request.get_json() - requestIdentifier = str( - ( - "/generate_basePlan", - incoming_data["General Goal"], - incoming_data["Initial Input Object"], - ) - ) - - if USE_CACHE: - if requestIdentifier in Request_Cache: - return jsonify(Request_Cache[requestIdentifier]) - - try: - basePlan = generate_basePlan( - General_Goal=incoming_data["General Goal"], - Agent_Board=AgentBoard, - AgentProfile_Dict=AgentProfile_Dict, - InitialObject_List=incoming_data["Initial Input Object"], - ) - except ValueError as e: - return jsonify({"error": str(e)}), 400 - except Exception as e: - return jsonify({"error": f"An unexpected error occurred: {str(e)}"}), 500 - basePlan_withRenderSpec = Add_Collaboration_Brief_FrontEnd(basePlan) - Request_Cache[requestIdentifier] = basePlan_withRenderSpec - response = jsonify(basePlan_withRenderSpec) - return response - - -@app.route("/executePlan", methods=["post"]) -def Handle_executePlan(): - incoming_data = request.get_json() - requestIdentifier = str( - ( - "/executePlan", - incoming_data["num_StepToRun"], - incoming_data["RehearsalLog"], - incoming_data["plan"], - ) - ) - - if USE_CACHE: - if requestIdentifier in Request_Cache: - return jsonify(Request_Cache[requestIdentifier]) - - RehearsalLog = executePlan( - incoming_data["plan"], - incoming_data["num_StepToRun"], - incoming_data["RehearsalLog"], - AgentProfile_Dict, - ) - Request_Cache[requestIdentifier] = RehearsalLog - response = jsonify(RehearsalLog) - return response - - -@app.route("/executePlanOptimized", methods=["post"]) -def Handle_executePlanOptimized(): +def convert_score_table_to_front_format(scoreTable: dict) -> dict: """ - 优化版流式执行计划(阶段1+2:步骤级流式 + 动作级智能并行) + 将后端 scoreTable 格式转换为前端期望的格式 - 返回 SSE 流,每完成一个动作就返回结果 - - 无依赖关系的动作并行执行 - - 有依赖关系的动作串行执行 + 后端格式: {aspect: {agent: {Score, Reason}}} + 前端格式: {aspectList, agentScores: {agent: {aspect: {score, reason}}}} - 支持参数: - plan: 执行计划 - num_StepToRun: 要运行的步骤数 - RehearsalLog: 已执行的历史记录 - existingKeyObjects: 已存在的KeyObjects(用于重新执行时传递中间结果) - execution_id: 执行ID(用于多用户隔离) + Args: + scoreTable: 后端评分表 - 前端使用 EventSource 接收 + Returns: + 转换后的数据,包含 aspectList 和 agentScores """ - incoming_data = request.get_json() + aspect_list = list(scoreTable.keys()) + agent_scores = {} - # 生成或获取 execution_id - plan = incoming_data.get("plan", {}) - execution_id = incoming_data.get("execution_id") - if not execution_id: - import time - execution_id = f"{plan.get('General Goal', '').replace(' ', '_')}_{int(time.time() * 1000)}" - - 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, - existingKeyObjects=incoming_data.get("existingKeyObjects"), - execution_id=execution_id, - ): - 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( - os.path.join(os.getcwd(), "RequestCache", "Request_Cache.json"), "w" - ) as json_file: - json.dump(Request_Cache, json_file, indent=4) - response = jsonify( - {"code": 200, "content": "request cashe sucessfully saved"} - ) - return response - - -@app.route("/setAgents", methods=["POST"]) -def set_agents(): - global AgentBoard, AgentProfile_Dict,yaml_data - AgentBoard = request.json - AgentProfile_Dict = {} - for item in AgentBoard: - name = item["Name"] - if all(item.get(field) for field in ["apiUrl","apiKey","apiModel"]): - agent_config = { - "profile": item["Profile"], - "apiUrl": item["apiUrl"], - "apiKey": item["apiKey"], - "apiModel": item["apiModel"], - "useCustomAPI":True + for aspect, agents_data in scoreTable.items(): + for agent_name, score_data in agents_data.items(): + if agent_name not in agent_scores: + agent_scores[agent_name] = {} + agent_scores[agent_name][aspect] = { + "score": score_data.get("Score", score_data.get("score", 0)), + "reason": score_data.get("Reason", score_data.get("reason", "")) } - else: - agent_config = { - "profile": item["Profile"], - "apiUrl": yaml_data.get("OPENAI_API_BASE"), - "apiKey": yaml_data.get("OPENAI_API_KEY"), - "apiModel": yaml_data.get("OPENAI_API_MODEL"), - "useCustomAPI":False - } - AgentProfile_Dict[name] = agent_config - return jsonify({"code": 200, "content": "set agentboard successfully"}) + return { + "aspectList": aspect_list, + "agentScores": agent_scores + } def init(): @@ -401,9 +124,7 @@ def init(): os.path.join(os.getcwd(), "RequestCache", "Request_Cache.json"), "r" ) as json_file: Request_Cache = json.load(json_file) - print(f"✅ Loaded Request_Cache with {len(Request_Cache)} entries") except Exception as e: - print(f"⚠️ Failed to load Request_Cache: {e}") Request_Cache = {} # Load Agent Board @@ -412,7 +133,6 @@ def init(): os.path.join(os.getcwd(), "AgentRepo", "agentBoard_v1.json"), "r", encoding="utf-8" ) as json_file: AgentBoard = json.load(json_file) - print(f"✅ Loaded AgentBoard with {len(AgentBoard)} agents") # Build AgentProfile_Dict AgentProfile_Dict = {} @@ -420,10 +140,8 @@ def init(): name = item["Name"] profile = item["Profile"] AgentProfile_Dict[name] = profile - print(f"✅ Built AgentProfile_Dict with {len(AgentProfile_Dict)} profiles") except Exception as e: - print(f"⚠️ Failed to load AgentBoard: {e}") AgentBoard = [] AgentProfile_Dict = {} @@ -432,14 +150,13 @@ def init(): @socketio.on('connect') def handle_connect(): """客户端连接""" - print(f"✅ WebSocket client connected: {request.sid}") emit('connected', {'sid': request.sid, 'message': 'WebSocket连接成功'}) @socketio.on('disconnect') def handle_disconnect(): """客户端断开连接""" - print(f"❌ WebSocket client disconnected: {request.sid}") + pass @socketio.on('ping') @@ -458,17 +175,19 @@ def handle_execute_plan_optimized_ws(data): """ WebSocket版本:优化版流式执行计划 支持步骤级流式 + 动作级智能并行 + 动态追加步骤 + 从指定步骤重新执行 + 执行完成后保存结果到数据库 请求格式: { "id": "request-id", "action": "execute_plan_optimized", "data": { + "task_id": "task-id", # 可选:如果需要保存到数据库 "plan": {...}, "num_StepToRun": null, "RehearsalLog": [], - "enable_dynamic": true, # 是否启用动态追加步骤 - "restart_from_step_index": 1 # 可选:从指定步骤重新执行(例如1表示从步骤2重新执行) + "enable_dynamic": true, + "restart_from_step_index": 1 } } """ @@ -480,13 +199,36 @@ def handle_execute_plan_optimized_ws(data): num_StepToRun = incoming_data.get("num_StepToRun") RehearsalLog = incoming_data.get("RehearsalLog", []) enable_dynamic = incoming_data.get("enable_dynamic", False) - restart_from_step_index = incoming_data.get("restart_from_step_index") # 新增:支持从指定步骤重新执行 + restart_from_step_index = incoming_data.get("restart_from_step_index") + task_id = incoming_data.get("task_id") + + # 执行开始前更新状态为 EXECUTING + with get_db_context() as db: + MultiAgentTaskCRUD.update_status(db, task_id, TaskStatus.EXECUTING) + + print(f"[WS] 开始执行计划: goal={plan.get('General Goal', '')}, dynamic={enable_dynamic}") + + # 收集每个步骤使用的 agent(用于写入 assigned_agents 字段) + assigned_agents_collection = {} + + def collect_assigned_agents_from_chunk(chunk: str): + """从 chunk 中提取 assigned_agents 信息""" + try: + import json + event_str = chunk.replace('data: ', '').replace('\n\n', '').strip() + if not event_str: + return + event = json.loads(event_str) + if event.get('type') == 'step_complete': + step_assigned_agents = event.get('assigned_agents', {}) + if step_assigned_agents: + assigned_agents_collection.update(step_assigned_agents) + except Exception as e: + pass # 如果指定了重新执行起始步骤,截断 RehearsalLog if restart_from_step_index is not None: - print(f"🔄 从步骤 {restart_from_step_index + 1} 重新执行,正在截断 RehearsalLog...") RehearsalLog = truncate_rehearsal_log(RehearsalLog, restart_from_step_index) - print(f"✅ RehearsalLog 已截断,保留 {sum(1 for node in RehearsalLog if node.get('LogNodeType') == 'step')} 个步骤的结果") # 如果前端传入了execution_id,使用前端的;否则生成新的 execution_id = incoming_data.get("execution_id") @@ -513,6 +255,8 @@ def handle_execute_plan_optimized_ws(data): AgentProfile_Dict=AgentProfile_Dict, execution_id=execution_id ): + # 收集 assigned_agents + collect_assigned_agents_from_chunk(chunk) emit('progress', { 'id': request_id, 'status': 'streaming', @@ -535,6 +279,8 @@ def handle_execute_plan_optimized_ws(data): AgentProfile_Dict=AgentProfile_Dict, execution_id=execution_id ): + # 收集 assigned_agents + collect_assigned_agents_from_chunk(chunk) emit('progress', { 'id': request_id, 'status': 'streaming', @@ -548,6 +294,57 @@ def handle_execute_plan_optimized_ws(data): 'data': None }) + print(f"[WS] 执行计划完成: execution_id={execution_id}") + + # 执行完成后保存到数据库 + if task_id: + try: + with get_db_context() as db: + # 计算已完成的步骤数 + completed_steps_count = len([node for node in RehearsalLog if node.get("LogNodeType") == "step"]) + # 获取计划总步骤数 + plan_steps_count = len(plan.get("Collaboration Process", [])) if plan else 0 + + # 更新执行ID(始终保存) + MultiAgentTaskCRUD.update_execution_id(db, task_id, execution_id) + + # 更新执行次数(始终保存) + MultiAgentTaskCRUD.increment_execution_count(db, task_id) + + # 判断是否完整执行:已完成步骤数 >= 计划步骤数 + is_complete_execution = completed_steps_count >= plan_steps_count and plan_steps_count > 0 + + if is_complete_execution: + # 完整执行:保存所有执行数据 + MultiAgentTaskCRUD.update_rehearsal_log(db, task_id, RehearsalLog) + print(f"[execute_plan_optimized] 完整执行完成,已保存 RehearsalLog({completed_steps_count} 个步骤),task_id={task_id}") + + # 保存执行结果(覆盖模式) + step_results = [node for node in RehearsalLog if node.get("LogNodeType") == "step"] + MultiAgentTaskCRUD.update_result(db, task_id, step_results) + print(f"[execute_plan_optimized] 完整执行完成,已保存 result({len(step_results)} 个步骤结果),task_id={task_id}") + + # 更新状态为完成 + MultiAgentTaskCRUD.update_status(db, task_id, TaskStatus.COMPLETED) + else: + # 未完整执行(用户停止):不保存执行数据,只更新状态为 STOPPED + MultiAgentTaskCRUD.update_status(db, task_id, TaskStatus.STOPPED) + print(f"[execute_plan_optimized] 用户停止执行,跳过保存执行数据,已完成 {completed_steps_count}/{plan_steps_count} 步骤,task_id={task_id}") + + # 任务大纲(用户可能编辑了)仍然保存 + if plan: + MultiAgentTaskCRUD.update_task_outline(db, task_id, plan) + print(f"[execute_plan_optimized] 已保存 task_outline 到数据库,task_id={task_id}") + + # # 保存 assigned_agents(每个步骤使用的 agent) + # # 注释原因:assigned_agents 只在生成阶段由用户手动选择写入,执行时不覆盖 + # if assigned_agents_collection: + # MultiAgentTaskCRUD.update_assigned_agents(db, task_id, assigned_agents_collection) + # print(f"[execute_plan_optimized] 已保存 assigned_agents 到数据库,task_id={task_id}") + except Exception: + import traceback + traceback.print_exc() + except Exception as e: # 发送错误信息 emit('progress', { @@ -593,7 +390,7 @@ def handle_add_steps_to_execution(data): added_count = dynamic_execution_manager.add_steps(execution_id, new_steps) if added_count > 0: - print(f"✅ 成功追加 {added_count} 个步骤到执行队列: {execution_id}") + print(f"[WS] 成功追加 {added_count} 个步骤: execution_id={execution_id}") emit('response', { 'id': request_id, 'status': 'success', @@ -603,7 +400,6 @@ def handle_add_steps_to_execution(data): } }) else: - print(f"⚠️ 无法追加步骤,执行ID不存在或已结束: {execution_id}") emit('response', { 'id': request_id, 'status': 'error', @@ -611,7 +407,6 @@ def handle_add_steps_to_execution(data): }) except Exception as e: - print(f"❌ 追加步骤失败: {str(e)}") emit('response', { 'id': request_id, 'status': 'error', @@ -722,6 +517,36 @@ def handle_generate_base_plan_ws(data): if USE_CACHE: Request_Cache[requestIdentifier] = basePlan_withRenderSpec + # 保存到数据库 + user_id = incoming_data.get("user_id", "default_user") + task_id = incoming_data.get("task_id") or str(uuid.uuid4()) + generation_id = str(uuid.uuid4()) + + with get_db_context() as db: + # 检查是否已存在任务 + existing_task = MultiAgentTaskCRUD.get_by_id(db, task_id) + + if existing_task: + # 更新现有任务 + MultiAgentTaskCRUD.update_task_outline(db, task_id, basePlan_withRenderSpec) + MultiAgentTaskCRUD.update_generation_id(db, task_id, generation_id) + MultiAgentTaskCRUD.update_status(db, task_id, TaskStatus.GENERATING) + else: + # 创建新任务 + MultiAgentTaskCRUD.create( + db=db, + task_id=task_id, # 使用已有的 task_id,不生成新的 + user_id=user_id, + query=incoming_data.get("General Goal", ""), + agents_info=AgentBoard or [], + task_outline=basePlan_withRenderSpec, + assigned_agents=None, + agent_scores=None, + result=None, + ) + MultiAgentTaskCRUD.update_generation_id(db, task_id, generation_id) + MultiAgentTaskCRUD.update_status(db, task_id, TaskStatus.GENERATING) + # 发送完成信号 emit('progress', { 'id': request_id, @@ -734,7 +559,11 @@ def handle_generate_base_plan_ws(data): emit('response', { 'id': request_id, 'status': 'success', - 'data': basePlan_withRenderSpec + 'data': { + "task_id": task_id, + "generation_id": generation_id, + "basePlan": basePlan_withRenderSpec + } }) except ValueError as e: @@ -765,10 +594,11 @@ def handle_fill_step_task_ws(data): """ request_id = data.get('id') incoming_data = data.get('data', {}) - - print(f"📥 [WS] 收到 fill_step_task 请求: {request_id}") + task_id = incoming_data.get("task_id") try: + print(f"[WS] 开始处理 fill_step_task: request_id={request_id}, task_id={task_id}") + # 检查缓存 requestIdentifier = str(( "/fill_stepTask", @@ -777,7 +607,6 @@ def handle_fill_step_task_ws(data): )) if USE_CACHE and requestIdentifier in Request_Cache: - print(f"✅ [WS] 使用缓存返回: {request_id}") emit('response', { 'id': request_id, 'status': 'success', @@ -793,8 +622,6 @@ def handle_fill_step_task_ws(data): 'message': f'🚀 开始填充步骤任务: {incoming_data.get("stepTask", {}).get("StepName", "")}' }) - print(f"⏳ [WS] 开始处理 fill_step_task...") - # 阶段1:生成智能体选择 emit('progress', { 'id': request_id, @@ -811,10 +638,28 @@ def handle_fill_step_task_ws(data): "OutputObject": stepTask.get("OutputObject"), "TaskContent": stepTask.get("TaskContent"), } + # 调整字段顺序:确保 Name 在 Icon 前面,避免 LLM 把 Icon 当名字用 + agent_board_for_llm = [] + for agent in AgentBoard: + # 按固定顺序重组:Name, Profile, Icon, Classification + new_agent = {} + if 'Name' in agent: + new_agent['Name'] = agent['Name'] + if 'Profile' in agent: + new_agent['Profile'] = agent['Profile'] + if 'Icon' in agent: + new_agent['Icon'] = agent['Icon'] + if 'Classification' in agent: + new_agent['Classification'] = agent['Classification'] + # 保留其他字段 + for k, v in agent.items(): + if k not in new_agent: + new_agent[k] = v + agent_board_for_llm.append(new_agent) AgentSelection = generate_AgentSelection( General_Goal=incoming_data.get("General Goal"), Current_Task=Current_Task, - Agent_Board=AgentBoard, + Agent_Board=agent_board_for_llm, ) emit('progress', { @@ -879,6 +724,38 @@ def handle_fill_step_task_ws(data): if USE_CACHE: Request_Cache[requestIdentifier] = filled_stepTask + # 保存到数据库 - 更新任务大纲和 assigned_agents + task_id = incoming_data.get("task_id") + if task_id: + with get_db_context() as db: + # 获取现有任务,更新步骤 + existing_task = MultiAgentTaskCRUD.get_by_id(db, task_id) + if existing_task and existing_task.task_outline: + task_outline = existing_task.task_outline.copy() + collaboration_process = task_outline.get("Collaboration Process", []) + + # 获取原始请求中的步骤ID + step_id = stepTask.get("Id") or stepTask.get("id") + # 更新对应步骤 - 使用 StepName 匹配 + step_name = stepTask.get("StepName") + for i, step in enumerate(collaboration_process): + if step.get("StepName") == step_name: + collaboration_process[i] = filled_stepTask + # 如果原始步骤没有ID,从更新后的步骤获取 + if not step_id: + step_id = filled_stepTask.get("Id") or filled_stepTask.get("id") + break + + task_outline["Collaboration Process"] = collaboration_process + + # 直接用SQL更新,绕过ORM事务问题 + import json + db.execute( + text("UPDATE multi_agent_tasks SET task_outline = :outline WHERE task_id = :id"), + {"outline": json.dumps(task_outline), "id": task_id} + ) + db.commit() + # 发送完成信号 emit('progress', { 'id': request_id, @@ -888,15 +765,17 @@ def handle_fill_step_task_ws(data): }) # 返回结果 - print(f"✅ [WS] fill_step_task 处理完成: {request_id}") emit('response', { 'id': request_id, 'status': 'success', - 'data': filled_stepTask + 'data': { + "task_id": task_id, + "filled_stepTask": filled_stepTask + } }) except Exception as e: - print(f"❌ [WS] fill_step_task 处理失败: {request_id}, 错误: {str(e)}") + print(f"[WS] fill_step_task 处理失败: {request_id}, error={str(e)}") emit('response', { 'id': request_id, 'status': 'error', @@ -978,6 +857,42 @@ def handle_fill_step_task_process_ws(data): if USE_CACHE: Request_Cache[requestIdentifier] = filled_stepTask + # 🆕 保存 TaskProcess 数据到 assigned_agents + task_id = incoming_data.get("task_id") + agents = incoming_data.get("agents", []) + if task_id and agents: + with get_db_context() as db: + # 获取步骤ID + step_id = stepTask.get("Id") or stepTask.get("id") + if step_id: + # 获取现有 assigned_agents,确保是 dict 类型 + task = MultiAgentTaskCRUD.get_by_id(db, task_id) + raw_assigned = task.assigned_agents + existing_assigned = raw_assigned if isinstance(raw_assigned, dict) else {} + + # 确保步骤数据结构存在 + if step_id not in existing_assigned: + existing_assigned[step_id] = {} + if "agent_combinations" not in existing_assigned[step_id]: + existing_assigned[step_id]["agent_combinations"] = {} + + # 生成 agentGroupKey(排序后的JSON) + agent_group_key = json.dumps(sorted(agents)) + + # 保存 TaskProcess 和 brief 数据 + existing_assigned[step_id]["agent_combinations"][agent_group_key] = { + "process": filled_stepTask.get("TaskProcess", []), + "brief": filled_stepTask.get("Collaboration_Brief_frontEnd", {}) + } + + # 更新数据库 + db.execute( + text("UPDATE multi_agent_tasks SET assigned_agents = :assigned WHERE task_id = :id"), + {"assigned": json.dumps(existing_assigned), "id": task_id} + ) + db.commit() + print(f"[fill_step_task_process] 已保存 agent_combinations: task_id={task_id}, step_id={step_id}, agents={agents}") + # 发送完成信号 emit('progress', { 'id': request_id, @@ -1392,6 +1307,26 @@ def handle_agent_select_modify_init_ws(data): if USE_CACHE: Request_Cache[requestIdentifier] = scoreTable + # 获取步骤ID(用于 agent_scores 的 key) + stepTask = incoming_data.get("stepTask", {}) + step_id = stepTask.get("Id") or stepTask.get("id") + + # 注意:assigned_agents 不在这里写入 + # AgentSelection 只有在 fill_step_task 完成后才会有值 + # assigned_agents 会在 fill_step_task 接口中写入 + + # 保存到数据库(只保存 agent_scores) + task_id = incoming_data.get("task_id") + if task_id and step_id: + with get_db_context() as db: + # 转换为前端期望格式 + front_format = convert_score_table_to_front_format(scoreTable) + # 按步骤ID包装评分数据 + step_scores = {step_id: front_format} + # 只更新智能体评分(跳过 assigned_agents,因为此时 AgentSelection 为空) + MultiAgentTaskCRUD.update_agent_scores(db, task_id, step_scores) + print(f"[agent_select_modify_init] 已保存 agent_scores: step_id={step_id}") + # 发送完成信号 emit('progress', { 'id': request_id, @@ -1401,10 +1336,14 @@ def handle_agent_select_modify_init_ws(data): }) # 返回结果 + # 注意:assigned_agents 不在此处返回,因为此时 AgentSelection 为空 emit('response', { 'id': request_id, 'status': 'success', - 'data': scoreTable + 'data': { + "task_id": task_id, + "scoreTable": scoreTable + } }) except Exception as e: @@ -1468,6 +1407,57 @@ def handle_agent_select_modify_add_aspect_ws(data): Agent_Board=AgentBoard ) + # 保存到数据库 + task_id = incoming_data.get("task_id") + if task_id: + with get_db_context() as db: + # 获取步骤ID + stepTask = incoming_data.get("stepTask", {}) + step_id = stepTask.get("Id") or stepTask.get("id") # 使用步骤级ID + + if step_id: + # 获取现有评分数据 + task = MultiAgentTaskCRUD.get_by_id(db, task_id) + existing_scores = task.agent_scores or {} + existing_step_data = existing_scores.get(step_id, {}) + + # 合并 aspectList(追加新维度,不重复) + existing_aspects = set(existing_step_data.get("aspectList", [])) + new_aspects = [a for a in aspectList if a not in existing_aspects] + merged_aspect_list = existing_step_data.get("aspectList", []) + new_aspects + + # 合并 agentScores(追加新维度的评分) + new_front_format = convert_score_table_to_front_format(scoreTable) + existing_agent_scores = existing_step_data.get("agentScores", {}) + new_agent_scores = new_front_format.get("agentScores", {}) + + # 合并每个 agent 的评分 + merged_agent_scores = {} + # 保留所有旧 agent 的评分 + for agent, scores in existing_agent_scores.items(): + merged_agent_scores[agent] = dict(scores) + # 追加新 agent 和新维度的评分 + for agent, scores in new_agent_scores.items(): + if agent not in merged_agent_scores: + merged_agent_scores[agent] = {} + for aspect, score_info in scores.items(): + merged_agent_scores[agent][aspect] = score_info + + # 构建合并后的数据 + merged_step_data = { + "aspectList": merged_aspect_list, + "agentScores": merged_agent_scores + } + + # 更新数据库 + existing_scores[step_id] = merged_step_data + db.execute( + text("UPDATE multi_agent_tasks SET agent_scores = :scores WHERE task_id = :id"), + {"scores": json.dumps(existing_scores), "id": task_id} + ) + db.commit() + print(f"[agent_select_modify_add_aspect] 已追加保存 agent_scores: step_id={step_id}, 新增维度={new_aspects}") + # 发送完成信号 emit('progress', { 'id': request_id, @@ -1484,7 +1474,10 @@ def handle_agent_select_modify_add_aspect_ws(data): emit('response', { 'id': request_id, 'status': 'success', - 'data': scoreTable + 'data': { + "task_id": task_id, + "scoreTable": scoreTable + } }) except Exception as e: @@ -1499,6 +1492,7 @@ def handle_agent_select_modify_add_aspect_ws(data): def handle_set_agents_ws(data): """ WebSocket版本:设置智能体 + 保存到 user_agents 数据库表 """ request_id = data.get('id') incoming_data = data.get('data', {}) @@ -1509,31 +1503,98 @@ def handle_set_agents_ws(data): AgentBoard = incoming_data AgentProfile_Dict = {} - for item in AgentBoard: - name = item["Name"] - if all(item.get(field) for field in ["apiUrl", "apiKey", "apiModel"]): - agent_config = { - "profile": item["Profile"], - "apiUrl": item["apiUrl"], - "apiKey": item["apiKey"], - "apiModel": item["apiModel"], - "useCustomAPI": True - } - else: - agent_config = { - "profile": item["Profile"], - "apiUrl": yaml_data.get("OPENAI_API_BASE"), - "apiKey": yaml_data.get("OPENAI_API_KEY"), - "apiModel": yaml_data.get("OPENAI_API_MODEL"), - "useCustomAPI": False - } - AgentProfile_Dict[name] = agent_config + # 保存到数据库 + saved_agents = [] + with get_db_context() as db: + for item in AgentBoard: + name = item["Name"] + if all(item.get(field) for field in ["apiUrl", "apiKey", "apiModel"]): + agent_config = { + "profile": item["Profile"], + "Icon": item.get("Icon", ""), + "Classification": item.get("Classification", ""), + "apiUrl": item["apiUrl"], + "apiKey": item["apiKey"], + "apiModel": item["apiModel"], + "useCustomAPI": True + } + else: + agent_config = { + "profile": item["Profile"], + "Icon": item.get("Icon", ""), + "Classification": item.get("Classification", ""), + "apiUrl": yaml_data.get("OPENAI_API_BASE"), + "apiKey": yaml_data.get("OPENAI_API_KEY"), + "apiModel": yaml_data.get("OPENAI_API_MODEL"), + "useCustomAPI": False + } + AgentProfile_Dict[name] = agent_config + + # 保存到数据库(使用 upsert,相同 user_id + agent_name 则更新,否则创建) + user_id = item.get("user_id", "default_user") + agent = UserAgentCRUD.upsert( + db=db, + user_id=user_id, + agent_name=name, + agent_config=agent_config, + ) + saved_agents.append(agent.to_dict()) # 返回结果 emit('response', { 'id': request_id, 'status': 'success', - 'data': {"code": 200, "content": "set agentboard successfully"} + 'data': { + "code": 200, + "content": "set agentboard successfully", + "saved_agents": saved_agents + } + }) + + except Exception as e: + emit('response', { + 'id': request_id, + 'status': 'error', + 'error': str(e) + }) + + +@socketio.on('get_agents') +def handle_get_agents_ws(data): + """ + WebSocket版本:获取智能体配置 + 从 user_agents 数据库表读取 + """ + request_id = data.get('id') + user_id = data.get('user_id', 'default_user') + + try: + # 从数据库获取用户的智能体配置 + with get_db_context() as db: + user_agents = UserAgentCRUD.get_by_user_id(db=db, user_id=user_id) + + # 转换为前端期望的格式 + agents = [] + for ua in user_agents: + config = ua.agent_config or {} + agents.append({ + 'Name': ua.agent_name, + 'Profile': config.get('profile', ''), + 'Icon': config.get('Icon', ''), + 'Classification': config.get('Classification', ''), + 'apiUrl': config.get('apiUrl', ''), + 'apiKey': config.get('apiKey', ''), + 'apiModel': config.get('apiModel', ''), + }) + + emit('response', { + 'id': request_id, + 'status': 'success', + 'data': { + 'code': 200, + 'content': 'get agents successfully', + 'agents': agents + } }) except Exception as e: @@ -1566,7 +1627,6 @@ def handle_stop_generation(data): # TODO: 这里可以添加实际的停止逻辑 # 例如:设置全局停止标志,通知所有正在运行的生成任务停止 - print(f"🛑 收到停止生成请求: goal={goal}") # 返回成功响应 emit('response', { @@ -1617,14 +1677,14 @@ def handle_pause_execution(data): success = execution_state_manager.pause_execution(execution_id) if success: - print(f"⏸️ [DEBUG] 暂停成功! execution_id={execution_id}") + print(f"[WS] pause_execution 成功: execution_id={execution_id}") emit('response', { 'id': request_id, 'status': 'success', 'data': {"message": "已暂停执行,可随时继续"} }) else: - print(f"⚠️ [DEBUG] 暂停失败,execution_id={execution_id}") + print(f"[WS] pause_execution 失败: execution_id={execution_id}") emit('response', { 'id': request_id, 'status': 'error', @@ -1632,7 +1692,7 @@ def handle_pause_execution(data): }) except Exception as e: - print(f"❌ 暂停执行失败: {str(e)}") + print(f"[WS] pause_execution 异常: {str(e)}") emit('response', { 'id': request_id, 'status': 'error', @@ -1674,14 +1734,14 @@ def handle_resume_execution(data): success = execution_state_manager.resume_execution(execution_id) if success: - print(f"▶️ 已恢复执行: execution_id={execution_id}") + print(f"[WS] resume_execution 成功: execution_id={execution_id}") emit('response', { 'id': request_id, 'status': 'success', 'data': {"message": "已恢复执行"} }) else: - print(f"⚠️ 恢复失败,execution_id={execution_id}") + print(f"[WS] resume_execution 失败: execution_id={execution_id}") emit('response', { 'id': request_id, 'status': 'error', @@ -1689,7 +1749,7 @@ def handle_resume_execution(data): }) except Exception as e: - print(f"❌ 恢复执行失败: {str(e)}") + print(f"[WS] resume_execution 异常: {str(e)}") emit('response', { 'id': request_id, 'status': 'error', @@ -1731,14 +1791,14 @@ def handle_stop_execution(data): success = execution_state_manager.stop_execution(execution_id) if success: - print(f"🛑 [DEBUG] 停止成功! execution_id={execution_id}") + print(f"[WS] stop_execution 成功: execution_id={execution_id}") emit('response', { 'id': request_id, 'status': 'success', 'data': {"message": "已停止执行"} }) else: - print(f"⚠️ [DEBUG] 停止失败,execution_id={execution_id}") + print(f"[WS] stop_execution 失败: execution_id={execution_id}") emit('response', { 'id': request_id, 'status': 'error', @@ -1746,7 +1806,596 @@ def handle_stop_execution(data): }) except Exception as e: - print(f"❌ 停止执行失败: {str(e)}") + print(f"[WS] stop_execution 异常: {str(e)}") + emit('response', { + 'id': request_id, + 'status': 'error', + 'error': str(e) + }) + + +# ==================== 历史记录管理 ==================== + +@socketio.on('get_plans') +def handle_get_plans(data): + """ + WebSocket版本:获取历史任务列表 + """ + # socketio 会把数据包装多层: + # 前端发送: { id: 'get_plans-xxx', action: 'get_plans', data: { id: 'ws_req_xxx' } } + # socketio 包装后: data = { id: 'get_plans-xxx', action: 'get_plans', data: {...} } + request_id = data.get('id') # socketio 包装的 id,用于响应匹配 + + try: + with get_db_context() as db: + # 获取最近的任务记录 + tasks = MultiAgentTaskCRUD.get_recent(db, limit=50) + + # 转换为前端期望的格式 + plans = [] + for task in tasks: + # 兼容旧数据格式(branches 可能是数组) + branches_data = task.branches + if branches_data and isinstance(branches_data, list): + # 旧格式:数组,转换为新格式对象 + branches_data = { + 'flow_branches': branches_data, + 'task_process_branches': {} + } + + plans.append({ + "id": task.task_id, # 以 task_id 为唯一标识 + "general_goal": task.query or '未知任务', + "status": task.status.value if task.status else 'unknown', + "execution_count": task.execution_count or 0, + "created_at": task.created_at.isoformat() if task.created_at else None, + # 完整数据用于恢复 + "task_outline": task.task_outline, + "assigned_agents": task.assigned_agents, + "agent_scores": task.agent_scores, + "agents_info": task.agents_info, + "branches": branches_data or {}, # 分支数据(新格式对象) + }) + + emit('response', { + 'id': request_id, + 'status': 'success', + 'data': plans + }) + + except Exception as e: + emit('response', { + 'id': request_id, + 'status': 'error', + 'error': str(e) + }) + + +@socketio.on('restore_plan') +def handle_restore_plan(data): + """ + WebSocket版本:恢复历史任务 + """ + # socketio 包装: data = { id: 'restore_plan-xxx', action: 'restore_plan', data: { id: 'ws_req_xxx', data: {...} } } + request_id = data.get('id') # socketio 包装的 id + incoming_data = data.get('data', {}).get('data', {}) # 真正的请求数据 + plan_id = incoming_data.get('plan_id') + + if not plan_id: + emit('response', { + 'id': request_id, + 'status': 'error', + 'error': '缺少 plan_id(task_id)' + }) + return + + try: + with get_db_context() as db: + # 以 task_id 为唯一标识查询 + task = MultiAgentTaskCRUD.get_by_id(db, plan_id) + + if not task: + emit('response', { + 'id': request_id, + 'status': 'error', + 'error': f'任务不存在: {plan_id}' + }) + return + + # 注意:恢复任务不增加执行次数,避免误统计 + # execution_count 只在真正执行任务时增加 + + # 兼容旧数据格式(branches 可能是数组) + branches_data = task.branches + if branches_data and isinstance(branches_data, list): + # 旧格式:数组,转换为新格式对象 + branches_data = { + 'flow_branches': branches_data, + 'task_process_branches': {} + } + + # 返回完整数据用于恢复 + restored_data = { + "id": task.task_id, + "general_goal": task.query or '未知任务', + "status": task.status.value if task.status else 'unknown', + "execution_count": task.execution_count or 0, + "created_at": task.created_at.isoformat() if task.created_at else None, + # 完整恢复数据 + "task_outline": task.task_outline, + "assigned_agents": task.assigned_agents, + "agent_scores": task.agent_scores, + "agents_info": task.agents_info, + "branches": branches_data or {}, # 分支数据(新格式对象) + "rehearsal_log": task.rehearsal_log, # 排练日志(完整执行数据,用于恢复执行状态) + } + + emit('response', { + 'id': request_id, + 'status': 'success', + 'data': restored_data + }) + + except Exception as e: + emit('response', { + 'id': request_id, + 'status': 'error', + 'error': str(e) + }) + + +@socketio.on('get_agent_scores') +def handle_get_agent_scores(data): + """ + WebSocket版本:获取指定任务的评分数据 + + 请求格式: + { + "id": "request-id", + "action": "get_agent_scores", + "data": { + "task_id": "task-id" + } + } + + 返回格式(与前端 ITaskScoreData 一致): + { + "task_id": "xxx", + "agent_scores": { + "stepId1": { + "aspectList": ["专业性", "协作能力"], + "agentScores": {"Agent-A": {"专业性": {"score": 4.5, "reason": "..."}}}, + "timestamp": 1699999999999 + } + } + } + """ + request_id = data.get('id') + incoming_data = data.get('data', {}) + task_id = incoming_data.get('task_id') + + if not task_id: + emit('response', { + 'id': request_id, + 'status': 'error', + 'error': '缺少 task_id 参数' + }) + return + + try: + with get_db_context() as db: + task = MultiAgentTaskCRUD.get_by_id(db, task_id) + + if not task: + emit('response', { + 'id': request_id, + 'status': 'error', + 'error': f'任务不存在: {task_id}' + }) + return + + # 返回评分数据 + emit('response', { + 'id': request_id, + 'status': 'success', + 'data': { + "task_id": task_id, + "agent_scores": task.agent_scores or {} + } + }) + + except Exception as e: + emit('response', { + 'id': request_id, + 'status': 'error', + 'error': str(e) + }) + + +@socketio.on('delete_plan') +def handle_delete_plan(data): + """ + WebSocket版本:删除历史任务 + """ + # socketio 包装: data = { id: 'delete_plan-xxx', action: 'delete_plan', data: { id: 'ws_req_xxx', data: {...} } } + request_id = data.get('id') # socketio 包装的 id + incoming_data = data.get('data', {}).get('data', {}) # 真正的请求数据 + plan_id = incoming_data.get('plan_id') + + if not plan_id: + emit('response', { + 'id': request_id, + 'status': 'error', + 'error': '缺少 plan_id(task_id)' + }) + return + + try: + with get_db_context() as db: + # 以 task_id 为唯一标识删除 + success = MultiAgentTaskCRUD.delete(db, plan_id) + + if not success: + emit('response', { + 'id': request_id, + 'status': 'error', + 'error': f'任务不存在或删除失败: {plan_id}' + }) + return + + # 通知所有客户端刷新历史列表 + socketio.emit('history_updated', {'task_id': plan_id}) + + emit('response', { + 'id': request_id, + 'status': 'success', + 'data': {"message": "删除成功"} + }) + + except Exception as e: + emit('response', { + 'id': request_id, + 'status': 'error', + 'error': str(e) + }) + + +@socketio.on('save_branches') +def handle_save_branches(data): + """ + WebSocket版本:保存任务分支数据 + + 请求格式: + { + "id": "request-id", + "action": "save_branches", + "data": { + "task_id": "task-id", + "branches": [...] // 分支数据数组 + } + } + + 数据库存储格式: + { + "branches": { + "flow_branches": [...], // 任务大纲探索分支 + "task_process_branches": {...} // 任务过程分支(可能不存在) + } + } + """ + request_id = data.get('id') + incoming_data = data.get('data', {}) + task_id = incoming_data.get('task_id') + flow_branches = incoming_data.get('branches', []) + + if not task_id: + emit('response', { + 'id': request_id, + 'status': 'error', + 'error': '缺少 task_id 参数' + }) + return + + try: + with get_db_context() as db: + # 获取现有的 branches 数据 + existing_task = MultiAgentTaskCRUD.get_by_id(db, task_id) + if existing_task: + # 使用深拷贝避免修改共享引用 + existing_branches = copy.deepcopy(existing_task.branches) if existing_task.branches else {} + + # 保留现有的 task_process_branches(关键:不要覆盖已有的任务过程分支) + task_process_branches = existing_branches.get('task_process_branches', {}) if isinstance(existing_branches, dict) else {} + + # 构建新的 branches 数据 + new_branches = { + 'flow_branches': flow_branches, + 'task_process_branches': task_process_branches + } + + # 更新数据库 + MultiAgentTaskCRUD.update_branches(db, task_id, new_branches) + print(f"[save_branches] 已保存分支数据到数据库,task_id={task_id}, flow_branches_count={len(flow_branches)}, task_process_count={len(task_process_branches)}") + + emit('response', { + 'id': request_id, + 'status': 'success', + 'data': { + "message": "分支数据保存成功", + "branches_count": len(flow_branches) + } + }) + + except Exception as e: + emit('response', { + 'id': request_id, + 'status': 'error', + 'error': str(e) + }) + + +@socketio.on('save_task_process_branches') +def handle_save_task_process_branches(data): + """ + WebSocket版本:保存任务过程分支数据 + + 请求格式: + { + "id": "request-id", + "action": "save_task_process_branches", + "data": { + "task_id": "task-id", // 大任务ID(数据库主键) + "branches": { + "stepId-1": { + '["AgentA","AgentB"]': [{...}, {...}], + '["AgentC"]': [...] + }, + "stepId-2": {...} + } + } + } + """ + request_id = data.get('id') + incoming_data = data.get('data', {}) + task_id = incoming_data.get('task_id') + branches = incoming_data.get('branches', {}) + + if not task_id: + emit('response', { + 'id': request_id, + 'status': 'error', + 'error': '缺少 task_id 参数' + }) + return + + try: + with get_db_context() as db: + # 获取现有的 branches 数据 + existing_task = MultiAgentTaskCRUD.get_by_id(db, task_id) + + if existing_task: + # 使用深拷贝避免修改共享引用 + existing_branches = copy.deepcopy(existing_task.branches) if existing_task.branches else {} + + # 保留现有的 flow_branches + existing_flow_branches = existing_branches.get('flow_branches', []) if isinstance(existing_branches, dict) else [] + + # 合并 task_process_branches(新数据与旧数据合并,而不是覆盖) + existing_task_process = existing_branches.get('task_process_branches', {}) if isinstance(existing_branches, dict) else {} + incoming_task_process = branches + + # 合并逻辑:对于每个 stepId,将新分支追加到已有分支中 + merged_task_process = dict(existing_task_process) + for stepId, stepData in incoming_task_process.items(): + if stepId in merged_task_process: + # stepId 已存在,合并 agentGroupKey 下的分支数组 + existing_agent_data = merged_task_process[stepId] + incoming_agent_data = stepData + for agentKey, newBranches in incoming_agent_data.items(): + if agentKey in existing_agent_data: + # 合并分支(去重,根据 branch.id) + existing_ids = {b.get('id') for b in existing_agent_data[agentKey] if b.get('id')} + for newBranch in newBranches: + if newBranch.get('id') not in existing_ids: + existing_agent_data[agentKey].append(newBranch) + else: + existing_agent_data[agentKey] = newBranches + else: + merged_task_process[stepId] = stepData + + # 构建新 branches 数据 + if isinstance(existing_branches, dict): + new_branches = dict(existing_branches) + new_branches['task_process_branches'] = merged_task_process + new_branches['flow_branches'] = existing_flow_branches + else: + new_branches = { + 'task_process_branches': merged_task_process, + 'flow_branches': existing_flow_branches if isinstance(existing_flow_branches, list) else [] + } + + # 直接更新数据库 + existing_task.branches = new_branches + db.flush() # 显式刷新,确保 SQLAlchemy 检测到变化 + db.commit() + + print(f"[save_task_process_branches] 保存完成,stepIds: {list(merged_task_process.keys())}") + else: + print(f"[save_task_process_branches] 警告: 找不到任务,task_id={task_id}") + + emit('response', { + 'id': request_id, + 'status': 'success', + 'data': { + "message": "任务过程分支数据保存成功", + "step_count": len(branches) + } + }) + + except Exception as e: + emit('response', { + 'id': request_id, + 'status': 'error', + 'error': str(e) + }) + + +@socketio.on('save_task_outline') +def handle_save_task_outline(data): + """ + WebSocket版本:保存任务大纲数据 + + 请求格式: + { + "id": "request-id", + "action": "save_task_outline", + "data": { + "task_id": "task-id", + "task_outline": {...} + } + } + """ + request_id = data.get('id') + incoming_data = data.get('data', {}) + task_id = incoming_data.get('task_id') + task_outline = incoming_data.get('task_outline') + + if not task_id: + emit('response', { + 'id': request_id, + 'status': 'error', + 'error': '缺少 task_id 参数' + }) + return + + try: + with get_db_context() as db: + # 更新任务大纲 + MultiAgentTaskCRUD.update_task_outline(db, task_id, task_outline) + print(f"[save_task_outline] 已保存任务大纲到数据库,task_id={task_id}") + + emit('response', { + 'id': request_id, + 'status': 'success', + 'data': { + "message": "任务大纲保存成功" + } + }) + + except Exception as e: + emit('response', { + 'id': request_id, + 'status': 'error', + 'error': str(e) + }) + + +@socketio.on('update_assigned_agents') +def handle_update_assigned_agents(data): + """ + WebSocket版本:更新指定步骤的 assigned_agents + + 请求格式: + { + "id": "request-id", + "action": "update_assigned_agents", + "data": { + "task_id": "task-id", // 大任务ID(数据库主键) + "step_id": "step-id", // 步骤级ID(小任务UUID) + "agents": ["AgentA", "AgentB"], // 选中的 agent 列表 + "confirmed_groups": [["AgentA"], ["AgentA", "AgentB"]], // 可选:确认的 agent 组合列表 + "save_combination": true // 可选:是否同时保存该组合的 TaskProcess(由 fill_step_task_process 处理) + } + } + """ + import json + request_id = data.get('id') + incoming_data = data.get('data', {}) + task_id = incoming_data.get('task_id') + step_id = incoming_data.get('step_id') + agents = incoming_data.get('agents', []) + confirmed_groups = incoming_data.get('confirmed_groups', []) + agent_combinations = incoming_data.get('agent_combinations', {}) + if not task_id: + emit('response', { + 'id': request_id, + 'status': 'error', + 'error': '缺少 task_id 参数' + }) + return + + if not step_id: + emit('response', { + 'id': request_id, + 'status': 'error', + 'error': '缺少 step_id 参数' + }) + return + + try: + with get_db_context() as db: + # 获取现有任务 + task = MultiAgentTaskCRUD.get_by_id(db, task_id) + if not task: + emit('response', { + 'id': request_id, + 'status': 'error', + 'error': f'任务不存在: {task_id}' + }) + return + + # 合并更新 assigned_agents,确保是 dict 类型 + raw_assigned = task.assigned_agents + existing_assigned = raw_assigned if isinstance(raw_assigned, dict) else {} + + # 确保步骤数据结构存在 + if step_id not in existing_assigned: + existing_assigned[step_id] = {} + + # 确保子结构存在 + if "current" not in existing_assigned[step_id]: + existing_assigned[step_id]["current"] = [] + if "confirmed_groups" not in existing_assigned[step_id]: + existing_assigned[step_id]["confirmed_groups"] = [] + if "agent_combinations" not in existing_assigned[step_id]: + existing_assigned[step_id]["agent_combinations"] = {} + + # 更新 current agents(当前选中的组合) + if agents: + existing_assigned[step_id]["current"] = agents + + # 更新 confirmed_groups(确认的组合列表) + if confirmed_groups: + existing_assigned[step_id]["confirmed_groups"] = confirmed_groups + + # 更新 agent_combinations(保存 TaskProcess 数据) + if agent_combinations: + # 合并新旧数据 + existing_combinations = existing_assigned[step_id].get("agent_combinations", {}) + for key, value in agent_combinations.items(): + existing_combinations[key] = value + existing_assigned[step_id]["agent_combinations"] = existing_combinations + + db.execute( + text("UPDATE multi_agent_tasks SET assigned_agents = :assigned WHERE task_id = :id"), + {"assigned": json.dumps(existing_assigned), "id": task_id} + ) + db.commit() + print(f"[update_assigned_agents] 已保存: task_id={task_id}, step_id={step_id}") + + emit('response', { + 'id': request_id, + 'status': 'success', + 'data': { + "message": "assigned_agents 更新成功", + "task_id": task_id, + "step_id": step_id, + "agents": agents, + "confirmed_groups": confirmed_groups + } + }) + + except Exception as e: emit('response', { 'id': request_id, 'status': 'error', @@ -1767,5 +2416,4 @@ if __name__ == "__main__": args = parser.parse_args() init() # 使用 socketio.run 替代 app.run,支持WebSocket - socketio.run(app, host="0.0.0.0", port=args.port, debug=True, allow_unsafe_werkzeug=True) - #socketio.run(app, host="0.0.0.0", port=args.port, debug=False, allow_unsafe_werkzeug=True) + socketio.run(app, host="0.0.0.0", port=args.port, debug=True, allow_unsafe_werkzeug=True) \ No newline at end of file diff --git a/frontend/src/api/index.ts b/frontend/src/api/index.ts index 4e47531..1f9566e 100644 --- a/frontend/src/api/index.ts +++ b/frontend/src/api/index.ts @@ -1,4 +1,3 @@ -import request from '@/utils/request' import websocket from '@/utils/websocket' import type { Agent, IApiStepTask, IRawPlanResponse, IRawStepTask } from '@/stores' import { withRetry } from '@/utils/retry' @@ -30,7 +29,7 @@ export type IExecuteRawResponse = { } /** - * SSE 流式事件类型 + * WebSocket 流式事件类型 */ export type StreamingEvent = | { @@ -77,101 +76,47 @@ export interface IFillAgentSelectionRequest { } class Api { - // 默认使用WebSocket - private useWebSocketDefault = true - - setAgents = ( - data: Pick[], - useWebSocket: boolean = this.useWebSocketDefault, - ) => { - // 如果启用WebSocket且已连接,使用WebSocket - if (useWebSocket && websocket.connected) { - return websocket.send('set_agents', data) - } - - // 否则使用REST API - return request({ - url: '/setAgents', - data, - method: 'POST', - }) + // 提取响应数据的公共方法 + private extractResponse(raw: any): T { + return (raw.data || raw) as T } + // 颜色向量转 HSL 字符串 + private vec2Hsl = (color: number[]): string => { + const [h, s, l] = color + return `hsl(${h}, ${s}%, ${l}%)` + } + + setAgents = (data: Pick[]) => + websocket.send('set_agents', data) + + getAgents = (user_id: string = 'default_user') => websocket.send('get_agents', { user_id }) + generateBasePlan = (data: { goal: string inputs: string[] apiUrl?: string apiKey?: string apiModel?: string - useWebSocket?: boolean onProgress?: (progress: { status: string stage?: string message?: string [key: string]: any }) => void - }) => { - const useWs = data.useWebSocket !== undefined ? data.useWebSocket : this.useWebSocketDefault - - // 如果启用WebSocket且已连接,使用WebSocket - if (useWs && websocket.connected) { - return websocket.send( - 'generate_base_plan', - { - 'General Goal': data.goal, - 'Initial Input Object': data.inputs, - apiUrl: data.apiUrl, - apiKey: data.apiKey, - apiModel: data.apiModel, - }, - undefined, - data.onProgress, - ) - } - - // 否则使用REST API - return request({ - url: '/generate_basePlan', - method: 'POST', - data: { + }) => + websocket.send( + 'generate_base_plan', + { 'General Goal': data.goal, 'Initial Input Object': data.inputs, apiUrl: data.apiUrl, apiKey: data.apiKey, apiModel: data.apiModel, }, - }) - } - - executePlan = (plan: IRawPlanResponse) => { - return request({ - url: '/executePlan', - method: 'POST', - 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, - })), - })), - }, - }, - }) - } + undefined, + data.onProgress, + ) /** * 优化版流式执行计划(支持动态追加步骤) @@ -182,23 +127,25 @@ class Api { onMessage: (event: StreamingEvent) => void, onError?: (error: Error) => void, onComplete?: () => void, - useWebSocket?: boolean, + _useWebSocket?: boolean, existingKeyObjects?: Record, enableDynamic?: boolean, onExecutionStarted?: (executionId: string) => void, executionId?: string, - restartFromStepIndex?: number, // 新增:从指定步骤重新执行的索引 - rehearsalLog?: any[], // 新增:传递截断后的 RehearsalLog + restartFromStepIndex?: number, + rehearsalLog?: any[], + TaskID?: string, ) => { - const useWs = useWebSocket !== undefined ? useWebSocket : this.useWebSocketDefault - + // eslint-disable-next-line @typescript-eslint/no-unused-vars + void _useWebSocket // 保留参数位置以保持兼容性 const data = { - RehearsalLog: rehearsalLog || [], // ✅ 使用传递的 RehearsalLog + RehearsalLog: rehearsalLog || [], // 使用传递的 RehearsalLog num_StepToRun: null, existingKeyObjects: existingKeyObjects || {}, enable_dynamic: enableDynamic || false, execution_id: executionId || null, restart_from_step_index: restartFromStepIndex ?? null, // 新增:传递重新执行索引 + task_id: TaskID || null, // 任务唯一标识,用于写入数据库 plan: { 'Initial Input Object': plan['Initial Input Object'], 'General Goal': plan['General Goal'], @@ -220,103 +167,46 @@ class Api { }, } - // 如果启用WebSocket且已连接,使用WebSocket - if (useWs && websocket.connected) { - websocket.subscribe( - 'execute_plan_optimized', - data, - // onProgress - (progressData) => { - try { - let event: StreamingEvent + websocket.subscribe( + 'execute_plan_optimized', + data, + // onProgress + (progressData) => { + try { + let event: StreamingEvent - // 处理不同类型的progress数据 - if (typeof progressData === 'string') { - event = JSON.parse(progressData) - } else { - event = progressData as StreamingEvent - } - - // 处理特殊事件类型 - if (event && typeof event === 'object') { - // 检查是否是execution_started事件 - if ('status' in event && event.status === 'execution_started') { - if ('execution_id' in event && onExecutionStarted) { - onExecutionStarted(event.execution_id as string) - } - return - } - } - - onMessage(event) - } catch (e) { - // Failed to parse WebSocket data + // 处理不同类型的progress数据 + if (typeof progressData === 'string') { + event = JSON.parse(progressData) + } else { + event = progressData as StreamingEvent } - }, - // onComplete - () => { - onComplete?.() - }, - // onError - (error) => { - onError?.(error) - }, - ) - return - } - // 否则使用原有的SSE方式 + // 处理特殊事件类型 + if (event && typeof event === 'object') { + // 检查是否是execution_started事件 + if ('status' in event && event.status === 'execution_started') { + if ('execution_id' in event && onExecutionStarted) { + onExecutionStarted(event.execution_id as string) + } + return + } + } - fetch('/api/executePlanOptimized', { - method: 'POST', - headers: { - 'Content-Type': 'application/json', + onMessage(event) + } catch (e) { + // Failed to parse WebSocket data + } }, - 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) { - // Failed to parse SSE data - } - } - } - } - }) - .catch((error) => { + // onComplete + () => { + onComplete?.() + }, + // onError + (error) => { onError?.(error) - }) + }, + ) } /** @@ -329,38 +219,16 @@ class Api { Baseline_Completion: number initialInputs: string[] goal: string - useWebSocket?: boolean onProgress?: (progress: { status: string stage?: string message?: string [key: string]: any }) => void - }) => { - const useWs = data.useWebSocket !== undefined ? data.useWebSocket : this.useWebSocketDefault - - // 如果启用WebSocket且已连接,使用WebSocket - if (useWs && websocket.connected) { - return websocket.send( - 'branch_plan_outline', - { - branch_Number: data.branch_Number, - Modification_Requirement: data.Modification_Requirement, - Existing_Steps: data.Existing_Steps, - Baseline_Completion: data.Baseline_Completion, - 'Initial Input Object': data.initialInputs, - 'General Goal': data.goal, - }, - undefined, - data.onProgress, - ) - } - - // 否则使用REST API - return request({ - url: '/branch_PlanOutline', - method: 'POST', - data: { + }) => + websocket.send( + 'branch_plan_outline', + { branch_Number: data.branch_Number, Modification_Requirement: data.Modification_Requirement, Existing_Steps: data.Existing_Steps, @@ -368,8 +236,9 @@ class Api { 'Initial Input Object': data.initialInputs, 'General Goal': data.goal, }, - }) - } + undefined, + data.onProgress, + ) /** * 分支任务流程 @@ -381,38 +250,16 @@ class Api { Baseline_Completion: number stepTaskExisting: any goal: string - useWebSocket?: boolean onProgress?: (progress: { status: string stage?: string message?: string [key: string]: any }) => void - }) => { - const useWs = data.useWebSocket !== undefined ? data.useWebSocket : this.useWebSocketDefault - - // 如果启用WebSocket且已连接,使用WebSocket - if (useWs && websocket.connected) { - return websocket.send( - 'branch_task_process', - { - branch_Number: data.branch_Number, - Modification_Requirement: data.Modification_Requirement, - Existing_Steps: data.Existing_Steps, - Baseline_Completion: data.Baseline_Completion, - stepTaskExisting: data.stepTaskExisting, - 'General Goal': data.goal, - }, - undefined, - data.onProgress, - ) - } - - // 否则使用REST API - return request({ - url: '/branch_TaskProcess', - method: 'POST', - data: { + }) => + websocket.send( + 'branch_task_process', + { branch_Number: data.branch_Number, Modification_Requirement: data.Modification_Requirement, Existing_Steps: data.Existing_Steps, @@ -420,14 +267,15 @@ class Api { stepTaskExisting: data.stepTaskExisting, 'General Goal': data.goal, }, - }) - } + undefined, + data.onProgress, + ) fillStepTask = async (data: { goal: string stepTask: any generation_id?: string - useWebSocket?: boolean + TaskID?: string onProgress?: (progress: { status: string stage?: string @@ -435,72 +283,31 @@ class Api { [key: string]: any }) => void }): Promise => { - const useWs = data.useWebSocket !== undefined ? data.useWebSocket : this.useWebSocketDefault - - // 定义实际的 API 调用逻辑 - const executeRequest = async (): Promise => { - if (useWs && websocket.connected) { - return await websocket.send( + const rawResponse = await withRetry( + () => + websocket.send( 'fill_step_task', { 'General Goal': data.goal, stepTask: data.stepTask, generation_id: data.generation_id || '', + task_id: data.TaskID || '', }, undefined, data.onProgress, - ) - } - // 否则使用REST API - return await request< - { - 'General Goal': string - stepTask: any + ), + { + maxRetries: 3, + initialDelayMs: 2000, + onRetry: (error, attempt, delay) => { + console.warn(` [fillStepTask] 第${attempt}次重试,等待 ${delay}ms...`, error?.message) }, - { - AgentSelection?: string[] - Collaboration_Brief_FrontEnd?: { - template: string - data: Record - } - InputObject_List?: string[] - OutputObject?: string - StepName?: string - TaskContent?: string - TaskProcess?: Array<{ - ID: string - ActionType: string - AgentName: string - Description: string - ImportantInput: string[] - }> - } - >({ - url: '/fill_stepTask', - method: 'POST', - data: { - 'General Goal': data.goal, - stepTask: data.stepTask, - }, - }) - } - - // 使用重试机制执行请求 - const rawResponse = await withRetry(executeRequest, { - maxRetries: 3, - initialDelayMs: 2000, - onRetry: (error, attempt, delay) => { - console.warn(`⚠️ [fillStepTask] 第${attempt}次重试,等待 ${delay}ms...`, error?.message) }, - }) + ) - // WebSocket 返回格式: { data: {...}, generation_id, execution_id } - // REST API 返回格式: {...} - const response = rawResponse.data || rawResponse - - const vec2Hsl = (color: number[]): string => { - const [h, s, l] = color - return `hsl(${h}, ${s}%, ${l}%)` + let response = this.extractResponse(rawResponse) + if (response?.filled_stepTask) { + response = response.filled_stepTask } const briefData: Record }> = {} @@ -509,15 +316,12 @@ class Api { briefData[key] = { text: (value as { text: string; color: number[] }).text, style: { - background: vec2Hsl((value as { text: string; color: number[] }).color), + background: this.vec2Hsl((value as { text: string; color: number[] }).color), }, } } } - /** - * 构建前端格式的 IRawStepTask - */ return { StepName: response.StepName || '', TaskContent: response.TaskContent || '', @@ -536,7 +340,7 @@ class Api { goal: string stepTask: IApiStepTask agents: string[] - useWebSocket?: boolean + TaskID?: string onProgress?: (progress: { status: string stage?: string @@ -544,15 +348,13 @@ class Api { [key: string]: any }) => void }): Promise => { - const useWs = data.useWebSocket !== undefined ? data.useWebSocket : this.useWebSocketDefault - - // 定义实际的 API 调用逻辑 - const executeRequest = async (): Promise => { - if (useWs && websocket.connected) { - return await websocket.send( + const rawResponse = await withRetry( + () => + websocket.send( 'fill_step_task_process', { 'General Goal': data.goal, + task_id: data.TaskID || undefined, stepTask_lackTaskProcess: { StepName: data.stepTask.name, TaskContent: data.stepTask.content, @@ -560,76 +362,26 @@ class Api { OutputObject: data.stepTask.output, AgentSelection: data.agents, }, + agents: data.agents, }, undefined, data.onProgress, - ) - } - // 否则使用REST API - return await request< - { - 'General Goal': string - stepTask_lackTaskProcess: { - StepName: string - TaskContent: string - InputObject_List: string[] - OutputObject: string - AgentSelection: string[] - } + ), + { + maxRetries: 3, + initialDelayMs: 2000, + onRetry: (error, attempt, delay) => { + console.warn( + `[fillStepTaskTaskProcess] 第${attempt}次重试,等待 ${delay}ms...`, + error?.message, + ) }, - { - StepName?: string - TaskContent?: string - InputObject_List?: string[] - OutputObject?: string - AgentSelection?: string[] - TaskProcess?: Array<{ - ID: string - ActionType: string - AgentName: string - Description: string - ImportantInput: string[] - }> - Collaboration_Brief_FrontEnd?: { - template: string - data: Record - } - } - >({ - url: '/fill_stepTask_TaskProcess', - method: 'POST', - data: { - 'General Goal': data.goal, - stepTask_lackTaskProcess: { - StepName: data.stepTask.name, - TaskContent: data.stepTask.content, - InputObject_List: data.stepTask.inputs, - OutputObject: data.stepTask.output, - AgentSelection: data.agents, - }, - }, - }) - } - - // 使用重试机制执行请求 - const rawResponse = await withRetry(executeRequest, { - maxRetries: 3, - initialDelayMs: 2000, - onRetry: (error, attempt, delay) => { - console.warn( - `⚠️ [fillStepTaskTaskProcess] 第${attempt}次重试,等待 ${delay}ms...`, - error?.message, - ) }, - }) + ) - // WebSocket 返回格式: { data: {...}, generation_id, execution_id } - // REST API 返回格式: {...} - const response = rawResponse.data || rawResponse - - const vec2Hsl = (color: number[]): string => { - const [h, s, l] = color - return `hsl(${h}, ${s}%, ${l}%)` + let response = this.extractResponse(rawResponse) + if (response?.filled_stepTask) { + response = response.filled_stepTask } const briefData: Record = {} @@ -638,7 +390,7 @@ class Api { briefData[key] = { text: (value as { text: string; color: number[] }).text, style: { - background: vec2Hsl((value as { text: string; color: number[] }).color), + background: this.vec2Hsl((value as { text: string; color: number[] }).color), }, } } @@ -672,7 +424,7 @@ class Api { agentSelectModifyInit = async (data: { goal: string stepTask: any - useWebSocket?: boolean + TaskID?: string onProgress?: (progress: { status: string stage?: string @@ -680,78 +432,54 @@ class Api { [key: string]: any }) => void }): Promise>> => { - const useWs = data.useWebSocket !== undefined ? data.useWebSocket : this.useWebSocketDefault - - // 调试日志:打印请求参数 const requestPayload = { 'General Goal': data.goal, stepTask: { + Id: data.stepTask.Id || data.stepTask.id, StepName: data.stepTask.StepName || data.stepTask.name, TaskContent: data.stepTask.TaskContent || data.stepTask.content, InputObject_List: data.stepTask.InputObject_List || data.stepTask.inputs, OutputObject: data.stepTask.OutputObject || data.stepTask.output, }, + task_id: data.TaskID || '', } - console.log('🔍 [agentSelectModifyInit] 请求参数:', { - goal: requestPayload['General Goal'], - stepTaskName: requestPayload.stepTask.StepName, - stepTaskContentLength: requestPayload.stepTask.TaskContent?.length, - useWebSocket: useWs && websocket.connected, - wsConnected: websocket.connected, - }) - // 定义实际的 API 调用逻辑 - const executeRequest = async (): Promise< - Record> - > => { - if (useWs && websocket.connected) { - return await websocket.send( + const rawResponse = await withRetry( + () => + websocket.send( 'agent_select_modify_init', requestPayload, undefined, data.onProgress, - ) - } - // 否则使用REST API - return await request< - { - 'General Goal': string - stepTask: any + ), + { + maxRetries: 3, + initialDelayMs: 2000, + onRetry: (error, attempt, delay) => { + console.warn( + `[agentSelectModifyInit] 第${attempt}次重试,等待 ${delay}ms...`, + error?.message, + ) }, - Record> - >({ - url: '/agentSelectModify_init', - method: 'POST', - data: requestPayload, - }) - } - - // 使用重试机制执行请求 - const rawResponse = await withRetry(executeRequest, { - maxRetries: 3, - initialDelayMs: 2000, - onRetry: (error, attempt, delay) => { - console.warn( - `⚠️ [agentSelectModifyInit] 第${attempt}次重试,等待 ${delay}ms...`, - error?.message, - ) }, - }) + ) - // WebSocket 返回格式: { data: {...}, generation_id, execution_id } - // REST API 返回格式: {...} - const response = rawResponse.data || rawResponse + let response = this.extractResponse(rawResponse) + if (response?.scoreTable) { + response = response.scoreTable + } const transformedData: Record> = {} - // 确保 response 存在且是有效对象 if (!response || typeof response !== 'object' || Array.isArray(response)) { console.warn('[agentSelectModifyInit] 后端返回数据格式异常:', response) return transformedData } for (const [aspect, agents] of Object.entries(response)) { - for (const [agentName, scoreInfo] of Object.entries(agents as Record || {})) { + for (const [agentName, scoreInfo] of Object.entries( + (agents as Record) || {}, + )) { if (!transformedData[agentName]) { transformedData[agentName] = {} } @@ -770,7 +498,14 @@ class Api { */ agentSelectModifyAddAspect = async (data: { aspectList: string[] - useWebSocket?: boolean + stepTask?: { + Id?: string + StepName?: string + TaskContent?: string + InputObject_List?: string[] + OutputObject?: string + } + TaskID?: string onProgress?: (progress: { status: string stage?: string @@ -781,53 +516,33 @@ class Api { aspectName: string agentScores: Record }> => { - const useWs = data.useWebSocket !== undefined ? data.useWebSocket : this.useWebSocketDefault - let response: Record> + const rawResponse = await websocket.send( + 'agent_select_modify_add_aspect', + { + aspectList: data.aspectList, + stepTask: data.stepTask, + task_id: data.TaskID || '', + }, + undefined, + data.onProgress, + ) - // 如果启用WebSocket且已连接,使用WebSocket - if (useWs && websocket.connected) { - const rawResponse = await websocket.send( - 'agent_select_modify_add_aspect', - { - aspectList: data.aspectList, - }, - undefined, - data.onProgress, - ) - // WebSocket 返回格式: { data: {...}, generation_id, execution_id } - response = rawResponse.data || rawResponse - } else { - // 否则使用REST API - response = await request< - { - aspectList: string[] - }, - Record> - >({ - url: '/agentSelectModify_addAspect', - method: 'POST', - data: { - aspectList: data.aspectList, - }, - }) - } + const response = this.extractResponse(rawResponse) - /** - * 获取新添加的维度 - */ const newAspect = data.aspectList[data.aspectList.length - 1] if (!newAspect) { throw new Error('aspectList is empty') } - const newAspectAgents = response[newAspect] + const scoreTable = response.scoreTable || response + const newAspectAgents = scoreTable[newAspect] const agentScores: Record = {} if (newAspectAgents) { - for (const [agentName, scoreInfo] of Object.entries(newAspectAgents)) { + for (const [agentName, scoreInfo] of Object.entries(newAspectAgents as Record)) { agentScores[agentName] = { - score: scoreInfo.Score, - reason: scoreInfo.Reason, + score: scoreInfo.Score || scoreInfo.score || 0, + reason: scoreInfo.Reason || scoreInfo.reason || '', } } } @@ -867,12 +582,126 @@ class Api { })), }) - // WebSocket 返回格式: { data: {...}, generation_id, execution_id } - // REST API 返回格式: {...} - const response = (rawResponse.data || rawResponse) as { added_count: number } + const response = this.extractResponse<{ added_count: number }>(rawResponse) return response?.added_count || 0 } + + /** + * 保存任务分支数据 + * @param taskId 任务ID + * @param branches 分支数据数组 + * @returns 是否保存成功 + */ + saveBranches = async (taskId: string, branches: any[]): Promise => { + if (!websocket.connected) { + throw new Error('WebSocket未连接') + } + + try { + const rawResponse = await websocket.send('save_branches', { + task_id: taskId, + branches, + }) + + const response = this.extractResponse<{ status: string }>(rawResponse) + return response?.status === 'success' || false + } catch (error) { + console.error('保存分支数据失败:', error) + return false + } + } + + /** + * 保存任务过程分支数据 + * @param TaskID 大任务ID(数据库主键) + * @param branches 任务过程分支数据(Map结构转对象) + * @returns 是否保存成功 + */ + saveTaskProcessBranches = async ( + TaskID: string, + branches: Record>, + ): Promise => { + if (!websocket.connected) { + throw new Error('WebSocket未连接') + } + + try { + const rawResponse = await websocket.send('save_task_process_branches', { + task_id: TaskID, + branches, + }) + + const response = this.extractResponse<{ status: string }>(rawResponse) + return response?.status === 'success' || false + } catch (error) { + console.error('保存任务过程分支数据失败:', error) + return false + } + } + + /** + * 更新任务大纲数据 + * @param taskId 任务ID + * @param taskOutline 完整的大纲数据 + * @returns 是否更新成功 + */ + updateTaskOutline = async (taskId: string, taskOutline: any): Promise => { + if (!websocket.connected) { + throw new Error('WebSocket未连接') + } + + try { + const rawResponse = await websocket.send('save_task_outline', { + task_id: taskId, + task_outline: taskOutline, + }) + + const response = this.extractResponse<{ status: string }>(rawResponse) + return response?.status === 'success' || false + } catch (error) { + console.error('更新任务大纲失败:', error) + return false + } + } + + /** + * 更新指定步骤的 assigned_agents + * @param params 参数对象 + * @returns 是否更新成功 + */ + updateAssignedAgents = async (params: { + task_id: string // 大任务ID(数据库主键) + step_id: string // 步骤级ID(小任务UUID) + agents: string[] // 选中的 agent 列表 + confirmed_groups?: string[][] // 可选:确认的 agent 组合列表 + agent_combinations?: Record // 可选:agent 组合的 TaskProcess 数据 + }): Promise => { + if (!websocket.connected) { + throw new Error('WebSocket未连接') + } + + try { + const rawResponse = await websocket.send('update_assigned_agents', { + task_id: params.task_id, + step_id: params.step_id, + agents: params.agents, + confirmed_groups: params.confirmed_groups, + agent_combinations: params.agent_combinations, + }) + + const response = this.extractResponse<{ status: string; error?: string }>(rawResponse) + if (response?.status === 'success') { + console.log('更新 assigned_agents 成功:', params) + return true + } + console.warn('更新 assigned_agents 失败:', response?.error) + return false + } catch (error) { + console.error('更新 assigned_agents 失败:', error) + return false + } + } } export default new Api() diff --git a/frontend/src/layout/components/Main/Task.vue b/frontend/src/layout/components/Main/Task.vue index 8de04b5..f153d5a 100644 --- a/frontend/src/layout/components/Main/Task.vue +++ b/frontend/src/layout/components/Main/Task.vue @@ -1,13 +1,15 @@ diff --git a/frontend/src/layout/components/Main/TaskTemplate/AgentRepo/index.vue b/frontend/src/layout/components/Main/TaskTemplate/AgentRepo/index.vue index 15a524e..98ea993 100644 --- a/frontend/src/layout/components/Main/TaskTemplate/AgentRepo/index.vue +++ b/frontend/src/layout/components/Main/TaskTemplate/AgentRepo/index.vue @@ -36,6 +36,7 @@ const handleFileSelect = (event: Event) => { if (input.files && input.files[0]) { const file = input.files[0] readFileContent(file) + input.value = '' } } diff --git a/frontend/src/layout/components/Main/TaskTemplate/HistoryList/index.vue b/frontend/src/layout/components/Main/TaskTemplate/HistoryList/index.vue new file mode 100644 index 0000000..042bc9d --- /dev/null +++ b/frontend/src/layout/components/Main/TaskTemplate/HistoryList/index.vue @@ -0,0 +1,376 @@ + + + + + diff --git a/frontend/src/layout/components/Main/TaskTemplate/TaskProcess/components/PlanTask.vue b/frontend/src/layout/components/Main/TaskTemplate/TaskProcess/components/PlanTask.vue index 669d66d..8354008 100644 --- a/frontend/src/layout/components/Main/TaskTemplate/TaskProcess/components/PlanTask.vue +++ b/frontend/src/layout/components/Main/TaskTemplate/TaskProcess/components/PlanTask.vue @@ -7,6 +7,7 @@ import { useAgentsStore, useSelectionStore, type IRawStepTask, + type TaskProcess, type IApiAgentAction } from '@/stores' import { getAgentMapIcon, getActionTypeDisplay } from '@/layout/components/config.ts' @@ -32,9 +33,6 @@ const branchInputRef = ref>() // 分支加载状态 const branchLoading = ref(false) -// Mock 数据配置 -const USE_MOCK_DATA = false - // 节点和边数据 const nodes = ref([]) const edges = ref([]) @@ -51,6 +49,191 @@ const BRANCHES_INIT_KEY_PREFIX = 'plan-task-branches-initialized-' //最后选中的分支ID const LAST_SELECTED_BRANCH_KEY = 'plan-task-last-selected-branch' +// ==================== 公共函数提取 ==================== + +// 获取 agent 个人简介 +const getAgentProfile = (agentName: string): string => { + return ( + agentsStore.agents.find((agent: any) => agent.Name === agentName)?.Profile || '暂无个人简介' + ) +} + +// 创建 VueFlow 边的通用函数 +const createFlowEdge = ( + source: string, + target: string, + sourceHandle: string = 'right', + targetHandle: string = 'left', + strokeColor: string = '#43a8aa', + isFirstNode: boolean = false +): Edge => { + return { + id: `edge-${source}-${target}`, + source, + target, + sourceHandle, + targetHandle, + type: 'smoothstep', + animated: true, + style: { + stroke: strokeColor, + strokeWidth: 2, + ...(isFirstNode ? { strokeDasharray: '5,5' } : {}) + }, + markerEnd: { + type: 'arrow' as any, + color: '#43a8aa', + width: 20, + height: 20, + strokeWidth: 2 + } + } +} + +// 创建分支 agent 节点的通用函数 +const createBranchAgentNode = ( + agentNodeId: string, + action: IApiAgentAction, + position: { x: number; y: number } +): Node => { + const agentInfo = getAgentMapIcon(action.agent) + const actionTypeInfo = getActionTypeDisplay(action.type) + const agentProfile = getAgentProfile(action.agent) + + return { + id: agentNodeId, + type: 'agent', + position, + data: { + agentName: action.agent, + agentIcon: agentInfo.icon, + agentColor: agentInfo.color, + actionTypeColor: actionTypeInfo?.color || '#909399', + agentDescription: action.description || '暂无描述', + agentProfile: agentProfile, + actionTypeName: actionTypeInfo?.name || '未知职责', + actionTypeKey: actionTypeInfo?.key || 'unknown', + isRoot: false, + isBranchTask: true + } + } +} + +// 保存分支到 store 的通用函数 +const saveBranchToStore = ( + branchType: 'root' | 'task', + newBranchNodes: Node[], + newBranchEdges: Edge[], + branchTasks: any[] +) => { + if (newBranchNodes.length === 0) return + + const taskStepId = currentTask.value?.Id || '' + const currentAgents = currentTask.value?.AgentSelection || [] + + isSyncing = true + selectionStore.addTaskProcessBranch(taskStepId, currentAgents, { + parentNodeId: addingBranchNodeId.value!, + branchContent: branchInput.value.trim(), + branchType, + nodes: JSON.parse(JSON.stringify(newBranchNodes)), + edges: JSON.parse(JSON.stringify(newBranchEdges)), + tasks: JSON.parse(JSON.stringify(branchTasks)) + }) + + setTimeout(() => { + isSyncing = false + }, 100) + + saveTaskProcessBranchesToDB() +} + +// 解析分支 API 响应的通用函数 +const parseBranchResponse = (response: any): IApiAgentAction[] => { + const newAgentActions: IApiAgentAction[] = [] + const responseData = response.data || response + + if (responseData && responseData.length > 0) { + const firstBranch = responseData[0] + firstBranch.forEach((action: any) => { + newAgentActions.push({ + id: action.ID || uuidv4(), + type: action.ActionType, + agent: action.AgentName, + description: action.Description, + inputs: action.ImportantInput || [] + }) + }) + } + + return newAgentActions +} + +// 获取主流程节点 ID 列表的通用函数 +const getMainProcessNodeIds = (nodeList?: Node[]): string[] => { + const targetNodes = nodeList || nodes.value + return targetNodes.filter(n => !n.data.isBranchTask && n.id !== 'root').map(n => n.id) +} + +// 设置主流程高亮的通用函数 +const highlightMainProcess = (nodeList?: Node[]) => { + const mainProcessNodes = getMainProcessNodeIds(nodeList) + selectedNodeIds.value = new Set(mainProcessNodes) +} + +// 创建分支节点和边的通用函数 +const createBranchNodesAndEdges = ( + newAgentActions: IApiAgentAction[], + branchStartX: number, + branchStartY: number, + parentNodeId: string, + strokeColor: string = '#67c23a' +): { nodes: Node[]; edges: Edge[] } => { + const newBranchNodes: Node[] = [] + const newBranchEdges: Edge[] = [] + const timestamp = Date.now() + const agentNodeIds: string[] = [] + + newAgentActions.forEach((action, index) => { + const agentNodeId = `branch-agent-${parentNodeId}-${timestamp}-${index}` + agentNodeIds.push(agentNodeId) + + // 计算位置:横向排列 + const nodeX = branchStartX + (parentNodeId === 'root' ? 100 : 0) + index * 120 + const nodeY = branchStartY + + // 创建 agent 节点 + const newAgentNode = createBranchAgentNode(agentNodeId, action, { x: nodeX, y: nodeY }) + nodes.value.push(newAgentNode) + newBranchNodes.push(newAgentNode) + + // 创建连接边 + if (index === 0) { + // 第一个 agent 连接到父节点 + const newEdge = createFlowEdge(parentNodeId, agentNodeId, 'bottom', 'left', strokeColor, true) + edges.value.push(newEdge) + newBranchEdges.push(newEdge) + } else { + // 后续 agent 连接到前一个 agent + const prevAgentNodeId = agentNodeIds[index - 1]! + const newEdge = createFlowEdge( + prevAgentNodeId, + agentNodeId, + 'right', + 'left', + strokeColor, + false + ) + edges.value.push(newEdge) + newBranchEdges.push(newEdge) + } + }) + + return { nodes: newBranchNodes, edges: newBranchEdges } +} + +// ==================== 原有函数保持不变 ==================== + // 获取分支的所有节点 const getAllBranchNodes = (startNodeId: string): string[] => { const visited = new Set() @@ -272,14 +455,44 @@ const initializeFlow = () => { )}` const branchesInitialized = sessionStorage.getItem(branchesInitKey) === 'true' - if (branchesInitialized && savedBranches.length > 0) { - nodes.value = [] - edges.value = [] + // 【修复】优先检查 store 中是否有数据,而不是依赖 sessionStorage 标记 + // 原逻辑:if (branchesInitialized && savedBranches.length > 0) + // 问题:从历史记录恢复时,store 有数据但 sessionStorage 无标记,导致重复创建"初始流程"分支 + if (savedBranches.length > 0) { + // 先创建根节点和主流程节点(作为分支的基础) + nodes.value = newNodes + edges.value = newEdges - savedBranches.forEach(branch => { - // 恢复节点 - branch.nodes.forEach(node => { - nodes.value.push(JSON.parse(JSON.stringify(node))) + // 找出非初始流程的分支(只处理真正需要偏移的分支) + const actualBranches = savedBranches.filter(branch => branch.branchContent !== '初始流程') + + // 统计每个父节点下有多少个分支,用于计算垂直偏移 + const parentBranchIndex: Record = {} + actualBranches.forEach(branch => { + parentBranchIndex[branch.parentNodeId] = parentBranchIndex[branch.parentNodeId] || 0 + }) + + // 恢复分支时,按顺序处理,同一父节点的分支依次向下偏移 + actualBranches.forEach(branch => { + const parentNodeId = branch.parentNodeId + + // 获取父节点的Y坐标 + const parentNode = nodes.value.find(n => n.id === parentNodeId) + const parentNodeY = parentNode?.position.y || 150 + + // 获取该分支在同一父节点下的索引 + const branchIndex = parentBranchIndex[parentNodeId] ?? 0 + parentBranchIndex[parentNodeId] = branchIndex + 1 + + // 计算分支起始Y坐标:父节点Y + 200 + 分支索引 * 250 + const branchStartY = parentNodeY + 200 + branchIndex * 250 + + // 恢复节点(计算新的Y坐标) + branch.nodes.forEach((node, nodeIndex) => { + const restoredNode = JSON.parse(JSON.stringify(node)) + // 计算新位置:分支起始Y + 节点索引 * 120(横向排列) + restoredNode.position.y = branchStartY + nodes.value.push(restoredNode) }) // 恢复边 branch.edges.forEach(edge => { @@ -287,6 +500,11 @@ const initializeFlow = () => { }) }) + // 【修复】补充标记已初始化,避免后续重复创建"初始流程"分支 + if (!branchesInitialized) { + sessionStorage.setItem(branchesInitKey, 'true') + } + // 恢复最后选中的分支 const lastSelectedBranchId = sessionStorage.getItem(LAST_SELECTED_BRANCH_KEY) if (lastSelectedBranchId) { @@ -297,9 +515,7 @@ const initializeFlow = () => { if (lastSelectedBranch.branchContent === '初始流程') { // 初始流程:高亮所有主流程节点(从恢复的nodes中获取) - nodesToHighlight = nodes.value - .filter(n => !n.data.isBranchTask && n.id !== 'root') - .map(n => n.id) + nodesToHighlight = getMainProcessNodeIds() } else { // 其他分支:高亮主流程路径 + 分支节点(支持多级分支) const firstBranchNode = lastSelectedBranch.nodes[0] @@ -311,7 +527,7 @@ const initializeFlow = () => { let currentNode = nodes.value.find(n => n.id === incomingEdge.source) while (currentNode && currentNode.id !== 'root') { nodesToHighlight.unshift(currentNode.id) - const prevEdge = edges.value.find(e => e.target === currentNode.id) + const prevEdge = edges.value.find(e => e.target === currentNode?.id) currentNode = prevEdge ? nodes.value.find(n => n.id === prevEdge.source) : undefined @@ -330,17 +546,11 @@ const initializeFlow = () => { selectedNodeIds.value = new Set(nodesToHighlight) } else { // 找不到最后选中的分支,默认选中初始流程的高亮状态 - const mainProcessNodes = nodes.value - .filter(n => !n.data.isBranchTask && n.id !== 'root') - .map(n => n.id) - selectedNodeIds.value = new Set(mainProcessNodes) + highlightMainProcess() } } else { // 没有保存的选中分支,默认选中初始流程的高亮状态 - const mainProcessNodes = nodes.value - .filter(n => !n.data.isBranchTask && n.id !== 'root') - .map(n => n.id) - selectedNodeIds.value = new Set(mainProcessNodes) + highlightMainProcess() } } else { // 首次初始化:设置节点和边,并保存为"初始流程"分支 @@ -362,14 +572,14 @@ const initializeFlow = () => { // 标记已初始化(针对该任务步骤和 agent 组合) sessionStorage.setItem(branchesInitKey, 'true') + // 保存任务过程分支到数据库 + saveTaskProcessBranchesToDB() + // 首次初始化时,设置初始流程为当前选中分支 selectionStore.setActiveTaskProcessBranch(taskStepId, currentAgents, initialBranchId) // 默认选中"初始流程"的高亮状态 - const mainProcessNodes = newNodes - .filter(n => !n.data.isBranchTask && n.id !== 'root') - .map(n => n.id) - selectedNodeIds.value = new Set(mainProcessNodes) + highlightMainProcess(newNodes) } } } @@ -426,7 +636,7 @@ const onNodeClick = (event: any) => { let topBranchNodeId: string | null = null if (branchParentChain.length > 0) { // 取父节点链的最后一个(最顶层的分支节点) - topBranchNodeId = branchParentChain[branchParentChain.length - 1] + topBranchNodeId = branchParentChain[branchParentChain.length - 1]! } else { // 如果没有分支父节点,当前节点就是最顶层 topBranchNodeId = nodeId @@ -460,8 +670,12 @@ const onNodeClick = (event: any) => { if (currentTask.value) { const completeTaskProcess: any[] = [] + // 类型守卫:检查是否为 TaskProcess 类型(有 Description 字段) + const isTaskProcess = (data: any): data is TaskProcess => + data && 'Description' in data + // 从 store 中获取"初始流程"副本(而不是使用 taskProcess.value) - const taskStepId = currentTask.value.Id + const taskStepId = currentTask.value.Id! const currentAgents = currentTask.value.AgentSelection || [] const branches = selectionStore.getTaskProcessBranches(taskStepId, currentAgents) const initialBranch = branches.find(branch => branch.branchContent === '初始流程') @@ -478,7 +692,7 @@ const onNodeClick = (event: any) => { // 主流程节点:从初始流程数据中获取 const originalIndex = node.data.originalIndex const processData = mainProcessData[originalIndex] - if (processData && processData.ID && processData.AgentName && processData.Description) { + if (processData && isTaskProcess(processData) && processData.ID && processData.AgentName && processData.Description) { completeTaskProcess.push(processData) } } else if (node.data.isBranchTask) { @@ -490,7 +704,7 @@ const onNodeClick = (event: any) => { const nodeIndex = parentBranch.nodes.findIndex(n => n.id === nodeId) if (nodeIndex !== -1 && parentBranch.tasks[nodeIndex]) { const taskData = parentBranch.tasks[nodeIndex] - if (taskData.ID && taskData.AgentName && taskData.Description) { + if (isTaskProcess(taskData) && taskData.ID && taskData.AgentName && taskData.Description) { completeTaskProcess.push(taskData) } } @@ -508,7 +722,7 @@ const onNodeClick = (event: any) => { } selectionStore.setActiveTaskProcessData( - currentTask.value.Id, + taskStepId, currentAgents, completeTaskProcess ) @@ -521,11 +735,7 @@ const onNodeClick = (event: any) => { } } else { // 点击的是主流程节点,高亮所有主流程节点(初始流程) - const mainProcessNodes = nodes.value - .filter(n => !n.data.isBranchTask && n.id !== 'root') - .map(n => n.id) - - selectedNodeIds.value = new Set(mainProcessNodes) + highlightMainProcess() // 点击主流程节点时,从 store 读取"初始流程"分支的副本 if (currentTask.value) { @@ -542,7 +752,7 @@ const onNodeClick = (event: any) => { selectionStore.setActiveTaskProcessData(taskStepId, currentAgents, initialBranch.tasks) // 同步更新 currentTask.TaskProcess(实现全局数据联动) - agentsStore.setCurrentTaskProcess(initialBranch.tasks) + agentsStore.setCurrentTaskProcess(initialBranch.tasks as unknown as TaskProcess[]) // 保存选中的分支ID到 sessionStorage sessionStorage.setItem(LAST_SELECTED_BRANCH_KEY, initialBranch.id) @@ -626,181 +836,48 @@ const submitBranch = async () => { // 判断是根节点还是 agent 节点 if (parentNodeId === 'root') { // 根节点分支 - let newAgentActions: IApiAgentAction[] = [] - if (USE_MOCK_DATA) { - // 使用 Mock API - const generalGoal = agentsStore.agentRawPlan.data?.['General Goal'] || '' + // 调用真实 API + const generalGoal = agentsStore.agentRawPlan.data?.['General Goal'] || '' - // 根节点分支:从零开始生成完整方案 - // Baseline_Completion = 0 表示没有已完成的部分,需要生成所有阶段 - // Existing_Steps 传空数组,不传递初始流程信息 - const response = await api.mockBranchTaskProcess({ - branch_Number: 1, - Modification_Requirement: branchContent, - Existing_Steps: [], // ← 根节点分支不传递现有步骤 - Baseline_Completion: 0, // ← 从零开始 - stepTaskExisting: currentTask.value, - goal: generalGoal - }) + // 根节点分支:从零开始生成完整方案 + // Baseline_Completion = 0 表示没有已完成的部分,需要生成所有阶段 + // Existing_Steps 传空数组,不传递初始流程信息 + const response = await api.branchTaskProcess({ + branch_Number: 1, + Modification_Requirement: branchContent, + Existing_Steps: [], // ← 根节点分支不传递现有步骤 + Baseline_Completion: 0, // ← 从零开始 + stepTaskExisting: currentTask.value, + goal: generalGoal + }) - // 后端返回格式: [[action1, action2], [action3, action4]] - // 取第一个分支 - if (response && response.length > 0) { - const firstBranch = response[0] + // WebSocket 返回格式: { data: [[action1, action2], [action3, action4]], ... } + // REST API 返回格式: [[action1, action2], [action3, action4]] + // 使用通用函数解析 API 响应 + const newAgentActions = parseBranchResponse(response) - // 直接遍历 action 数组 - firstBranch.forEach((action: any) => { - // 直接使用接口返回的 ActionType - newAgentActions.push({ - id: action.ID || uuidv4(), - type: action.ActionType, - agent: action.AgentName, - description: action.Description, - inputs: action.ImportantInput || [] - }) - }) - } - - ElMessage.success('[Mock] 任务流程分支创建成功') - } else { - // 调用真实 API - const generalGoal = agentsStore.agentRawPlan.data?.['General Goal'] || '' - - // 根节点分支:从零开始生成完整方案 - // Baseline_Completion = 0 表示没有已完成的部分,需要生成所有阶段 - // Existing_Steps 传空数组,不传递初始流程信息 - const response = await api.branchTaskProcess({ - branch_Number: 1, - Modification_Requirement: branchContent, - Existing_Steps: [], // ← 根节点分支不传递现有步骤 - Baseline_Completion: 0, // ← 从零开始 - stepTaskExisting: currentTask.value, - goal: generalGoal - }) - - // WebSocket 返回格式: { data: [[action1, action2], [action3, action4]], ... } - // REST API 返回格式: [[action1, action2], [action3, action4]] - const responseData = response.data || response - // 后端返回格式: [[action1, action2], [action3, action4]] - // 取第一个分支 - if (responseData && responseData.length > 0) { - const firstBranch = responseData[0] - - // 直接遍历 action 数组 - firstBranch.forEach((action: any) => { - // 直接使用接口返回的 ActionType - newAgentActions.push({ - id: action.ID || uuidv4(), - type: action.ActionType, - agent: action.AgentName, - description: action.Description, - inputs: action.ImportantInput || [] - }) - }) - } - - ElMessage.success('任务流程分支创建成功') - } + ElMessage.success('任务流程分支创建成功') // 创建新的 agent 节点 if (newAgentActions.length > 0) { // 计算分支起始位置:在根节点下方(固定位置) const branchStartX = parentNode.position.x - const branchStartY = parentNode.position.y + 200 // 固定位置:父节点下方200px - const timestamp = Date.now() - const agentNodeIds: string[] = [] + const branchStartY = parentNode.position.y + 200 - // 为每个动作创建一个 agent 节点 - newAgentActions.forEach((action, index) => { - const agentNodeId = `branch-agent-${parentNodeId}-${timestamp}-${index}` - agentNodeIds.push(agentNodeId) - - const agentInfo = getAgentMapIcon(action.agent) - const actionTypeInfo = getActionTypeDisplay(action.type) - - // 从 agentsStore 中获取 agent 的个人简介 - const agentProfile = - agentsStore.agents.find((agent: any) => agent.Name === action.agent)?.Profile || - '暂无个人简介' - - // 计算位置:横向排列 - const nodeX = branchStartX + 100 + index * 120 - const nodeY = branchStartY - - // 创建 agent 节点 - const newAgentNode: Node = { - id: agentNodeId, - type: 'agent', - position: { x: nodeX, y: nodeY }, - data: { - agentName: action.agent, - agentIcon: agentInfo.icon, - agentColor: agentInfo.color, - actionTypeColor: actionTypeInfo?.color || '#909399', - agentDescription: action.description || '暂无描述', - agentProfile: agentProfile, - actionTypeName: actionTypeInfo?.name || '未知职责', - actionTypeKey: actionTypeInfo?.key || 'unknown', - isRoot: false, - isBranchTask: true // 标记为分支任务 - } - } - - nodes.value.push(newAgentNode) - newBranchNodes.push(newAgentNode) - - // 创建连接边 - if (index === 0) { - // 第一个 agent 连接到父节点(根节点) - const newEdge: Edge = { - id: `edge-${parentNodeId}-${agentNodeId}`, - source: parentNodeId, - target: agentNodeId, - sourceHandle: 'bottom', - targetHandle: 'left', - type: 'smoothstep', - animated: true, - style: { stroke: '#67c23a', strokeWidth: 2, strokeDasharray: '5,5' }, - markerEnd: { - type: 'arrow' as any, - color: '#43a8aa', - width: 20, - height: 20, - strokeWidth: 2 - } - } - edges.value.push(newEdge) - newBranchEdges.push(newEdge) - } else { - // 后续 agent 连接到前一个 agent - const prevAgentNodeId = agentNodeIds[index - 1] - const newEdge: Edge = { - id: `edge-${prevAgentNodeId}-${agentNodeId}`, - source: prevAgentNodeId, - target: agentNodeId, - sourceHandle: 'right', - targetHandle: 'left', - type: 'smoothstep', - animated: true, - style: { stroke: '#67c23a', strokeWidth: 2 }, - markerEnd: { - type: 'arrow' as any, - color: '#43a8aa', - width: 20, - height: 20, - strokeWidth: 2 - } - } - edges.value.push(newEdge) - newBranchEdges.push(newEdge) - } - }) + // 使用通用函数创建分支节点和边 + const result = createBranchNodesAndEdges( + newAgentActions, + branchStartX, + branchStartY, + parentNodeId, + '#67c23a' // 根节点分支颜色 + ) + newBranchNodes.push(...result.nodes) + newBranchEdges.push(...result.edges) // 保存分支数据到 store if (newBranchNodes.length > 0) { - // 将 IApiAgentAction 转换为 TaskProcess 格式用于存储 - // 与 fill-step-task-mock.ts 中的 TaskProcess 格式保持一致 const branchTasks = newAgentActions.map(action => ({ ID: action.id || uuidv4(), ActionType: action.type, @@ -809,291 +886,94 @@ const submitBranch = async () => { ImportantInput: action.inputs || [] })) - // 使用任务过程分支存储 - // 注意:需要对 nodes 和 edges 进行深拷贝,避免保存响应式引用 - const taskStepId = currentTask.value?.Id || '' - const currentAgents = currentTask.value?.AgentSelection || [] - isSyncing = true // 设置标志,避免 watch 触发重复同步 - selectionStore.addTaskProcessBranch(taskStepId, currentAgents, { - parentNodeId: parentNodeId, - branchContent: branchContent, - branchType: 'root', - nodes: JSON.parse(JSON.stringify(newBranchNodes)), - edges: JSON.parse(JSON.stringify(newBranchEdges)), - tasks: JSON.parse(JSON.stringify(branchTasks)) - }) - setTimeout(() => { - isSyncing = false - }, 100) + saveBranchToStore('root', newBranchNodes, newBranchEdges, branchTasks) } } } else { // Agent 节点分支 const parentIsBranchTask = parentNode.data.isBranchTask || false const parentOriginalIndex = parentNode.data.originalIndex ?? 0 - let newAgentActions: IApiAgentAction[] = [] - if (USE_MOCK_DATA) { - // 使用 Mock API - const generalGoal = agentsStore.agentRawPlan.data?.['General Goal'] || '' - const currentTaskProcess = taskProcess.value || [] + // 调用真实 API + const generalGoal = agentsStore.agentRawPlan.data?.['General Goal'] || '' + const currentTaskProcess = taskProcess.value || [] - // 根据父节点类型构建 existingSteps - let existingSteps: any[] = [] - let baselineCompletion = 100 + // 根据父节点类型构建 existingSteps + let existingSteps: any[] = [] + let baselineCompletion = 100 - if (!parentIsBranchTask) { - // 父节点是主流程节点:传递主流程从 0 到父节点的步骤 - baselineCompletion = - currentTaskProcess.length > 0 - ? Math.round(((parentOriginalIndex + 1) / currentTaskProcess.length) * 100) - : 100 + if (!parentIsBranchTask) { + // 父节点是主流程节点:传递主流程从 0 到父节点的步骤 + baselineCompletion = + currentTaskProcess.length > 0 + ? Math.round(((parentOriginalIndex + 1) / currentTaskProcess.length) * 100) + : 100 - existingSteps = currentTaskProcess.slice(0, parentOriginalIndex + 1).map((p: any) => ({ - ID: p.ID, - ActionType: p.ActionType, - AgentName: p.AgentName, - Description: p.Description, - ImportantInput: p.ImportantInput || [] - })) - } else { - // 父节点是分支节点:从分支数据中获取步骤 - const taskStepId = currentTask.value?.Id || '' - const currentAgents = currentTask.value?.AgentSelection || [] - const branches = selectionStore.getTaskProcessBranches(taskStepId, currentAgents) - - // 找到父节点所属的分支 - const parentBranch = branches.find(branch => - branch.nodes.some(n => n.id === parentNode.id) - ) - - if (parentBranch && parentBranch.tasks) { - // 获取分支中从第一个节点到父节点的所有步骤 - const parentIndexInBranch = parentBranch.nodes.findIndex(n => n.id === parentNode.id) - existingSteps = parentBranch.tasks.slice(0, parentIndexInBranch + 1).map((p: any) => ({ - ID: p.ID, - ActionType: p.ActionType, - AgentName: p.AgentName, - Description: p.Description, - ImportantInput: p.ImportantInput || [] - })) - - // 分支节点的基线完成度:根据主流程进度计算 - baselineCompletion = - currentTaskProcess.length > 0 - ? Math.round(((parentOriginalIndex + 1) / currentTaskProcess.length) * 100) - : 100 - } - } - - // 调用 Mock API - const response = await api.mockBranchTaskProcess({ - branch_Number: 1, - Modification_Requirement: branchContent, - Existing_Steps: existingSteps, - Baseline_Completion: baselineCompletion, - stepTaskExisting: currentTask.value, - goal: generalGoal - }) - - // 后端返回格式: [[action1, action2], [action3, action4]] - // 取第一个分支 - if (response && response.length > 0) { - const firstBranch = response[0] - - // 直接遍历 action 数组 - firstBranch.forEach((action: any) => { - // 直接使用接口返回的 ActionType - newAgentActions.push({ - id: action.ID || uuidv4(), - type: action.ActionType, - agent: action.AgentName, - description: action.Description, - inputs: action.ImportantInput || [] - }) - }) - } - - ElMessage.success('[Mock] 任务流程分支创建成功') + existingSteps = currentTaskProcess.slice(0, parentOriginalIndex + 1).map((p: any) => ({ + ID: p.ID, + ActionType: p.ActionType, + AgentName: p.AgentName, + Description: p.Description, + ImportantInput: p.ImportantInput || [] + })) } else { - // 调用真实 API - const generalGoal = agentsStore.agentRawPlan.data?.['General Goal'] || '' - const currentTaskProcess = taskProcess.value || [] + // 父节点是分支节点:从分支数据中获取步骤 + const taskStepId = currentTask.value?.Id || '' + const currentAgents = currentTask.value?.AgentSelection || [] + const branches = selectionStore.getTaskProcessBranches(taskStepId, currentAgents) - // 根据父节点类型构建 existingSteps - let existingSteps: any[] = [] - let baselineCompletion = 100 + // 找到父节点所属的分支 + const parentBranch = branches.find(branch => branch.nodes.some(n => n.id === parentNode.id)) - if (!parentIsBranchTask) { - // 父节点是主流程节点:传递主流程从 0 到父节点的步骤 - baselineCompletion = - currentTaskProcess.length > 0 - ? Math.round(((parentOriginalIndex + 1) / currentTaskProcess.length) * 100) - : 100 - - existingSteps = currentTaskProcess.slice(0, parentOriginalIndex + 1).map((p: any) => ({ + if (parentBranch && parentBranch.tasks) { + // 获取分支中从第一个节点到父节点的所有步骤 + const parentIndexInBranch = parentBranch.nodes.findIndex(n => n.id === parentNode.id) + existingSteps = parentBranch.tasks.slice(0, parentIndexInBranch + 1).map((p: any) => ({ ID: p.ID, ActionType: p.ActionType, AgentName: p.AgentName, Description: p.Description, ImportantInput: p.ImportantInput || [] })) - } else { - // 父节点是分支节点:从分支数据中获取步骤 - const taskStepId = currentTask.value?.Id || '' - const currentAgents = currentTask.value?.AgentSelection || [] - const branches = selectionStore.getTaskProcessBranches(taskStepId, currentAgents) - // 找到父节点所属的分支 - const parentBranch = branches.find(branch => - branch.nodes.some(n => n.id === parentNode.id) - ) - - if (parentBranch && parentBranch.tasks) { - // 获取分支中从第一个节点到父节点的所有步骤 - const parentIndexInBranch = parentBranch.nodes.findIndex(n => n.id === parentNode.id) - existingSteps = parentBranch.tasks.slice(0, parentIndexInBranch + 1).map((p: any) => ({ - ID: p.ID, - ActionType: p.ActionType, - AgentName: p.AgentName, - Description: p.Description, - ImportantInput: p.ImportantInput || [] - })) - - // 分支节点的基线完成度:根据主流程进度计算 - baselineCompletion = - currentTaskProcess.length > 0 - ? Math.round(((parentOriginalIndex + 1) / currentTaskProcess.length) * 100) - : 100 - } + // 分支节点的基线完成度:根据主流程进度计算 + baselineCompletion = + currentTaskProcess.length > 0 + ? Math.round(((parentOriginalIndex + 1) / currentTaskProcess.length) * 100) + : 100 } - - const response = await api.branchTaskProcess({ - branch_Number: 1, - Modification_Requirement: branchContent, - Existing_Steps: existingSteps, - Baseline_Completion: baselineCompletion, - stepTaskExisting: currentTask.value, - goal: generalGoal - }) - - // WebSocket 返回格式: { data: [[action1, action2], [action3, action4]], ... } - // REST API 返回格式: [[action1, action2], [action3, action4]] - const responseData = response.data || response - // 后端返回格式: [[action1, action2], [action3, action4]] - // 取第一个分支 - if (responseData && responseData.length > 0) { - const firstBranch = responseData[0] - - // 直接遍历 action 数组 - firstBranch.forEach((action: any) => { - // 直接使用接口返回的 ActionType - newAgentActions.push({ - id: action.ID || uuidv4(), - type: action.ActionType, - agent: action.AgentName, - description: action.Description, - inputs: action.ImportantInput || [] - }) - }) - } - - ElMessage.success('任务流程分支创建成功') } + const response = await api.branchTaskProcess({ + branch_Number: 1, + Modification_Requirement: branchContent, + Existing_Steps: existingSteps, + Baseline_Completion: baselineCompletion, + stepTaskExisting: currentTask.value, + goal: generalGoal + }) + + // 使用通用函数解析 API 响应 + const newAgentActions = parseBranchResponse(response) + + ElMessage.success('任务流程分支创建成功') + // 创建新的 agent 节点 if (newAgentActions.length > 0) { // 计算分支起始位置:在父 agent 节点右下方(固定位置) const branchStartX = parentNode.position.x + 150 - const branchStartY = parentNode.position.y + 200 // 固定位置:父节点下方200px - const timestamp = Date.now() - const agentNodeIds: string[] = [] + const branchStartY = parentNode.position.y + 200 - // 为每个动作创建一个 agent 节点 - newAgentActions.forEach((action, index) => { - const agentNodeId = `branch-agent-${parentNodeId}-${timestamp}-${index}` - agentNodeIds.push(agentNodeId) - - const agentInfo = getAgentMapIcon(action.agent) - const actionTypeInfo = getActionTypeDisplay(action.type) - - // 从 agentsStore 中获取 agent 的个人简介 - const agentProfile = - agentsStore.agents.find((agent: any) => agent.Name === action.agent)?.Profile || - '暂无个人简介' - - // 计算位置:横向排列 - const nodeX = branchStartX + index * 120 - const nodeY = branchStartY - - // 创建 agent 节点 - const newAgentNode: Node = { - id: agentNodeId, - type: 'agent', - position: { x: nodeX, y: nodeY }, - data: { - agentName: action.agent, - agentIcon: agentInfo.icon, - agentColor: agentInfo.color, - actionTypeColor: actionTypeInfo?.color || '#909399', - agentDescription: action.description || '暂无描述', - agentProfile: agentProfile, - actionTypeName: actionTypeInfo?.name || '未知职责', - actionTypeKey: actionTypeInfo?.key || 'unknown', - isRoot: false, - isBranchTask: true // 标记为分支任务 - } - } - - nodes.value.push(newAgentNode) - newBranchNodes.push(newAgentNode) - - // 创建连接边 - if (index === 0) { - // 第一个 agent 连接到父节点 - const newEdge: Edge = { - id: `edge-${parentNodeId}-${agentNodeId}`, - source: parentNodeId, - target: agentNodeId, - sourceHandle: 'bottom', - targetHandle: 'left', - type: 'smoothstep', - animated: true, - style: { stroke: '#409eff', strokeWidth: 2, strokeDasharray: '5,5' }, - markerEnd: { - type: 'arrow' as any, - color: '#43a8aa', - width: 20, - height: 20, - strokeWidth: 2 - } - } - edges.value.push(newEdge) - newBranchEdges.push(newEdge) - } else { - // 后续 agent 连接到前一个 agent - const prevAgentNodeId = agentNodeIds[index - 1] - const newEdge: Edge = { - id: `edge-${prevAgentNodeId}-${agentNodeId}`, - source: prevAgentNodeId, - target: agentNodeId, - sourceHandle: 'right', - targetHandle: 'left', - type: 'smoothstep', - animated: true, - style: { stroke: '#409eff', strokeWidth: 2 }, - markerEnd: { - type: 'arrow' as any, - color: '#43a8aa', - width: 20, - height: 20, - strokeWidth: 2 - } - } - edges.value.push(newEdge) - newBranchEdges.push(newEdge) - } - }) + // 使用通用函数创建分支节点和边 + const result = createBranchNodesAndEdges( + newAgentActions, + branchStartX, + branchStartY, + parentNodeId, + '#409eff' // Agent 节点分支颜色 + ) + newBranchNodes.push(...result.nodes) + newBranchEdges.push(...result.edges) // 保存分支数据到 store if (newBranchNodes.length > 0) { @@ -1105,22 +985,7 @@ const submitBranch = async () => { ImportantInput: action.inputs || [] })) - // 使用任务过程分支存储 - // 注意:需要对 nodes 和 edges 进行深拷贝,避免保存响应式引用 - const taskStepId = currentTask.value?.Id || '' - const currentAgents = currentTask.value?.AgentSelection || [] - isSyncing = true // 设置标志,避免 watch 触发重复同步 - selectionStore.addTaskProcessBranch(taskStepId, currentAgents, { - parentNodeId: parentNodeId, - branchContent: branchContent, - branchType: 'task', - nodes: JSON.parse(JSON.stringify(newBranchNodes)), - edges: JSON.parse(JSON.stringify(newBranchEdges)), - tasks: JSON.parse(JSON.stringify(branchTasks)) - }) - setTimeout(() => { - isSyncing = false - }, 100) + saveBranchToStore('task', newBranchNodes, newBranchEdges, branchTasks) } } } @@ -1154,6 +1019,20 @@ const handleBranchKeydown = (event: KeyboardEvent) => { } onConnect(params => addEdges(params)) + +// 保存任务过程分支到数据库 +const saveTaskProcessBranchesToDB = async () => { + const TaskID = (window as any).__CURRENT_TASK_ID__ + if (TaskID) { + try { + await selectionStore.saveTaskProcessBranchesToDB(TaskID) + } catch (error) { + console.error('保存任务过程分支数据失败:', error) + } + } else { + console.warn('[saveTaskProcessBranchesToDB] 未找到 TaskID,跳过保存') + } +}