From a4b94f43c15762bd119a40a35b379fcaa16bdaa9 Mon Sep 17 00:00:00 2001 From: liailing1026 <1815388873@qq.com> Date: Mon, 16 Mar 2026 14:37:14 +0800 Subject: [PATCH] =?UTF-8?q?feat:=E4=BF=AE=E5=A4=8D=E6=89=A7=E8=A1=8C?= =?UTF-8?q?=E8=BF=87=E7=A8=8B=E7=BC=96=E6=8E=92=E4=B8=AD=E5=88=9D=E5=A7=8B?= =?UTF-8?q?=E5=88=86=E6=94=AF=E5=88=A0=E9=99=A4=E5=8D=95=E4=B8=AA=E8=8A=82?= =?UTF-8?q?=E7=82=B9bug?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/server.py | 35 +- .../TaskProcess/components/PlanTask.vue | 328 +++++++++++++++++- frontend/src/stores/modules/selection.ts | 13 +- 3 files changed, 354 insertions(+), 22 deletions(-) diff --git a/backend/server.py b/backend/server.py index 3704818..ca23f28 100644 --- a/backend/server.py +++ b/backend/server.py @@ -2807,14 +2807,39 @@ def handle_delete_task_process_node(data): nodes = branch.get('nodes', []) tasks = branch.get('tasks', []) - # 找到并删除节点 - for i, node in enumerate(nodes): + # 找到要删除的节点 + node_to_delete = None + for node in nodes: if node.get('id') == node_id: - nodes.pop(i) - if i < len(tasks): - tasks.pop(i) + node_to_delete = node break + if node_to_delete: + # 从 nodes 中删除 + nodes = [n for n in nodes if n.get('id') != node_id] + + # 从 tasks 中删除:使用 originalIndex 精确匹配 + # nodes 包含 root 节点,所以 agent 节点的 originalIndex 对应 tasks 的索引 + node_data = node_to_delete.get('data', {}) + original_index = node_data.get('originalIndex') + + if original_index is not None and 0 <= original_index < len(tasks): + tasks.pop(original_index) + else: + # 降级方案:使用 agentName + actionType 匹配 + agent_name = node_data.get('agentName') + action_type_key = node_data.get('actionTypeKey', '').lower() + action_type_map = { + 'propose': 'Propose', + 'review': 'Critique', + 'improve': 'Improve', + 'summary': 'Finalize' + } + target_action_type = action_type_map.get(action_type_key, action_type_key) + tasks = [t for t in tasks + if not (t.get('AgentName') == agent_name + and t.get('ActionType') == target_action_type)] + # 更新分支数据(包括 nodes, tasks, edges) branch['nodes'] = nodes branch['tasks'] = tasks 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 d60cf43..8c6d681 100644 --- a/frontend/src/layout/components/Main/TaskTemplate/TaskProcess/components/PlanTask.vue +++ b/frontend/src/layout/components/Main/TaskTemplate/TaskProcess/components/PlanTask.vue @@ -409,11 +409,14 @@ const getNodeDeletable = ( // 获取所有分支数据 const savedBranches = selectionStore.getTaskProcessBranches(taskStepId, currentAgents) - // 遍历所有分支,用节点 id 在 nodes 数组中查找 + // 遍历所有分支,查找该节点是否属于某个分支的第一个智能体节点 for (const branch of savedBranches) { - // 检查该节点是否是该分支的第一个节点 - const firstNode = branch.nodes?.[0] - if (firstNode && firstNode.id === nodeId) { + // 找到从 parentNodeId 出发的第一个智能体节点 + // 遍历分支的所有边,找到从 parentNodeId 出来的边连接的第一个节点 + const firstEdge = branch.edges?.find((edge: any) => edge.source === branch.parentNodeId) + const firstNodeId = firstEdge?.target + + if (firstNodeId && firstNodeId === nodeId) { // 只有该分支的第一个节点才显示删除分支按钮 return { isDeletable: true, @@ -452,13 +455,6 @@ const handleDeleteBranch = (nodeId: string) => { ElMessage.error('未找到该分支') return } - - console.log( - '[删除分支] 找到的 branchId:', - targetBranch.id, - 'parentNodeId:', - nodeInfo.parentNodeId - ) branchIdToDelete.value = targetBranch.id deleteDialogVisible.value = true } @@ -481,6 +477,16 @@ const confirmDeleteBranch = async () => { return } + // ========== 新增:检查是否只剩下1个分支 ========== + // 如果当前任务只剩下1个分支,则不可删除 + if (savedBranches.length <= 1) { + ElMessage.error('至少需要保留1个分支,无法删除') + deleteDialogVisible.value = false + branchIdToDelete.value = null + return + } + // ========== 新增结束 ========== + // 2. 获取该分支的所有节点ID const branchNodeIds = branchToDelete.nodes ? branchToDelete.nodes.map((node: any) => node.id) : [] @@ -500,6 +506,11 @@ const confirmDeleteBranch = async () => { const TaskID = (window as any).__CURRENT_TASK_ID__ if (TaskID) { await selectionStore.deleteTaskProcessBranchFromDB(TaskID, taskStepId, branchId) + + // ========== 同步更新 task_outline ========== + // 删除分支后,同步更新 task_outline 中的 TaskProcess 数据 + await syncTaskOutlineAfterBranchDelete(branchToDelete, taskStepId) + // ========== 同步更新结束 ========== } // 8. 刷新视图 @@ -520,6 +531,239 @@ const handleDeleteNode = (nodeId: string) => { deleteNodeDialogVisible.value = true } +// 同步更新 task_outline 中删除分支后的数据 +const syncTaskOutlineAfterBranchDelete = async (branchToDelete: any, taskStepId: string) => { + try { + // 从 agentsStore 获取 task_outline + const taskOutline = agentsStore.agentRawPlan.data + if (!taskOutline) { + console.warn('[syncTaskOutline] taskOutline 不存在,跳过同步') + return + } + + // 找到对应的步骤 + const collaborationProcess = taskOutline['Collaboration Process'] || [] + const stepIndex = collaborationProcess.findIndex((step: any) => step.Id === taskStepId) + + if (stepIndex === -1) { + console.warn('[syncTaskOutline] 未找到对应的步骤:', taskStepId) + return + } + + // 获取分支中所有要删除的节点信息 + const nodesToDelete = branchToDelete.nodes || [] + const agentsToDelete = nodesToDelete.map((node: any) => ({ + agentName: node.data?.agentName, + actionTypeKey: node.data?.actionTypeKey + })) + + const step = collaborationProcess[stepIndex] + const taskProcess = step.TaskProcess || [] + + // 过滤掉要删除的 Action(通过 agentName + actionTypeKey 匹配) + const newTaskProcess = taskProcess.filter((action: any) => { + const match = agentsToDelete.find( + (a: any) => a.agentName === action.AgentName && a.actionTypeKey === action.ActionType + ) + return !match + }) + + // 更新 step 的 TaskProcess + taskOutline['Collaboration Process'][stepIndex].TaskProcess = newTaskProcess + + // 保存到数据库 + const TaskID = (window as any).__CURRENT_TASK_ID__ + if (TaskID) { + await api.updateTaskOutline(TaskID, taskOutline) + } + } catch (error) { + console.error('[syncTaskOutline] 分支删除后同步 taskOutline 失败:', error) + } +} + +// 同步更新 task_outline 中的 TaskProcess 数据(使用已保存的节点信息) +const syncTaskOutlineAfterNodeDeleteWithInfo = async ( + nodeId: string, + taskStepId: string, + nodeInfo: { agentName: string; actionTypeKey: string } +) => { + try { + // 从 agentsStore 获取 task_outline + const rawPlan = agentsStore.agentRawPlan + const taskOutline = rawPlan?.data + + if (!taskOutline) { + return + } + + // 找到对应的步骤 + const collaborationProcess = taskOutline['Collaboration Process'] || [] + const stepIndex = collaborationProcess.findIndex((step: any) => step.Id === taskStepId) + + if (stepIndex === -1) { + return + } + + const step = collaborationProcess[stepIndex]! + const taskProcess = step.TaskProcess || [] + + const { agentName, actionTypeKey } = nodeInfo + + // actionTypeKey 到 ActionType 的映射 + const actionTypeKeyToActionType: Record = { + propose: 'Propose', + review: 'Critique', + improve: 'Improve', + summary: 'Finalize' + } + + // 将 actionTypeKey 转换为正确的 ActionType + const targetActionType = actionTypeKeyToActionType[actionTypeKey] || actionTypeKey + + // 在 TaskProcess 中找到匹配的 Action 并删除 + // 通过 agentName + ActionType 匹配 + const newTaskProcess = taskProcess.filter((action: any) => { + // 如果 ActionType 不匹配,直接保留 + if (action.ActionType !== targetActionType) { + return true + } + // 如果 ActionType 匹配但 agentName 不匹配,保留 + if (action.AgentName !== agentName) { + return true + } + // 如果 agentName 和 ActionType 都匹配,说明是要删除的节点 + return false + }) + + // 检查是否有删除 + if (newTaskProcess.length === taskProcess.length) { + return + } + + // 更新 step 的 TaskProcess + const targetStep = taskOutline['Collaboration Process']?.[stepIndex] + if (targetStep) { + targetStep.TaskProcess = newTaskProcess + } + + // ========== 同步更新初始分支中的 tasks ========== + // 更新初始分支中保存的 tasks,使其与 taskOutline 保持一致 + const currentAgents = currentTask.value?.AgentSelection || [] + const savedBranches = selectionStore.getTaskProcessBranches(taskStepId, currentAgents) + const initialBranch = savedBranches.find(branch => branch.branchContent === '初始流程') + if (initialBranch) { + // 更新初始分支的 tasks + initialBranch.tasks = newTaskProcess + // 保存到数据库 + saveTaskProcessBranchesToDB() + } + // ========== 同步更新结束 ========== + + // 保存到数据库 + const TaskID = (window as any).__CURRENT_TASK_ID__ + + if (TaskID) { + await api.updateTaskOutline(TaskID, taskOutline) + } else { + console.error('[syncTaskOutline] TaskID 不存在') + } + } catch (error) { + console.error('[syncTaskOutline] 同步 taskOutline 失败:', error) + } +} + +// 同步更新 task_outline 中的 TaskProcess 数据(备用方案,从 store 查找) +const syncTaskOutlineAfterNodeDelete = async (nodeId: string, taskStepId: string) => { + try { + // 从 agentsStore 获取 task_outline + const rawPlan = agentsStore.agentRawPlan + const taskOutline = rawPlan?.data + + if (!taskOutline) { + return + } + + // 找到对应的步骤 + const collaborationProcess = taskOutline['Collaboration Process'] || [] + const stepIndex = collaborationProcess.findIndex((step: any) => step.Id === taskStepId) + + if (stepIndex === -1) { + return + } + + const step = collaborationProcess[stepIndex]! + const taskProcess = step.TaskProcess || [] + + // 获取被删除节点的 agentName 和 actionTypeKey + // 需要从分支数据中获取节点信息 + const currentAgents = currentTask.value?.AgentSelection || [] + const savedBranches = selectionStore.getTaskProcessBranches(taskStepId, currentAgents) + + const targetBranch = savedBranches.find(branch => + branch.nodes?.some((n: any) => n.id === nodeId) + ) + + if (!targetBranch) { + return + } + + const deletedNode = targetBranch.nodes?.find((n: any) => n.id === nodeId) + + if (!deletedNode) { + return + } + + const agentName = deletedNode.data?.agentName + const actionTypeKey = deletedNode.data?.actionTypeKey + + // actionTypeKey 到 ActionType 的映射 + const actionTypeKeyToActionType: Record = { + propose: 'Propose', + review: 'Critique', + improve: 'Improve', + summary: 'Finalize' + } + + // 将 actionTypeKey 转换为正确的 ActionType + const targetActionType = actionTypeKeyToActionType[actionTypeKey] || actionTypeKey + + // 在 TaskProcess 中找到匹配的 Action 并删除 + // 通过 agentName + ActionType 匹配 + const newTaskProcess = taskProcess.filter((action: any) => { + // 如果 ActionType 不匹配,直接保留 + if (action.ActionType !== targetActionType) { + return true + } + // 如果 ActionType 匹配但 agentName 不匹配,保留 + if (action.AgentName !== agentName) { + return true + } + // 如果 agentName 和 ActionType 都匹配,说明是要删除的节点 + return false + }) + + // 检查是否有删除 + if (newTaskProcess.length === taskProcess.length) { + return + } + + // 更新 step 的 TaskProcess + const targetStep = taskOutline['Collaboration Process']?.[stepIndex] + if (targetStep) { + targetStep.TaskProcess = newTaskProcess + } + + // 保存到数据库 + const TaskID = (window as any).__CURRENT_TASK_ID__ + + if (TaskID) { + await api.updateTaskOutline(TaskID, taskOutline) + } + } catch (error) { + console.error('[syncTaskOutline] 同步 taskOutline 失败:', error) + } +} + // 确认删除单个节点 const confirmDeleteNode = async () => { if (!nodeIdToDelete.value) return @@ -528,6 +772,41 @@ const confirmDeleteNode = async () => { const taskStepId = currentTask.value?.Id const currentAgents = currentTask.value?.AgentSelection || [] + // ========== 新增:检查职责类型数量 ========== + // 在删除前检查该分支中该职责类型是否只剩1个,如果是则不可删除 + if (taskStepId && currentAgents.length > 0) { + const savedBranches = selectionStore.getTaskProcessBranches(taskStepId, currentAgents) + const targetBranch = savedBranches.find(branch => + branch.nodes?.some((n: any) => n.id === nodeId) + ) + + if (targetBranch) { + // 获取被删除节点的职责类型 + const deletedNode = targetBranch.nodes?.find((n: any) => n.id === nodeId) + const actionTypeKey = deletedNode?.data?.actionTypeKey + const actionTypeName = deletedNode?.data?.actionTypeName || '该职责' + + // 统计该分支中每种职责类型的数量 + const actionTypeCount = new Map() + targetBranch.nodes?.forEach((n: any) => { + const type = n.data?.actionTypeKey + if (type) { + actionTypeCount.set(type, (actionTypeCount.get(type) || 0) + 1) + } + }) + + // 检查被删除节点对应的职责类型数量 + const currentCount = actionTypeCount.get(actionTypeKey) || 0 + if (currentCount <= 1) { + ElMessage.error(`${actionTypeName}类型至少需要保留1个节点,无法删除`) + deleteNodeDialogVisible.value = false + nodeIdToDelete.value = null + return + } + } + } + // ========== 新增结束 ========== + // 0. 检查被删除的节点是否是分支的第一个节点(带删除分支按钮) const nodeDeletable = getNodeDeletable(nodeId) const deletedBranchId = nodeDeletable.isDeletable ? nodeDeletable.branchId : null @@ -583,7 +862,6 @@ const confirmDeleteNode = async () => { ...childIncomingEdge.data, branchId: deletedBranchId } - console.log('[删除节点] 已更新新节点的边信息:', childNodeId, deletedBranchId) } }) } @@ -596,6 +874,19 @@ const confirmDeleteNode = async () => { // 找到被删除节点所属的分支 const targetBranch = branches.find(branch => branch.nodes?.some((n: any) => n.id === nodeId)) + // ========== 在删除节点之前先保存节点信息 ========== + let deletedNodeInfo: { agentName: string; actionTypeKey: string } | null = null + if (targetBranch) { + const deletedNode = targetBranch.nodes?.find((n: any) => n.id === nodeId) + if (deletedNode) { + deletedNodeInfo = { + agentName: deletedNode.data?.agentName, + actionTypeKey: deletedNode.data?.actionTypeKey + } + } + } + // ========== 保存节点信息结束 ========== + if (targetBranch) { // 使用精准删除方法(前端 store) const success = selectionStore.removeNodeFromBranch( @@ -609,8 +900,6 @@ const confirmDeleteNode = async () => { ) if (success) { - console.log('[删除节点] 前端精准删除成功') - // 调用后端接口删除节点,传递更新后的 edges 数据 const TaskID = (window as any).__CURRENT_TASK_ID__ if (TaskID) { @@ -621,7 +910,16 @@ const confirmDeleteNode = async () => { nodeId, edges.value ) - console.log('[删除节点] 后端删除结果:', result) + + // ========== 同步更新 task_outline ========== + // 删除节点后,同步更新 task_outline 中的 TaskProcess 数据 + // 传递之前保存的节点信息,而不是从 store 中查找 + if (deletedNodeInfo) { + await syncTaskOutlineAfterNodeDeleteWithInfo(nodeId, taskStepId, deletedNodeInfo) + } else { + await syncTaskOutlineAfterNodeDelete(nodeId, taskStepId) + } + // ========== 同步更新结束 ========== } } else { console.error('[删除节点] 前端精准删除失败') diff --git a/frontend/src/stores/modules/selection.ts b/frontend/src/stores/modules/selection.ts index 03fabb5..5f481e4 100644 --- a/frontend/src/stores/modules/selection.ts +++ b/frontend/src/stores/modules/selection.ts @@ -300,9 +300,18 @@ export const useSelectionStore = defineStore('selection', () => { const nodeIndex = branch.nodes.findIndex((n: any) => n.id === nodeId) if (nodeIndex === -1) return false - // 精准删除:只删除对应索引的节点和任务 + // 获取被删除节点的信息 + const deletedNode = branch.nodes[nodeIndex] + const originalIndex = deletedNode?.data?.originalIndex + + // 精准删除:删除 nodes 中的节点 branch.nodes.splice(nodeIndex, 1) - branch.tasks.splice(nodeIndex, 1) + + // 精准删除 tasks:使用 originalIndex 直接定位 + // nodes 包含 root 节点,所以 agent 节点的 originalIndex 对应 tasks 的索引 + if (originalIndex !== undefined && originalIndex < branch.tasks.length) { + branch.tasks.splice(originalIndex, 1) + } // 更新 edges:移除相关边,添加新边 if (incomingEdges.length > 0 || outgoingEdges.length > 0) {