2025-10-29 10:22:14 +08:00
|
|
|
|
<script setup lang="ts">
|
2026-01-09 13:54:32 +08:00
|
|
|
|
import { computed, onUnmounted, ref, reactive, nextTick, watch, onMounted } from 'vue'
|
2025-10-29 10:22:14 +08:00
|
|
|
|
import { throttle } from 'lodash'
|
|
|
|
|
|
import { AnchorLocations, BezierConnector } from '@jsplumb/browser-ui'
|
2026-01-22 17:22:30 +08:00
|
|
|
|
import { ElMessage, ElMessageBox } from 'element-plus'
|
2025-12-21 15:28:59 +08:00
|
|
|
|
import AdditionalOutputCard from './AdditionalOutputCard.vue'
|
2025-10-29 10:22:14 +08:00
|
|
|
|
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'
|
2026-01-22 17:22:30 +08:00
|
|
|
|
import { type IRawStepTask, useAgentsStore, type IRawPlanResponse } from '@/stores'
|
2026-01-21 15:18:15 +08:00
|
|
|
|
import api, { type StreamingEvent } from '@/api'
|
2025-12-15 20:46:54 +08:00
|
|
|
|
import ProcessCard from '../TaskProcess/ProcessCard.vue'
|
2025-10-29 10:22:14 +08:00
|
|
|
|
import ExecutePlan from './ExecutePlan.vue'
|
2026-01-22 17:22:30 +08:00
|
|
|
|
import websocket from '@/utils/websocket'
|
2025-10-29 10:22:14 +08:00
|
|
|
|
|
|
|
|
|
|
const emit = defineEmits<{
|
|
|
|
|
|
(e: 'refreshLine'): void
|
|
|
|
|
|
(el: 'setCurrentTask', task: IRawStepTask): void
|
|
|
|
|
|
}>()
|
|
|
|
|
|
|
|
|
|
|
|
const agentsStore = useAgentsStore()
|
2025-12-15 20:46:54 +08:00
|
|
|
|
const drawerVisible = ref(false)
|
2025-12-31 19:04:58 +08:00
|
|
|
|
|
2026-01-09 13:54:32 +08:00
|
|
|
|
const collaborationProcess = computed(() => {
|
|
|
|
|
|
return agentsStore.agentRawPlan.data?.['Collaboration Process'] ?? []
|
2025-10-29 10:22:14 +08:00
|
|
|
|
})
|
|
|
|
|
|
|
2026-01-22 17:22:30 +08:00
|
|
|
|
// 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
|
2025-12-21 15:28:59 +08:00
|
|
|
|
watch(
|
|
|
|
|
|
() => agentsStore.additionalOutputs,
|
|
|
|
|
|
() => {
|
|
|
|
|
|
nextTick(() => {
|
|
|
|
|
|
setTimeout(() => {
|
|
|
|
|
|
jsplumb.repaintEverything()
|
|
|
|
|
|
}, 0)
|
|
|
|
|
|
})
|
|
|
|
|
|
},
|
|
|
|
|
|
{ deep: true }
|
|
|
|
|
|
)
|
|
|
|
|
|
|
2026-01-22 17:22:30 +08:00
|
|
|
|
// Edit logic
|
2026-01-14 17:54:00 +08:00
|
|
|
|
const editMode = ref(false)
|
|
|
|
|
|
const editMap = reactive<Record<string, boolean>>({})
|
|
|
|
|
|
const editBuffer = reactive<Record<string, string | undefined>>({})
|
2025-12-21 15:28:59 +08:00
|
|
|
|
const showPopover = ref(false)
|
2025-12-15 20:46:54 +08:00
|
|
|
|
function getProcessDescription(stepId: string, processId: string) {
|
|
|
|
|
|
const step = collaborationProcess.value.find(s => s.Id === stepId)
|
|
|
|
|
|
if (step) {
|
|
|
|
|
|
const process = step.TaskProcess.find(p => p.ID === processId)
|
|
|
|
|
|
return process?.Description || ''
|
|
|
|
|
|
}
|
|
|
|
|
|
return ''
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function handleOpenEdit(stepId: string, processId: string) {
|
|
|
|
|
|
if (!editMode.value) return
|
|
|
|
|
|
const key = `${stepId}-${processId}`
|
|
|
|
|
|
editMap[key] = true
|
|
|
|
|
|
editBuffer[key] = getProcessDescription(stepId, processId)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function handleSaveEdit(stepId: string, processId: string, value: string) {
|
|
|
|
|
|
const key = `${stepId}-${processId}`
|
|
|
|
|
|
const step = collaborationProcess.value.find(s => s.Id === stepId)
|
|
|
|
|
|
if (step) {
|
|
|
|
|
|
const process = step.TaskProcess.find(p => p.ID === processId)
|
|
|
|
|
|
if (process) {
|
|
|
|
|
|
process.Description = value
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
editMap[key] = false
|
|
|
|
|
|
}
|
2025-10-29 10:22:14 +08:00
|
|
|
|
const jsplumb = new Jsplumb('task-results-main', {
|
|
|
|
|
|
connector: {
|
|
|
|
|
|
type: BezierConnector.type,
|
2025-12-15 20:46:54 +08:00
|
|
|
|
options: { curviness: 30, stub: 10 }
|
|
|
|
|
|
}
|
2025-10-29 10:22:14 +08:00
|
|
|
|
})
|
|
|
|
|
|
|
2026-01-22 17:22:30 +08:00
|
|
|
|
// Refresh connections in real-time when collapsing panels
|
2025-12-15 20:46:54 +08:00
|
|
|
|
let timer: ReturnType<typeof setInterval> | null = null
|
2025-10-29 10:22:14 +08:00
|
|
|
|
function handleCollapse() {
|
|
|
|
|
|
if (timer) {
|
|
|
|
|
|
clearInterval(timer)
|
|
|
|
|
|
}
|
|
|
|
|
|
timer = setInterval(() => {
|
|
|
|
|
|
jsplumb.repaintEverything()
|
|
|
|
|
|
emit('refreshLine')
|
2025-12-15 20:46:54 +08:00
|
|
|
|
}, 1) as ReturnType<typeof setInterval>
|
2025-10-29 10:22:14 +08:00
|
|
|
|
|
2026-01-22 17:22:30 +08:00
|
|
|
|
// Default fully open after 3 seconds
|
2025-10-29 10:22:14 +08:00
|
|
|
|
const timer1 = setTimeout(() => {
|
2025-12-15 20:46:54 +08:00
|
|
|
|
if (timer) {
|
|
|
|
|
|
clearInterval(timer)
|
|
|
|
|
|
timer = null
|
|
|
|
|
|
}
|
2025-10-29 10:22:14 +08:00
|
|
|
|
}, 3000)
|
|
|
|
|
|
|
|
|
|
|
|
onUnmounted(() => {
|
|
|
|
|
|
if (timer) {
|
|
|
|
|
|
clearInterval(timer)
|
|
|
|
|
|
}
|
|
|
|
|
|
if (timer1) {
|
|
|
|
|
|
clearInterval(timer1)
|
|
|
|
|
|
}
|
|
|
|
|
|
})
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-22 17:22:30 +08:00
|
|
|
|
// Create internal connections
|
2025-10-29 10:22:14 +08:00
|
|
|
|
function createInternalLine(id?: string) {
|
|
|
|
|
|
const arr: ConnectArg[] = []
|
|
|
|
|
|
jsplumb.reset()
|
2025-12-15 20:46:54 +08:00
|
|
|
|
collaborationProcess.value.forEach(item => {
|
2025-10-29 10:22:14 +08:00
|
|
|
|
// 创建左侧流程与产出的连线
|
|
|
|
|
|
arr.push({
|
|
|
|
|
|
sourceId: `task-results-${item.Id}-0`,
|
|
|
|
|
|
targetId: `task-results-${item.Id}-1`,
|
2025-12-15 20:46:54 +08:00
|
|
|
|
anchor: [AnchorLocations.Left, AnchorLocations.Left]
|
2025-10-29 10:22:14 +08:00
|
|
|
|
})
|
2025-12-15 20:46:54 +08:00
|
|
|
|
collaborationProcess.value.forEach(jitem => {
|
2025-10-29 10:22:14 +08:00
|
|
|
|
// 创建左侧产出与上一步流程的连线
|
|
|
|
|
|
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: {
|
2025-12-15 20:46:54 +08:00
|
|
|
|
type: 'output'
|
|
|
|
|
|
}
|
2025-10-29 10:22:14 +08:00
|
|
|
|
})
|
|
|
|
|
|
}
|
|
|
|
|
|
// 创建右侧任务程序与InputObject字段的连线
|
2025-12-15 20:46:54 +08:00
|
|
|
|
jitem.TaskProcess.forEach(i => {
|
2025-10-29 10:22:14 +08:00
|
|
|
|
if (i.ImportantInput?.includes(`InputObject:${item.OutputObject}`)) {
|
|
|
|
|
|
const color = getActionTypeDisplay(i.ActionType)?.color ?? ''
|
2025-11-03 09:44:14 +08:00
|
|
|
|
const sourceId = `task-results-${item.Id}-1`
|
|
|
|
|
|
const targetId = `task-results-${jitem.Id}-0-${i.ID}`
|
2025-10-29 10:22:14 +08:00
|
|
|
|
arr.push({
|
|
|
|
|
|
sourceId,
|
|
|
|
|
|
targetId,
|
|
|
|
|
|
anchor: [AnchorLocations.Right, AnchorLocations.Right],
|
|
|
|
|
|
config: {
|
|
|
|
|
|
stops: [
|
|
|
|
|
|
[0, color],
|
2025-12-15 20:46:54 +08:00
|
|
|
|
[1, color]
|
2025-10-29 10:22:14 +08:00
|
|
|
|
],
|
2025-12-15 20:46:54 +08:00
|
|
|
|
transparent: targetId !== id
|
|
|
|
|
|
}
|
2025-10-29 10:22:14 +08:00
|
|
|
|
})
|
|
|
|
|
|
}
|
|
|
|
|
|
})
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
// 创建右侧TaskProcess内部连线
|
2025-12-15 20:46:54 +08:00
|
|
|
|
item.TaskProcess?.forEach(i => {
|
2025-10-29 10:22:14 +08:00
|
|
|
|
if (!i.ImportantInput?.length) {
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
2025-12-15 20:46:54 +08:00
|
|
|
|
item.TaskProcess?.forEach(i2 => {
|
2025-10-29 10:22:14 +08:00
|
|
|
|
if (i.ImportantInput.includes(`ActionResult:${i2.ID}`)) {
|
|
|
|
|
|
const color = getActionTypeDisplay(i.ActionType)?.color ?? ''
|
2025-11-03 09:44:14 +08:00
|
|
|
|
const sourceId = `task-results-${item.Id}-0-${i2.ID}`
|
|
|
|
|
|
const targetId = `task-results-${item.Id}-0-${i.ID}`
|
2025-10-29 10:22:14 +08:00
|
|
|
|
arr.push({
|
|
|
|
|
|
sourceId,
|
|
|
|
|
|
targetId,
|
|
|
|
|
|
anchor: [AnchorLocations.Right, AnchorLocations.Right],
|
|
|
|
|
|
config: {
|
|
|
|
|
|
stops: [
|
|
|
|
|
|
[0, color],
|
2025-12-15 20:46:54 +08:00
|
|
|
|
[1, color]
|
2025-10-29 10:22:14 +08:00
|
|
|
|
],
|
2025-12-15 20:46:54 +08:00
|
|
|
|
transparent: targetId !== id
|
|
|
|
|
|
}
|
2025-10-29 10:22:14 +08:00
|
|
|
|
})
|
|
|
|
|
|
}
|
|
|
|
|
|
})
|
|
|
|
|
|
})
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
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-22 17:22:30 +08:00
|
|
|
|
message: '准备执行任务...'
|
2026-01-21 15:18:15 +08:00
|
|
|
|
})
|
2025-12-15 20:46:54 +08:00
|
|
|
|
|
2026-01-22 17:22:30 +08:00
|
|
|
|
// 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)
|
|
|
|
|
|
|
|
|
|
|
|
// Store current step execution index (for sequential execution)
|
|
|
|
|
|
const currentExecutionIndex = ref(0)
|
|
|
|
|
|
|
|
|
|
|
|
// Execute next batch of ready steps (batch execution to maintain dependencies)
|
|
|
|
|
|
async function executeNextReadyBatch() {
|
|
|
|
|
|
const steps = collaborationProcess.value
|
|
|
|
|
|
|
|
|
|
|
|
// Collect all ready but unexecuted steps (in order, until hitting unready step)
|
|
|
|
|
|
const readySteps: IRawStepTask[] = []
|
|
|
|
|
|
|
|
|
|
|
|
for (let i = 0; i < steps.length; i++) {
|
|
|
|
|
|
const step = steps[i]
|
|
|
|
|
|
if (!step) continue
|
|
|
|
|
|
|
|
|
|
|
|
// 如果步骤已就绪,加入批量执行列表
|
|
|
|
|
|
if (isStepReady(step)) {
|
|
|
|
|
|
const stepName = step.StepName || step.Id || ''
|
|
|
|
|
|
const status = stepExecutionStatus.value[stepName]
|
|
|
|
|
|
|
|
|
|
|
|
// Only collect unexecuted steps
|
|
|
|
|
|
if (!status || status === StepExecutionStatus.READY) {
|
|
|
|
|
|
readySteps.push(step)
|
|
|
|
|
|
}
|
|
|
|
|
|
} 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
|
2026-01-21 15:18:15 +08:00
|
|
|
|
}
|
2026-01-22 17:22:30 +08:00
|
|
|
|
|
|
|
|
|
|
// If paused, ignore events
|
|
|
|
|
|
if (isPaused.value) {
|
|
|
|
|
|
return
|
2026-01-21 15:18:15 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-22 17:22:30 +08:00
|
|
|
|
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}`
|
2026-01-21 15:18:15 +08:00
|
|
|
|
}
|
2026-01-22 17:22:30 +08:00
|
|
|
|
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
|
2026-01-21 15:18:15 +08:00
|
|
|
|
}
|
2026-01-22 17:22:30 +08:00
|
|
|
|
},
|
|
|
|
|
|
// 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()
|
|
|
|
|
|
}
|
|
|
|
|
|
)
|
|
|
|
|
|
})
|
2026-01-21 15:18:15 +08:00
|
|
|
|
|
2026-01-22 17:22:30 +08:00
|
|
|
|
// 批量执行成功后,递归执行下一批
|
|
|
|
|
|
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('所有步骤已完成')
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2026-01-21 15:18:15 +08:00
|
|
|
|
|
2026-01-22 17:22:30 +08:00
|
|
|
|
// 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('暂停执行失败')
|
|
|
|
|
|
// 暂停失败时,保持原状态不变(仍然是非暂停状态)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2026-01-21 15:18:15 +08:00
|
|
|
|
|
2026-01-22 17:22:30 +08:00
|
|
|
|
// Handle execute button click
|
|
|
|
|
|
async function handleExecuteButtonClick() {
|
|
|
|
|
|
// If streaming, show pause/resume functionality
|
|
|
|
|
|
if (isStreaming.value) {
|
|
|
|
|
|
await handlePauseResume()
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
2026-01-21 15:18:15 +08:00
|
|
|
|
|
2026-01-22 17:22:30 +08:00
|
|
|
|
// Otherwise, execute normal task execution logic
|
|
|
|
|
|
await handleRun()
|
|
|
|
|
|
}
|
2026-01-21 15:18:15 +08:00
|
|
|
|
|
2026-01-22 17:22:30 +08:00
|
|
|
|
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'
|
2026-01-21 15:18:15 +08:00
|
|
|
|
}
|
|
|
|
|
|
)
|
2026-01-22 17:22:30 +08:00
|
|
|
|
return
|
2025-10-29 10:22:14 +08:00
|
|
|
|
}
|
2026-01-22 17:22:30 +08:00
|
|
|
|
|
|
|
|
|
|
// 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()
|
2025-10-29 10:22:14 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-22 17:22:30 +08:00
|
|
|
|
// View task process
|
2025-12-15 20:46:54 +08:00
|
|
|
|
async function handleTaskProcess() {
|
|
|
|
|
|
drawerVisible.value = true
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-22 17:22:30 +08:00
|
|
|
|
// Reset execution results
|
2026-01-09 13:54:32 +08:00
|
|
|
|
function handleRefresh() {
|
|
|
|
|
|
agentsStore.setExecutePlan([])
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-22 17:22:30 +08:00
|
|
|
|
// Add scroll state indicator
|
2025-10-29 10:22:14 +08:00
|
|
|
|
const isScrolling = ref(false)
|
2025-12-15 20:46:54 +08:00
|
|
|
|
let scrollTimer: ReturnType<typeof setTimeout> | null = null
|
2025-10-29 10:22:14 +08:00
|
|
|
|
|
2026-01-22 17:22:30 +08:00
|
|
|
|
// Modify scroll handler
|
2025-10-29 10:22:14 +08:00
|
|
|
|
function handleScroll() {
|
|
|
|
|
|
isScrolling.value = true
|
|
|
|
|
|
emit('refreshLine')
|
|
|
|
|
|
// 清除之前的定时器
|
|
|
|
|
|
if (scrollTimer) {
|
|
|
|
|
|
clearTimeout(scrollTimer)
|
|
|
|
|
|
}
|
|
|
|
|
|
jsplumb.repaintEverything()
|
|
|
|
|
|
|
|
|
|
|
|
// 设置滚动结束检测
|
|
|
|
|
|
scrollTimer = setTimeout(() => {
|
|
|
|
|
|
isScrolling.value = false
|
2025-12-15 20:46:54 +08:00
|
|
|
|
}, 300) as ReturnType<typeof setTimeout>
|
2025-10-29 10:22:14 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-22 17:22:30 +08:00
|
|
|
|
// Modify mouse event handler
|
2025-12-15 20:46:54 +08:00
|
|
|
|
const handleMouseEnter = throttle(id => {
|
2025-10-29 10:22:14 +08:00
|
|
|
|
if (!isScrolling.value) {
|
|
|
|
|
|
createInternalLine(id)
|
|
|
|
|
|
}
|
2026-01-09 13:54:32 +08:00
|
|
|
|
}, 0)
|
2025-10-29 10:22:14 +08:00
|
|
|
|
|
|
|
|
|
|
const handleMouseLeave = throttle(() => {
|
|
|
|
|
|
if (!isScrolling.value) {
|
|
|
|
|
|
createInternalLine()
|
|
|
|
|
|
}
|
2026-01-09 13:54:32 +08:00
|
|
|
|
}, 0)
|
2025-10-29 10:22:14 +08:00
|
|
|
|
|
2025-10-31 18:42:31 +08:00
|
|
|
|
function clear() {
|
|
|
|
|
|
jsplumb.reset()
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-22 17:22:30 +08:00
|
|
|
|
// Encapsulate line redraw method
|
2026-01-09 13:54:32 +08:00
|
|
|
|
const redrawInternalLines = (highlightId?: string) => {
|
2026-01-22 17:22:30 +08:00
|
|
|
|
// Waiting DOM 更新完成
|
2026-01-09 13:54:32 +08:00
|
|
|
|
nextTick(() => {
|
|
|
|
|
|
// 清除旧连线
|
|
|
|
|
|
jsplumb.reset()
|
|
|
|
|
|
|
2026-01-22 17:22:30 +08:00
|
|
|
|
// Waiting DOM 稳定后重新绘制
|
2026-01-09 13:54:32 +08:00
|
|
|
|
setTimeout(() => {
|
|
|
|
|
|
createInternalLine(highlightId)
|
|
|
|
|
|
}, 100)
|
|
|
|
|
|
})
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-22 17:22:30 +08:00
|
|
|
|
// Watch collaborationProcess changes, auto redraw connections
|
2026-01-09 13:54:32 +08:00
|
|
|
|
watch(
|
|
|
|
|
|
() => collaborationProcess,
|
|
|
|
|
|
() => {
|
|
|
|
|
|
redrawInternalLines()
|
|
|
|
|
|
},
|
|
|
|
|
|
{ deep: true }
|
|
|
|
|
|
)
|
|
|
|
|
|
|
2026-01-22 17:22:30 +08:00
|
|
|
|
// Initialize connections after component mount
|
2026-01-09 13:54:32 +08:00
|
|
|
|
onMounted(() => {
|
|
|
|
|
|
// 初始化时绘制连线
|
|
|
|
|
|
nextTick(() => {
|
|
|
|
|
|
setTimeout(() => {
|
|
|
|
|
|
createInternalLine()
|
|
|
|
|
|
}, 100)
|
|
|
|
|
|
})
|
|
|
|
|
|
})
|
|
|
|
|
|
|
2026-01-22 17:22:30 +08:00
|
|
|
|
// Button interaction state management
|
2026-01-09 13:54:32 +08:00
|
|
|
|
const buttonHoverState = ref<'process' | 'execute' | 'refresh' | null>(null)
|
2025-12-15 20:46:54 +08:00
|
|
|
|
let buttonHoverTimer: ReturnType<typeof setTimeout> | null = null
|
|
|
|
|
|
const handleProcessMouseEnter = () => {
|
|
|
|
|
|
if (buttonHoverTimer) {
|
|
|
|
|
|
clearTimeout(buttonHoverTimer)
|
|
|
|
|
|
buttonHoverTimer = null
|
|
|
|
|
|
}
|
|
|
|
|
|
buttonHoverState.value = 'process'
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const handleExecuteMouseEnter = () => {
|
|
|
|
|
|
if (buttonHoverTimer) {
|
|
|
|
|
|
clearTimeout(buttonHoverTimer)
|
|
|
|
|
|
buttonHoverTimer = null
|
|
|
|
|
|
}
|
|
|
|
|
|
if (agentsStore.agentRawPlan.data) {
|
|
|
|
|
|
buttonHoverState.value = 'execute'
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-09 13:54:32 +08:00
|
|
|
|
const handleRefreshMouseEnter = () => {
|
|
|
|
|
|
if (buttonHoverTimer) {
|
|
|
|
|
|
clearTimeout(buttonHoverTimer)
|
|
|
|
|
|
buttonHoverTimer = null
|
|
|
|
|
|
}
|
|
|
|
|
|
if (agentsStore.executePlan.length > 0) {
|
|
|
|
|
|
buttonHoverState.value = 'refresh'
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-15 20:46:54 +08:00
|
|
|
|
const handleButtonMouseLeave = () => {
|
|
|
|
|
|
// 添加防抖,防止快速切换时的抖动
|
|
|
|
|
|
if (buttonHoverTimer) {
|
|
|
|
|
|
clearTimeout(buttonHoverTimer)
|
|
|
|
|
|
}
|
|
|
|
|
|
buttonHoverTimer = setTimeout(() => {
|
|
|
|
|
|
buttonHoverState.value = null
|
|
|
|
|
|
}, 50) // 适当减少延迟时间
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-22 17:22:30 +08:00
|
|
|
|
// Cleanup when leaving component
|
2025-12-15 20:46:54 +08:00
|
|
|
|
onUnmounted(() => {
|
|
|
|
|
|
if (buttonHoverTimer) {
|
|
|
|
|
|
clearTimeout(buttonHoverTimer)
|
|
|
|
|
|
}
|
|
|
|
|
|
})
|
2026-01-22 17:22:30 +08:00
|
|
|
|
// Calculate button class names
|
2025-12-15 20:46:54 +08:00
|
|
|
|
const processBtnClass = computed(() => {
|
2026-01-09 13:54:32 +08:00
|
|
|
|
if (buttonHoverState.value === 'refresh' || buttonHoverState.value === 'execute') {
|
|
|
|
|
|
return 'circle'
|
|
|
|
|
|
}
|
2025-12-15 20:46:54 +08:00
|
|
|
|
return buttonHoverState.value === 'process' ? 'ellipse' : 'circle'
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
const executeBtnClass = computed(() => {
|
2026-01-09 13:54:32 +08:00
|
|
|
|
if (buttonHoverState.value === 'process' || buttonHoverState.value === 'refresh') {
|
2025-12-15 20:46:54 +08:00
|
|
|
|
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-22 17:22:30 +08:00
|
|
|
|
// Calculate whether to show button text
|
2025-12-15 20:46:54 +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-22 17:22:30 +08:00
|
|
|
|
// Calculate button titles
|
2025-12-15 20:46:54 +08:00
|
|
|
|
const processBtnTitle = computed(() => {
|
2026-01-22 17:22:30 +08:00
|
|
|
|
return buttonHoverState.value === 'process' ? '查看任务流程' : '点击查看任务流程'
|
2025-12-15 20:46:54 +08:00
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
const executeBtnTitle = computed(() => {
|
2026-01-22 17:22:30 +08:00
|
|
|
|
return showExecuteText.value ? '任务执行' : '点击执行任务'
|
2025-12-15 20:46:54 +08:00
|
|
|
|
})
|
|
|
|
|
|
|
2026-01-09 13:54:32 +08:00
|
|
|
|
const refreshBtnTitle = computed(() => {
|
2026-01-22 17:22:30 +08:00
|
|
|
|
return showRefreshText.value ? '重置结果' : '点击重置执行状态'
|
2026-01-09 13:54:32 +08:00
|
|
|
|
})
|
|
|
|
|
|
|
2025-10-29 10:22:14 +08:00
|
|
|
|
defineExpose({
|
|
|
|
|
|
createInternalLine,
|
2025-12-15 20:46:54 +08:00
|
|
|
|
clear
|
2025-10-29 10:22:14 +08:00
|
|
|
|
})
|
|
|
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
|
|
<template>
|
2025-12-15 20:46:54 +08:00
|
|
|
|
<div
|
|
|
|
|
|
class="h-full flex flex-col relative"
|
|
|
|
|
|
id="task-results"
|
|
|
|
|
|
:class="{ 'is-running': agentsStore.executePlan.length > 0 }"
|
|
|
|
|
|
>
|
2026-01-21 15:18:15 +08:00
|
|
|
|
<!-- 执行进度提示 -->
|
|
|
|
|
|
<div v-if="loading" class="execution-progress-hint">
|
|
|
|
|
|
<el-icon class="is-loading"><Loading /></el-icon>
|
|
|
|
|
|
<span>{{ executionProgress.message }}</span>
|
|
|
|
|
|
<span v-if="executionProgress.totalSteps > 0" class="progress">
|
|
|
|
|
|
{{ executionProgress.currentStep }}/{{ executionProgress.totalSteps }}
|
|
|
|
|
|
</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
2025-10-29 10:22:14 +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]">
|
2025-12-15 20:46:54 +08:00
|
|
|
|
<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]"
|
2025-12-15 20:46:54 +08:00
|
|
|
|
@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>
|
2026-01-22 17:22:30 +08:00
|
|
|
|
<!-- Task Process按钮 -->
|
2025-12-15 20:46:54 +08:00
|
|
|
|
<el-button
|
|
|
|
|
|
:class="processBtnClass"
|
|
|
|
|
|
:color="variables.tertiary"
|
|
|
|
|
|
:title="processBtnTitle"
|
|
|
|
|
|
@mouseenter="handleProcessMouseEnter"
|
|
|
|
|
|
@click="handleTaskProcess"
|
2025-12-31 19:04:58 +08:00
|
|
|
|
style="order: 1"
|
2025-12-15 20:46:54 +08:00
|
|
|
|
>
|
|
|
|
|
|
<svg-icon icon-class="process" />
|
|
|
|
|
|
<span v-if="showProcessText" class="btn-text">任务过程</span>
|
2025-10-29 10:22:14 +08:00
|
|
|
|
</el-button>
|
2025-12-15 20:46:54 +08:00
|
|
|
|
|
2026-01-22 17:22:30 +08:00
|
|
|
|
<!-- Execute按钮 -->
|
2025-12-15 20:46:54 +08:00
|
|
|
|
<el-popover
|
|
|
|
|
|
:disabled="Boolean(agentsStore.agentRawPlan.data)"
|
2026-01-22 17:22:30 +08:00
|
|
|
|
title="请先输入任务再执行"
|
2025-12-15 20:46:54 +08:00
|
|
|
|
:visible="showPopover"
|
|
|
|
|
|
@hide="showPopover = false"
|
2025-12-31 19:04:58 +08:00
|
|
|
|
style="order: 2"
|
2025-12-15 20:46:54 +08:00
|
|
|
|
>
|
2025-10-31 18:42:31 +08:00
|
|
|
|
<template #reference>
|
|
|
|
|
|
<el-button
|
2025-12-15 20:46:54 +08:00
|
|
|
|
:class="executeBtnClass"
|
2025-10-31 18:42:31 +08:00
|
|
|
|
:color="variables.tertiary"
|
2026-01-22 17:22:30 +08:00
|
|
|
|
:title="isStreaming ? (isPaused ? '点击继续执行' : '点击暂停执行') : executeBtnTitle"
|
|
|
|
|
|
:disabled="
|
|
|
|
|
|
!agentsStore.agentRawPlan.data || (!isStreaming && loading) || isButtonLoading
|
|
|
|
|
|
"
|
2025-12-15 20:46:54 +08:00
|
|
|
|
@mouseenter="handleExecuteMouseEnter"
|
2026-01-22 17:22:30 +08:00
|
|
|
|
@click="handleExecuteButtonClick"
|
2025-10-31 18:42:31 +08:00
|
|
|
|
>
|
2026-01-22 17:22:30 +08:00
|
|
|
|
<!-- 按钮短暂加载状态(防止双击) -->
|
|
|
|
|
|
<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 图标 -->
|
2026-01-12 11:17:18 +08:00
|
|
|
|
<svg-icon v-else icon-class="action" />
|
2026-01-22 17:22:30 +08:00
|
|
|
|
|
|
|
|
|
|
<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>
|
2025-10-31 18:42:31 +08:00
|
|
|
|
</el-button>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
</el-popover>
|
2025-12-15 20:46:54 +08:00
|
|
|
|
|
|
|
|
|
|
<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)">
|
2026-01-22 17:22:30 +08:00
|
|
|
|
<el-empty v-if="!collaborationProcess.length" description="暂无任务流程" />
|
2025-12-15 20:46:54 +08:00
|
|
|
|
<div v-else class="process-list">
|
|
|
|
|
|
<!-- 使用ProcessCard组件显示每个AgentSelection -->
|
|
|
|
|
|
<ProcessCard
|
|
|
|
|
|
v-for="step in collaborationProcess"
|
|
|
|
|
|
:key="step.Id"
|
|
|
|
|
|
:step="step"
|
|
|
|
|
|
@open-edit="handleOpenEdit"
|
|
|
|
|
|
@save-edit="handleSaveEdit"
|
|
|
|
|
|
/>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</el-scrollbar>
|
|
|
|
|
|
</el-drawer>
|
2025-10-29 10:22:14 +08:00
|
|
|
|
</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]"
|
2025-10-29 10:22:14 +08:00
|
|
|
|
@scroll="handleScroll"
|
|
|
|
|
|
>
|
|
|
|
|
|
<div id="task-results-main" class="px-[40px] relative">
|
2025-12-21 15:28:59 +08:00
|
|
|
|
<!-- 额外产物卡片 -->
|
|
|
|
|
|
<div
|
|
|
|
|
|
v-if="agentsStore.additionalOutputs && agentsStore.additionalOutputs.length > 0"
|
|
|
|
|
|
class="mt-6"
|
|
|
|
|
|
:key="`additional-outputs-${agentsStore.additionalOutputs.length}`"
|
|
|
|
|
|
>
|
|
|
|
|
|
<div class="space-y-4 mb-4">
|
|
|
|
|
|
<AdditionalOutputCard
|
|
|
|
|
|
v-for="(_, index) in agentsStore.additionalOutputs"
|
|
|
|
|
|
:key="`additional-${index}-${agentsStore.additionalOutputs[index]}`"
|
|
|
|
|
|
:index="index"
|
|
|
|
|
|
/>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
2025-12-15 20:46:54 +08:00
|
|
|
|
<!-- 原有的流程和产物 -->
|
2025-10-29 10:22:14 +08:00
|
|
|
|
<div v-for="item in collaborationProcess" :key="item.Id" class="card-item">
|
|
|
|
|
|
<el-card
|
|
|
|
|
|
class="card-item w-full relative"
|
2025-10-31 18:42:31 +08:00
|
|
|
|
:class="agentsStore.currentTask?.StepName === item.StepName ? 'active-card' : ''"
|
2025-10-29 10:22:14 +08:00
|
|
|
|
: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}`"
|
2026-01-22 17:22:30 +08:00
|
|
|
|
:disabled="!hasActionResult(item, item1.ID)"
|
2025-10-29 10:22:14 +08:00
|
|
|
|
@mouseenter="() => handleMouseEnter(`task-results-${item.Id}-0-${item1.ID}`)"
|
|
|
|
|
|
@mouseleave="handleMouseLeave"
|
|
|
|
|
|
>
|
2026-01-22 17:22:30 +08:00
|
|
|
|
<!-- 执行中且没有结果时显示 loading 图标 -->
|
|
|
|
|
|
<template v-if="loading && !hasActionResult(item, item1.ID)" #icon>
|
2025-10-29 10:22:14 +08:00
|
|
|
|
<SvgIcon icon-class="loading" size="20px" class="animate-spin" />
|
|
|
|
|
|
</template>
|
2026-01-22 17:22:30 +08:00
|
|
|
|
<!-- 没有执行计划时隐藏图标 -->
|
2025-10-31 18:42:31 +08:00
|
|
|
|
<template v-else-if="!agentsStore.executePlan.length" #icon>
|
2025-10-29 10:22:14 +08:00
|
|
|
|
<span></span>
|
|
|
|
|
|
</template>
|
2026-01-22 17:22:30 +08:00
|
|
|
|
<!-- 有结果时不提供 #icon,让 Element Plus 显示默认箭头 -->
|
2025-10-29 10:22:14 +08:00
|
|
|
|
<template #title>
|
2025-12-15 20:46:54 +08:00
|
|
|
|
<!-- 运行之前背景颜色是var(--color-bg-detail-list),运行之后背景颜色是var(--color-bg-detail-list-run) -->
|
|
|
|
|
|
<div
|
|
|
|
|
|
class="flex items-center gap-[15px] rounded-[20px]"
|
|
|
|
|
|
:class="{
|
2026-01-22 17:22:30 +08:00
|
|
|
|
'bg-[var(--color-bg-detail-list)]': !hasActionResult(item, item1.ID),
|
|
|
|
|
|
'bg-[var(--color-bg-detail-list-run)]': hasActionResult(item, item1.ID)
|
2025-12-15 20:46:54 +08:00
|
|
|
|
}"
|
|
|
|
|
|
>
|
2025-10-29 10:22:14 +08:00
|
|
|
|
<!-- 右侧链接点 -->
|
|
|
|
|
|
<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"
|
2025-12-15 20:46:54 +08:00
|
|
|
|
color="#fff"
|
2025-10-29 10:22:14 +08:00
|
|
|
|
size="24px"
|
|
|
|
|
|
/>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="text-[16px]">
|
2025-12-15 20:46:54 +08:00
|
|
|
|
<span
|
|
|
|
|
|
:class="{
|
2026-01-22 17:22:30 +08:00
|
|
|
|
'text-[var(--color-text-result-detail)]': !hasActionResult(
|
|
|
|
|
|
item,
|
|
|
|
|
|
item1.ID
|
|
|
|
|
|
),
|
|
|
|
|
|
'text-[var(--color-text-result-detail-run)]': hasActionResult(
|
|
|
|
|
|
item,
|
|
|
|
|
|
item1.ID
|
|
|
|
|
|
)
|
2025-12-15 20:46:54 +08:00
|
|
|
|
}"
|
|
|
|
|
|
>{{ item1.AgentName }}: </span
|
|
|
|
|
|
>
|
2025-10-29 10:22:14 +08:00
|
|
|
|
<span :style="{ color: getActionTypeDisplay(item1.ActionType)?.color }">
|
|
|
|
|
|
{{ getActionTypeDisplay(item1.ActionType)?.name }}
|
|
|
|
|
|
</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
<ExecutePlan
|
|
|
|
|
|
:action-id="item1.ID"
|
|
|
|
|
|
:node-id="item.StepName"
|
2025-10-31 18:42:31 +08:00
|
|
|
|
:execute-plans="agentsStore.executePlan"
|
2025-10-29 10:22:14 +08:00
|
|
|
|
/>
|
|
|
|
|
|
</el-collapse-item>
|
|
|
|
|
|
</el-collapse>
|
|
|
|
|
|
</el-card>
|
|
|
|
|
|
|
|
|
|
|
|
<el-card
|
2025-10-31 18:42:31 +08:00
|
|
|
|
class="card-item w-full relative output-object-card"
|
|
|
|
|
|
:class="agentsStore.currentTask?.StepName === item.StepName ? 'active-card' : ''"
|
2025-10-29 10:22:14 +08:00
|
|
|
|
:id="`task-results-${item.Id}-1`"
|
|
|
|
|
|
@click="emit('setCurrentTask', item)"
|
|
|
|
|
|
>
|
2025-10-31 18:42:31 +08:00
|
|
|
|
<!-- <div class="text-[18px]">{{ item.OutputObject }}</div>-->
|
2026-01-22 17:22:30 +08:00
|
|
|
|
<el-collapse @change="handleCollapse" :key="agentsStore.executePlan.length">
|
2025-10-31 18:42:31 +08:00
|
|
|
|
<el-collapse-item
|
|
|
|
|
|
class="output-object"
|
2026-01-22 17:22:30 +08:00
|
|
|
|
:disabled="!hasObjectResult(item.OutputObject)"
|
2025-10-31 18:42:31 +08:00
|
|
|
|
>
|
2026-01-22 17:22:30 +08:00
|
|
|
|
<!-- 执行中且没有结果时显示 loading 图标 -->
|
|
|
|
|
|
<template v-if="loading && !hasObjectResult(item.OutputObject)" #icon>
|
2025-10-31 18:42:31 +08:00
|
|
|
|
<SvgIcon icon-class="loading" size="20px" class="animate-spin" />
|
|
|
|
|
|
</template>
|
2026-01-22 17:22:30 +08:00
|
|
|
|
<!-- 没有执行计划时隐藏图标 -->
|
2025-10-31 18:42:31 +08:00
|
|
|
|
<template v-else-if="!agentsStore.executePlan.length" #icon>
|
|
|
|
|
|
<span></span>
|
|
|
|
|
|
</template>
|
2026-01-22 17:22:30 +08:00
|
|
|
|
<!-- 有结果时不提供 #icon,让 Element Plus 显示默认箭头 -->
|
2025-10-31 18:42:31 +08:00
|
|
|
|
<template #title>
|
2025-12-15 20:46:54 +08:00
|
|
|
|
<div
|
|
|
|
|
|
class="text-[18px]"
|
|
|
|
|
|
:class="{
|
2026-01-22 17:22:30 +08:00
|
|
|
|
'text-[var(--color-text-result-detail)]': !hasObjectResult(item.OutputObject),
|
|
|
|
|
|
'text-[var(--color-text-result-detail-run)]': hasObjectResult(
|
|
|
|
|
|
item.OutputObject
|
|
|
|
|
|
)
|
2025-12-15 20:46:54 +08:00
|
|
|
|
}"
|
|
|
|
|
|
>
|
|
|
|
|
|
{{ item.OutputObject }}
|
|
|
|
|
|
</div>
|
2025-10-31 18:42:31 +08:00
|
|
|
|
</template>
|
2025-12-15 20:46:54 +08:00
|
|
|
|
<ExecutePlan
|
|
|
|
|
|
:node-id="item.OutputObject"
|
|
|
|
|
|
:execute-plans="agentsStore.executePlan"
|
|
|
|
|
|
/>
|
2025-10-31 18:42:31 +08:00
|
|
|
|
</el-collapse-item>
|
|
|
|
|
|
</el-collapse>
|
2025-10-29 10:22:14 +08:00
|
|
|
|
</el-card>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
|
|
<style scoped lang="scss">
|
2026-01-21 15:18:15 +08:00
|
|
|
|
// 执行进度提示样式
|
|
|
|
|
|
.execution-progress-hint {
|
|
|
|
|
|
position: fixed;
|
|
|
|
|
|
top: 80px;
|
|
|
|
|
|
right: 20px;
|
|
|
|
|
|
background: var(--el-bg-color);
|
|
|
|
|
|
border: 1px solid var(--el-border-color);
|
|
|
|
|
|
border-radius: 8px;
|
|
|
|
|
|
padding: 12px 16px;
|
|
|
|
|
|
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1);
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
gap: 8px;
|
|
|
|
|
|
z-index: 1000;
|
|
|
|
|
|
animation: slideInRight 0.3s ease-out;
|
|
|
|
|
|
max-width: 400px;
|
|
|
|
|
|
|
|
|
|
|
|
.message {
|
|
|
|
|
|
flex: 1;
|
|
|
|
|
|
font-size: 14px;
|
|
|
|
|
|
color: var(--el-text-color-primary);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.progress {
|
|
|
|
|
|
color: var(--el-color-primary);
|
|
|
|
|
|
font-weight: bold;
|
|
|
|
|
|
margin-left: 4px;
|
|
|
|
|
|
font-size: 14px;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
@keyframes slideInRight {
|
|
|
|
|
|
from {
|
|
|
|
|
|
opacity: 0;
|
|
|
|
|
|
transform: translateX(20px);
|
|
|
|
|
|
}
|
|
|
|
|
|
to {
|
|
|
|
|
|
opacity: 1;
|
|
|
|
|
|
transform: translateX(0);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-15 20:46:54 +08:00
|
|
|
|
#task-results.is-running {
|
|
|
|
|
|
--color-bg-detail-list: var(--color-bg-detail-list-run); // 直接指向 100 % 版本
|
|
|
|
|
|
}
|
2025-10-29 10:22:14 +08:00
|
|
|
|
#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;
|
2025-12-15 20:46:54 +08:00
|
|
|
|
background: var(--color-bg-detail-list-run);
|
2025-10-29 10:22:14 +08:00
|
|
|
|
min-height: 41px;
|
|
|
|
|
|
line-height: 41px;
|
|
|
|
|
|
border-radius: 20px;
|
|
|
|
|
|
transition: border-radius 1ms;
|
|
|
|
|
|
position: relative;
|
|
|
|
|
|
|
|
|
|
|
|
.el-collapse-item__title {
|
2025-12-15 20:46:54 +08:00
|
|
|
|
background: var(--color-bg-detail-list);
|
2025-10-29 10:22:14 +08:00
|
|
|
|
border-radius: 20px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.el-icon {
|
|
|
|
|
|
font-size: 20px;
|
2025-12-15 20:46:54 +08:00
|
|
|
|
font-weight: 900;
|
|
|
|
|
|
background: var(--color-bg-icon-rotate);
|
|
|
|
|
|
border-radius: 50px;
|
|
|
|
|
|
color: #d8d8d8;
|
2025-10-29 10:22:14 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
&.is-active {
|
|
|
|
|
|
border-bottom-left-radius: 0;
|
|
|
|
|
|
border-bottom-right-radius: 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-10-31 18:42:31 +08:00
|
|
|
|
.output-object {
|
|
|
|
|
|
.el-collapse-item__header {
|
|
|
|
|
|
background: none;
|
|
|
|
|
|
|
|
|
|
|
|
.el-collapse-item__title {
|
|
|
|
|
|
background: none;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.el-collapse-item__wrap {
|
|
|
|
|
|
background: none;
|
|
|
|
|
|
|
|
|
|
|
|
.card-item {
|
2025-12-15 20:46:54 +08:00
|
|
|
|
background: var(--color-bg-detail);
|
2025-12-31 19:04:58 +08:00
|
|
|
|
padding: 5px;
|
2025-10-31 18:42:31 +08:00
|
|
|
|
padding-top: 10px;
|
|
|
|
|
|
border-radius: 7px;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-10-29 10:22:14 +08:00
|
|
|
|
.el-collapse-item__wrap {
|
|
|
|
|
|
border: none;
|
2025-12-15 20:46:54 +08:00
|
|
|
|
background: var(--color-bg-detail-list);
|
2025-10-29 10:22:14 +08:00
|
|
|
|
border-bottom-left-radius: 20px;
|
|
|
|
|
|
border-bottom-right-radius: 20px;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
:deep(.el-card) {
|
|
|
|
|
|
.el-card__body {
|
|
|
|
|
|
padding-right: 40px;
|
2025-12-15 20:46:54 +08:00
|
|
|
|
background-color: var(--color-bg-detail);
|
|
|
|
|
|
&:hover {
|
|
|
|
|
|
background-color: var(--color-card-bg-result-hover);
|
|
|
|
|
|
}
|
2025-10-29 10:22:14 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-10-31 18:42:31 +08:00
|
|
|
|
.output-object-card {
|
|
|
|
|
|
:deep(.el-card__body) {
|
|
|
|
|
|
padding-top: 0;
|
|
|
|
|
|
padding-bottom: 0;
|
|
|
|
|
|
padding-right: 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.active-card {
|
2025-12-15 20:46:54 +08:00
|
|
|
|
background: linear-gradient(var(--color-bg-tertiary), var(--color-bg-tertiary)) padding-box,
|
2025-10-31 18:42:31 +08:00
|
|
|
|
linear-gradient(to right, #00c8d2, #315ab4) border-box;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-10-29 10:22:14 +08:00
|
|
|
|
.card-item + .card-item {
|
|
|
|
|
|
margin-top: 10px;
|
|
|
|
|
|
}
|
2025-12-15 20:46:54 +08:00
|
|
|
|
|
|
|
|
|
|
.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
|
|
|
|
//按钮交互样式
|
2025-12-15 20:46:54 +08:00
|
|
|
|
.task-button-group {
|
2025-12-31 19:04:58 +08:00
|
|
|
|
display: flex;
|
|
|
|
|
|
flex-direction: row-reverse;
|
2025-12-15 20:46:54 +08:00
|
|
|
|
.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;
|
2025-12-15 20:46:54 +08:00
|
|
|
|
overflow: hidden !important;
|
|
|
|
|
|
white-space: nowrap !important;
|
2026-01-09 13:54:32 +08:00
|
|
|
|
border: 1px solid transparent !important;
|
|
|
|
|
|
border-color: transparent !important;
|
2025-12-15 20:46:54 +08:00
|
|
|
|
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;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-15 20:46:54 +08:00
|
|
|
|
&:hover {
|
2026-01-09 13:54:32 +08:00
|
|
|
|
transform: translateY(-2px) translateZ(0) !important;
|
2025-12-15 20:46:54 +08:00
|
|
|
|
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;
|
2025-12-15 20:46:54 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
&.is-disabled {
|
|
|
|
|
|
opacity: 0.5;
|
|
|
|
|
|
cursor: not-allowed !important;
|
|
|
|
|
|
|
|
|
|
|
|
&:hover {
|
|
|
|
|
|
transform: none !important;
|
|
|
|
|
|
box-shadow: none !important;
|
|
|
|
|
|
filter: none !important;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-22 17:22:30 +08:00
|
|
|
|
// Circle state
|
2025-12-15 20:46:54 +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;
|
2025-12-15 20:46:54 +08:00
|
|
|
|
padding: 0 !important;
|
|
|
|
|
|
border-radius: 50% !important;
|
|
|
|
|
|
|
|
|
|
|
|
.btn-text {
|
|
|
|
|
|
display: none !important;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-22 17:22:30 +08:00
|
|
|
|
// Ellipse state
|
2025-12-15 20:46:54 +08:00
|
|
|
|
.ellipse {
|
|
|
|
|
|
height: 40px !important;
|
|
|
|
|
|
border-radius: 20px !important;
|
|
|
|
|
|
padding: 0 16px !important;
|
|
|
|
|
|
gap: 8px;
|
|
|
|
|
|
|
2026-01-22 17:22:30 +08:00
|
|
|
|
// Task process button - fixed left, expand right
|
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-15 20:46:54 +08:00
|
|
|
|
}
|
2025-12-31 19:04:58 +08:00
|
|
|
|
|
2026-01-22 17:22:30 +08:00
|
|
|
|
// Task execution button - fixed right, expand left
|
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-15 20:46:54 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-31 19:04:58 +08:00
|
|
|
|
@keyframes fadeInLeft {
|
|
|
|
|
|
from {
|
|
|
|
|
|
opacity: 0;
|
|
|
|
|
|
transform: translateX(5px);
|
|
|
|
|
|
}
|
|
|
|
|
|
to {
|
|
|
|
|
|
opacity: 1;
|
|
|
|
|
|
transform: translateX(0);
|
|
|
|
|
|
}
|
2025-12-15 20:46:54 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-31 19:04:58 +08:00
|
|
|
|
@keyframes fadeInRight {
|
2025-12-15 20:46:54 +08:00
|
|
|
|
from {
|
|
|
|
|
|
opacity: 0;
|
|
|
|
|
|
transform: translateX(-5px);
|
|
|
|
|
|
}
|
|
|
|
|
|
to {
|
|
|
|
|
|
opacity: 1;
|
|
|
|
|
|
transform: translateX(0);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-10-29 10:22:14 +08:00
|
|
|
|
}
|
|
|
|
|
|
</style>
|