feat:RESTful API架构改WebSocket架构-执行结果可以分步显示版本
This commit is contained in:
@@ -2,15 +2,17 @@
|
||||
import { computed, onUnmounted, ref, reactive, nextTick, watch, onMounted } from 'vue'
|
||||
import { throttle } from 'lodash'
|
||||
import { AnchorLocations, BezierConnector } from '@jsplumb/browser-ui'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import AdditionalOutputCard from './AdditionalOutputCard.vue'
|
||||
import SvgIcon from '@/components/SvgIcon/index.vue'
|
||||
import { getActionTypeDisplay, getAgentMapIcon } from '@/layout/components/config.ts'
|
||||
import { type ConnectArg, Jsplumb } from '@/layout/components/Main/TaskTemplate/utils.ts'
|
||||
import variables from '@/styles/variables.module.scss'
|
||||
import { type IRawStepTask, useAgentsStore } from '@/stores'
|
||||
import { type IRawStepTask, useAgentsStore, type IRawPlanResponse } from '@/stores'
|
||||
import api, { type StreamingEvent } from '@/api'
|
||||
import ProcessCard from '../TaskProcess/ProcessCard.vue'
|
||||
import ExecutePlan from './ExecutePlan.vue'
|
||||
import websocket from '@/utils/websocket'
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'refreshLine'): void
|
||||
@@ -24,7 +26,106 @@ const collaborationProcess = computed(() => {
|
||||
return agentsStore.agentRawPlan.data?.['Collaboration Process'] ?? []
|
||||
})
|
||||
|
||||
// 监听额外产物变化
|
||||
// Step execution status enum
|
||||
enum StepExecutionStatus {
|
||||
WAITING = 'waiting', // Waiting for data
|
||||
READY = 'ready', // Ready to execute
|
||||
RUNNING = 'running', // Currently running
|
||||
COMPLETED = 'completed', // Execution completed
|
||||
FAILED = 'failed' // Execution failed
|
||||
}
|
||||
|
||||
// Execution status for each step
|
||||
const stepExecutionStatus = ref<Record<string, StepExecutionStatus>>({})
|
||||
|
||||
// Check if step is ready to execute (has TaskProcess data)
|
||||
const isStepReady = (step: IRawStepTask) => {
|
||||
return step.TaskProcess && step.TaskProcess.length > 0
|
||||
}
|
||||
|
||||
// 判断动作是否有执行结果
|
||||
const hasActionResult = (step: IRawStepTask, actionId: string) => {
|
||||
const stepResult = agentsStore.executePlan.find(
|
||||
r => r.NodeId === step.StepName && r.LogNodeType === 'step'
|
||||
)
|
||||
if (!stepResult || !stepResult.ActionHistory) {
|
||||
return false
|
||||
}
|
||||
return stepResult.ActionHistory.some(action => action.ID === actionId)
|
||||
}
|
||||
|
||||
// 判断 OutputObject 是否有执行结果
|
||||
const hasObjectResult = (outputObject?: string) => {
|
||||
if (!outputObject) return false
|
||||
return agentsStore.executePlan.some(r => r.NodeId === outputObject && r.LogNodeType === 'object')
|
||||
}
|
||||
|
||||
// Get execution status of a step
|
||||
const getStepStatus = (step: IRawStepTask): StepExecutionStatus => {
|
||||
const stepName = step.StepName || step.Id || ''
|
||||
|
||||
// If status is already recorded, return it
|
||||
if (stepExecutionStatus.value[stepName]) {
|
||||
return stepExecutionStatus.value[stepName]
|
||||
}
|
||||
|
||||
// Check if has TaskProcess data
|
||||
if (isStepReady(step)) {
|
||||
return StepExecutionStatus.READY
|
||||
} else {
|
||||
return StepExecutionStatus.WAITING
|
||||
}
|
||||
}
|
||||
|
||||
// Calculate preparation status of all steps
|
||||
const stepsReadyStatus = computed(() => {
|
||||
const steps = collaborationProcess.value
|
||||
const readySteps: string[] = []
|
||||
const waitingSteps: string[] = []
|
||||
|
||||
steps.forEach(step => {
|
||||
if (isStepReady(step)) {
|
||||
readySteps.push(step.StepName || 'Unknown step')
|
||||
} else {
|
||||
waitingSteps.push(step.StepName || 'Unknown step')
|
||||
}
|
||||
})
|
||||
|
||||
return {
|
||||
ready: readySteps,
|
||||
waiting: waitingSteps,
|
||||
allReady: waitingSteps.length === 0,
|
||||
totalCount: steps.length,
|
||||
readyCount: readySteps.length
|
||||
}
|
||||
})
|
||||
|
||||
// Watch step data changes, update waiting step status
|
||||
watch(
|
||||
() => collaborationProcess.value,
|
||||
newSteps => {
|
||||
newSteps.forEach(step => {
|
||||
const stepName = step.StepName || step.Id || ''
|
||||
const currentStatus = stepExecutionStatus.value[stepName]
|
||||
|
||||
// If step was waiting and now has data, set to ready
|
||||
if (currentStatus === StepExecutionStatus.WAITING && isStepReady(step)) {
|
||||
stepExecutionStatus.value[stepName] = StepExecutionStatus.READY
|
||||
|
||||
// 如果正在执行中,自动执行下一批就绪的步骤
|
||||
if (autoExecuteEnabled.value && loading.value) {
|
||||
executeNextReadyBatch()
|
||||
}
|
||||
}
|
||||
})
|
||||
},
|
||||
{ deep: true }
|
||||
)
|
||||
|
||||
// Enable auto-execution (auto-execute when new steps are ready)
|
||||
const autoExecuteEnabled = ref(true)
|
||||
|
||||
// Watch additional outputs changes
|
||||
watch(
|
||||
() => agentsStore.additionalOutputs,
|
||||
() => {
|
||||
@@ -37,7 +138,7 @@ watch(
|
||||
{ deep: true }
|
||||
)
|
||||
|
||||
// 编辑逻辑
|
||||
// Edit logic
|
||||
const editMode = ref(false)
|
||||
const editMap = reactive<Record<string, boolean>>({})
|
||||
const editBuffer = reactive<Record<string, string | undefined>>({})
|
||||
@@ -76,7 +177,7 @@ const jsplumb = new Jsplumb('task-results-main', {
|
||||
}
|
||||
})
|
||||
|
||||
// 操作折叠面板时要实时的刷新连线
|
||||
// Refresh connections in real-time when collapsing panels
|
||||
let timer: ReturnType<typeof setInterval> | null = null
|
||||
function handleCollapse() {
|
||||
if (timer) {
|
||||
@@ -87,7 +188,7 @@ function handleCollapse() {
|
||||
emit('refreshLine')
|
||||
}, 1) as ReturnType<typeof setInterval>
|
||||
|
||||
// 默认三秒后已经完全打开
|
||||
// Default fully open after 3 seconds
|
||||
const timer1 = setTimeout(() => {
|
||||
if (timer) {
|
||||
clearInterval(timer)
|
||||
@@ -105,7 +206,7 @@ function handleCollapse() {
|
||||
})
|
||||
}
|
||||
|
||||
// 创建内部连线
|
||||
// Create internal connections
|
||||
function createInternalLine(id?: string) {
|
||||
const arr: ConnectArg[] = []
|
||||
jsplumb.reset()
|
||||
@@ -188,160 +289,340 @@ const executionProgress = ref({
|
||||
currentAction: 0,
|
||||
totalActions: 0,
|
||||
currentStepName: '',
|
||||
message: '正在执行...'
|
||||
message: '准备执行任务...'
|
||||
})
|
||||
|
||||
async function handleRun() {
|
||||
// 清空之前的执行结果
|
||||
agentsStore.setExecutePlan([])
|
||||
const tempResults: any[] = []
|
||||
// Pause functionality state
|
||||
const isPaused = ref(false) // Whether paused
|
||||
const isStreaming = ref(false) // Whether streaming data (backend started returning)
|
||||
const isButtonLoading = ref(false) // Button brief loading state (prevent double-click)
|
||||
|
||||
try {
|
||||
loading.value = true
|
||||
// Store current step execution index (for sequential execution)
|
||||
const currentExecutionIndex = ref(0)
|
||||
|
||||
// 使用优化版流式API(阶段1+2:步骤级流式 + 动作级智能并行)
|
||||
api.executePlanOptimized(
|
||||
agentsStore.agentRawPlan.data!,
|
||||
// onMessage: 处理每个事件
|
||||
(event: StreamingEvent) => {
|
||||
switch (event.type) {
|
||||
case 'step_start':
|
||||
// 步骤开始
|
||||
executionProgress.value = {
|
||||
currentStep: event.step_index + 1,
|
||||
totalSteps: event.total_steps,
|
||||
currentAction: 0,
|
||||
totalActions: 0,
|
||||
currentStepName: event.step_name,
|
||||
message: `正在执行步骤 ${event.step_index + 1}/${event.total_steps}: ${
|
||||
event.step_name
|
||||
}`
|
||||
}
|
||||
console.log(
|
||||
`📋 步骤 ${event.step_index + 1}/${event.total_steps} 开始: ${event.step_name}`
|
||||
)
|
||||
break
|
||||
// Execute next batch of ready steps (batch execution to maintain dependencies)
|
||||
async function executeNextReadyBatch() {
|
||||
const steps = collaborationProcess.value
|
||||
|
||||
case 'action_complete':
|
||||
// 动作完成
|
||||
const parallelInfo = event.batch_info?.is_parallel
|
||||
? ` [批次 ${event.batch_info!.batch_index + 1}, 并行 ${
|
||||
event.batch_info!.batch_size
|
||||
} 个]`
|
||||
: ''
|
||||
// Collect all ready but unexecuted steps (in order, until hitting unready step)
|
||||
const readySteps: IRawStepTask[] = []
|
||||
|
||||
executionProgress.value = {
|
||||
...executionProgress.value,
|
||||
currentAction: event.completed_actions,
|
||||
totalActions: event.total_actions,
|
||||
message: `步骤 ${event.step_index + 1}/${executionProgress.value.totalSteps}: ${
|
||||
event.step_name
|
||||
} - 动作 ${event.completed_actions}/${event.total_actions} 完成${parallelInfo}`
|
||||
}
|
||||
for (let i = 0; i < steps.length; i++) {
|
||||
const step = steps[i]
|
||||
if (!step) continue
|
||||
|
||||
console.log(
|
||||
`✅ 动作 ${event.completed_actions}/${event.total_actions} 完成${parallelInfo}: ${event.action_result.ActionType} by ${event.action_result.AgentName}`
|
||||
)
|
||||
// 如果步骤已就绪,加入批量执行列表
|
||||
if (isStepReady(step)) {
|
||||
const stepName = step.StepName || step.Id || ''
|
||||
const status = stepExecutionStatus.value[stepName]
|
||||
|
||||
// 实时更新到 store(找到对应的步骤并添加 ActionHistory)
|
||||
const step = collaborationProcess.value.find(s => s.StepName === event.step_name)
|
||||
if (step) {
|
||||
const stepLogNode = tempResults.find(
|
||||
r => r.NodeId === event.step_name && r.LogNodeType === 'step'
|
||||
)
|
||||
if (!stepLogNode) {
|
||||
// 创建步骤日志节点
|
||||
const newStepLog = {
|
||||
LogNodeType: 'step',
|
||||
NodeId: event.step_name,
|
||||
InputName_List: step.InputObject_List || [],
|
||||
OutputName: step.OutputObject || '',
|
||||
chatLog: [],
|
||||
inputObject_Record: [],
|
||||
ActionHistory: [event.action_result]
|
||||
}
|
||||
tempResults.push(newStepLog)
|
||||
} else {
|
||||
// 追加动作结果
|
||||
stepLogNode.ActionHistory.push(event.action_result)
|
||||
}
|
||||
|
||||
// 更新 store
|
||||
agentsStore.setExecutePlan([...tempResults])
|
||||
}
|
||||
break
|
||||
|
||||
case 'step_complete':
|
||||
// 步骤完成
|
||||
console.log(`🎯 步骤完成: ${event.step_name}`)
|
||||
|
||||
// 更新步骤日志节点
|
||||
const existingStepLog = tempResults.find(
|
||||
r => r.NodeId === event.step_name && r.LogNodeType === 'step'
|
||||
)
|
||||
if (existingStepLog) {
|
||||
existingStepLog.ActionHistory = event.step_log_node.ActionHistory
|
||||
} else {
|
||||
tempResults.push(event.step_log_node)
|
||||
}
|
||||
|
||||
// 添加对象日志节点
|
||||
tempResults.push(event.object_log_node)
|
||||
|
||||
// 更新 store
|
||||
agentsStore.setExecutePlan([...tempResults])
|
||||
break
|
||||
|
||||
case 'execution_complete':
|
||||
// 执行完成
|
||||
executionProgress.value.message = `执行完成!共 ${event.total_steps} 个步骤`
|
||||
console.log(`🎉 执行完成,共 ${event.total_steps} 个步骤`)
|
||||
|
||||
// 确保所有结果都保存到 store
|
||||
agentsStore.setExecutePlan([...tempResults])
|
||||
break
|
||||
|
||||
case 'error':
|
||||
// 错误
|
||||
console.error('❌ 执行错误:', event.message)
|
||||
executionProgress.value.message = `执行错误: ${event.message}`
|
||||
break
|
||||
}
|
||||
},
|
||||
// onError: 处理错误
|
||||
(error: Error) => {
|
||||
console.error('❌ 流式执行错误:', error)
|
||||
executionProgress.value.message = `执行失败: ${error.message}`
|
||||
},
|
||||
// onComplete: 执行完成
|
||||
() => {
|
||||
console.log('✅ 流式执行完成')
|
||||
loading.value = false
|
||||
// Only collect unexecuted steps
|
||||
if (!status || status === StepExecutionStatus.READY) {
|
||||
readySteps.push(step)
|
||||
}
|
||||
)
|
||||
} catch (error) {
|
||||
console.error('执行失败:', error)
|
||||
executionProgress.value.message = '执行失败,请重试'
|
||||
} finally {
|
||||
// loading 会在 onComplete 中设置为 false
|
||||
} else {
|
||||
// Stop at first unready step (maintain step order)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if (readySteps.length > 0) {
|
||||
try {
|
||||
// Mark all steps to be executed as running
|
||||
readySteps.forEach(step => {
|
||||
const stepName = step.StepName || step.Id || ''
|
||||
stepExecutionStatus.value[stepName] = StepExecutionStatus.RUNNING
|
||||
})
|
||||
|
||||
// 构建包含所有已就绪步骤的计划数据(批量发送,保持依赖关系)
|
||||
const batchPlan: IRawPlanResponse = {
|
||||
'General Goal': agentsStore.agentRawPlan.data?.['General Goal'] || '',
|
||||
'Initial Input Object': agentsStore.agentRawPlan.data?.['Initial Input Object'] || [],
|
||||
'Collaboration Process': readySteps // Key: batch send steps
|
||||
}
|
||||
|
||||
const tempResults: any[] = []
|
||||
|
||||
// Execute these steps in batch
|
||||
await new Promise<void>((resolve, reject) => {
|
||||
api.executePlanOptimized(
|
||||
batchPlan,
|
||||
// onMessage: handle each event
|
||||
(event: StreamingEvent) => {
|
||||
// When backend starts returning data, set isStreaming (only once)
|
||||
if (!isStreaming.value) {
|
||||
isStreaming.value = true
|
||||
}
|
||||
|
||||
// If paused, ignore events
|
||||
if (isPaused.value) {
|
||||
return
|
||||
}
|
||||
|
||||
switch (event.type) {
|
||||
case 'step_start':
|
||||
// 使用后端返回的 step_index 和 total_steps
|
||||
executionProgress.value = {
|
||||
currentStep: (event.step_index || 0) + 1,
|
||||
totalSteps: event.total_steps || collaborationProcess.value.length,
|
||||
currentAction: 0,
|
||||
totalActions: 0,
|
||||
currentStepName: event.step_name,
|
||||
message: `正在执行步骤 ${event.step_index + 1}/${
|
||||
event.total_steps || collaborationProcess.value.length
|
||||
}: ${event.step_name}`
|
||||
}
|
||||
break
|
||||
|
||||
case 'action_complete':
|
||||
const parallelInfo = event.batch_info?.is_parallel
|
||||
? ` [并行 ${event.batch_info!.batch_size} 个动作]`
|
||||
: ''
|
||||
|
||||
// 使用后端返回的 step_index,total_steps 使用当前进度中的值
|
||||
const stepIndexForAction = event.step_index || 0
|
||||
const totalStepsValue =
|
||||
executionProgress.value.totalSteps || collaborationProcess.value.length
|
||||
executionProgress.value = {
|
||||
...executionProgress.value,
|
||||
currentAction: event.completed_actions,
|
||||
totalActions: event.total_actions,
|
||||
message: `步骤 ${stepIndexForAction + 1}/${totalStepsValue}: ${
|
||||
event.step_name
|
||||
} - 动作 ${event.completed_actions}/${event.total_actions} 完成${parallelInfo}`
|
||||
}
|
||||
|
||||
// Update store in real-time
|
||||
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]
|
||||
}
|
||||
tempResults.push(newStepLog)
|
||||
agentsStore.setExecutePlan([...currentResults, newStepLog])
|
||||
} else {
|
||||
stepLogNode.ActionHistory.push(event.action_result)
|
||||
agentsStore.setExecutePlan([...currentResults])
|
||||
}
|
||||
}
|
||||
break
|
||||
|
||||
case 'step_complete':
|
||||
stepExecutionStatus.value[event.step_name] = StepExecutionStatus.COMPLETED
|
||||
|
||||
// Update complete step log
|
||||
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) {
|
||||
// 添加新的 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':
|
||||
// 所有步骤都标记为完成
|
||||
readySteps.forEach(step => {
|
||||
const stepName = step.StepName || step.Id || ''
|
||||
if (stepExecutionStatus.value[stepName] !== StepExecutionStatus.COMPLETED) {
|
||||
stepExecutionStatus.value[stepName] = StepExecutionStatus.COMPLETED
|
||||
}
|
||||
})
|
||||
|
||||
resolve()
|
||||
break
|
||||
|
||||
case 'error':
|
||||
console.error(' 执行错误:', event.message)
|
||||
executionProgress.value.message = `执行错误: ${event.message}`
|
||||
readySteps.forEach(step => {
|
||||
const stepName = step.StepName || step.Id || ''
|
||||
stepExecutionStatus.value[stepName] = StepExecutionStatus.FAILED
|
||||
})
|
||||
reject(new Error(event.message))
|
||||
break
|
||||
}
|
||||
},
|
||||
// onError
|
||||
(error: Error) => {
|
||||
console.error(' 流式执行错误:', error)
|
||||
executionProgress.value.message = `执行失败: ${error.message}`
|
||||
readySteps.forEach(step => {
|
||||
const stepName = step.StepName || step.Id || ''
|
||||
stepExecutionStatus.value[stepName] = StepExecutionStatus.FAILED
|
||||
})
|
||||
reject(error)
|
||||
},
|
||||
// onComplete
|
||||
() => {
|
||||
resolve()
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
// 批量执行成功后,递归执行下一批
|
||||
await executeNextReadyBatch()
|
||||
} catch (error) {
|
||||
ElMessage.error('批量执行失败')
|
||||
// 重置所有执行状态
|
||||
loading.value = false
|
||||
isPaused.value = false
|
||||
isStreaming.value = false
|
||||
}
|
||||
} else {
|
||||
// No more ready steps
|
||||
loading.value = false
|
||||
// 重置暂停和流式状态
|
||||
isPaused.value = false
|
||||
isStreaming.value = false
|
||||
|
||||
// Check if there are still waiting steps
|
||||
const hasWaitingSteps = steps.some(step => step && !isStepReady(step))
|
||||
|
||||
if (hasWaitingSteps) {
|
||||
const waitingStepNames = steps
|
||||
.filter(step => step && !isStepReady(step))
|
||||
.map(step => step?.StepName || '未知')
|
||||
executionProgress.value.message = `等待 ${waitingStepNames.length} 个步骤数据填充中...`
|
||||
ElMessage.info(`等待 ${waitingStepNames.length} 个步骤数据填充中...`)
|
||||
} else {
|
||||
executionProgress.value.message = '所有步骤已完成'
|
||||
ElMessage.success('所有步骤已完成')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 查看任务过程
|
||||
// Pause/Resume handler
|
||||
async function handlePauseResume() {
|
||||
if (isPaused.value) {
|
||||
// Resume execution
|
||||
try {
|
||||
if (websocket.connected) {
|
||||
await websocket.send('resume_execution', {
|
||||
goal: agentsStore.agentRawPlan.data?.['General Goal'] || ''
|
||||
})
|
||||
// 只有在收到成功响应后才更新状态
|
||||
isPaused.value = false
|
||||
ElMessage.success('已恢复执行')
|
||||
} else {
|
||||
ElMessage.warning('WebSocket未连接,无法恢复执行')
|
||||
}
|
||||
} catch (error) {
|
||||
ElMessage.error('恢复执行失败')
|
||||
// 恢复失败时,保持原状态不变(仍然是暂停状态)
|
||||
}
|
||||
} else {
|
||||
// Pause execution
|
||||
try {
|
||||
if (websocket.connected) {
|
||||
await websocket.send('pause_execution', {
|
||||
goal: agentsStore.agentRawPlan.data?.['General Goal'] || ''
|
||||
})
|
||||
// 只有在收到成功响应后才更新状态
|
||||
isPaused.value = true
|
||||
ElMessage.success('已暂停执行,可稍后继续')
|
||||
} else {
|
||||
ElMessage.warning('WebSocket未连接,无法暂停')
|
||||
}
|
||||
} catch (error) {
|
||||
ElMessage.error('暂停执行失败')
|
||||
// 暂停失败时,保持原状态不变(仍然是非暂停状态)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Handle execute button click
|
||||
async function handleExecuteButtonClick() {
|
||||
// If streaming, show pause/resume functionality
|
||||
if (isStreaming.value) {
|
||||
await handlePauseResume()
|
||||
return
|
||||
}
|
||||
|
||||
// Otherwise, execute normal task execution logic
|
||||
await handleRun()
|
||||
}
|
||||
|
||||
async function handleRun() {
|
||||
// Check if there are ready steps
|
||||
const readySteps = stepsReadyStatus.value.ready
|
||||
const waitingSteps = stepsReadyStatus.value.waiting
|
||||
|
||||
if (readySteps.length === 0 && waitingSteps.length > 0) {
|
||||
ElMessageBox.confirm(
|
||||
`All ${waitingSteps.length} steps的数据还在填充中:\n\n${waitingSteps.join(
|
||||
'、'
|
||||
)}\n\n建议等待数据填充完成后再执行。`,
|
||||
'Step data not ready',
|
||||
{
|
||||
confirmButtonText: 'I Understand',
|
||||
cancelButtonText: 'Close',
|
||||
type: 'warning'
|
||||
}
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
// Set button brief loading state (prevent double-click)
|
||||
isButtonLoading.value = true
|
||||
setTimeout(() => {
|
||||
isButtonLoading.value = false
|
||||
}, 1000)
|
||||
|
||||
// Reset pause and streaming state
|
||||
isPaused.value = false
|
||||
isStreaming.value = false
|
||||
|
||||
// Start execution
|
||||
loading.value = true
|
||||
currentExecutionIndex.value = 0
|
||||
|
||||
// Clear previous execution results and status
|
||||
agentsStore.setExecutePlan([])
|
||||
stepExecutionStatus.value = {}
|
||||
|
||||
// Start batch executing first batch of ready steps
|
||||
await executeNextReadyBatch()
|
||||
}
|
||||
|
||||
// View task process
|
||||
async function handleTaskProcess() {
|
||||
drawerVisible.value = true
|
||||
}
|
||||
|
||||
// 重置执行结果
|
||||
// Reset execution results
|
||||
function handleRefresh() {
|
||||
agentsStore.setExecutePlan([])
|
||||
}
|
||||
|
||||
// 添加滚动状态标识
|
||||
// Add scroll state indicator
|
||||
const isScrolling = ref(false)
|
||||
let scrollTimer: ReturnType<typeof setTimeout> | null = null
|
||||
|
||||
// 修改滚动处理函数
|
||||
// Modify scroll handler
|
||||
function handleScroll() {
|
||||
isScrolling.value = true
|
||||
emit('refreshLine')
|
||||
@@ -357,7 +638,7 @@ function handleScroll() {
|
||||
}, 300) as ReturnType<typeof setTimeout>
|
||||
}
|
||||
|
||||
// 修改鼠标事件处理函数
|
||||
// Modify mouse event handler
|
||||
const handleMouseEnter = throttle(id => {
|
||||
if (!isScrolling.value) {
|
||||
createInternalLine(id)
|
||||
@@ -374,21 +655,21 @@ function clear() {
|
||||
jsplumb.reset()
|
||||
}
|
||||
|
||||
//封装连线重绘方法
|
||||
// Encapsulate line redraw method
|
||||
const redrawInternalLines = (highlightId?: string) => {
|
||||
// 等待 DOM 更新完成
|
||||
// Waiting DOM 更新完成
|
||||
nextTick(() => {
|
||||
// 清除旧连线
|
||||
jsplumb.reset()
|
||||
|
||||
// 等待 DOM 稳定后重新绘制
|
||||
// Waiting DOM 稳定后重新绘制
|
||||
setTimeout(() => {
|
||||
createInternalLine(highlightId)
|
||||
}, 100)
|
||||
})
|
||||
}
|
||||
|
||||
//监听 collaborationProcess 变化,自动重绘连线
|
||||
// Watch collaborationProcess changes, auto redraw connections
|
||||
watch(
|
||||
() => collaborationProcess,
|
||||
() => {
|
||||
@@ -397,7 +678,7 @@ watch(
|
||||
{ deep: true }
|
||||
)
|
||||
|
||||
// 组件挂载后初始化连线
|
||||
// Initialize connections after component mount
|
||||
onMounted(() => {
|
||||
// 初始化时绘制连线
|
||||
nextTick(() => {
|
||||
@@ -407,7 +688,7 @@ onMounted(() => {
|
||||
})
|
||||
})
|
||||
|
||||
//按钮交互状态管理
|
||||
// Button interaction state management
|
||||
const buttonHoverState = ref<'process' | 'execute' | 'refresh' | null>(null)
|
||||
let buttonHoverTimer: ReturnType<typeof setTimeout> | null = null
|
||||
const handleProcessMouseEnter = () => {
|
||||
@@ -448,13 +729,13 @@ const handleButtonMouseLeave = () => {
|
||||
}, 50) // 适当减少延迟时间
|
||||
}
|
||||
|
||||
// 添加离开组件时的清理
|
||||
// Cleanup when leaving component
|
||||
onUnmounted(() => {
|
||||
if (buttonHoverTimer) {
|
||||
clearTimeout(buttonHoverTimer)
|
||||
}
|
||||
})
|
||||
// 计算按钮类名
|
||||
// Calculate button class names
|
||||
const processBtnClass = computed(() => {
|
||||
if (buttonHoverState.value === 'refresh' || buttonHoverState.value === 'execute') {
|
||||
return 'circle'
|
||||
@@ -476,7 +757,7 @@ const refreshBtnClass = computed(() => {
|
||||
return agentsStore.executePlan.length > 0 ? 'ellipse' : 'circle'
|
||||
})
|
||||
|
||||
// 计算按钮是否显示文字
|
||||
// Calculate whether to show button text
|
||||
const showProcessText = computed(() => {
|
||||
return buttonHoverState.value === 'process'
|
||||
})
|
||||
@@ -490,17 +771,17 @@ const showRefreshText = computed(() => {
|
||||
return buttonHoverState.value === 'refresh'
|
||||
})
|
||||
|
||||
// 计算按钮标题
|
||||
// Calculate button titles
|
||||
const processBtnTitle = computed(() => {
|
||||
return buttonHoverState.value === 'process' ? '任务过程' : '点击查看任务过程'
|
||||
return buttonHoverState.value === 'process' ? '查看任务流程' : '点击查看任务流程'
|
||||
})
|
||||
|
||||
const executeBtnTitle = computed(() => {
|
||||
return showExecuteText.value ? '任务执行' : '点击运行'
|
||||
return showExecuteText.value ? '任务执行' : '点击执行任务'
|
||||
})
|
||||
|
||||
const refreshBtnTitle = computed(() => {
|
||||
return showRefreshText.value ? '重置执行结果' : '点击重置执行状态'
|
||||
return showRefreshText.value ? '重置结果' : '点击重置执行状态'
|
||||
})
|
||||
|
||||
defineExpose({
|
||||
@@ -544,7 +825,7 @@ defineExpose({
|
||||
<svg-icon icon-class="refresh" />
|
||||
<span v-if="showRefreshText" class="btn-text">重置</span>
|
||||
</el-button>
|
||||
<!-- 任务过程按钮 -->
|
||||
<!-- Task Process按钮 -->
|
||||
<el-button
|
||||
:class="processBtnClass"
|
||||
:color="variables.tertiary"
|
||||
@@ -557,10 +838,10 @@ defineExpose({
|
||||
<span v-if="showProcessText" class="btn-text">任务过程</span>
|
||||
</el-button>
|
||||
|
||||
<!-- 任务执行按钮 -->
|
||||
<!-- Execute按钮 -->
|
||||
<el-popover
|
||||
:disabled="Boolean(agentsStore.agentRawPlan.data)"
|
||||
title="请先输入要执行的任务"
|
||||
title="请先输入任务再执行"
|
||||
:visible="showPopover"
|
||||
@hide="showPopover = false"
|
||||
style="order: 2"
|
||||
@@ -569,14 +850,35 @@ defineExpose({
|
||||
<el-button
|
||||
:class="executeBtnClass"
|
||||
:color="variables.tertiary"
|
||||
:title="executeBtnTitle"
|
||||
:disabled="!agentsStore.agentRawPlan.data || loading"
|
||||
:title="isStreaming ? (isPaused ? '点击继续执行' : '点击暂停执行') : executeBtnTitle"
|
||||
:disabled="
|
||||
!agentsStore.agentRawPlan.data || (!isStreaming && loading) || isButtonLoading
|
||||
"
|
||||
@mouseenter="handleExecuteMouseEnter"
|
||||
@click="handleRun"
|
||||
@click="handleExecuteButtonClick"
|
||||
>
|
||||
<svg-icon v-if="loading" icon-class="loading" class="animate-spin" />
|
||||
<!-- 按钮短暂加载状态(防止双击) -->
|
||||
<svg-icon v-if="isButtonLoading" icon-class="loading" class="animate-spin" />
|
||||
|
||||
<!-- 执行中加载状态(已废弃,保留以防万一) -->
|
||||
<svg-icon
|
||||
v-else-if="loading && !isStreaming"
|
||||
icon-class="loading"
|
||||
class="animate-spin"
|
||||
/>
|
||||
|
||||
<!-- 流式传输中且未Pause:显示Pause图标 -->
|
||||
<svg-icon v-else-if="isStreaming && !isPaused" icon-class="Pause" size="20px" />
|
||||
|
||||
<!-- 流式传输中且已Pause:显示播放/Resume图标 -->
|
||||
<svg-icon v-else-if="isStreaming && isPaused" icon-class="video-play" size="20px" />
|
||||
|
||||
<!-- 默认状态:显示 action 图标 -->
|
||||
<svg-icon v-else icon-class="action" />
|
||||
<span v-if="showExecuteText" class="btn-text">任务执行</span>
|
||||
|
||||
<span v-if="showExecuteText && !isStreaming" class="btn-text">任务执行</span>
|
||||
<span v-else-if="isStreaming && isPaused" class="btn-text">继续执行</span>
|
||||
<span v-else-if="isStreaming" class="btn-text">暂停执行</span>
|
||||
</el-button>
|
||||
</template>
|
||||
</el-popover>
|
||||
@@ -597,7 +899,7 @@ defineExpose({
|
||||
</div>
|
||||
</template>
|
||||
<el-scrollbar height="calc(100vh - 120px)">
|
||||
<el-empty v-if="!collaborationProcess.length" description="暂无任务过程" />
|
||||
<el-empty v-if="!collaborationProcess.length" description="暂无任务流程" />
|
||||
<div v-else class="process-list">
|
||||
<!-- 使用ProcessCard组件显示每个AgentSelection -->
|
||||
<ProcessCard
|
||||
@@ -648,23 +950,26 @@ defineExpose({
|
||||
v-for="item1 in item.TaskProcess"
|
||||
:key="`task-results-${item.Id}-${item1.ID}`"
|
||||
:name="`task-results-${item.Id}-${item1.ID}`"
|
||||
:disabled="Boolean(!agentsStore.executePlan.length || loading)"
|
||||
:disabled="!hasActionResult(item, item1.ID)"
|
||||
@mouseenter="() => handleMouseEnter(`task-results-${item.Id}-0-${item1.ID}`)"
|
||||
@mouseleave="handleMouseLeave"
|
||||
>
|
||||
<template v-if="loading" #icon>
|
||||
<!-- 执行中且没有结果时显示 loading 图标 -->
|
||||
<template v-if="loading && !hasActionResult(item, item1.ID)" #icon>
|
||||
<SvgIcon icon-class="loading" size="20px" class="animate-spin" />
|
||||
</template>
|
||||
<!-- 没有执行计划时隐藏图标 -->
|
||||
<template v-else-if="!agentsStore.executePlan.length" #icon>
|
||||
<span></span>
|
||||
</template>
|
||||
<!-- 有结果时不提供 #icon,让 Element Plus 显示默认箭头 -->
|
||||
<template #title>
|
||||
<!-- 运行之前背景颜色是var(--color-bg-detail-list),运行之后背景颜色是var(--color-bg-detail-list-run) -->
|
||||
<div
|
||||
class="flex items-center gap-[15px] rounded-[20px]"
|
||||
:class="{
|
||||
'bg-[var(--color-bg-detail-list)]': !agentsStore.executePlan.length,
|
||||
'bg-[var(--color-bg-detail-list-run)]': agentsStore.executePlan.length
|
||||
'bg-[var(--color-bg-detail-list)]': !hasActionResult(item, item1.ID),
|
||||
'bg-[var(--color-bg-detail-list-run)]': hasActionResult(item, item1.ID)
|
||||
}"
|
||||
>
|
||||
<!-- 右侧链接点 -->
|
||||
@@ -685,9 +990,14 @@ defineExpose({
|
||||
<div class="text-[16px]">
|
||||
<span
|
||||
:class="{
|
||||
'text-[var(--color-text-result-detail)]': !agentsStore.executePlan.length,
|
||||
'text-[var(--color-text-result-detail-run)]':
|
||||
agentsStore.executePlan.length
|
||||
'text-[var(--color-text-result-detail)]': !hasActionResult(
|
||||
item,
|
||||
item1.ID
|
||||
),
|
||||
'text-[var(--color-text-result-detail-run)]': hasActionResult(
|
||||
item,
|
||||
item1.ID
|
||||
)
|
||||
}"
|
||||
>{{ item1.AgentName }}: </span
|
||||
>
|
||||
@@ -713,23 +1023,28 @@ defineExpose({
|
||||
@click="emit('setCurrentTask', item)"
|
||||
>
|
||||
<!-- <div class="text-[18px]">{{ item.OutputObject }}</div>-->
|
||||
<el-collapse @change="handleCollapse">
|
||||
<el-collapse @change="handleCollapse" :key="agentsStore.executePlan.length">
|
||||
<el-collapse-item
|
||||
class="output-object"
|
||||
:disabled="Boolean(!agentsStore.executePlan.length || loading)"
|
||||
:disabled="!hasObjectResult(item.OutputObject)"
|
||||
>
|
||||
<template v-if="loading" #icon>
|
||||
<!-- 执行中且没有结果时显示 loading 图标 -->
|
||||
<template v-if="loading && !hasObjectResult(item.OutputObject)" #icon>
|
||||
<SvgIcon icon-class="loading" size="20px" class="animate-spin" />
|
||||
</template>
|
||||
<!-- 没有执行计划时隐藏图标 -->
|
||||
<template v-else-if="!agentsStore.executePlan.length" #icon>
|
||||
<span></span>
|
||||
</template>
|
||||
<!-- 有结果时不提供 #icon,让 Element Plus 显示默认箭头 -->
|
||||
<template #title>
|
||||
<div
|
||||
class="text-[18px]"
|
||||
:class="{
|
||||
'text-[var(--color-text-result-detail)]': !agentsStore.executePlan.length,
|
||||
'text-[var(--color-text-result-detail-run)]': agentsStore.executePlan.length
|
||||
'text-[var(--color-text-result-detail)]': !hasObjectResult(item.OutputObject),
|
||||
'text-[var(--color-text-result-detail-run)]': hasObjectResult(
|
||||
item.OutputObject
|
||||
)
|
||||
}"
|
||||
>
|
||||
{{ item.OutputObject }}
|
||||
@@ -980,7 +1295,7 @@ defineExpose({
|
||||
}
|
||||
}
|
||||
|
||||
// 圆形状态
|
||||
// Circle state
|
||||
.circle {
|
||||
width: 40px !important;
|
||||
height: 40px !important;
|
||||
@@ -994,14 +1309,14 @@ defineExpose({
|
||||
}
|
||||
}
|
||||
|
||||
// 椭圆形状态
|
||||
// Ellipse state
|
||||
.ellipse {
|
||||
height: 40px !important;
|
||||
border-radius: 20px !important;
|
||||
padding: 0 16px !important;
|
||||
gap: 8px;
|
||||
|
||||
// 任务过程按钮 - 左边固定,向右展开
|
||||
// Task process button - fixed left, expand right
|
||||
&:nth-child(1) {
|
||||
justify-content: flex-start !important;
|
||||
|
||||
@@ -1016,7 +1331,7 @@ defineExpose({
|
||||
}
|
||||
}
|
||||
|
||||
// 任务执行按钮 - 右边固定,向左展开
|
||||
// Task execution button - fixed right, expand left
|
||||
&:nth-child(2) {
|
||||
justify-content: flex-end !important;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user