feat:用户修改步骤后重新执行实现
This commit is contained in:
@@ -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:
|
||||
|
||||
@@ -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(
|
||||
|
||||
5
frontend/components.d.ts
vendored
5
frontend/components.d.ts
vendored
@@ -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']
|
||||
|
||||
@@ -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'],
|
||||
|
||||
@@ -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<Record<string, StepExecutionStatus>>({})
|
||||
|
||||
// 用于标记暂停时的"最后动作完成"状态
|
||||
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) {
|
||||
// 保存旧值用于比较
|
||||
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<string, any> {
|
||||
const keyObjects: Record<string, any> = {}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -221,6 +221,19 @@ export const useAgentsStore = defineStore('agents', () => {
|
||||
|
||||
// 当前的展示的任务流程
|
||||
const currentTask = ref<IRawStepTask>()
|
||||
|
||||
// 记录用户修改过的步骤索引(用于重新执行)
|
||||
const modifiedSteps = ref<Set<number>>(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,
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
Reference in New Issue
Block a user