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

1068 lines
31 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'
2025-12-21 15:28:59 +08:00
import AdditionalOutputCard from './AdditionalOutputCard.vue'
import SvgIcon from '@/components/SvgIcon/index.vue'
import { getActionTypeDisplay, getAgentMapIcon } from '@/layout/components/config.ts'
import { type ConnectArg, Jsplumb } from '@/layout/components/Main/TaskTemplate/utils.ts'
import variables from '@/styles/variables.module.scss'
import { type IRawStepTask, useAgentsStore } from '@/stores'
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'
const emit = defineEmits<{
(e: 'refreshLine'): void
(el: 'setCurrentTask', task: IRawStepTask): void
}>()
const agentsStore = useAgentsStore()
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-12-21 15:28:59 +08:00
// 监听额外产物变化
watch(
() => agentsStore.additionalOutputs,
() => {
nextTick(() => {
setTimeout(() => {
jsplumb.repaintEverything()
}, 0)
})
},
{ deep: true }
)
// 编辑逻辑
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)
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
}
const jsplumb = new Jsplumb('task-results-main', {
connector: {
type: BezierConnector.type,
options: { curviness: 30, stub: 10 }
}
})
// 操作折叠面板时要实时的刷新连线
let timer: ReturnType<typeof setInterval> | null = null
function handleCollapse() {
if (timer) {
clearInterval(timer)
}
timer = setInterval(() => {
jsplumb.repaintEverything()
emit('refreshLine')
}, 1) as ReturnType<typeof setInterval>
// 默认三秒后已经完全打开
const timer1 = setTimeout(() => {
if (timer) {
clearInterval(timer)
timer = null
}
}, 3000)
onUnmounted(() => {
if (timer) {
clearInterval(timer)
}
if (timer1) {
clearInterval(timer1)
}
})
}
// 创建内部连线
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: '',
message: '正在执行...'
})
async function handleRun() {
2026-01-21 15:18:15 +08:00
// 清空之前的执行结果
agentsStore.setExecutePlan([])
const tempResults: any[] = []
try {
loading.value = true
2026-01-21 15:18:15 +08:00
// 使用优化版流式API阶段1+2步骤级流式 + 动作级智能并行)
api.executePlanOptimized(
agentsStore.agentRawPlan.data!,
// onMessage: 处理每个事件
(event: StreamingEvent) => {
switch (event.type) {
case 'step_start':
// 步骤开始
executionProgress.value = {
currentStep: event.step_index + 1,
totalSteps: event.total_steps,
currentAction: 0,
totalActions: 0,
currentStepName: event.step_name,
message: `正在执行步骤 ${event.step_index + 1}/${event.total_steps}: ${
event.step_name
}`
}
console.log(
`📋 步骤 ${event.step_index + 1}/${event.total_steps} 开始: ${event.step_name}`
)
break
case 'action_complete':
// 动作完成
const parallelInfo = event.batch_info?.is_parallel
? ` [批次 ${event.batch_info!.batch_index + 1}, 并行 ${
event.batch_info!.batch_size
} ]`
: ''
executionProgress.value = {
...executionProgress.value,
currentAction: event.completed_actions,
totalActions: event.total_actions,
message: `步骤 ${event.step_index + 1}/${executionProgress.value.totalSteps}: ${
event.step_name
} - 动作 ${event.completed_actions}/${event.total_actions} 完成${parallelInfo}`
}
console.log(
`✅ 动作 ${event.completed_actions}/${event.total_actions} 完成${parallelInfo}: ${event.action_result.ActionType} by ${event.action_result.AgentName}`
)
// 实时更新到 store找到对应的步骤并添加 ActionHistory
const step = collaborationProcess.value.find(s => s.StepName === event.step_name)
if (step) {
const stepLogNode = tempResults.find(
r => r.NodeId === event.step_name && r.LogNodeType === 'step'
)
if (!stepLogNode) {
// 创建步骤日志节点
const newStepLog = {
LogNodeType: 'step',
NodeId: event.step_name,
InputName_List: step.InputObject_List || [],
OutputName: step.OutputObject || '',
chatLog: [],
inputObject_Record: [],
ActionHistory: [event.action_result]
}
tempResults.push(newStepLog)
} else {
// 追加动作结果
stepLogNode.ActionHistory.push(event.action_result)
}
// 更新 store
agentsStore.setExecutePlan([...tempResults])
}
break
case 'step_complete':
// 步骤完成
console.log(`🎯 步骤完成: ${event.step_name}`)
// 更新步骤日志节点
const existingStepLog = tempResults.find(
r => r.NodeId === event.step_name && r.LogNodeType === 'step'
)
if (existingStepLog) {
existingStepLog.ActionHistory = event.step_log_node.ActionHistory
} else {
tempResults.push(event.step_log_node)
}
// 添加对象日志节点
tempResults.push(event.object_log_node)
// 更新 store
agentsStore.setExecutePlan([...tempResults])
break
case 'execution_complete':
// 执行完成
executionProgress.value.message = `执行完成!共 ${event.total_steps} 个步骤`
console.log(`🎉 执行完成,共 ${event.total_steps} 个步骤`)
// 确保所有结果都保存到 store
agentsStore.setExecutePlan([...tempResults])
break
case 'error':
// 错误
console.error('❌ 执行错误:', event.message)
executionProgress.value.message = `执行错误: ${event.message}`
break
}
},
// onError: 处理错误
(error: Error) => {
console.error('❌ 流式执行错误:', error)
executionProgress.value.message = `执行失败: ${error.message}`
},
// onComplete: 执行完成
() => {
console.log('✅ 流式执行完成')
loading.value = false
}
)
} catch (error) {
console.error('执行失败:', error)
executionProgress.value.message = '执行失败,请重试'
} finally {
2026-01-21 15:18:15 +08:00
// loading 会在 onComplete 中设置为 false
}
}
// 查看任务过程
async function handleTaskProcess() {
drawerVisible.value = true
}
2026-01-09 13:54:32 +08:00
// 重置执行结果
function handleRefresh() {
agentsStore.setExecutePlan([])
}
// 添加滚动状态标识
const isScrolling = ref(false)
let scrollTimer: ReturnType<typeof setTimeout> | null = null
// 修改滚动处理函数
function handleScroll() {
isScrolling.value = true
emit('refreshLine')
// 清除之前的定时器
if (scrollTimer) {
clearTimeout(scrollTimer)
}
jsplumb.repaintEverything()
// 设置滚动结束检测
scrollTimer = setTimeout(() => {
isScrolling.value = false
}, 300) as ReturnType<typeof setTimeout>
}
// 修改鼠标事件处理函数
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-14 17:54:00 +08:00
//封装连线重绘方法
2026-01-09 13:54:32 +08:00
const redrawInternalLines = (highlightId?: string) => {
// 等待 DOM 更新完成
nextTick(() => {
// 清除旧连线
jsplumb.reset()
// 等待 DOM 稳定后重新绘制
setTimeout(() => {
createInternalLine(highlightId)
}, 100)
})
}
2026-01-14 17:54:00 +08:00
//监听 collaborationProcess 变化,自动重绘连线
2026-01-09 13:54:32 +08:00
watch(
() => collaborationProcess,
() => {
redrawInternalLines()
},
{ deep: true }
)
2026-01-14 17:54:00 +08:00
// 组件挂载后初始化连线
2026-01-09 13:54:32 +08:00
onMounted(() => {
// 初始化时绘制连线
nextTick(() => {
setTimeout(() => {
createInternalLine()
}, 100)
})
})
2025-12-21 15:28:59 +08:00
//按钮交互状态管理
2026-01-09 13:54:32 +08:00
const buttonHoverState = ref<'process' | 'execute' | 'refresh' | null>(null)
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'
}
}
const handleButtonMouseLeave = () => {
// 添加防抖,防止快速切换时的抖动
if (buttonHoverTimer) {
clearTimeout(buttonHoverTimer)
}
buttonHoverTimer = setTimeout(() => {
buttonHoverState.value = null
}, 50) // 适当减少延迟时间
}
// 添加离开组件时的清理
onUnmounted(() => {
if (buttonHoverTimer) {
clearTimeout(buttonHoverTimer)
}
})
// 计算按钮类名
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'
})
// 计算按钮是否显示文字
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'
})
// 计算按钮标题
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 ? '重置执行结果' : '点击重置执行状态'
})
defineExpose({
createInternalLine,
clear
})
</script>
<template>
<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-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>
<!-- 任务过程按钮 -->
<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>
<!-- 任务执行按钮 -->
<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="executeBtnTitle"
:disabled="!agentsStore.agentRawPlan.data || loading"
@mouseenter="handleExecuteMouseEnter"
@click="handleRun"
>
<svg-icon v-if="loading" icon-class="loading" class="animate-spin" />
<svg-icon v-else icon-class="action" />
<span v-if="showExecuteText" 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"
@open-edit="handleOpenEdit"
@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">
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>
<!-- 原有的流程和产物 -->
<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="Boolean(!agentsStore.executePlan.length || loading)"
@mouseenter="() => handleMouseEnter(`task-results-${item.Id}-0-${item1.ID}`)"
@mouseleave="handleMouseLeave"
>
<template v-if="loading" #icon>
<SvgIcon icon-class="loading" size="20px" class="animate-spin" />
</template>
<template v-else-if="!agentsStore.executePlan.length" #icon>
<span></span>
</template>
<template #title>
<!-- 运行之前背景颜色是var(--color-bg-detail-list),运行之后背景颜色是var(--color-bg-detail-list-run) -->
<div
class="flex items-center gap-[15px] rounded-[20px]"
:class="{
'bg-[var(--color-bg-detail-list)]': !agentsStore.executePlan.length,
'bg-[var(--color-bg-detail-list-run)]': agentsStore.executePlan.length
}"
>
<!-- 右侧链接点 -->
<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)]': !agentsStore.executePlan.length,
'text-[var(--color-text-result-detail-run)]':
agentsStore.executePlan.length
}"
>{{ 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">
<el-collapse-item
class="output-object"
:disabled="Boolean(!agentsStore.executePlan.length || loading)"
>
<template v-if="loading" #icon>
<SvgIcon icon-class="loading" size="20px" class="animate-spin" />
</template>
<template v-else-if="!agentsStore.executePlan.length" #icon>
<span></span>
</template>
<template #title>
<div
class="text-[18px]"
:class="{
'text-[var(--color-text-result-detail)]': !agentsStore.executePlan.length,
'text-[var(--color-text-result-detail-run)]': agentsStore.executePlan.length
}"
>
{{ 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">
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);
}
}
#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;
}
}
}
// 圆形状态
.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;
}
}
// 椭圆形状态
.ellipse {
height: 40px !important;
border-radius: 20px !important;
padding: 0 16px !important;
gap: 8px;
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
// 任务执行按钮 - 右边固定,向左展开
&: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);
}
}
}
}
</style>