feat:rename subtree from frontend-vue to frontend
This commit is contained in:
@@ -0,0 +1,105 @@
|
||||
<script setup lang="ts">
|
||||
import type { IExecuteRawResponse } from '@/api'
|
||||
import { computed } from 'vue'
|
||||
import MarkdownIt from 'markdown-it'
|
||||
import DOMPurify from 'dompurify'
|
||||
import Iod from './Iod.vue'
|
||||
|
||||
const props = defineProps<{
|
||||
executePlans: IExecuteRawResponse[]
|
||||
nodeId?: string
|
||||
actionId?: string
|
||||
}>()
|
||||
|
||||
const md = new MarkdownIt({
|
||||
html: true,
|
||||
linkify: true,
|
||||
typographer: true,
|
||||
breaks: true,
|
||||
})
|
||||
|
||||
function sanitize(str?: string) {
|
||||
if (!str) {
|
||||
return ''
|
||||
}
|
||||
const cleanStr = str
|
||||
.replace(/\\n/g, '\n')
|
||||
.replace(/\n\s*\d+\./g, '\n$&')
|
||||
const html = md.render(cleanStr)
|
||||
return html
|
||||
// return DOMPurify.sanitize(html)
|
||||
}
|
||||
|
||||
interface Data {
|
||||
Description: string
|
||||
Content: string
|
||||
LogNodeType: string
|
||||
}
|
||||
const data = computed<Data | null>(() => {
|
||||
for (const result of props.executePlans) {
|
||||
if (result.NodeId === props.nodeId) {
|
||||
// LogNodeType 为 object直接渲染Content
|
||||
if (result.LogNodeType === 'object') {
|
||||
return {
|
||||
Description: props.nodeId,
|
||||
Content: sanitize(result.content),
|
||||
LogNodeType: result.LogNodeType,
|
||||
}
|
||||
}
|
||||
|
||||
if (!result.ActionHistory) {
|
||||
return null
|
||||
}
|
||||
|
||||
for (const action of result.ActionHistory) {
|
||||
if (action.ID === props.actionId) {
|
||||
return {
|
||||
Description: action.Description,
|
||||
Content: sanitize(action.Action_Result),
|
||||
LogNodeType: result.LogNodeType,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return null
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div v-if="data" class="card-item w-full pl-[56px] pr-[41px]">
|
||||
<!-- 分割线 -->
|
||||
<div v-if="data.LogNodeType !== 'object'" class="h-[1px] w-full bg-[#494B51] my-[8px]"></div>
|
||||
<div
|
||||
v-if="data.Description"
|
||||
class="text-[16px] flex items-center gap-1 text-[var(--color-text-secondary)] mb-1"
|
||||
>
|
||||
{{ data.Description }}
|
||||
<Iod v-if="data.LogNodeType !== 'object'"/>
|
||||
</div>
|
||||
<div class="rounded-[8px] p-[15px] text-[14px] bg-[var(--color-bg-quaternary)]">
|
||||
<div
|
||||
class="markdown-content max-h-[240px] overflow-y-auto max-w-full"
|
||||
v-html="data.Content"
|
||||
></div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.card-item + .card-item {
|
||||
margin-top: 10px;
|
||||
}
|
||||
.markdown-content {
|
||||
:deep(code) {
|
||||
display: block;
|
||||
width: 100px;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
:deep(pre) {
|
||||
overflow-x: auto;
|
||||
max-width: 100%;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,76 @@
|
||||
<script setup lang="ts">
|
||||
import { readConfig } from '@/utils/readJson.ts'
|
||||
import { onMounted } from 'vue'
|
||||
|
||||
interface Iod {
|
||||
name: string
|
||||
data_space: string
|
||||
doId: string
|
||||
fromRepo: string
|
||||
}
|
||||
|
||||
const data = ref<Iod[]>([])
|
||||
const displayIndex = ref(0)
|
||||
|
||||
const displayIod = computed(() => {
|
||||
return data.value[displayIndex.value]!
|
||||
})
|
||||
|
||||
onMounted(async () => {
|
||||
const res = await readConfig<{ data: Iod[] }>('iodConfig.json')
|
||||
data.value = res.data
|
||||
})
|
||||
|
||||
function handleNext() {
|
||||
if (displayIndex.value === data.value.length - 1) {
|
||||
displayIndex.value = 0
|
||||
} else {
|
||||
displayIndex.value++
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<el-popover trigger="hover" width="440">
|
||||
<template #reference>
|
||||
<div
|
||||
class="rounded-full w-[20px] h-[20px] bg-[var(--color-bg-quaternary)] flex justify-center items-center cursor-pointer"
|
||||
>
|
||||
{{ data.length }}
|
||||
</div>
|
||||
</template>
|
||||
<template #default v-if="data.length">
|
||||
<div>
|
||||
<div class="flex justify-between items-center p-2 pb-0 rounded-[8px] text-[16px] font-bold">
|
||||
<span>数联网搜索结果</span>
|
||||
<div class="flex items-center gap-3">
|
||||
<div>{{ `${displayIndex + 1}/${data.length}` }}</div>
|
||||
<el-button type="primary" size="small" @click="handleNext">下一个</el-button>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 分割线 -->
|
||||
<div class="h-[1px] w-full bg-[#494B51] my-[8px]"></div>
|
||||
<div class="p-2 pt-0">
|
||||
<div class="flex items-center w-full gap-3">
|
||||
<div class="font-bold w-[75px] text-right flex-shrink-0">名称:</div>
|
||||
<div class="text-[var(--color-text-secondary)] flex-1 break-words">{{ displayIod.name }}</div>
|
||||
</div>
|
||||
<div class="flex items-center w-full gap-3">
|
||||
<div class="font-bold w-[75px] text-right flex-shrink-0">数据空间:</div>
|
||||
<div class="text-[var(--color-text-secondary)] lex-1 break-words">{{ displayIod.data_space }}</div>
|
||||
</div>
|
||||
<div class="flex items-center w-full gap-3">
|
||||
<div class="font-bold w-[75px] text-right flex-shrink-0">DOID:</div>
|
||||
<div class="text-[var(--color-text-secondary)] lex-1 break-words">{{ displayIod.doId }}</div>
|
||||
</div>
|
||||
<div class="flex items-center w-full gap-3">
|
||||
<div class="font-bold w-[75px] text-right flex-shrink-0">来源仓库:</div>
|
||||
<div class="text-[var(--color-text-secondary)] flex-1 break-words break-al">{{ displayIod.fromRepo }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</el-popover>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss"></style>
|
||||
@@ -0,0 +1,400 @@
|
||||
<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 from '@/api'
|
||||
|
||||
import ExecutePlan from './ExecutePlan.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) => {
|
||||
// 创建左侧流程与产出的连线
|
||||
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)
|
||||
async function handleRun() {
|
||||
try {
|
||||
loading.value = true
|
||||
const d = await api.executePlan(agentsStore.agentRawPlan.data!)
|
||||
agentsStore.setExecutePlan(d)
|
||||
} 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)
|
||||
|
||||
function clear() {
|
||||
jsplumb.reset()
|
||||
}
|
||||
|
||||
defineExpose({
|
||||
createInternalLine,
|
||||
clear,
|
||||
})
|
||||
</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-popover :disabled="Boolean(agentsStore.agentRawPlan.data)" title="请先输入要执行的任务">
|
||||
<template #reference>
|
||||
<el-button
|
||||
circle
|
||||
:color="variables.tertiary"
|
||||
title="点击运行"
|
||||
:disabled="!agentsStore.agentRawPlan.data"
|
||||
@click="handleRun"
|
||||
>
|
||||
<svg-icon icon-class="action" />
|
||||
</el-button>
|
||||
</template>
|
||||
</el-popover>
|
||||
</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"
|
||||
:class="agentsStore.currentTask?.StepName === item.StepName ? 'active-card' : ''"
|
||||
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-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>
|
||||
<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="agentsStore.executePlan"
|
||||
/>
|
||||
</el-collapse-item>
|
||||
</el-collapse>
|
||||
</el-card>
|
||||
|
||||
<el-card
|
||||
class="card-item w-full relative output-object-card"
|
||||
shadow="hover"
|
||||
: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]">{{ item.OutputObject }}</div>
|
||||
</template>
|
||||
<ExecutePlan :node-id="item.OutputObject" :execute-plans="agentsStore.executePlan" />
|
||||
</el-collapse-item>
|
||||
</el-collapse>
|
||||
</el-card>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
#task-results {
|
||||
: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;
|
||||
}
|
||||
}
|
||||
|
||||
.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-secondary);
|
||||
padding: 25px;
|
||||
padding-top: 10px;
|
||||
border-radius: 7px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.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;
|
||||
}
|
||||
}
|
||||
|
||||
.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;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user