feat:用户修改步骤后重新执行实现
This commit is contained in:
@@ -428,7 +428,7 @@ def executePlan_streaming_dynamic(
|
|||||||
if execution_id:
|
if execution_id:
|
||||||
# 动态模式:循环获取下一个步骤
|
# 动态模式:循环获取下一个步骤
|
||||||
# 等待新步骤的最大次数(避免无限等待)
|
# 等待新步骤的最大次数(避免无限等待)
|
||||||
max_empty_wait_cycles = 60 # 最多等待60次,每次等待1秒
|
max_empty_wait_cycles = 5 # 最多等待60次,每次等待1秒
|
||||||
empty_wait_count = 0
|
empty_wait_count = 0
|
||||||
|
|
||||||
while True:
|
while True:
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ from AgentCoord.PlanEngine.AgentSelectModify import (
|
|||||||
import os
|
import os
|
||||||
import yaml
|
import yaml
|
||||||
import argparse
|
import argparse
|
||||||
|
from typing import List, Dict
|
||||||
|
|
||||||
# initialize global variables
|
# initialize global variables
|
||||||
yaml_file = os.path.join(os.getcwd(), "config", "config.yaml")
|
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')
|
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"])
|
@app.route("/fill_stepTask_TaskProcess", methods=["post"])
|
||||||
def Handle_fill_stepTask_TaskProcess():
|
def Handle_fill_stepTask_TaskProcess():
|
||||||
incoming_data = request.get_json()
|
incoming_data = request.get_json()
|
||||||
@@ -412,7 +447,7 @@ def handle_ping():
|
|||||||
def handle_execute_plan_optimized_ws(data):
|
def handle_execute_plan_optimized_ws(data):
|
||||||
"""
|
"""
|
||||||
WebSocket版本:优化版流式执行计划
|
WebSocket版本:优化版流式执行计划
|
||||||
支持步骤级流式 + 动作级智能并行 + 动态追加步骤
|
支持步骤级流式 + 动作级智能并行 + 动态追加步骤 + 从指定步骤重新执行
|
||||||
|
|
||||||
请求格式:
|
请求格式:
|
||||||
{
|
{
|
||||||
@@ -422,7 +457,8 @@ def handle_execute_plan_optimized_ws(data):
|
|||||||
"plan": {...},
|
"plan": {...},
|
||||||
"num_StepToRun": null,
|
"num_StepToRun": null,
|
||||||
"RehearsalLog": [],
|
"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")
|
num_StepToRun = incoming_data.get("num_StepToRun")
|
||||||
RehearsalLog = incoming_data.get("RehearsalLog", [])
|
RehearsalLog = incoming_data.get("RehearsalLog", [])
|
||||||
enable_dynamic = incoming_data.get("enable_dynamic", False)
|
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,使用前端的;否则生成新的
|
||||||
execution_id = incoming_data.get("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__":
|
if __name__ == "__main__":
|
||||||
parser = argparse.ArgumentParser(
|
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']
|
ElCard: typeof import('element-plus/es')['ElCard']
|
||||||
ElCollapse: typeof import('element-plus/es')['ElCollapse']
|
ElCollapse: typeof import('element-plus/es')['ElCollapse']
|
||||||
ElCollapseItem: typeof import('element-plus/es')['ElCollapseItem']
|
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']
|
ElDrawer: typeof import('element-plus/es')['ElDrawer']
|
||||||
ElEmpty: typeof import('element-plus/es')['ElEmpty']
|
ElEmpty: typeof import('element-plus/es')['ElEmpty']
|
||||||
|
ElIcon: typeof import('element-plus/es')['ElIcon']
|
||||||
ElInput: typeof import('element-plus/es')['ElInput']
|
ElInput: typeof import('element-plus/es')['ElInput']
|
||||||
ElPopover: typeof import('element-plus/es')['ElPopover']
|
ElPopover: typeof import('element-plus/es')['ElPopover']
|
||||||
ElScrollbar: typeof import('element-plus/es')['ElScrollbar']
|
ElScrollbar: typeof import('element-plus/es')['ElScrollbar']
|
||||||
|
ElTag: typeof import('element-plus/es')['ElTag']
|
||||||
ElTooltip: typeof import('element-plus/es')['ElTooltip']
|
ElTooltip: typeof import('element-plus/es')['ElTooltip']
|
||||||
MultiLineTooltip: typeof import('./src/components/MultiLineTooltip/index.vue')['default']
|
MultiLineTooltip: typeof import('./src/components/MultiLineTooltip/index.vue')['default']
|
||||||
|
Notification: typeof import('./src/components/Notification/Notification.vue')['default']
|
||||||
RouterLink: typeof import('vue-router')['RouterLink']
|
RouterLink: typeof import('vue-router')['RouterLink']
|
||||||
RouterView: typeof import('vue-router')['RouterView']
|
RouterView: typeof import('vue-router')['RouterView']
|
||||||
SvgIcon: typeof import('./src/components/SvgIcon/index.vue')['default']
|
SvgIcon: typeof import('./src/components/SvgIcon/index.vue')['default']
|
||||||
|
|||||||
@@ -180,15 +180,18 @@ class Api {
|
|||||||
enableDynamic?: boolean,
|
enableDynamic?: boolean,
|
||||||
onExecutionStarted?: (executionId: string) => void,
|
onExecutionStarted?: (executionId: string) => void,
|
||||||
executionId?: string,
|
executionId?: string,
|
||||||
|
restartFromStepIndex?: number, // 新增:从指定步骤重新执行的索引
|
||||||
|
rehearsalLog?: any[], // 新增:传递截断后的 RehearsalLog
|
||||||
) => {
|
) => {
|
||||||
const useWs = useWebSocket !== undefined ? useWebSocket : this.useWebSocketDefault
|
const useWs = useWebSocket !== undefined ? useWebSocket : this.useWebSocketDefault
|
||||||
|
|
||||||
const data = {
|
const data = {
|
||||||
RehearsalLog: [],
|
RehearsalLog: rehearsalLog || [], // ✅ 使用传递的 RehearsalLog
|
||||||
num_StepToRun: null,
|
num_StepToRun: null,
|
||||||
existingKeyObjects: existingKeyObjects || {},
|
existingKeyObjects: existingKeyObjects || {},
|
||||||
enable_dynamic: enableDynamic || false,
|
enable_dynamic: enableDynamic || false,
|
||||||
execution_id: executionId || null,
|
execution_id: executionId || null,
|
||||||
|
restart_from_step_index: restartFromStepIndex ?? null, // 新增:传递重新执行索引
|
||||||
plan: {
|
plan: {
|
||||||
'Initial Input Object': plan['Initial Input Object'],
|
'Initial Input Object': plan['Initial Input Object'],
|
||||||
'General Goal': plan['General Goal'],
|
'General Goal': plan['General Goal'],
|
||||||
|
|||||||
@@ -2,7 +2,6 @@
|
|||||||
import { computed, onUnmounted, ref, reactive, nextTick, watch, onMounted } from 'vue'
|
import { computed, onUnmounted, ref, reactive, nextTick, watch, onMounted } from 'vue'
|
||||||
import { throttle } from 'lodash'
|
import { throttle } from 'lodash'
|
||||||
import { AnchorLocations, BezierConnector } from '@jsplumb/browser-ui'
|
import { AnchorLocations, BezierConnector } from '@jsplumb/browser-ui'
|
||||||
import { ElMessageBox } from 'element-plus'
|
|
||||||
import AdditionalOutputCard from './AdditionalOutputCard.vue'
|
import AdditionalOutputCard from './AdditionalOutputCard.vue'
|
||||||
import SvgIcon from '@/components/SvgIcon/index.vue'
|
import SvgIcon from '@/components/SvgIcon/index.vue'
|
||||||
import { getActionTypeDisplay, getAgentMapIcon } from '@/layout/components/config.ts'
|
import { getActionTypeDisplay, getAgentMapIcon } from '@/layout/components/config.ts'
|
||||||
@@ -42,6 +41,7 @@ const stepExecutionStatus = ref<Record<string, StepExecutionStatus>>({})
|
|||||||
|
|
||||||
// 用于标记暂停时的"最后动作完成"状态
|
// 用于标记暂停时的"最后动作完成"状态
|
||||||
const isPausing = ref(false) // 正在请求暂停(等待当前动作完成)
|
const isPausing = ref(false) // 正在请求暂停(等待当前动作完成)
|
||||||
|
const isRestarting = ref(false) // 正在重新执行(停止旧执行导致的错误不应显示)
|
||||||
|
|
||||||
// Check if step is ready to execute (has TaskProcess data)
|
// Check if step is ready to execute (has TaskProcess data)
|
||||||
const isStepReady = (step: IRawStepTask) => {
|
const isStepReady = (step: IRawStepTask) => {
|
||||||
@@ -211,7 +211,23 @@ function handleSaveEdit(stepId: string, processId: string, value: string) {
|
|||||||
if (step) {
|
if (step) {
|
||||||
const process = step.TaskProcess.find(p => p.ID === processId)
|
const process = step.TaskProcess.find(p => p.ID === processId)
|
||||||
if (process) {
|
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
|
editMap[key] = false
|
||||||
@@ -341,7 +357,6 @@ const executionProgress = ref({
|
|||||||
const {
|
const {
|
||||||
notifications,
|
notifications,
|
||||||
progress: showProgress,
|
progress: showProgress,
|
||||||
updateProgress,
|
|
||||||
updateProgressDetail,
|
updateProgressDetail,
|
||||||
success,
|
success,
|
||||||
info,
|
info,
|
||||||
@@ -457,9 +472,12 @@ async function executeNextReadyBatch() {
|
|||||||
switch (event.type) {
|
switch (event.type) {
|
||||||
case 'step_start':
|
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 = {
|
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,
|
totalSteps: collaborationProcess.value.length,
|
||||||
currentAction: 0,
|
currentAction: 0,
|
||||||
totalActions: 0,
|
totalActions: 0,
|
||||||
@@ -495,8 +513,13 @@ async function executeNextReadyBatch() {
|
|||||||
: ''
|
: ''
|
||||||
|
|
||||||
// 使用全局步骤索引
|
// 使用全局步骤索引
|
||||||
const globalStepIndexForAction = collaborationProcess.value.findIndex(s => s.StepName === event.step_name)
|
const globalStepIndexForAction = collaborationProcess.value.findIndex(
|
||||||
const stepIndexForAction = globalStepIndexForAction >= 0 ? globalStepIndexForAction + 1 : (event.step_index || 0) + 1
|
s => s.StepName === event.step_name
|
||||||
|
)
|
||||||
|
const stepIndexForAction =
|
||||||
|
globalStepIndexForAction >= 0
|
||||||
|
? globalStepIndexForAction + 1
|
||||||
|
: (event.step_index || 0) + 1
|
||||||
const totalStepsValue = collaborationProcess.value.length
|
const totalStepsValue = collaborationProcess.value.length
|
||||||
|
|
||||||
executionProgress.value = {
|
executionProgress.value = {
|
||||||
@@ -594,6 +617,13 @@ async function executeNextReadyBatch() {
|
|||||||
const errorMessage = event.message || event.error || '未知错误'
|
const errorMessage = event.message || event.error || '未知错误'
|
||||||
console.error('执行错误:', errorMessage)
|
console.error('执行错误:', errorMessage)
|
||||||
|
|
||||||
|
// 如果正在重新执行,不显示错误通知(因为旧执行的步骤中断是预期的)
|
||||||
|
if (isRestarting.value) {
|
||||||
|
console.log('ℹ️ 正在重新执行,忽略旧执行的中断错误')
|
||||||
|
resolve() // 正常完成,不显示错误
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// 关闭进度通知并显示错误通知
|
// 关闭进度通知并显示错误通知
|
||||||
if (currentProgressNotificationId.value) {
|
if (currentProgressNotificationId.value) {
|
||||||
removeNotification(currentProgressNotificationId.value)
|
removeNotification(currentProgressNotificationId.value)
|
||||||
@@ -614,6 +644,13 @@ async function executeNextReadyBatch() {
|
|||||||
(err: Error) => {
|
(err: Error) => {
|
||||||
console.error('流式执行错误:', err)
|
console.error('流式执行错误:', err)
|
||||||
|
|
||||||
|
// 如果正在重新执行,不显示错误通知(因为旧执行的步骤中断是预期的)
|
||||||
|
if (isRestarting.value) {
|
||||||
|
console.log('ℹ️ 正在重新执行,忽略旧执行的中断错误')
|
||||||
|
resolve() // 正常完成,不显示错误
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// 关闭进度通知并显示错误通知
|
// 关闭进度通知并显示错误通知
|
||||||
if (currentProgressNotificationId.value) {
|
if (currentProgressNotificationId.value) {
|
||||||
removeNotification(currentProgressNotificationId.value)
|
removeNotification(currentProgressNotificationId.value)
|
||||||
@@ -695,7 +732,25 @@ function removeNotification(id: string) {
|
|||||||
// Pause/Resume handler
|
// Pause/Resume handler
|
||||||
async function handlePauseResume() {
|
async function handlePauseResume() {
|
||||||
if (isPaused.value) {
|
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 {
|
try {
|
||||||
if (websocket.connected) {
|
if (websocket.connected) {
|
||||||
await websocket.send('resume_execution', {
|
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
|
// Handle execute button click
|
||||||
async function handleExecuteButtonClick() {
|
async function handleExecuteButtonClick() {
|
||||||
// If streaming, show pause/resume functionality
|
// If streaming, show pause/resume functionality
|
||||||
@@ -757,16 +1177,12 @@ async function handleRun() {
|
|||||||
const waitingSteps = stepsReadyStatus.value.waiting
|
const waitingSteps = stepsReadyStatus.value.waiting
|
||||||
|
|
||||||
if (readySteps.length === 0 && waitingSteps.length > 0) {
|
if (readySteps.length === 0 && waitingSteps.length > 0) {
|
||||||
ElMessageBox.confirm(
|
warning(
|
||||||
`All ${waitingSteps.length} steps的数据还在填充中:\n\n${waitingSteps.join(
|
'步骤数据未就绪',
|
||||||
|
`${waitingSteps.length} 个步骤的数据还在填充中:${waitingSteps.join(
|
||||||
'、'
|
'、'
|
||||||
)}\n\n建议等待数据填充完成后再执行。`,
|
)}。建议等待数据填充完成后再执行。`,
|
||||||
'Step data not ready',
|
{ duration: 5000 }
|
||||||
{
|
|
||||||
confirmButtonText: 'I Understand',
|
|
||||||
cancelButtonText: 'Close',
|
|
||||||
type: 'warning'
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -201,7 +201,23 @@ const saveEditing = () => {
|
|||||||
if (editingTaskId.value && editingContent.value.trim()) {
|
if (editingTaskId.value && editingContent.value.trim()) {
|
||||||
const taskToUpdate = collaborationProcess.value.find(item => item.Id === editingTaskId.value)
|
const taskToUpdate = collaborationProcess.value.find(item => item.Id === editingTaskId.value)
|
||||||
if (taskToUpdate) {
|
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
|
editingTaskId.value = null
|
||||||
|
|||||||
@@ -221,6 +221,19 @@ export const useAgentsStore = defineStore('agents', () => {
|
|||||||
|
|
||||||
// 当前的展示的任务流程
|
// 当前的展示的任务流程
|
||||||
const currentTask = ref<IRawStepTask>()
|
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) {
|
function setCurrentTask(task: IRawStepTask) {
|
||||||
const existingTask = currentTask.value
|
const existingTask = currentTask.value
|
||||||
|
|
||||||
@@ -472,6 +485,11 @@ export const useAgentsStore = defineStore('agents', () => {
|
|||||||
// 停止填充状态
|
// 停止填充状态
|
||||||
hasStoppedFilling,
|
hasStoppedFilling,
|
||||||
setHasStoppedFilling,
|
setHasStoppedFilling,
|
||||||
|
// 重新执行相关
|
||||||
|
modifiedSteps,
|
||||||
|
addModifiedStep,
|
||||||
|
clearModifiedSteps,
|
||||||
|
hasModifiedSteps,
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user