diff --git a/backend/AgentCoord/RehearsalEngine_V2/ExecutePlan_Optimized.py b/backend/AgentCoord/RehearsalEngine_V2/ExecutePlan_Optimized.py index 44ae5af..c8150f5 100644 --- a/backend/AgentCoord/RehearsalEngine_V2/ExecutePlan_Optimized.py +++ b/backend/AgentCoord/RehearsalEngine_V2/ExecutePlan_Optimized.py @@ -428,7 +428,7 @@ def executePlan_streaming_dynamic( if execution_id: # 动态模式:循环获取下一个步骤 # 等待新步骤的最大次数(避免无限等待) - max_empty_wait_cycles = 60 # 最多等待60次,每次等待1秒 + max_empty_wait_cycles = 5 # 最多等待60次,每次等待1秒 empty_wait_count = 0 while True: diff --git a/backend/server.py b/backend/server.py index 0859717..1f603ec 100644 --- a/backend/server.py +++ b/backend/server.py @@ -18,6 +18,7 @@ from AgentCoord.PlanEngine.AgentSelectModify import ( import os import yaml import argparse +from typing import List, Dict # initialize global variables yaml_file = os.path.join(os.getcwd(), "config", "config.yaml") @@ -39,6 +40,40 @@ app.config['SECRET_KEY'] = 'agentcoord-secret-key' socketio = SocketIO(app, cors_allowed_origins="*", async_mode='threading') +def truncate_rehearsal_log(RehearsalLog: List, restart_from_step_index: int) -> List: + """ + 截断 RehearsalLog,只保留指定索引之前的步骤结果 + + Args: + RehearsalLog: 原始日志列表 + restart_from_step_index: 重新执行的起始步骤索引(例如:1 表示保留步骤0,从步骤1重新执行) + + Returns: + 截断后的 RehearsalLog + + 示例: + restart_from_step_index = 1 + RehearsalLog = [step0, object0, step1, object1, step2, object2] + 返回 = [step0, object0] # 只保留步骤0的结果 + """ + truncated_log = [] + step_count = 0 + + for logNode in RehearsalLog: + if logNode.get("LogNodeType") == "step": + # 只保留 restart_from_step_index 之前的步骤 + if step_count < restart_from_step_index: + truncated_log.append(logNode) + step_count += 1 + elif logNode.get("LogNodeType") == "object": + # object 节点:如果对应的 step 在保留范围内,保留它 + # 策略:保留所有在截断点之前的 object + if step_count <= restart_from_step_index: + truncated_log.append(logNode) + + return truncated_log + + @app.route("/fill_stepTask_TaskProcess", methods=["post"]) def Handle_fill_stepTask_TaskProcess(): incoming_data = request.get_json() @@ -412,7 +447,7 @@ def handle_ping(): def handle_execute_plan_optimized_ws(data): """ WebSocket版本:优化版流式执行计划 - 支持步骤级流式 + 动作级智能并行 + 动态追加步骤 + 支持步骤级流式 + 动作级智能并行 + 动态追加步骤 + 从指定步骤重新执行 请求格式: { @@ -422,7 +457,8 @@ def handle_execute_plan_optimized_ws(data): "plan": {...}, "num_StepToRun": null, "RehearsalLog": [], - "enable_dynamic": true # 是否启用动态追加步骤 + "enable_dynamic": true, # 是否启用动态追加步骤 + "restart_from_step_index": 1 # 可选:从指定步骤重新执行(例如1表示从步骤2重新执行) } } """ @@ -434,6 +470,13 @@ 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") # 新增:支持从指定步骤重新执行 + + # 如果指定了重新执行起始步骤,截断 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") @@ -1650,6 +1693,66 @@ def handle_resume_execution(data): }) +@socketio.on('stop_execution') +def handle_stop_execution(data): + """ + WebSocket版本:停止任务执行 + + 请求格式: + { + "id": "request-id", + "action": "stop_execution", + "data": { + "goal": "任务描述" + } + } + """ + request_id = data.get('id') + incoming_data = data.get('data', {}) + + try: + from AgentCoord.RehearsalEngine_V2.execution_state import execution_state_manager + + goal = incoming_data.get('goal', '') + + # 检查当前执行的任务是否匹配 + current_goal = execution_state_manager.get_goal() + if current_goal and current_goal != goal: + print(f"⚠️ 任务目标不匹配: 当前={current_goal}, 请求={goal}") + emit('response', { + 'id': request_id, + 'status': 'error', + 'error': '任务目标不匹配' + }) + return + + # 调用执行状态管理器停止 + success = execution_state_manager.stop_execution() + + if success: + print(f"🛑 [DEBUG] 停止成功! 当前状态: {execution_state_manager.get_status().value}") + print(f"🛑 [DEBUG] should_stop: {execution_state_manager._should_stop}") + emit('response', { + 'id': request_id, + 'status': 'success', + 'data': {"message": "已停止执行"} + }) + else: + print(f"⚠️ [DEBUG] 停止失败,当前状态: {execution_state_manager.get_status().value}") + emit('response', { + 'id': request_id, + 'status': 'error', + 'error': f'无法停止,当前状态: {execution_state_manager.get_status().value}' + }) + + except Exception as e: + print(f"❌ 停止执行失败: {str(e)}") + emit('response', { + 'id': request_id, + 'status': 'error', + 'error': str(e) + }) + if __name__ == "__main__": parser = argparse.ArgumentParser( diff --git a/frontend/components.d.ts b/frontend/components.d.ts index a901abc..ab2fb32 100644 --- a/frontend/components.d.ts +++ b/frontend/components.d.ts @@ -16,15 +16,16 @@ declare module 'vue' { ElCard: typeof import('element-plus/es')['ElCard'] ElCollapse: typeof import('element-plus/es')['ElCollapse'] ElCollapseItem: typeof import('element-plus/es')['ElCollapseItem'] - ElDialog: typeof import('element-plus/es')['ElDialog'] - ElDivider: typeof import('element-plus/es')['ElDivider'] ElDrawer: typeof import('element-plus/es')['ElDrawer'] ElEmpty: typeof import('element-plus/es')['ElEmpty'] + ElIcon: typeof import('element-plus/es')['ElIcon'] ElInput: typeof import('element-plus/es')['ElInput'] ElPopover: typeof import('element-plus/es')['ElPopover'] ElScrollbar: typeof import('element-plus/es')['ElScrollbar'] + ElTag: typeof import('element-plus/es')['ElTag'] ElTooltip: typeof import('element-plus/es')['ElTooltip'] MultiLineTooltip: typeof import('./src/components/MultiLineTooltip/index.vue')['default'] + Notification: typeof import('./src/components/Notification/Notification.vue')['default'] RouterLink: typeof import('vue-router')['RouterLink'] RouterView: typeof import('vue-router')['RouterView'] SvgIcon: typeof import('./src/components/SvgIcon/index.vue')['default'] diff --git a/frontend/src/api/index.ts b/frontend/src/api/index.ts index 75099c9..2da1c98 100644 --- a/frontend/src/api/index.ts +++ b/frontend/src/api/index.ts @@ -180,15 +180,18 @@ class Api { enableDynamic?: boolean, onExecutionStarted?: (executionId: string) => void, executionId?: string, + restartFromStepIndex?: number, // 新增:从指定步骤重新执行的索引 + rehearsalLog?: any[], // 新增:传递截断后的 RehearsalLog ) => { const useWs = useWebSocket !== undefined ? useWebSocket : this.useWebSocketDefault const data = { - RehearsalLog: [], + RehearsalLog: rehearsalLog || [], // ✅ 使用传递的 RehearsalLog num_StepToRun: null, existingKeyObjects: existingKeyObjects || {}, enable_dynamic: enableDynamic || false, execution_id: executionId || null, + restart_from_step_index: restartFromStepIndex ?? null, // 新增:传递重新执行索引 plan: { 'Initial Input Object': plan['Initial Input Object'], 'General Goal': plan['General Goal'], diff --git a/frontend/src/layout/components/Main/TaskTemplate/TaskResult/index.vue b/frontend/src/layout/components/Main/TaskTemplate/TaskResult/index.vue index c0c1182..29d7892 100644 --- a/frontend/src/layout/components/Main/TaskTemplate/TaskResult/index.vue +++ b/frontend/src/layout/components/Main/TaskTemplate/TaskResult/index.vue @@ -2,7 +2,6 @@ import { computed, onUnmounted, ref, reactive, nextTick, watch, onMounted } from 'vue' import { throttle } from 'lodash' import { AnchorLocations, BezierConnector } from '@jsplumb/browser-ui' -import { ElMessageBox } from 'element-plus' import AdditionalOutputCard from './AdditionalOutputCard.vue' import SvgIcon from '@/components/SvgIcon/index.vue' import { getActionTypeDisplay, getAgentMapIcon } from '@/layout/components/config.ts' @@ -42,6 +41,7 @@ const stepExecutionStatus = ref>({}) // 用于标记暂停时的"最后动作完成"状态 const isPausing = ref(false) // 正在请求暂停(等待当前动作完成) +const isRestarting = ref(false) // 正在重新执行(停止旧执行导致的错误不应显示) // Check if step is ready to execute (has TaskProcess data) const isStepReady = (step: IRawStepTask) => { @@ -211,7 +211,23 @@ function handleSaveEdit(stepId: string, processId: string, value: string) { if (step) { const process = step.TaskProcess.find(p => p.ID === processId) if (process) { - process.Description = value + // 保存旧值用于比较 + const oldValue = process.Description + + // 只有内容真正变化时才更新和记录修改 + if (value !== oldValue) { + process.Description = value + // 记录修改过的步骤索引到 store + const stepIndex = collaborationProcess.value.findIndex(s => s.Id === stepId) + if (stepIndex >= 0) { + agentsStore.addModifiedStep(stepIndex) + console.log( + `📝 步骤 ${stepIndex + 1} (${step.StepName}) 的描述已被修改,将从该步骤重新执行` + ) + } + } else { + console.log(`ℹ️ 步骤 ${step.StepName} 的描述未发生变化,不记录修改`) + } } } editMap[key] = false @@ -341,7 +357,6 @@ const executionProgress = ref({ const { notifications, progress: showProgress, - updateProgress, updateProgressDetail, success, info, @@ -457,9 +472,12 @@ async function executeNextReadyBatch() { switch (event.type) { case 'step_start': // 使用全局步骤索引计算当前步骤 - const globalStepIndex = collaborationProcess.value.findIndex(s => s.StepName === event.step_name) + const globalStepIndex = collaborationProcess.value.findIndex( + s => s.StepName === event.step_name + ) executionProgress.value = { - currentStep: globalStepIndex >= 0 ? globalStepIndex + 1 : (event.step_index || 0) + 1, + currentStep: + globalStepIndex >= 0 ? globalStepIndex + 1 : (event.step_index || 0) + 1, totalSteps: collaborationProcess.value.length, currentAction: 0, totalActions: 0, @@ -495,8 +513,13 @@ async function executeNextReadyBatch() { : '' // 使用全局步骤索引 - const globalStepIndexForAction = collaborationProcess.value.findIndex(s => s.StepName === event.step_name) - const stepIndexForAction = globalStepIndexForAction >= 0 ? globalStepIndexForAction + 1 : (event.step_index || 0) + 1 + const globalStepIndexForAction = collaborationProcess.value.findIndex( + s => s.StepName === event.step_name + ) + const stepIndexForAction = + globalStepIndexForAction >= 0 + ? globalStepIndexForAction + 1 + : (event.step_index || 0) + 1 const totalStepsValue = collaborationProcess.value.length executionProgress.value = { @@ -594,6 +617,13 @@ async function executeNextReadyBatch() { const errorMessage = event.message || event.error || '未知错误' console.error('执行错误:', errorMessage) + // 如果正在重新执行,不显示错误通知(因为旧执行的步骤中断是预期的) + if (isRestarting.value) { + console.log('ℹ️ 正在重新执行,忽略旧执行的中断错误') + resolve() // 正常完成,不显示错误 + return + } + // 关闭进度通知并显示错误通知 if (currentProgressNotificationId.value) { removeNotification(currentProgressNotificationId.value) @@ -614,6 +644,13 @@ async function executeNextReadyBatch() { (err: Error) => { console.error('流式执行错误:', err) + // 如果正在重新执行,不显示错误通知(因为旧执行的步骤中断是预期的) + if (isRestarting.value) { + console.log('ℹ️ 正在重新执行,忽略旧执行的中断错误') + resolve() // 正常完成,不显示错误 + return + } + // 关闭进度通知并显示错误通知 if (currentProgressNotificationId.value) { removeNotification(currentProgressNotificationId.value) @@ -695,7 +732,25 @@ function removeNotification(id: string) { // Pause/Resume handler async function handlePauseResume() { if (isPaused.value) { - // Resume execution - 正常恢复执行 + // Resume execution - 检查是否有修改的步骤 + if (agentsStore.hasModifiedSteps()) { + // 有修改的步骤,必须从最早修改的步骤重新执行 + // 因为步骤之间是串行依赖的,修改了步骤2会导致步骤2-6都需要重新执行 + const earliestModifiedIndex = Math.min(...agentsStore.modifiedSteps) + const modifiedStepName = + collaborationProcess.value[earliestModifiedIndex]?.StepName || + `步骤${earliestModifiedIndex + 1}` + + console.log(`🔄 从步骤 ${earliestModifiedIndex + 1} (${modifiedStepName}) 重新执行`) + success('开始重新执行', `检测到步骤修改,从步骤 ${earliestModifiedIndex + 1} 重新执行`) + + // 直接调用重新执行函数 + await restartFromStep(earliestModifiedIndex) + + return + } + + // 正常恢复执行(没有修改) try { if (websocket.connected) { await websocket.send('resume_execution', { @@ -739,6 +794,371 @@ async function handlePauseResume() { } } +// ==================== 重新执行功能 ==================== + +/** + * 从RehearsalLog中提取KeyObjects + * @param rehearsalLog RehearsalLog数组 + * @returns KeyObjects字典 + */ +function buildKeyObjectsFromLog(rehearsalLog: any[]): Record { + const keyObjects: Record = {} + + for (const logNode of rehearsalLog) { + if (logNode.LogNodeType === 'object' && logNode.content) { + keyObjects[logNode.NodeId] = logNode.content + } + } + + return keyObjects +} + +/** + * 构建截断后的 RehearsalLog(使用步骤名称匹配) + * @param restartFromStepIndex 重新执行的起始步骤索引(例如:1 表示从步骤2重新执行) + * @returns 截断后的 RehearsalLog + */ +function buildTruncatedRehearsalLog(restartFromStepIndex: number) { + const steps = agentsStore.agentRawPlan.data?.['Collaboration Process'] || [] + const truncatedLog: any[] = [] + + for (const logNode of agentsStore.executePlan) { + if (logNode.LogNodeType === 'step') { + const stepIndex = steps.findIndex((s: any) => s.StepName === logNode.NodeId) + if (stepIndex >= 0 && stepIndex < restartFromStepIndex) { + truncatedLog.push(logNode) + } + } else if (logNode.LogNodeType === 'object') { + const stepIndex = steps.findIndex((s: any) => s.OutputObject === logNode.NodeId) + if (stepIndex >= 0 && stepIndex < restartFromStepIndex) { + truncatedLog.push(logNode) + } + } + } + + return truncatedLog +} + +/** + * 清除执行结果中指定步骤及之后的记录 + * @param fromStepIndex 起始步骤索引 + */ +function clearExecutionResults(fromStepIndex: number) { + const steps = agentsStore.agentRawPlan.data?.['Collaboration Process'] || [] + + // 过滤掉要重新执行的步骤及其之后的所有步骤 + agentsStore.executePlan = agentsStore.executePlan.filter(logNode => { + if (logNode.LogNodeType === 'step') { + // 找到该步骤在原始步骤列表中的索引 + const stepIndex = steps.findIndex((s: any) => s.StepName === logNode.NodeId) + return stepIndex < fromStepIndex + } + // 对于 object 节点,也需要判断 + if (logNode.LogNodeType === 'object') { + // 找到该 object 对应的步骤索引 + const stepIndex = steps.findIndex((s: any) => s.OutputObject === logNode.NodeId) + return stepIndex < fromStepIndex + } + return true + }) +} + +/** + * 重置步骤执行状态 + * @param fromStepIndex 起始步骤索引 + */ +function resetStepStatus(fromStepIndex: number) { + const steps = agentsStore.agentRawPlan.data?.['Collaboration Process'] || [] + + for (let i = fromStepIndex; i < steps.length; i++) { + const step = steps[i] + if (!step) continue + const stepName = step.StepName || step.Id || '' + stepExecutionStatus.value[stepName] = StepExecutionStatus.READY + } +} + +/** + * 从指定步骤重新执行 + * @param stepIndex 要重新执行的步骤索引(例如:1 表示从步骤2重新执行) + */ +async function restartFromStep(stepIndex: number) { + try { + loading.value = true + isRestarting.value = true // 标记正在重新执行 + + // 清空修改记录(避免重复执行) + agentsStore.clearModifiedSteps() + + // 0. 先停止旧的执行(避免双重执行) + if (websocket.connected && currentExecutionId.value) { + try { + console.log('🛑 停止旧的执行任务,避免双重执行') + const stopResponse = await websocket.send('stop_execution', { + goal: agentsStore.agentRawPlan.data?.['General Goal'] || '' + }) + console.log('✅ 后端确认停止:', stopResponse) + // 等待一下确保后端完全停止 + await new Promise(resolve => setTimeout(resolve, 1000)) + } catch (err) { + console.warn('⚠️ 停止旧执行失败(可能已经停止):', err) + } + } + + // 1. 构建截断后的 RehearsalLog + const truncatedLog = buildTruncatedRehearsalLog(stepIndex) + + // 2. 从截断日志中提取 KeyObjects + const existingKeyObjects = buildKeyObjectsFromLog(truncatedLog) + + console.log('📦 传递给后端的数据:', { + truncatedLogLength: truncatedLog.length, + keyObjectsCount: Object.keys(existingKeyObjects).length, + keyObjectsKeys: Object.keys(existingKeyObjects) + }) + + // 3. 清除前端执行结果中指定步骤及之后的记录 + clearExecutionResults(stepIndex) + + // 3.5 关闭旧的进度通知(避免通知混乱) + if (currentProgressNotificationId.value) { + removeNotification(currentProgressNotificationId.value) + currentProgressNotificationId.value = null + } + + // 4. 重置步骤状态 + resetStepStatus(stepIndex) + + // 4.5 强制触发 Vue 响应式更新 + nextTick(() => { + // 触发 UI 更新 + }) + + // 5. 重置执行状态为执行中 + isPaused.value = false + isStreaming.value = true + loading.value = true + + // 6. 调用执行 API,传递截断后的 RehearsalLog 和 KeyObjects + api.executePlanOptimized( + agentsStore.agentRawPlan.data!, + // onMessage - 使用内联函数处理事件 + (event: StreamingEvent) => { + // 如果正在暂停(isPausing)或已暂停(isPaused),只允许特定事件 + // 这样可以确保当前正在完成的动作的结果能够被正确保存 + if (isPausing.value || isPaused.value) { + if ( + event.type !== 'action_complete' && + event.type !== 'step_complete' && + event.type !== 'error' + ) { + return + } + } + + switch (event.type) { + case 'step_start': + stepExecutionStatus.value[event.step_name] = StepExecutionStatus.RUNNING + + // 使用全局步骤索引计算当前步骤 + const globalStepIndex = collaborationProcess.value.findIndex( + s => s.StepName === event.step_name + ) + const currentStepNumber = + globalStepIndex >= 0 ? globalStepIndex + 1 : (event.step_index || 0) + 1 + + // 创建或更新进度通知 + if (!currentProgressNotificationId.value) { + currentProgressNotificationId.value = showProgress( + '重新执行中', + currentStepNumber, + collaborationProcess.value.length + ) + updateProgressDetail( + currentProgressNotificationId.value, + `步骤 ${currentStepNumber}/${collaborationProcess.value.length}`, + `正在执行: ${event.step_name}` + ) + } else { + updateProgressDetail( + currentProgressNotificationId.value, + `步骤 ${currentStepNumber}/${collaborationProcess.value.length}`, + `正在执行: ${event.step_name}`, + currentStepNumber, + collaborationProcess.value.length + ) + } + break + + case 'action_complete': + // 检测是否正在暂停(等待当前动作完成) + if (isPausing.value) { + // 当前动作完成,完成暂停 + isPaused.value = true + isPausing.value = false + success('已暂停', '已暂停执行,可稍后继续') + } + + // 实时更新store + const existingStep = collaborationProcess.value.find( + s => s.StepName === event.step_name + ) + if (existingStep) { + const currentResults = agentsStore.executePlan + const stepLogNode = currentResults.find( + r => r.NodeId === event.step_name && r.LogNodeType === 'step' + ) + if (!stepLogNode) { + const newStepLog = { + LogNodeType: 'step', + NodeId: event.step_name, + InputName_List: existingStep.InputObject_List || [], + OutputName: existingStep.OutputObject || '', + chatLog: [], + inputObject_Record: [], + ActionHistory: [event.action_result] + } + agentsStore.setExecutePlan([...currentResults, newStepLog]) + } else { + stepLogNode.ActionHistory.push(event.action_result) + agentsStore.setExecutePlan([...currentResults]) + } + } + + // 使用全局步骤索引 + const globalStepIndexForAction = collaborationProcess.value.findIndex( + s => s.StepName === event.step_name + ) + const stepNumberForAction = + globalStepIndexForAction >= 0 + ? globalStepIndexForAction + 1 + : (event.step_index || 0) + 1 + + // 更新进度通知 + if (currentProgressNotificationId.value) { + const parallelInfo = event.batch_info?.is_parallel + ? ` [并行 ${event.batch_info!.batch_size} 个动作]` + : '' + + updateProgressDetail( + currentProgressNotificationId.value, + `步骤 ${stepNumberForAction}/${collaborationProcess.value.length}`, + `${event.step_name} - 动作 ${event.completed_actions}/${event.total_actions} 完成${parallelInfo}`, + stepNumberForAction, + collaborationProcess.value.length + ) + } + break + + case 'step_complete': + stepExecutionStatus.value[event.step_name] = StepExecutionStatus.COMPLETED + + // 更新完整步骤日志 + const currentResults = agentsStore.executePlan + const existingLog = currentResults.find( + r => r.NodeId === event.step_name && r.LogNodeType === 'step' + ) + if (existingLog) { + existingLog.ActionHistory = event.step_log_node.ActionHistory + agentsStore.setExecutePlan([...currentResults]) + } else if (event.step_log_node) { + agentsStore.setExecutePlan([...currentResults, event.step_log_node]) + } + // 添加object_log_node + const updatedResults = agentsStore.executePlan + if (event.object_log_node) { + agentsStore.setExecutePlan([...updatedResults, event.object_log_node]) + } + break + + case 'execution_complete': + const steps = collaborationProcess.value + steps.forEach(step => { + const stepName = step.StepName || step.Id || '' + if (stepExecutionStatus.value[stepName] !== StepExecutionStatus.COMPLETED) { + stepExecutionStatus.value[stepName] = StepExecutionStatus.COMPLETED + } + }) + + // 关闭进度通知并显示完成通知 + if (currentProgressNotificationId.value) { + removeNotification(currentProgressNotificationId.value) + currentProgressNotificationId.value = null + } + + success('重新执行完成', `所有步骤已执行完成`, { duration: 3000 }) + loading.value = false + isPaused.value = false + isStreaming.value = false + break + + case 'error': + const errorMessage = event.message || '未知错误' + console.error('重新执行错误:', errorMessage) + + // 关闭进度通知 + if (currentProgressNotificationId.value) { + removeNotification(currentProgressNotificationId.value) + currentProgressNotificationId.value = null + } + + // 静默处理所有错误,不显示任何通知 + loading.value = false + isPaused.value = false + isStreaming.value = false + isRestarting.value = false // 重新执行结束 + break + } + }, + // onError + (err: Error) => { + console.error('重新执行错误:', err) + + // 关闭进度通知 + if (currentProgressNotificationId.value) { + removeNotification(currentProgressNotificationId.value) + currentProgressNotificationId.value = null + } + + // 静默处理所有错误,不显示任何通知 + loading.value = false + isPaused.value = false + isStreaming.value = false + isRestarting.value = false // 重新执行结束 + }, + // onComplete + () => { + // 关闭进度通知 + if (currentProgressNotificationId.value) { + removeNotification(currentProgressNotificationId.value) + currentProgressNotificationId.value = null + } + + loading.value = false + isPaused.value = false + isStreaming.value = false + isRestarting.value = false // 重新执行结束 + }, + true, // useWebSocket + existingKeyObjects, // ✅ 传递从截断日志中提取的KeyObjects + true, // enableDynamic + (executionId: string) => { + currentExecutionId.value = executionId + }, + undefined, // executionId + stepIndex, // restartFromStepIndex + truncatedLog // ✅ 传递截断后的 RehearsalLog + ) + + success('重新执行', `正在从步骤 ${stepIndex + 1} 重新执行...`) + } catch (err) { + console.error('重新执行失败:', err) + error('重新执行失败', '无法启动重新执行') + loading.value = false + isRestarting.value = false // 重新执行失败,也要重置标记 + } +} + // Handle execute button click async function handleExecuteButtonClick() { // If streaming, show pause/resume functionality @@ -757,16 +1177,12 @@ async function handleRun() { const waitingSteps = stepsReadyStatus.value.waiting if (readySteps.length === 0 && waitingSteps.length > 0) { - ElMessageBox.confirm( - `All ${waitingSteps.length} steps的数据还在填充中:\n\n${waitingSteps.join( + warning( + '步骤数据未就绪', + `${waitingSteps.length} 个步骤的数据还在填充中:${waitingSteps.join( '、' - )}\n\n建议等待数据填充完成后再执行。`, - 'Step data not ready', - { - confirmButtonText: 'I Understand', - cancelButtonText: 'Close', - type: 'warning' - } + )}。建议等待数据填充完成后再执行。`, + { duration: 5000 } ) return } diff --git a/frontend/src/layout/components/Main/TaskTemplate/TaskSyllabus/index.vue b/frontend/src/layout/components/Main/TaskTemplate/TaskSyllabus/index.vue index 115944d..88069bc 100644 --- a/frontend/src/layout/components/Main/TaskTemplate/TaskSyllabus/index.vue +++ b/frontend/src/layout/components/Main/TaskTemplate/TaskSyllabus/index.vue @@ -201,7 +201,23 @@ const saveEditing = () => { if (editingTaskId.value && editingContent.value.trim()) { const taskToUpdate = collaborationProcess.value.find(item => item.Id === editingTaskId.value) if (taskToUpdate) { - taskToUpdate.TaskContent = editingContent.value.trim() + // 保存旧值用于比较 + const oldValue = taskToUpdate.TaskContent + const newValue = editingContent.value.trim() + + // 只有内容真正变化时才更新和记录修改 + if (newValue !== oldValue) { + taskToUpdate.TaskContent = newValue + + // 记录修改过的步骤索引到 store(用于重新执行) + const stepIndex = collaborationProcess.value.findIndex(item => item.Id === editingTaskId.value) + if (stepIndex >= 0) { + agentsStore.addModifiedStep(stepIndex) + console.log(`📝 步骤 ${stepIndex + 1} (${taskToUpdate.StepName}) 的 TaskContent 已被修改,将从该步骤重新执行`) + } + } else { + console.log(`ℹ️ 步骤 ${taskToUpdate.StepName} 的 TaskContent 未发生变化,不记录修改`) + } } } editingTaskId.value = null diff --git a/frontend/src/stores/modules/agents.ts b/frontend/src/stores/modules/agents.ts index 009dad7..6ce9216 100644 --- a/frontend/src/stores/modules/agents.ts +++ b/frontend/src/stores/modules/agents.ts @@ -221,6 +221,19 @@ export const useAgentsStore = defineStore('agents', () => { // 当前的展示的任务流程 const currentTask = ref() + + // 记录用户修改过的步骤索引(用于重新执行) + const modifiedSteps = ref>(new Set()) + function addModifiedStep(stepIndex: number) { + modifiedSteps.value.add(stepIndex) + } + function clearModifiedSteps() { + modifiedSteps.value.clear() + } + function hasModifiedSteps() { + return modifiedSteps.value.size > 0 + } + function setCurrentTask(task: IRawStepTask) { const existingTask = currentTask.value @@ -472,6 +485,11 @@ export const useAgentsStore = defineStore('agents', () => { // 停止填充状态 hasStoppedFilling, setHasStoppedFilling, + // 重新执行相关 + modifiedSteps, + addModifiedStep, + clearModifiedSteps, + hasModifiedSteps, } })