@@ -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 ) {
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 < 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
}