Files
AgentCoord/frontend/src/layout/components/Main/TaskTemplate/TaskResult/index.vue

1816 lines
54 KiB
Vue
Raw Normal View History

<script setup lang="ts">
2026-01-09 13:54:32 +08:00
import { computed, onUnmounted, ref, reactive, nextTick, watch, onMounted } from 'vue'
import { throttle } from 'lodash'
import { AnchorLocations, BezierConnector } from '@jsplumb/browser-ui'
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, type IRawPlanResponse } from '@/stores'
2026-01-21 15:18:15 +08:00
import api, { type StreamingEvent } from '@/api'
import ProcessCard from '../TaskProcess/ProcessCard.vue'
import ExecutePlan from './ExecutePlan.vue'
import websocket from '@/utils/websocket'
import Notification from '@/components/Notification/Notification.vue'
import { useNotification } from '@/composables/useNotification'
// 定义组件 props
const props = defineProps<{
TaskID?: string // 任务唯一标识,用于写入数据库
}>()
// 定义组件事件
const emit = defineEmits<{
(e: 'refreshLine'): void
(e: 'setCurrentTask', task: IRawStepTask): void
}>()
const agentsStore = useAgentsStore()
const drawerVisible = ref(false)
2026-01-09 13:54:32 +08:00
const collaborationProcess = computed(() => {
return agentsStore.agentRawPlan.data?.['Collaboration Process'] ?? []
})
2026-01-30 15:27:00 +08:00
// 步骤执行状态枚举
enum StepExecutionStatus {
2026-01-30 15:27:00 +08:00
WAITING = 'waiting', // 等待数据
READY = 'ready', // 准备执行
RUNNING = 'running', // 正在执行
COMPLETED = 'completed', // 执行完成
FAILED = 'failed' // 执行失败
}
2026-01-30 15:27:00 +08:00
// 每个步骤的执行状态
const stepExecutionStatus = ref<Record<string, StepExecutionStatus>>({})
2026-01-30 15:27:00 +08:00
const isPausing = ref(false) // 正在请求暂停
const isRestarting = ref(false) // 正在重新执行
2026-01-30 15:27:00 +08:00
// 检查步骤是否准备好执行
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')
}
2026-01-30 15:27:00 +08:00
// 获取折叠面板图标状态
type IconStatus = 'loading' | 'empty' | 'hidden' | 'default'
const getCollapseIconStatus = (hasResult: boolean): IconStatus => {
if (loading.value && !hasResult && !isPaused.value) {
return 'loading'
}
2026-01-30 15:27:00 +08:00
if (isPaused && !hasResult) {
return 'empty'
}
2026-01-30 15:27:00 +08:00
if (!agentsStore.executePlan.length) {
return 'hidden'
}
return 'default'
}
2026-01-30 15:27:00 +08:00
// 计算所有步骤的准备状态
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
}
})
2026-01-30 15:27:00 +08:00
//监听步骤数据变化,更新步骤状态并动态追加新步骤
watch(
() => collaborationProcess.value,
newSteps => {
newSteps.forEach(step => {
const stepId = step.Id || step.StepName || ''
const stepName = step.StepName || step.Id || ''
const currentStatus = stepExecutionStatus.value[stepName]
if (isStepReady(step)) {
// 步骤数据已就绪,更新状态
if (!currentStatus || currentStatus === StepExecutionStatus.WAITING) {
stepExecutionStatus.value[stepName] = StepExecutionStatus.READY
}
// 动态追加新步骤到执行队列
if (loading.value && isStreaming.value && currentExecutionId.value) {
if (!sentStepIds.value.has(stepId)) {
sentStepIds.value.add(stepId)
// 异步追加步骤到后端执行队列
api
.addStepsToExecution(currentExecutionId.value, [step])
.then(addedCount => {
if (addedCount > 0) {
const totalStepsCount = collaborationProcess.value.length
executionProgress.value.totalSteps = totalStepsCount
} else {
sentStepIds.value.delete(stepId)
}
})
.catch(error => {
sentStepIds.value.delete(stepId)
})
}
} else if (loading.value && !isStreaming.value) {
console.log(`⚠️ 步骤 ${stepName} 已就绪,但尚未开始流式传输`)
} else if (loading.value && isStreaming.value && !currentExecutionId.value) {
console.log(`⚠️ 步骤 ${stepName} 已就绪但currentExecutionId为空`)
}
} else {
// 步骤未就绪设置为WAITING
if (!currentStatus) {
stepExecutionStatus.value[stepName] = StepExecutionStatus.WAITING
}
}
})
},
{ deep: true }
)
2026-01-30 15:27:00 +08:00
// 弹窗提示(执行按钮用)
2025-12-21 15:28:59 +08:00
const showPopover = ref(false)
2026-01-30 15:27:00 +08:00
// 处理保存编辑(比较新旧值,记录修改步骤)
function handleSaveEdit(stepId: string, processId: string, value: string) {
const step = collaborationProcess.value.find(s => s.Id === stepId)
if (step) {
const process = step.TaskProcess.find(p => p.ID === processId)
if (process) {
const oldValue = process.Description
if (value !== oldValue) {
process.Description = value
const stepIndex = collaborationProcess.value.findIndex(s => s.Id === stepId)
if (stepIndex >= 0) {
agentsStore.addModifiedStep(stepIndex)
}
}
}
}
}
2026-01-30 15:27:00 +08:00
const jsplumb = new Jsplumb('task-results-main', {
connector: {
type: BezierConnector.type,
options: { curviness: 30, stub: 10 }
}
})
2026-01-30 15:27:00 +08:00
// 折叠面板时实时刷新连线
let timer: ReturnType<typeof setInterval> | null = null
function handleCollapse() {
if (timer) {
clearInterval(timer)
}
timer = setInterval(() => {
jsplumb.repaintEverything()
emit('refreshLine')
}, 1) as ReturnType<typeof setInterval>
2026-01-30 15:27:00 +08:00
// 默认3秒后完全展开
const timer1 = setTimeout(() => {
if (timer) {
clearInterval(timer)
timer = null
}
}, 3000)
onUnmounted(() => {
if (timer) {
clearInterval(timer)
}
if (timer1) {
clearInterval(timer1)
}
})
}
2026-01-30 15:27:00 +08:00
// 创建内部连线
function createInternalLine(id?: string) {
const arr: ConnectArg[] = []
jsplumb.reset()
collaborationProcess.value.forEach(item => {
// 创建左侧流程与产出的连线
arr.push({
sourceId: `task-results-${item.Id}-0`,
targetId: `task-results-${item.Id}-1`,
anchor: [AnchorLocations.Left, AnchorLocations.Left]
})
collaborationProcess.value.forEach(jitem => {
// 创建左侧产出与上一步流程的连线
if (item.InputObject_List!.includes(jitem.OutputObject ?? '')) {
arr.push({
sourceId: `task-results-${jitem.Id}-1`,
targetId: `task-results-${item.Id}-0`,
anchor: [AnchorLocations.Left, AnchorLocations.Left],
config: {
type: 'output'
}
})
}
// 创建右侧任务程序与InputObject字段的连线
jitem.TaskProcess.forEach(i => {
if (i.ImportantInput?.includes(`InputObject:${item.OutputObject}`)) {
const color = getActionTypeDisplay(i.ActionType)?.color ?? ''
const sourceId = `task-results-${item.Id}-1`
const targetId = `task-results-${jitem.Id}-0-${i.ID}`
arr.push({
sourceId,
targetId,
anchor: [AnchorLocations.Right, AnchorLocations.Right],
config: {
stops: [
[0, color],
[1, color]
],
transparent: targetId !== id
}
})
}
})
})
// 创建右侧TaskProcess内部连线
item.TaskProcess?.forEach(i => {
if (!i.ImportantInput?.length) {
return
}
item.TaskProcess?.forEach(i2 => {
if (i.ImportantInput.includes(`ActionResult:${i2.ID}`)) {
const color = getActionTypeDisplay(i.ActionType)?.color ?? ''
const sourceId = `task-results-${item.Id}-0-${i2.ID}`
const targetId = `task-results-${item.Id}-0-${i.ID}`
arr.push({
sourceId,
targetId,
anchor: [AnchorLocations.Right, AnchorLocations.Right],
config: {
stops: [
[0, color],
[1, color]
],
transparent: targetId !== id
}
})
}
})
})
})
jsplumb.connects(arr)
jsplumb.repaintEverything()
}
const loading = ref(false)
2026-01-21 15:18:15 +08:00
const executionProgress = ref({
currentStep: 0,
totalSteps: 0,
currentAction: 0,
totalActions: 0,
currentStepName: ''
2026-01-21 15:18:15 +08:00
})
2026-01-30 15:27:00 +08:00
// 通知系统
const {
notifications,
progress: showProgress,
updateProgressDetail,
2026-01-26 17:25:23 +08:00
updateNotificationTitle,
success,
2026-01-30 15:27:00 +08:00
removeNotification,
info,
warning,
error
} = useNotification()
const currentProgressNotificationId = ref<string | null>(null)
2026-01-30 15:27:00 +08:00
const currentProgressTitle = ref('任务执行中') //追踪当前进度通知的标题
2026-01-26 17:25:23 +08:00
2026-01-30 15:27:00 +08:00
// 更新进度通知的标题
2026-01-26 17:25:23 +08:00
function updateProgressNotificationTitle(title: string) {
currentProgressTitle.value = title
if (currentProgressNotificationId.value) {
updateNotificationTitle(currentProgressNotificationId.value, title)
}
}
2026-01-30 15:27:00 +08:00
// 暂停功能状态
const isPaused = ref(false)
const isStreaming = ref(false)
const isButtonLoading = ref(false)
2026-01-30 15:27:00 +08:00
// 动态执行状态
const currentExecutionId = ref<string | null>(null)
const sentStepIds = ref<Set<string>>(new Set())
2026-01-30 15:27:00 +08:00
// 防止重复执行调用的标记
const isExecutingNextBatch = ref(false)
2026-01-30 15:27:00 +08:00
// 收集所有已就绪但未执行的步骤
function collectReadySteps(): IRawStepTask[] {
const steps = collaborationProcess.value
const readySteps: IRawStepTask[] = []
2026-01-30 15:27:00 +08:00
for (let i = 0; i < steps.length; i++) {
const step = steps[i]
if (!step) continue
2026-01-30 15:27:00 +08:00
const stepId = step.Id || step.StepName || ''
const status = stepExecutionStatus.value[stepId]
if (isStepReady(step) && !sentStepIds.value.has(stepId)) {
if (!status || status === StepExecutionStatus.READY) {
readySteps.push(step)
sentStepIds.value.add(stepId)
}
}
}
2026-01-30 15:27:00 +08:00
return readySteps
}
2026-01-30 15:27:00 +08:00
//确保有 executionId不存在则生成
function ensureExecutionId() {
if (!currentExecutionId.value) {
const generalGoal = agentsStore.agentRawPlan.data?.['General Goal'] || ''
const timestamp = Date.now()
currentExecutionId.value = `${generalGoal.replace(/\s+/g, '_')}_${timestamp}`
}
}
2026-01-30 15:27:00 +08:00
//标记步骤为运行中状态
function markStepsRunning(readySteps: IRawStepTask[]) {
readySteps.forEach(step => {
const stepName = step.StepName || step.Id || ''
stepExecutionStatus.value[stepName] = StepExecutionStatus.RUNNING
})
}
2026-01-30 15:27:00 +08:00
//构建批量执行计划
function buildBatchPlan(readySteps: IRawStepTask[]): IRawPlanResponse {
return {
'General Goal': agentsStore.agentRawPlan.data?.['General Goal'] || '',
'Initial Input Object': agentsStore.agentRawPlan.data?.['Initial Input Object'] || [],
'Collaboration Process': readySteps
}
}
2026-01-30 15:27:00 +08:00
//执行批量步骤
async function executeBatchSteps(readySteps: IRawStepTask[]) {
const batchPlan = buildBatchPlan(readySteps)
2026-01-30 15:27:00 +08:00
await new Promise<void>((resolve, reject) => {
const callbacks = createExecutionCallbacks({
title: '任务执行中',
useProgress: true,
onCompleteCallback: () => resolve()
})
2026-01-30 15:27:00 +08:00
api.executePlanOptimized(
batchPlan,
callbacks.onMessage,
(err: Error) => {
callbacks.onError(err)
const errorMessage = err.message || '未知错误'
if (isRestarting.value) {
resolve()
return
}
error('执行错误', errorMessage, { duration: 5000 })
readySteps.forEach(step => {
const stepName = step.StepName || step.Id || ''
2026-01-30 15:27:00 +08:00
stepExecutionStatus.value[stepName] = StepExecutionStatus.FAILED
})
2026-01-30 15:27:00 +08:00
reject(err)
},
() => callbacks.onComplete(),
true,
{},
true,
(executionId: string) => {
console.log('动态执行已启动执行ID:', executionId)
isStreaming.value = true
currentExecutionId.value = executionId
},
currentExecutionId.value || undefined,
undefined,
undefined,
props.TaskID || undefined
2026-01-30 15:27:00 +08:00
)
})
}
2026-01-30 15:27:00 +08:00
//处理没有已就绪步骤的情况
function handleNoReadySteps() {
loading.value = false
isPaused.value = false
isStreaming.value = false
2026-01-21 15:18:15 +08:00
2026-01-30 15:27:00 +08:00
const steps = collaborationProcess.value
const hasWaitingSteps = steps.some(step => step && !isStepReady(step))
2026-01-30 15:27:00 +08:00
if (hasWaitingSteps) {
const waitingStepNames = steps
.filter(step => step && !isStepReady(step))
.map(step => step?.StepName || '未知')
info('等待数据填充', `等待 ${waitingStepNames.length} 个步骤数据填充中...`)
} else {
success('执行完成', '所有步骤已完成')
}
}
//执行下一批已就绪的步骤(使用动态追加模式)支持在执行过程中动态追加新步骤
async function executeNextReadyBatch() {
if (isExecutingNextBatch.value) {
return
}
isExecutingNextBatch.value = true
try {
const readySteps = collectReadySteps()
if (readySteps.length > 0) {
try {
ensureExecutionId()
markStepsRunning(readySteps)
await executeBatchSteps(readySteps)
} catch (err) {
error('执行失败', '批量执行失败')
loading.value = false
isPaused.value = false
isStreaming.value = false
}
} else {
2026-01-30 15:27:00 +08:00
handleNoReadySteps()
}
} finally {
isExecutingNextBatch.value = false
}
}
2026-01-30 15:27:00 +08:00
// 暂停/继续处理函数
async function handlePauseResume() {
if (isPaused.value) {
2026-01-30 15:27:00 +08:00
// 恢复执行 - 检查是否有修改的步骤
if (agentsStore.hasModifiedSteps()) {
// 有修改的步骤,必须从最早修改的步骤重新执行
const earliestModifiedIndex = Math.min(...agentsStore.modifiedSteps)
success('开始重新执行', `检测到步骤修改,从步骤 ${earliestModifiedIndex + 1} 重新执行`)
await restartFromStep(earliestModifiedIndex)
return
}
2026-01-30 15:27:00 +08:00
// 正常恢复执行
try {
if (websocket.connected) {
2026-02-02 17:09:20 +08:00
// 检查 execution_id 是否存在
if (!currentExecutionId.value) {
warning('无法恢复', '执行ID不存在请等待执行开始')
return
}
await websocket.send('resume_execution', {
2026-02-02 17:09:20 +08:00
execution_id: currentExecutionId.value
})
2026-01-30 15:27:00 +08:00
isPaused.value = false
isPausing.value = false
2026-01-26 17:25:23 +08:00
updateProgressNotificationTitle('任务执行中')
success('已恢复', '已恢复执行')
} else {
warning('无法恢复', 'WebSocket未连接无法恢复执行')
}
2026-01-30 15:27:00 +08:00
} catch (err) {
error('恢复失败', '恢复执行失败')
}
} else {
2026-01-30 15:27:00 +08:00
// 暂停执行
try {
if (websocket.connected) {
2026-02-02 17:09:20 +08:00
// 检查 execution_id 是否存在
if (!currentExecutionId.value) {
warning('无法暂停', '执行ID不存在请等待执行开始')
isPausing.value = false
return
}
// 先设置 isPausing允许接收当前正在执行的动作的结果
isPausing.value = true
2026-01-26 17:25:23 +08:00
info('暂停中', '正在等待当前动作完成')
await websocket.send('pause_execution', {
2026-02-02 17:09:20 +08:00
execution_id: currentExecutionId.value
})
2026-01-30 15:27:00 +08:00
/* isPaused = true
*而是等待当前动作完成后 action_complete 事件中设置
*这样可以确保在动作真正完成后才显示"已暂停"
**/
} else {
warning('无法暂停', 'WebSocket未连接无法暂停')
isPausing.value = false
}
2026-01-30 15:27:00 +08:00
} catch (err) {
error('暂停失败', '暂停执行失败')
isPausing.value = false
}
}
}
2026-01-21 15:18:15 +08:00
2026-01-30 15:27:00 +08:00
/**
* 创建执行回调函数
* @param options 配置选项
* @returns 包含 onMessage, onError, onComplete 的对象
*/
function createExecutionCallbacks(options: {
title?: string
isRestart?: boolean
onCompleteCallback?: () => void
useProgress?: boolean // 是否使用进度追踪模式
}) {
const {
title = '任务执行中',
isRestart = false,
onCompleteCallback,
useProgress = false
} = options
const handleStepStart = (event: any) => {
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
// 创建或更新进度通知(根据状态显示不同标题)
const progressTitle = isPausing.value ? '暂停中' : title
updateProgressNotificationTitle(progressTitle)
if (!currentProgressNotificationId.value) {
currentProgressNotificationId.value = showProgress(
progressTitle,
currentStepNumber,
collaborationProcess.value.length
)
}
updateProgressDetail(
currentProgressNotificationId.value,
`步骤 ${currentStepNumber}/${collaborationProcess.value.length}`,
`正在执行: ${event.step_name}`,
currentStepNumber,
collaborationProcess.value.length
)
}
// 用于 executeNextReadyBatch 的特殊处理函数
const handleStepStartWithProgress = (event: any) => {
// 当后端开始返回数据时,设置 isStreaming
if (!isStreaming.value) {
isStreaming.value = true
}
const globalStepIndex = collaborationProcess.value.findIndex(
s => s.StepName === event.step_name
)
executionProgress.value = {
currentStep: globalStepIndex >= 0 ? globalStepIndex + 1 : (event.step_index || 0) + 1,
totalSteps: collaborationProcess.value.length,
currentAction: 0,
totalActions: 0,
currentStepName: event.step_name
}
handleStepStart(event)
}
const handleActionCompleteWithProgress = (event: any) => {
executionProgress.value = {
...executionProgress.value,
currentAction: event.completed_actions,
totalActions: event.total_actions
}
handleActionComplete(event)
}
const handleActionComplete = (event: any) => {
// 检测是否正在暂停(等待当前动作完成)
if (isPausing.value) {
isPaused.value = true
isPausing.value = false
updateProgressNotificationTitle('已暂停')
}
// 实时更新 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
const totalStepsValue = collaborationProcess.value.length
// 更新进度通知
if (currentProgressNotificationId.value) {
const parallelInfo = event.batch_info?.is_parallel
? ` [并行 ${event.batch_info!.batch_size} 个动作]`
: ''
updateProgressDetail(
currentProgressNotificationId.value,
`步骤 ${stepNumberForAction}/${totalStepsValue}`,
`${event.step_name} - 动作 ${event.completed_actions}/${event.total_actions} 完成${parallelInfo}`,
stepNumberForAction,
totalStepsValue
)
}
}
const handleStepComplete = (event: any) => {
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])
}
}
const handleExecutionComplete = () => {
// 标记所有步骤为完成
collaborationProcess.value.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
}
// 重置标题
currentProgressTitle.value = '任务执行中'
success(isRestart ? '重新执行完成' : '任务执行完成', '所有步骤已执行完成', { duration: 3000 })
loading.value = false
isPaused.value = false
isStreaming.value = false
if (isRestart) {
isRestarting.value = false
}
onCompleteCallback?.()
}
const handleError = (err: Error) => {
console.error(isRestart ? '重新执行错误:' : '流式执行错误:', err)
// 关闭进度通知
if (currentProgressNotificationId.value) {
removeNotification(currentProgressNotificationId.value)
currentProgressNotificationId.value = null
}
// 静默处理所有错误,不显示任何通知
loading.value = false
isPaused.value = false
isStreaming.value = false
if (isRestart) {
isRestarting.value = false
}
}
const 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':
if (useProgress) {
handleStepStartWithProgress(event as any)
} else {
handleStepStart(event as any)
}
break
case 'action_complete':
if (useProgress) {
handleActionCompleteWithProgress(event as any)
} else {
handleActionComplete(event as any)
}
break
case 'step_complete':
handleStepComplete(event as any)
break
case 'execution_complete':
handleExecutionComplete()
break
case 'error':
// 错误处理由 onError 回调处理
break
}
}
const onError = (err: Error) => {
handleError(err)
}
const onComplete = () => {
// 关闭进度通知
if (currentProgressNotificationId.value) {
removeNotification(currentProgressNotificationId.value)
currentProgressNotificationId.value = null
}
loading.value = false
isPaused.value = false
isStreaming.value = false
if (isRestart) {
isRestarting.value = false
}
onCompleteCallback?.()
}
return { onMessage, onError, onComplete }
}
/**
* 从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 // 标记正在重新执行
2026-01-30 15:27:00 +08:00
// 清空修改记录
agentsStore.clearModifiedSteps()
2026-02-02 17:09:20 +08:00
// 保存旧的 execution_id 用于停止
const oldExecutionId = currentExecutionId.value
2026-01-30 15:27:00 +08:00
// 停止旧的执行
2026-02-02 17:09:20 +08:00
if (websocket.connected && oldExecutionId) {
try {
2026-02-02 17:09:20 +08:00
await websocket.send('stop_execution', {
execution_id: oldExecutionId
})
// 等待一下确保后端完全停止
await new Promise(resolve => setTimeout(resolve, 1000))
} catch (err) {
console.warn('⚠️ 停止旧执行失败(可能已经停止):', err)
}
}
2026-02-02 17:09:20 +08:00
// 前端生成新的 execution_id确保前端和后端使用同一个 ID
const generalGoal = agentsStore.agentRawPlan.data?.['General Goal'] || ''
const newExecutionId = `${generalGoal.replace(/\s+/g, '_')}_${Date.now()}`
currentExecutionId.value = newExecutionId
console.log('🔄 [DEBUG] restartFromStep: 生成新的 execution_id =', newExecutionId)
2026-01-30 15:27:00 +08:00
// 构建截断后的 RehearsalLog
const truncatedLog = buildTruncatedRehearsalLog(stepIndex)
2026-01-30 15:27:00 +08:00
//从截断日志中提取 KeyObjects
const existingKeyObjects = buildKeyObjectsFromLog(truncatedLog)
clearExecutionResults(stepIndex)
2026-01-30 15:27:00 +08:00
// 关闭旧的进度通知
if (currentProgressNotificationId.value) {
removeNotification(currentProgressNotificationId.value)
currentProgressNotificationId.value = null
}
2026-01-30 15:27:00 +08:00
//重置步骤状态
resetStepStatus(stepIndex)
2026-01-30 15:27:00 +08:00
// 强制触发 Vue 响应式更新
nextTick(() => {})
2026-01-30 15:27:00 +08:00
//重置执行状态为执行中
isPaused.value = false
isStreaming.value = true
loading.value = true
2026-01-30 15:27:00 +08:00
// 调用执行 API传递截断后的 RehearsalLog 和 KeyObjects
const callbacks = createExecutionCallbacks({
title: '重新执行中',
isRestart: true
})
api.executePlanOptimized(
agentsStore.agentRawPlan.data!,
2026-01-30 15:27:00 +08:00
callbacks.onMessage,
callbacks.onError,
callbacks.onComplete,
true,
existingKeyObjects,
true,
(executionId: string) => {
2026-01-30 15:27:00 +08:00
isStreaming.value = true
currentExecutionId.value = executionId
},
2026-02-02 17:09:20 +08:00
newExecutionId, // 传入前端生成的 execution_id
2026-01-30 15:27:00 +08:00
stepIndex,
truncatedLog,
props.TaskID || undefined // 传入 TaskID 用于更新数据库
)
success('重新执行', `正在从步骤 ${stepIndex + 1} 重新执行...`)
} catch (err) {
error('重新执行失败', '无法启动重新执行')
loading.value = false
2026-01-30 15:27:00 +08:00
isRestarting.value = false
}
}
2026-01-30 15:27:00 +08:00
// 处理执行按钮点击
async function handleExecuteButtonClick() {
if (isStreaming.value) {
await handlePauseResume()
return
}
await handleRun()
}
2026-01-21 15:18:15 +08:00
async function handleRun() {
2026-01-30 15:27:00 +08:00
// 检查是否有已就绪的步骤
const readySteps = stepsReadyStatus.value.ready
const waitingSteps = stepsReadyStatus.value.waiting
if (readySteps.length === 0 && waitingSteps.length > 0) {
warning(
'步骤数据未就绪',
`${waitingSteps.length} 个步骤的数据还在填充中:${waitingSteps.join(
'、'
)}建议等待数据填充完成后再执行`,
{ duration: 5000 }
2026-01-21 15:18:15 +08:00
)
return
}
2026-01-30 15:27:00 +08:00
// 设置按钮短暂加载状态
isButtonLoading.value = true
setTimeout(() => {
isButtonLoading.value = false
}, 1000)
2026-01-30 15:27:00 +08:00
// 重置暂停和流式传输状态
isPaused.value = false
isStreaming.value = false
2026-01-26 17:25:23 +08:00
isPausing.value = false
2026-01-30 15:27:00 +08:00
//重置进度通知标题
2026-01-26 17:25:23 +08:00
currentProgressTitle.value = '任务执行中'
2026-01-30 15:27:00 +08:00
// 开始执行
loading.value = true
2026-01-30 15:27:00 +08:00
// 清除之前的执行结果和状态
agentsStore.setExecutePlan([])
stepExecutionStatus.value = {}
sentStepIds.value.clear()
currentExecutionId.value = null
2026-01-30 15:27:00 +08:00
// 开始批量执行第一批已就绪的步骤
await executeNextReadyBatch()
}
2026-01-30 15:27:00 +08:00
// 查看任务流程
async function handleTaskProcess() {
drawerVisible.value = true
}
2026-01-30 15:27:00 +08:00
// 重置执行结果
2026-02-02 17:09:20 +08:00
async function handleRefresh() {
// 如果有正在执行的任务,先通知后端停止
if (websocket.connected && currentExecutionId.value) {
try {
await websocket.send('stop_execution', {
execution_id: currentExecutionId.value
})
// 等待一下确保后端完全停止
await new Promise(resolve => setTimeout(resolve, 500))
} catch (err) {
console.warn('⚠️ 停止执行失败(可能已经停止):', err)
}
}
// 重置所有状态
2026-01-09 13:54:32 +08:00
agentsStore.setExecutePlan([])
2026-02-02 17:09:20 +08:00
stepExecutionStatus.value = {}
sentStepIds.value.clear()
currentExecutionId.value = null
isPaused.value = false
isStreaming.value = false
isPausing.value = false
loading.value = false
isRestarting.value = false
// 重置进度通知标题
currentProgressTitle.value = '任务执行中'
// 关闭进度通知
if (currentProgressNotificationId.value) {
removeNotification(currentProgressNotificationId.value)
currentProgressNotificationId.value = null
}
success('已重置', '执行状态已重置')
2026-01-09 13:54:32 +08:00
}
2026-01-30 15:27:00 +08:00
// 添加滚动状态指示器
const isScrolling = ref(false)
let scrollTimer: ReturnType<typeof setTimeout> | null = null
2026-01-30 15:27:00 +08:00
// 修改滚动处理函数
function handleScroll() {
isScrolling.value = true
emit('refreshLine')
// 清除之前的定时器
if (scrollTimer) {
clearTimeout(scrollTimer)
}
jsplumb.repaintEverything()
// 设置滚动结束检测
scrollTimer = setTimeout(() => {
isScrolling.value = false
}, 300) as ReturnType<typeof setTimeout>
}
2026-01-30 15:27:00 +08:00
// 修改鼠标事件处理函数
const handleMouseEnter = throttle(id => {
if (!isScrolling.value) {
createInternalLine(id)
}
2026-01-09 13:54:32 +08:00
}, 0)
const handleMouseLeave = throttle(() => {
if (!isScrolling.value) {
createInternalLine()
}
2026-01-09 13:54:32 +08:00
}, 0)
function clear() {
jsplumb.reset()
}
2026-01-30 15:27:00 +08:00
// 封装连线重绘方法
2026-01-09 13:54:32 +08:00
const redrawInternalLines = (highlightId?: string) => {
2026-01-30 15:27:00 +08:00
// 等待DOM更新完成
2026-01-09 13:54:32 +08:00
nextTick(() => {
// 清除旧连线
jsplumb.reset()
2026-01-30 15:27:00 +08:00
// 等待DOM稳定后重新绘制
2026-01-09 13:54:32 +08:00
setTimeout(() => {
createInternalLine(highlightId)
}, 100)
})
}
2026-01-30 15:27:00 +08:00
// 监听 collaborationProcess 变化,自动重绘连线
2026-01-09 13:54:32 +08:00
watch(
() => collaborationProcess,
() => {
redrawInternalLines()
},
{ deep: true }
)
2026-01-30 15:27:00 +08:00
// 组件挂载后初始化连线
2026-01-09 13:54:32 +08:00
onMounted(() => {
// 初始化时绘制连线
nextTick(() => {
setTimeout(() => {
createInternalLine()
}, 100)
})
})
2026-01-30 15:27:00 +08:00
// 按钮交互状态管理
type ButtonState = 'process' | 'execute' | 'refresh' | null
const buttonHoverState = ref<ButtonState>(null)
let buttonHoverTimer: ReturnType<typeof setTimeout> | null = null
2026-01-30 15:27:00 +08:00
// 清除按钮 hover 定时器
const clearButtonHoverTimer = () => {
if (buttonHoverTimer) {
clearTimeout(buttonHoverTimer)
buttonHoverTimer = null
}
}
2026-01-30 15:27:00 +08:00
// 设置按钮 hover 状态
const setButtonHoverState = (state: ButtonState, condition?: boolean) => {
clearButtonHoverTimer()
if (condition ?? true) {
buttonHoverState.value = state
2026-01-09 13:54:32 +08:00
}
}
2026-01-30 15:27:00 +08:00
const handleProcessMouseEnter = () => setButtonHoverState('process')
const handleExecuteMouseEnter = () =>
setButtonHoverState('execute', !!agentsStore.agentRawPlan.data)
const handleRefreshMouseEnter = () =>
setButtonHoverState('refresh', agentsStore.executePlan.length > 0)
const handleButtonMouseLeave = () => {
2026-01-30 15:27:00 +08:00
clearButtonHoverTimer()
buttonHoverTimer = setTimeout(() => {
buttonHoverState.value = null
2026-01-30 15:27:00 +08:00
}, 50)
}
2026-01-30 15:27:00 +08:00
// 离开组件时清理
onUnmounted(() => {
if (buttonHoverTimer) {
clearTimeout(buttonHoverTimer)
}
})
2026-01-30 15:27:00 +08:00
// 计算按钮类名
const processBtnClass = computed(() => {
2026-01-09 13:54:32 +08:00
if (buttonHoverState.value === 'refresh' || buttonHoverState.value === 'execute') {
return 'circle'
}
return buttonHoverState.value === 'process' ? 'ellipse' : 'circle'
})
const executeBtnClass = computed(() => {
2026-01-09 13:54:32 +08:00
if (buttonHoverState.value === 'process' || buttonHoverState.value === 'refresh') {
return 'circle'
}
return agentsStore.agentRawPlan.data ? 'ellipse' : 'circle'
})
2026-01-09 13:54:32 +08:00
const refreshBtnClass = computed(() => {
if (buttonHoverState.value === 'process' || buttonHoverState.value === 'execute') {
return 'circle'
}
return agentsStore.executePlan.length > 0 ? 'ellipse' : 'circle'
})
2026-01-30 15:27:00 +08:00
// 计算是否显示按钮文字
const showProcessText = computed(() => {
return buttonHoverState.value === 'process'
})
const showExecuteText = computed(() => {
if (buttonHoverState.value === 'process') return false
return agentsStore.agentRawPlan.data
})
2026-01-09 13:54:32 +08:00
const showRefreshText = computed(() => {
return buttonHoverState.value === 'refresh'
})
2026-01-30 15:27:00 +08:00
// 计算按钮标题
const processBtnTitle = computed(() => {
return buttonHoverState.value === 'process' ? '查看任务流程' : '点击查看任务流程'
})
const executeBtnTitle = computed(() => {
return showExecuteText.value ? '任务执行' : '点击执行任务'
})
2026-01-09 13:54:32 +08:00
const refreshBtnTitle = computed(() => {
return showRefreshText.value ? '重置结果' : '点击重置执行状态'
2026-01-09 13:54:32 +08:00
})
defineExpose({
createInternalLine,
clear
})
</script>
<template>
<div
class="h-full flex flex-col relative"
id="task-results"
:class="{ 'is-running': agentsStore.executePlan.length > 0 }"
>
<!-- Notification 通知系统 -->
<Notification :notifications="notifications" @close="id => removeNotification(id)" />
2026-01-21 15:18:15 +08:00
<!-- 标题与执行按钮 -->
2025-12-31 19:04:58 +08:00
<div class="text-[18px] font-bold mb-[7px] flex justify-between items-center px-[20px]">
<span class="text-[var(--color-text-title-header)]">执行结果</span>
<div
2026-01-14 17:54:00 +08:00
class="flex items-center justify-end gap-[10px] task-button-group w-[230px]"
@mouseleave="handleButtonMouseLeave"
>
2026-01-09 13:54:32 +08:00
<!-- 刷新按钮 -->
<el-button
:class="refreshBtnClass"
:color="variables.tertiary"
:title="refreshBtnTitle"
:disabled="agentsStore.executePlan.length === 0"
@mouseenter="handleRefreshMouseEnter"
@click="handleRefresh"
style="order: 0"
>
<svg-icon icon-class="refresh" />
<span v-if="showRefreshText" class="btn-text">重置</span>
</el-button>
<!-- Task Process按钮 -->
<el-button
:class="processBtnClass"
:color="variables.tertiary"
:title="processBtnTitle"
@mouseenter="handleProcessMouseEnter"
@click="handleTaskProcess"
2025-12-31 19:04:58 +08:00
style="order: 1"
>
<svg-icon icon-class="process" />
<span v-if="showProcessText" class="btn-text">任务过程</span>
</el-button>
<!-- Execute按钮 -->
<el-popover
:disabled="Boolean(agentsStore.agentRawPlan.data)"
title="请先输入任务再执行"
:visible="showPopover"
@hide="showPopover = false"
2025-12-31 19:04:58 +08:00
style="order: 2"
>
<template #reference>
<el-button
:class="executeBtnClass"
:color="variables.tertiary"
:title="isStreaming ? (isPaused ? '点击继续执行' : '点击暂停执行') : executeBtnTitle"
:disabled="
!agentsStore.agentRawPlan.data || (!isStreaming && loading) || isButtonLoading
"
@mouseenter="handleExecuteMouseEnter"
@click="handleExecuteButtonClick"
>
<!-- 按钮短暂加载状态防止双击 -->
<svg-icon v-if="isButtonLoading" icon-class="loading" class="animate-spin" />
2026-01-30 15:27:00 +08:00
<!-- 执行中加载状态 -->
<svg-icon
v-else-if="loading && !isStreaming"
icon-class="loading"
class="animate-spin"
/>
<!-- 流式传输中且正在暂停等待当前动作完成显示Loading图标 -->
<svg-icon
v-else-if="isStreaming && isPausing"
icon-class="loading"
size="20px"
class="btn-icon animate-spin"
/>
<!-- 流式传输中且未暂停显示Pause图标 -->
<svg-icon
v-else-if="isStreaming && !isPaused && !isPausing"
icon-class="Pause"
size="20px"
class="btn-icon"
/>
<!-- 流式传输中且已暂停显示播放/Resume图标 -->
<svg-icon
v-else-if="isStreaming && isPaused"
icon-class="video-play"
size="20px"
class="btn-icon"
/>
<!-- 默认状态显示 action 图标 -->
<svg-icon v-else icon-class="action" />
<span v-if="showExecuteText && !isStreaming" class="btn-text">任务执行</span>
<span v-else-if="isStreaming && isPaused" class="btn-text">继续执行</span>
<span v-else-if="isStreaming && isPausing" class="btn-text">暂停中...</span>
<span v-else-if="isStreaming" class="btn-text">暂停执行</span>
</el-button>
</template>
</el-popover>
<el-drawer
v-model="drawerVisible"
title="任务过程"
direction="rtl"
size="30%"
:destroy-on-close="false"
>
<!-- 头部工具栏 -->
<template #header>
<div class="drawer-header">
<span class="title">任务过程</span>
<!-- <el-button v-if="!editMode" text icon="Edit" @click="editMode = true" />
<el-button v-else text icon="Check" @click="save" /> -->
</div>
</template>
<el-scrollbar height="calc(100vh - 120px)">
<el-empty v-if="!collaborationProcess.length" description="暂无任务过程" />
<div v-else class="process-list">
<!-- 使用ProcessCard组件显示每个AgentSelection -->
<ProcessCard
v-for="step in collaborationProcess"
:key="step.Id"
:step="step"
@save-edit="handleSaveEdit"
/>
</div>
</el-scrollbar>
</el-drawer>
</div>
</div>
<!-- 内容 -->
<div
v-loading="agentsStore.agentRawPlan.loading"
2025-12-31 19:04:58 +08:00
class="flex-1 overflow-auto relative ml-[20px] mr-[20px]"
@scroll="handleScroll"
>
<div id="task-results-main" class="px-[40px] relative">
2026-01-30 15:27:00 +08:00
<!-- 流程和产物 -->
<div v-for="item in collaborationProcess" :key="item.Id" class="card-item">
<el-card
class="card-item w-full relative"
:class="agentsStore.currentTask?.StepName === item.StepName ? 'active-card' : ''"
:id="`task-results-${item.Id}-0`"
@click="emit('setCurrentTask', item)"
>
<div class="text-[18px] mb-[15px]">{{ item.StepName }}</div>
<!-- 折叠面板 -->
<el-collapse @change="handleCollapse">
<el-collapse-item
v-for="item1 in item.TaskProcess"
:key="`task-results-${item.Id}-${item1.ID}`"
:name="`task-results-${item.Id}-${item1.ID}`"
:disabled="!hasActionResult(item, item1.ID)"
@mouseenter="() => handleMouseEnter(`task-results-${item.Id}-0-${item1.ID}`)"
@mouseleave="handleMouseLeave"
>
2026-01-30 15:27:00 +08:00
<!-- 图标状态loading -->
<template
v-if="getCollapseIconStatus(hasActionResult(item, item1.ID)) === 'loading'"
#icon
>
<SvgIcon icon-class="loading" size="20px" class="animate-spin" />
</template>
2026-01-30 15:27:00 +08:00
<!-- 图标状态empty hidden -->
<template
v-else-if="
['empty', 'hidden'].includes(
getCollapseIconStatus(hasActionResult(item, item1.ID))
)
"
#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)]': !hasActionResult(item, item1.ID),
'bg-[var(--color-bg-detail-list-run)]': hasActionResult(item, item1.ID)
}"
>
<!-- 右侧链接点 -->
<div
class="absolute right-0 top-1/2 transform -translate-y-1/2"
:id="`task-results-${item.Id}-0-${item1.ID}`"
></div>
<div
class="w-[41px] h-[41px] rounded-full flex items-center justify-center"
:style="{ background: getAgentMapIcon(item1.AgentName).color }"
>
<svg-icon
:icon-class="getAgentMapIcon(item1.AgentName).icon"
color="#fff"
size="24px"
/>
</div>
<div class="text-[16px]">
<span
:class="{
'text-[var(--color-text-result-detail)]': !hasActionResult(
item,
item1.ID
),
'text-[var(--color-text-result-detail-run)]': hasActionResult(
item,
item1.ID
)
}"
>{{ item1.AgentName }}:&nbsp; &nbsp;</span
>
<span :style="{ color: getActionTypeDisplay(item1.ActionType)?.color }">
{{ getActionTypeDisplay(item1.ActionType)?.name }}
</span>
</div>
</div>
</template>
<ExecutePlan
:action-id="item1.ID"
:node-id="item.StepName"
:execute-plans="agentsStore.executePlan"
/>
</el-collapse-item>
</el-collapse>
</el-card>
<el-card
class="card-item w-full relative output-object-card"
:class="agentsStore.currentTask?.StepName === item.StepName ? 'active-card' : ''"
:id="`task-results-${item.Id}-1`"
@click="emit('setCurrentTask', item)"
>
<!-- <div class="text-[18px]">{{ item.OutputObject }}</div>-->
<el-collapse @change="handleCollapse" :key="agentsStore.executePlan.length">
<el-collapse-item
class="output-object"
:disabled="!hasObjectResult(item.OutputObject)"
>
2026-01-30 15:27:00 +08:00
<!-- 图标状态loading -->
<template
v-if="getCollapseIconStatus(hasObjectResult(item.OutputObject)) === 'loading'"
#icon
>
<SvgIcon icon-class="loading" size="20px" class="animate-spin" />
</template>
2026-01-30 15:27:00 +08:00
<!-- 图标状态empty hidden -->
<template
v-else-if="
['empty', 'hidden'].includes(
getCollapseIconStatus(hasObjectResult(item.OutputObject))
)
"
#icon
>
<span></span>
</template>
<!-- 有结果时不提供 #icon Element Plus 显示默认箭头 -->
<template #title>
<div
class="text-[18px]"
:class="{
'text-[var(--color-text-result-detail)]': !hasObjectResult(item.OutputObject),
'text-[var(--color-text-result-detail-run)]': hasObjectResult(
item.OutputObject
)
}"
>
{{ item.OutputObject }}
</div>
</template>
<ExecutePlan
:node-id="item.OutputObject"
:execute-plans="agentsStore.executePlan"
/>
</el-collapse-item>
</el-collapse>
</el-card>
</div>
</div>
</div>
</div>
</template>
<style scoped lang="scss">
#task-results.is-running {
--color-bg-detail-list: var(--color-bg-detail-list-run); // 直接指向 100 % 版本
}
#task-results {
:deep(.el-collapse) {
border: none;
border-radius: 20px;
.el-collapse-item + .el-collapse-item {
margin-top: 10px;
}
.el-collapse-item__header {
border: none;
background: var(--color-bg-detail-list-run);
min-height: 41px;
line-height: 41px;
border-radius: 20px;
transition: border-radius 1ms;
position: relative;
.el-collapse-item__title {
background: var(--color-bg-detail-list);
border-radius: 20px;
}
.el-icon {
font-size: 20px;
font-weight: 900;
background: var(--color-bg-icon-rotate);
border-radius: 50px;
color: #d8d8d8;
}
&.is-active {
border-bottom-left-radius: 0;
border-bottom-right-radius: 0;
}
}
.output-object {
.el-collapse-item__header {
background: none;
.el-collapse-item__title {
background: none;
}
}
.el-collapse-item__wrap {
background: none;
.card-item {
background: var(--color-bg-detail);
2025-12-31 19:04:58 +08:00
padding: 5px;
padding-top: 10px;
border-radius: 7px;
}
}
}
.el-collapse-item__wrap {
border: none;
background: var(--color-bg-detail-list);
border-bottom-left-radius: 20px;
border-bottom-right-radius: 20px;
}
}
:deep(.el-card) {
.el-card__body {
padding-right: 40px;
background-color: var(--color-bg-detail);
&:hover {
background-color: var(--color-card-bg-result-hover);
}
}
}
.output-object-card {
:deep(.el-card__body) {
padding-top: 0;
padding-bottom: 0;
padding-right: 0;
}
}
.active-card {
background: linear-gradient(var(--color-bg-tertiary), var(--color-bg-tertiary)) padding-box,
linear-gradient(to right, #00c8d2, #315ab4) border-box;
}
.card-item + .card-item {
margin-top: 10px;
}
.additional-output-card {
border: 1px dashed #dcdfe6;
opacity: 0.9;
box-shadow: var(--color-agent-list-hover-shadow);
&:hover {
border-color: #409eff;
opacity: 1;
}
:deep(.el-card__body) {
padding: 20px;
}
// 编辑区域样式调整
.el-collapse {
border: none;
.el-collapse-item {
.el-collapse-item__header {
background: var(--color-bg-detail);
min-height: 36px;
line-height: 36px;
border-radius: 8px;
.el-collapse-item__title {
background: transparent;
font-size: 14px;
padding-left: 0;
}
.el-icon {
font-size: 16px;
}
}
.el-collapse-item__wrap {
background: var(--color-bg-detail);
border-radius: 0 0 8px 8px;
}
}
}
}
2026-01-14 17:54:00 +08:00
//按钮交互样式
.task-button-group {
2025-12-31 19:04:58 +08:00
display: flex;
flex-direction: row-reverse;
.el-button {
display: inline-flex !important;
align-items: center !important;
justify-content: center !important;
2026-01-14 17:54:00 +08:00
transition: width 0.2s ease-out, padding 0.2s ease-out, border-radius 0.2s ease-out,
transform 0.2s ease-out, box-shadow 0.2s ease-out, filter 0.2s ease-out !important;
overflow: hidden !important;
white-space: nowrap !important;
2026-01-09 13:54:32 +08:00
border: 1px solid transparent !important;
border-color: transparent !important;
color: var(--color-text-primary) !important;
position: relative;
2026-01-09 13:54:32 +08:00
background-color: var(--color-bg-tertiary) !important;
2025-12-31 19:04:58 +08:00
gap: 0px !important;
outline: none !important;
box-shadow: none !important;
-webkit-tap-highlight-color: transparent !important;
2026-01-09 13:54:32 +08:00
backface-visibility: hidden !important;
-webkit-backface-visibility: hidden !important;
transform: translateZ(0) !important;
will-change: transform, width, padding, border-radius !important;
&::before,
&::after {
display: none !important;
}
&:hover {
2026-01-09 13:54:32 +08:00
transform: translateY(-2px) translateZ(0) !important;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15) !important;
filter: brightness(1.1) !important;
2026-01-09 13:54:32 +08:00
border-color: transparent !important;
}
&.is-disabled {
opacity: 0.5;
cursor: not-allowed !important;
&:hover {
transform: none !important;
box-shadow: none !important;
filter: none !important;
}
}
}
2026-01-30 15:27:00 +08:00
// 圆形状态
.circle {
width: 40px !important;
height: 40px !important;
min-width: 40px !important;
2026-01-14 17:54:00 +08:00
max-width: 40px !important;
padding: 0 !important;
border-radius: 50% !important;
.btn-text {
display: none !important;
}
}
2026-01-30 15:27:00 +08:00
// 椭圆状态
.ellipse {
height: 40px !important;
border-radius: 20px !important;
padding: 0 16px !important;
gap: 8px !important;
2026-01-30 15:27:00 +08:00
// 任务流程按钮 - 固定在左侧,向右展开
2025-12-31 19:04:58 +08:00
&:nth-child(1) {
justify-content: flex-start !important;
.btn-text {
display: inline-block !important;
font-size: 14px;
font-weight: 500;
margin-right: 8px;
margin-left: 0;
opacity: 1;
animation: fadeInLeft 0.3s ease forwards;
}
}
2025-12-31 19:04:58 +08:00
2026-01-30 15:27:00 +08:00
// 任务执行按钮 - 固定在右侧,向左展开
2025-12-31 19:04:58 +08:00
&:nth-child(2) {
justify-content: flex-end !important;
.btn-text {
display: inline-block !important;
font-size: 14px;
font-weight: 500;
margin-left: 8px;
margin-right: 0;
opacity: 1;
animation: fadeInRight 0.3s ease forwards;
}
}
// .btn-text {
// display: inline-block !important;
// font-size: 14px;
// font-weight: 500;
// margin-left: 4px;
// opacity: 1;
// animation: fadeIn 0.3s ease forwards;
// }
}
2025-12-31 19:04:58 +08:00
@keyframes fadeInLeft {
from {
opacity: 0;
transform: translateX(5px);
}
to {
opacity: 1;
transform: translateX(0);
}
}
2025-12-31 19:04:58 +08:00
@keyframes fadeInRight {
from {
opacity: 0;
transform: translateX(-5px);
}
to {
opacity: 1;
transform: translateX(0);
}
}
// 按钮图标间距
.btn-icon {
margin-right: 8px !important;
}
}
}
</style>