366 lines
11 KiB
Vue
366 lines
11 KiB
Vue
|
|
<script setup lang="ts">
|
||
|
|
import { computed, onUnmounted, ref } 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 } from '@/stores'
|
||
|
|
import api, { type IExecuteRawResponse } from '@/api'
|
||
|
|
|
||
|
|
import ExecutePlan from './ExecutePlan.vue'
|
||
|
|
import Iod from './Iod.vue'
|
||
|
|
|
||
|
|
|
||
|
|
const emit = defineEmits<{
|
||
|
|
(e: 'refreshLine'): void
|
||
|
|
(el: 'setCurrentTask', task: IRawStepTask): void
|
||
|
|
}>()
|
||
|
|
|
||
|
|
const agentsStore = useAgentsStore()
|
||
|
|
|
||
|
|
const collaborationProcess = computed(() => {
|
||
|
|
return agentsStore.agentRawPlan.data?.['Collaboration Process'] ?? []
|
||
|
|
})
|
||
|
|
|
||
|
|
const jsplumb = new Jsplumb('task-results-main', {
|
||
|
|
connector: {
|
||
|
|
type: BezierConnector.type,
|
||
|
|
options: { curviness: 30, stub: 10 },
|
||
|
|
},
|
||
|
|
})
|
||
|
|
|
||
|
|
// 操作折叠面板时要实时的刷新连线
|
||
|
|
let timer: number
|
||
|
|
function handleCollapse() {
|
||
|
|
if (timer) {
|
||
|
|
clearInterval(timer)
|
||
|
|
}
|
||
|
|
timer = setInterval(() => {
|
||
|
|
jsplumb.repaintEverything()
|
||
|
|
emit('refreshLine')
|
||
|
|
}, 1)
|
||
|
|
|
||
|
|
// 默认三秒后已经完全打开
|
||
|
|
const timer1 = setTimeout(() => {
|
||
|
|
clearInterval(timer)
|
||
|
|
}, 3000)
|
||
|
|
|
||
|
|
onUnmounted(() => {
|
||
|
|
if (timer) {
|
||
|
|
clearInterval(timer)
|
||
|
|
}
|
||
|
|
if (timer1) {
|
||
|
|
clearInterval(timer1)
|
||
|
|
}
|
||
|
|
})
|
||
|
|
}
|
||
|
|
|
||
|
|
// 创建内部连线
|
||
|
|
function createInternalLine(id?: string) {
|
||
|
|
const arr: ConnectArg[] = []
|
||
|
|
jsplumb.reset()
|
||
|
|
collaborationProcess.value.forEach((item) => {
|
||
|
|
// 创建左侧流程与产出的连线
|
||
|
|
// jsplumb.connect(`task-results-${item.Id}-0`, `task-results-${item.Id}-1`, [
|
||
|
|
// AnchorLocations.Left,
|
||
|
|
// AnchorLocations.Left,
|
||
|
|
// ])
|
||
|
|
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 ?? '')) {
|
||
|
|
// jsplumb.connect(
|
||
|
|
// `task-results-${jitem.Id}-1`,
|
||
|
|
// `task-results-${item.Id}-0`,
|
||
|
|
// [AnchorLocations.Left, AnchorLocations.Left],
|
||
|
|
// {
|
||
|
|
// type: 'output',
|
||
|
|
// },
|
||
|
|
// )
|
||
|
|
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-${jitem.Id}-0-${i.ID}`
|
||
|
|
const targetId = `task-results-${item.Id}-1`
|
||
|
|
// jsplumb.connect(sourceId, targetId, [AnchorLocations.Right, AnchorLocations.Right], {
|
||
|
|
// stops: [
|
||
|
|
// [0, color],
|
||
|
|
// [1, color],
|
||
|
|
// ],
|
||
|
|
// transparent: sourceId !== id,
|
||
|
|
// })
|
||
|
|
arr.push({
|
||
|
|
sourceId,
|
||
|
|
targetId,
|
||
|
|
anchor: [AnchorLocations.Right, AnchorLocations.Right],
|
||
|
|
config: {
|
||
|
|
stops: [
|
||
|
|
[0, color],
|
||
|
|
[1, color],
|
||
|
|
],
|
||
|
|
transparent: sourceId !== 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-${i.ID}`
|
||
|
|
const targetId = `task-results-${item.Id}-0-${i2.ID}`
|
||
|
|
// jsplumb.connect(sourceId, targetId, [AnchorLocations.Right, AnchorLocations.Right], {
|
||
|
|
// stops: [
|
||
|
|
// [0, color],
|
||
|
|
// [1, color],
|
||
|
|
// ],
|
||
|
|
// transparent: sourceId !== id,
|
||
|
|
// })
|
||
|
|
arr.push({
|
||
|
|
sourceId,
|
||
|
|
targetId,
|
||
|
|
anchor: [AnchorLocations.Right, AnchorLocations.Right],
|
||
|
|
config: {
|
||
|
|
stops: [
|
||
|
|
[0, color],
|
||
|
|
[1, color],
|
||
|
|
],
|
||
|
|
transparent: sourceId !== id,
|
||
|
|
},
|
||
|
|
})
|
||
|
|
}
|
||
|
|
})
|
||
|
|
})
|
||
|
|
})
|
||
|
|
|
||
|
|
jsplumb.connects(arr)
|
||
|
|
jsplumb.repaintEverything()
|
||
|
|
}
|
||
|
|
|
||
|
|
const results = ref<IExecuteRawResponse[]>([])
|
||
|
|
const loading = ref(false)
|
||
|
|
async function handleRun() {
|
||
|
|
try {
|
||
|
|
loading.value = true
|
||
|
|
results.value = await api.executePlan(agentsStore.agentRawPlan.data!)
|
||
|
|
} finally {
|
||
|
|
loading.value = false
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// 添加滚动状态标识
|
||
|
|
const isScrolling = ref(false)
|
||
|
|
let scrollTimer: number
|
||
|
|
|
||
|
|
// 修改滚动处理函数
|
||
|
|
function handleScroll() {
|
||
|
|
isScrolling.value = true
|
||
|
|
emit('refreshLine')
|
||
|
|
// 清除之前的定时器
|
||
|
|
if (scrollTimer) {
|
||
|
|
clearTimeout(scrollTimer)
|
||
|
|
}
|
||
|
|
jsplumb.repaintEverything()
|
||
|
|
|
||
|
|
// 设置滚动结束检测
|
||
|
|
scrollTimer = setTimeout(() => {
|
||
|
|
isScrolling.value = false
|
||
|
|
}, 300)
|
||
|
|
}
|
||
|
|
|
||
|
|
// 修改鼠标事件处理函数
|
||
|
|
const handleMouseEnter = throttle((id) => {
|
||
|
|
if (!isScrolling.value) {
|
||
|
|
createInternalLine(id)
|
||
|
|
}
|
||
|
|
}, 100)
|
||
|
|
|
||
|
|
const handleMouseLeave = throttle(() => {
|
||
|
|
if (!isScrolling.value) {
|
||
|
|
createInternalLine()
|
||
|
|
}
|
||
|
|
}, 100)
|
||
|
|
|
||
|
|
defineExpose({
|
||
|
|
createInternalLine,
|
||
|
|
})
|
||
|
|
</script>
|
||
|
|
|
||
|
|
<template>
|
||
|
|
<div class="h-full flex flex-col relative" id="task-results">
|
||
|
|
<!-- 标题与执行按钮 -->
|
||
|
|
<div class="text-[18px] font-bold mb-[18px] flex justify-between items-center px-[20px]">
|
||
|
|
<span>执行结果</span>
|
||
|
|
<div class="flex items-center gap-[14px]">
|
||
|
|
<el-button circle :color="variables.tertiary" disabled title="点击刷新">
|
||
|
|
<svg-icon icon-class="refresh" />
|
||
|
|
</el-button>
|
||
|
|
<el-button circle :color="variables.tertiary" title="点击运行" @click="handleRun">
|
||
|
|
<svg-icon icon-class="action" />
|
||
|
|
</el-button>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
<!-- 内容 -->
|
||
|
|
<div
|
||
|
|
v-loading="agentsStore.agentRawPlan.loading"
|
||
|
|
class="flex-1 overflow-auto relative"
|
||
|
|
@scroll="handleScroll"
|
||
|
|
>
|
||
|
|
<div id="task-results-main" class="px-[40px] relative">
|
||
|
|
<div v-for="item in collaborationProcess" :key="item.Id" class="card-item">
|
||
|
|
<el-card
|
||
|
|
class="card-item w-full relative"
|
||
|
|
shadow="hover"
|
||
|
|
:id="`task-results-${item.Id}-0`"
|
||
|
|
@click="emit('setCurrentTask', item)"
|
||
|
|
>
|
||
|
|
<div class="text-[18px] mb-[15px]">{{ item.StepName }}</div>
|
||
|
|
<!-- 折叠面板 -->
|
||
|
|
<el-collapse @change="handleCollapse">
|
||
|
|
<!-- <el-tooltip-->
|
||
|
|
<!-- :disabled="Boolean(true || results.length || loading)"-->
|
||
|
|
<!-- placement="top"-->
|
||
|
|
<!-- effect="light"-->
|
||
|
|
<!-- content="请点击右上角的循行按钮,查看运行结果"-->
|
||
|
|
<!-- >-->
|
||
|
|
<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(!results.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="!results.length" #icon>
|
||
|
|
<span></span>
|
||
|
|
</template>
|
||
|
|
<template #title>
|
||
|
|
<div class="flex items-center gap-[15px]">
|
||
|
|
<!-- 右侧链接点 -->
|
||
|
|
<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="var(--color-text)"
|
||
|
|
size="24px"
|
||
|
|
/>
|
||
|
|
</div>
|
||
|
|
<div class="text-[16px]">
|
||
|
|
<span>{{ item1.AgentName }}: </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="results"
|
||
|
|
/>
|
||
|
|
</el-collapse-item>
|
||
|
|
<!-- </el-tooltip>-->
|
||
|
|
</el-collapse>
|
||
|
|
</el-card>
|
||
|
|
|
||
|
|
<el-card
|
||
|
|
class="card-item w-full relative"
|
||
|
|
shadow="hover"
|
||
|
|
:id="`task-results-${item.Id}-1`"
|
||
|
|
@click="emit('setCurrentTask', item)"
|
||
|
|
>
|
||
|
|
<div class="text-[18px]">{{ item.OutputObject }}</div>
|
||
|
|
</el-card>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
</template>
|
||
|
|
|
||
|
|
<style scoped lang="scss">
|
||
|
|
#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-secondary);
|
||
|
|
min-height: 41px;
|
||
|
|
line-height: 41px;
|
||
|
|
border-radius: 20px;
|
||
|
|
transition: border-radius 1ms;
|
||
|
|
position: relative;
|
||
|
|
|
||
|
|
.el-collapse-item__title {
|
||
|
|
background: var(--color-bg-secondary);
|
||
|
|
border-radius: 20px;
|
||
|
|
}
|
||
|
|
|
||
|
|
.el-icon {
|
||
|
|
font-size: 20px;
|
||
|
|
font-weight: bold;
|
||
|
|
}
|
||
|
|
|
||
|
|
&.is-active {
|
||
|
|
border-bottom-left-radius: 0;
|
||
|
|
border-bottom-right-radius: 0;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
.el-collapse-item__wrap {
|
||
|
|
border: none;
|
||
|
|
background: var(--color-bg-secondary);
|
||
|
|
border-bottom-left-radius: 20px;
|
||
|
|
border-bottom-right-radius: 20px;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
:deep(.el-card) {
|
||
|
|
.el-card__body {
|
||
|
|
padding-right: 40px;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
.card-item + .card-item {
|
||
|
|
margin-top: 10px;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
</style>
|