feat(agent): 支持自定义API配置并优化UI交互
- 为agent.json添加apiUrl、apiKey、apiModel字段支持 - 更新API接口类型定义,支持传递自定义API配置 - 优化AgentRepoList组件UI样式和交互效果 - 增强JSON文件上传校验逻辑,支持API配置验证 - 改进任务结果页面布局和视觉呈现 - 添加任务过程查看抽屉功能 - 实现执行按钮动态样式和悬停效果 - 优化节点连接线渲染逻辑和性能
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
import { getActionTypeDisplay, getAgentMapIcon } from '@/layout/components/config.ts'
|
||||
import SvgIcon from '@/components/SvgIcon/index.vue'
|
||||
import { type Agent, useAgentsStore } from '@/stores'
|
||||
@@ -10,9 +11,9 @@ const porps = defineProps<{
|
||||
|
||||
const taskProcess = computed(() => {
|
||||
const list = agentsStore.currentTask?.TaskProcess ?? []
|
||||
return list.map((item) => ({
|
||||
return list.map(item => ({
|
||||
...item,
|
||||
key: uuidv4(),
|
||||
key: uuidv4()
|
||||
}))
|
||||
})
|
||||
|
||||
@@ -26,35 +27,38 @@ const agentsStore = useAgentsStore()
|
||||
class="user-item"
|
||||
:class="agentsStore.currentTask?.AgentSelection?.includes(item.Name) ? 'active-card' : ''"
|
||||
>
|
||||
<div class="flex items-center justify-between relative h-[41px]">
|
||||
<div class="flex items-center justify-between relative h-[43px]">
|
||||
<div
|
||||
class="w-[44px] h-[44px] rounded-full flex items-center justify-center flex-shrink-0 relative right-[2px] icon-container"
|
||||
:style="{ background: getAgentMapIcon(item.Name).color }"
|
||||
>
|
||||
<svg-icon
|
||||
:icon-class="getAgentMapIcon(item.Name).icon"
|
||||
color="var(--color-text)"
|
||||
size="24px"
|
||||
/>
|
||||
<svg-icon :icon-class="getAgentMapIcon(item.Name).icon" color="#fff" size="24px" />
|
||||
</div>
|
||||
<div class="flex-1 text-[14px] flex flex-col items-end justify-end truncate ml-1">
|
||||
<span
|
||||
class="w-full truncate text-right"
|
||||
:style="
|
||||
agentsStore.currentTask?.AgentSelection?.includes(item.Name) ? 'color:#00F3FF' : ''
|
||||
"
|
||||
>{{ item.Name }}</span
|
||||
>
|
||||
<div class="flex-1 text-[14px] textClass flex flex-col items-end justify-end truncate ml-1">
|
||||
<div class="flex items-center justify-end gap-2 w-full">
|
||||
<span
|
||||
class="truncate"
|
||||
:style="
|
||||
agentsStore.currentTask?.AgentSelection?.includes(item.Name)
|
||||
? 'color:var(--color-accent)'
|
||||
: ''
|
||||
"
|
||||
>{{ item.Name }}</span
|
||||
>
|
||||
</div>
|
||||
<div
|
||||
v-if="agentsStore.currentTask?.AgentSelection?.includes(item.Name)"
|
||||
class="flex items-center gap-[7px] h-[8px] mr-1"
|
||||
>
|
||||
<!-- 小圆点 -->
|
||||
<div
|
||||
v-for="item1 in taskProcess.filter((i) => i.AgentName === item.Name)"
|
||||
v-for="item1 in taskProcess.filter(i => i.AgentName === item.Name)"
|
||||
:key="item1.key"
|
||||
class="w-[6px] h-[6px] rounded-full"
|
||||
:style="{ background: getActionTypeDisplay(item1.ActionType)?.color }"
|
||||
:style="{
|
||||
background: getActionTypeDisplay(item1.ActionType)?.color,
|
||||
border: `1px solid ${getActionTypeDisplay(item1.ActionType)?.border}`
|
||||
}"
|
||||
></div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -71,7 +75,7 @@ const agentsStore = useAgentsStore()
|
||||
</div>
|
||||
<div class="p-[8px] pt-0">
|
||||
<div
|
||||
v-for="(item1, index1) in taskProcess.filter((i) => i.AgentName === item.Name)"
|
||||
v-for="(item1, index1) in taskProcess.filter(i => i.AgentName === item.Name)"
|
||||
:key="item1.key"
|
||||
class="text-[12px]"
|
||||
>
|
||||
@@ -89,8 +93,8 @@ const agentsStore = useAgentsStore()
|
||||
</div>
|
||||
<!-- 分割线 -->
|
||||
<div
|
||||
v-if="index1 !== taskProcess.filter((i) => i.AgentName === item.Name).length - 1"
|
||||
class="h-[1px] w-full bg-[#494B51] my-[8px]"
|
||||
v-if="index1 !== taskProcess.filter(i => i.AgentName === item.Name).length - 1"
|
||||
class="h-[1px] w-full bg-[var(--color-border-default)] my-[8px]"
|
||||
></div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -100,13 +104,14 @@ const agentsStore = useAgentsStore()
|
||||
|
||||
<style scoped lang="scss">
|
||||
.user-item {
|
||||
background: #1d222b;
|
||||
background: var(--color-agent-list-bg);
|
||||
border-radius: 40px;
|
||||
padding-right: 12px;
|
||||
cursor: pointer;
|
||||
transition: all 0.25s ease;
|
||||
color: #969696;
|
||||
border: 2px solid transparent;
|
||||
color: var(--color-text-detail);
|
||||
border: 1px solid var(--color-agent-list-border);
|
||||
box-sizing: border-box;
|
||||
.duty-info {
|
||||
transition: height 0.25s ease;
|
||||
height: 0;
|
||||
@@ -118,16 +123,30 @@ const agentsStore = useAgentsStore()
|
||||
}
|
||||
|
||||
&:hover {
|
||||
box-shadow: 0 3px 10px rgba(0, 0, 0, 0.2);
|
||||
color: #b8b8b8;
|
||||
box-shadow: var(--color-agent-list-hover-shadow);
|
||||
color: var(--color-text);
|
||||
background: var(--color-agent-list-hover-bg);
|
||||
border: 1px solid var(--color-agent-list-hover-border);
|
||||
}
|
||||
}
|
||||
|
||||
.textClass {
|
||||
color: var(--color-text-agent-list);
|
||||
&:hover {
|
||||
color: var(--color-text-agent-list-hover);
|
||||
}
|
||||
}
|
||||
|
||||
.active-card {
|
||||
background:
|
||||
linear-gradient(#171B22, #171B22) padding-box,
|
||||
linear-gradient(to right, #00c8d2, #315ab4) border-box;
|
||||
background: linear-gradient(var(--color-bg-quaternary), var(--color-bg-quaternary)) padding-box,
|
||||
linear-gradient(to right, var(--color-accent), var(--color-accent-secondary)) border-box;
|
||||
border: 1px solid var(--color-agent-list-border);
|
||||
|
||||
&:hover {
|
||||
box-shadow: var(--color-agent-list-hover-shadow);
|
||||
color: var(--color-text);
|
||||
background: var(--color-agent-list-hover-bg);
|
||||
border: 1px solid var(--color-agent-list-hover-border);
|
||||
border-radius: 20px;
|
||||
.duty-info {
|
||||
height: auto;
|
||||
@@ -138,4 +157,10 @@ const agentsStore = useAgentsStore()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 添加头像容器样式修复
|
||||
.icon-container {
|
||||
right: 0 !important;
|
||||
margin-left: 0px;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, computed, onMounted } from 'vue'
|
||||
import { ElNotification } from 'element-plus'
|
||||
import { pick } from 'lodash'
|
||||
|
||||
@@ -7,7 +8,6 @@ import api from '@/api/index.ts'
|
||||
import SvgIcon from '@/components/SvgIcon/index.vue'
|
||||
import { agentMapDuty } from '@/layout/components/config.ts'
|
||||
import { type Agent, useAgentsStore } from '@/stores'
|
||||
import { onMounted } from 'vue'
|
||||
import { readConfig } from '@/utils/readJson.ts'
|
||||
import AgentRepoList from './AgentRepoList.vue'
|
||||
|
||||
@@ -19,7 +19,7 @@ onMounted(async () => {
|
||||
const res = await readConfig<Agent[]>('agent.json')
|
||||
agentsStore.setAgents(res)
|
||||
}
|
||||
await api.setAgents(agentsStore.agents.map((item) => pick(item, ['Name', 'Profile'])))
|
||||
await api.setAgents(agentsStore.agents.map(item => pick(item, ['Name', 'Profile'])))
|
||||
})
|
||||
|
||||
// 上传agent文件
|
||||
@@ -37,50 +37,150 @@ const handleFileSelect = (event: Event) => {
|
||||
}
|
||||
}
|
||||
|
||||
const readFileContent = async (file: File) => {
|
||||
const reader = new FileReader()
|
||||
reader.onload = async (e) => {
|
||||
if (!e.target?.result) {
|
||||
return
|
||||
}
|
||||
try {
|
||||
const json = JSON.parse(e.target.result?.toString?.() ?? '{}')
|
||||
// 处理 JSON 数据
|
||||
if (Array.isArray(json)) {
|
||||
const isValid = json.every(
|
||||
(item) =>
|
||||
typeof item.Name === 'string' &&
|
||||
typeof item.Icon === 'string' &&
|
||||
typeof item.Profile === 'string',
|
||||
)
|
||||
if (isValid) {
|
||||
// 处理有效的 JSON 数据
|
||||
agentsStore.setAgents(
|
||||
json.map((item) => ({
|
||||
Name: item.Name,
|
||||
Icon: item.Icon.replace(/\.png$/, ''),
|
||||
Profile: item.Profile,
|
||||
Classification: item.Classification,
|
||||
})),
|
||||
)
|
||||
await api.setAgents(json.map((item) => pick(item, ['Name', 'Profile'])))
|
||||
} else {
|
||||
ElNotification.error({
|
||||
title: '错误',
|
||||
message: 'JSON 格式错误',
|
||||
})
|
||||
}
|
||||
} else {
|
||||
console.error('JSON is not an array')
|
||||
ElNotification.error({
|
||||
title: '错误',
|
||||
message: 'JSON 格式错误',
|
||||
})
|
||||
// 在validateApiConfig函数中添加更详细的日志
|
||||
const validateApiConfig = (agent: any) => {
|
||||
const hasApiUrl = 'apiUrl' in agent
|
||||
const hasApiKey = 'apiKey' in agent
|
||||
const hasApiModel = 'apiModel' in agent
|
||||
|
||||
// 三个字段必须同时存在或同时不存在
|
||||
if (hasApiUrl !== hasApiKey || hasApiKey !== hasApiModel) {
|
||||
console.error('❌ API配置不完整:', {
|
||||
agentName: agent.Name,
|
||||
missingFields: {
|
||||
apiUrl: !hasApiUrl,
|
||||
apiKey: !hasApiKey,
|
||||
apiModel: !hasApiModel
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
})
|
||||
return false
|
||||
}
|
||||
|
||||
if (hasApiUrl && hasApiKey && hasApiModel) {
|
||||
console.log('✅ API配置完整,将使用自定义API配置:', {
|
||||
apiUrl: agent.apiUrl,
|
||||
apiKey: agent.apiKey ? '***' + agent.apiKey.slice(-4) : '未设置',
|
||||
apiModel: agent.apiModel
|
||||
})
|
||||
} else {
|
||||
console.log('ℹ️ 未设置API配置,将使用默认URL配置')
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
const readFileContent = (file: File) => {
|
||||
console.log('📁 开始读取文件:', file.name, '大小:', file.size, '类型:', file.type)
|
||||
|
||||
const reader = new FileReader()
|
||||
reader.onload = e => {
|
||||
try {
|
||||
console.log('📄 文件读取完成,开始解析JSON')
|
||||
const content = e.target?.result as string
|
||||
const jsonData = JSON.parse(content)
|
||||
|
||||
console.log('🔍 解析的JSON数据:', jsonData)
|
||||
console.log(
|
||||
'📊 数据类型:',
|
||||
Array.isArray(jsonData) ? '数组' : '对象',
|
||||
'长度:',
|
||||
Array.isArray(jsonData) ? jsonData.length : 'N/A'
|
||||
)
|
||||
|
||||
if (!Array.isArray(jsonData)) {
|
||||
console.error('❌ JSON格式错误: 必须为数组格式')
|
||||
ElMessage.error('JSON格式错误: 必须为数组格式')
|
||||
return
|
||||
}
|
||||
|
||||
console.log('🔍 开始验证智能体数据...')
|
||||
const validAgents = jsonData.filter((agent, index) => {
|
||||
console.log(`🔍 验证第${index + 1}个智能体:`, agent.Name || '未命名')
|
||||
// 验证必需字段
|
||||
if (!agent.Name || typeof agent.Name !== 'string') {
|
||||
console.error(`❌ 智能体${index + 1}缺少Name字段或格式错误`)
|
||||
return false
|
||||
}
|
||||
if (!agent.Icon || typeof agent.Icon !== 'string') {
|
||||
console.error(`❌ 智能体${index + 1}缺少Icon字段或格式错误`)
|
||||
return false
|
||||
}
|
||||
if (!agent.Profile || typeof agent.Profile !== 'string') {
|
||||
console.error(`❌ 智能体${index + 1}缺少Profile字段或格式错误`)
|
||||
return false
|
||||
}
|
||||
|
||||
// 验证API配置
|
||||
if (!validateApiConfig(agent)) {
|
||||
console.error(`❌ 智能体${index + 1}API配置验证失败`)
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
})
|
||||
|
||||
console.log('🔄 开始处理智能体数据...')
|
||||
// 修改发送到后端的数据
|
||||
const processedAgents = validAgents.map(agent => ({
|
||||
Name: agent.Name,
|
||||
Profile: agent.Profile,
|
||||
Icon: agent.Icon,
|
||||
Classification: agent.Classification || '',
|
||||
apiUrl: agent.apiUrl,
|
||||
apiKey: agent.apiKey,
|
||||
apiModel: agent.apiModel
|
||||
}))
|
||||
|
||||
console.log(
|
||||
'📤 发送到后端的智能体数据:',
|
||||
processedAgents.map(a => ({
|
||||
Name: a.Name,
|
||||
apiUrl: a.apiUrl,
|
||||
apiKey: a.apiKey ? '***' + a.apiKey.slice(-4) : '未设置',
|
||||
apiModel: a.apiModel
|
||||
}))
|
||||
)
|
||||
|
||||
// 调用API
|
||||
api
|
||||
.setAgents(processedAgents)
|
||||
.then(response => {
|
||||
console.log('✅ 后端API调用成功,响应:', response)
|
||||
ElMessage.success('智能体上传成功')
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('❌ 后端API调用失败:', error)
|
||||
ElMessage.error('智能体上传失败')
|
||||
})
|
||||
|
||||
// 更新store
|
||||
console.log('💾 更新智能体store...')
|
||||
agentsStore.setAgents(processedAgents)
|
||||
console.log('✅ 智能体store更新完成')
|
||||
|
||||
// 调用API
|
||||
console.log('🌐 开始调用后端API...')
|
||||
api
|
||||
.setAgents(processedAgents)
|
||||
.then(() => {
|
||||
console.log('✅ 后端API调用成功')
|
||||
ElMessage.success('智能体上传成功')
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('❌ 后端API调用失败:', error)
|
||||
ElMessage.error('智能体上传失败')
|
||||
})
|
||||
} catch (error) {
|
||||
console.error('❌ JSON解析错误:', error)
|
||||
ElMessage.error('JSON解析错误')
|
||||
}
|
||||
}
|
||||
|
||||
reader.onerror = error => {
|
||||
console.error('❌ 文件读取错误:', error)
|
||||
ElMessage.error('文件读取错误')
|
||||
}
|
||||
|
||||
reader.readAsText(file)
|
||||
}
|
||||
|
||||
@@ -95,7 +195,7 @@ const agentList = computed(() => {
|
||||
if (!agentsStore.agents.length) {
|
||||
return {
|
||||
selected,
|
||||
unselected,
|
||||
unselected
|
||||
}
|
||||
}
|
||||
for (const agent of agentsStore.agents) {
|
||||
@@ -110,14 +210,14 @@ const agentList = computed(() => {
|
||||
obj[agent.Classification] = arr
|
||||
unselected.push({
|
||||
title: agent.Classification,
|
||||
data: arr,
|
||||
data: arr
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
selected,
|
||||
unselected: unselected,
|
||||
unselected: unselected
|
||||
}
|
||||
})
|
||||
</script>
|
||||
@@ -126,7 +226,7 @@ const agentList = computed(() => {
|
||||
<div class="agent-repo h-full flex flex-col" id="agent-repo">
|
||||
<!-- 头部 -->
|
||||
<div class="flex items-center justify-between">
|
||||
<span class="text-[18px] font-bold">智能体库</span>
|
||||
<span class="text-[18px] font-bold text-[var(--color-text-title-header)]">智能体库</span>
|
||||
<!-- 上传文件 -->
|
||||
<input type="file" accept=".json" @change="handleFileSelect" class="hidden" ref="fileInput" />
|
||||
<div class="plus-button" @click="triggerFileSelect">
|
||||
@@ -144,14 +244,22 @@ const agentList = computed(() => {
|
||||
</div>
|
||||
</div>
|
||||
<!-- 底部提示栏 -->
|
||||
<div class="w-full grid grid-cols-3 gap-x-[10px] bg-[#1d222b] rounded-[20px] p-[8px] mt-[10px]">
|
||||
<div
|
||||
class="w-full grid grid-cols-3 gap-x-[10px] bg-[var(--color-bg-indicator)] rounded-[20px] p-[8px] mt-[10px]"
|
||||
>
|
||||
<div
|
||||
v-for="item in Object.values(agentMapDuty)"
|
||||
:key="item.key"
|
||||
class="flex items-center justify-center gap-x-1"
|
||||
>
|
||||
<div
|
||||
class="w-[8px] h-[8px] rounded-full"
|
||||
:style="{
|
||||
background: item.color,
|
||||
border: `1px solid ${item.border}`
|
||||
}"
|
||||
></div>
|
||||
<span class="text-[12px]">{{ item.name }}</span>
|
||||
<div class="w-[8px] h-[8px] rounded-full" :style="{ background: item.color }"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -162,7 +270,7 @@ const agentList = computed(() => {
|
||||
padding: 0 8px;
|
||||
|
||||
.plus-button {
|
||||
background: #1d2128;
|
||||
background: var(--color-bg-tertiary);
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
padding: 0;
|
||||
@@ -174,7 +282,7 @@ const agentList = computed(() => {
|
||||
transition: all 0.3s ease;
|
||||
|
||||
&:hover {
|
||||
background: #374151;
|
||||
background: var(--color-bg-quaternary);
|
||||
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,16 +15,14 @@ const md = new MarkdownIt({
|
||||
html: true,
|
||||
linkify: true,
|
||||
typographer: true,
|
||||
breaks: true,
|
||||
breaks: true
|
||||
})
|
||||
|
||||
function sanitize(str?: string) {
|
||||
if (!str) {
|
||||
return ''
|
||||
}
|
||||
const cleanStr = str
|
||||
.replace(/\\n/g, '\n')
|
||||
.replace(/\n\s*\d+\./g, '\n$&')
|
||||
const cleanStr = str.replace(/\\n/g, '\n').replace(/\n\s*\d+\./g, '\n$&')
|
||||
const html = md.render(cleanStr)
|
||||
return html
|
||||
// return DOMPurify.sanitize(html)
|
||||
@@ -43,7 +41,7 @@ const data = computed<Data | null>(() => {
|
||||
return {
|
||||
Description: props.nodeId,
|
||||
Content: sanitize(result.content),
|
||||
LogNodeType: result.LogNodeType,
|
||||
LogNodeType: result.LogNodeType
|
||||
}
|
||||
}
|
||||
|
||||
@@ -56,7 +54,7 @@ const data = computed<Data | null>(() => {
|
||||
return {
|
||||
Description: action.Description,
|
||||
Content: sanitize(action.Action_Result),
|
||||
LogNodeType: result.LogNodeType,
|
||||
LogNodeType: result.LogNodeType
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -75,9 +73,11 @@ const data = computed<Data | null>(() => {
|
||||
class="text-[16px] flex items-center gap-1 text-[var(--color-text-secondary)] mb-1"
|
||||
>
|
||||
{{ data.Description }}
|
||||
<Iod v-if="data.LogNodeType !== 'object'"/>
|
||||
<Iod v-if="data.LogNodeType !== 'object'" />
|
||||
</div>
|
||||
<div class="rounded-[8px] p-[15px] text-[14px] bg-[var(--color-bg-quaternary)]">
|
||||
<div
|
||||
class="rounded-[8px] p-[15px] text-[14px] bg-[var(--color-bg-result-detail)] text-[var(--color-text-detail)]"
|
||||
>
|
||||
<div
|
||||
class="markdown-content max-h-[240px] overflow-y-auto max-w-full"
|
||||
v-html="data.Content"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<script setup lang="ts">
|
||||
import { readConfig } from '@/utils/readJson.ts'
|
||||
import { onMounted } from 'vue'
|
||||
import { ref, computed, onMounted } from 'vue'
|
||||
|
||||
interface Iod {
|
||||
name: string
|
||||
@@ -28,10 +28,22 @@ function handleNext() {
|
||||
displayIndex.value++
|
||||
}
|
||||
}
|
||||
|
||||
function handlePrev() {
|
||||
if (displayIndex.value === 0) {
|
||||
displayIndex.value = data.value.length - 1
|
||||
} else {
|
||||
displayIndex.value--
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<el-popover trigger="hover" width="440">
|
||||
<el-popover
|
||||
trigger="hover"
|
||||
width="440"
|
||||
popper-style="background-color: var(--color-bg-result); border: none;"
|
||||
>
|
||||
<template #reference>
|
||||
<div
|
||||
class="rounded-full w-[20px] h-[20px] bg-[var(--color-bg-quaternary)] flex justify-center items-center cursor-pointer"
|
||||
@@ -40,34 +52,67 @@ function handleNext() {
|
||||
</div>
|
||||
</template>
|
||||
<template #default v-if="data.length">
|
||||
<div>
|
||||
<div class="bg-[var(--color-bg-result)]">
|
||||
<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>{{ `${displayIndex + 1}/${data.length}` }}</div>
|
||||
<el-button type="primary" size="small" @click="handleNext">下一个</el-button> -->
|
||||
<!-- 关闭 -->
|
||||
<SvgIcon icon-class="close" size="15px" />
|
||||
</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 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-detail)] 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-detail)] 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-detail)] 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-detail)] flex-1 break-words break-al">
|
||||
{{ displayIod.fromRepo }}
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div
|
||||
class="card-item w-[80px] h-[25px] flex justify-between items-center rounded-[25px] bg-[#b1b1b1] ml-auto px-2"
|
||||
>
|
||||
<div class="text-[14px] text-[#ffffff] font-medium">
|
||||
{{ `${displayIndex + 1}/${data.length}` }}
|
||||
</div>
|
||||
<div class="flex items-center gap-1">
|
||||
<svg-icon
|
||||
icon-class="left"
|
||||
size="15px"
|
||||
@click="handlePrev"
|
||||
class="cursor-pointer hover:opacity-70"
|
||||
></svg-icon>
|
||||
<svg-icon
|
||||
icon-class="right"
|
||||
size="15px"
|
||||
@click="handleNext"
|
||||
class="cursor-pointer hover:opacity-70"
|
||||
></svg-icon>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</el-popover>
|
||||
|
||||
@@ -9,7 +9,7 @@ import { type ConnectArg, Jsplumb } from '@/layout/components/Main/TaskTemplate/
|
||||
import variables from '@/styles/variables.module.scss'
|
||||
import { type IRawStepTask, useAgentsStore } from '@/stores'
|
||||
import api from '@/api'
|
||||
|
||||
import ProcessCard from '../TaskProcess/ProcessCard.vue'
|
||||
import ExecutePlan from './ExecutePlan.vue'
|
||||
|
||||
const emit = defineEmits<{
|
||||
@@ -18,21 +18,68 @@ const emit = defineEmits<{
|
||||
}>()
|
||||
|
||||
const agentsStore = useAgentsStore()
|
||||
|
||||
|
||||
const drawerVisible = ref(false)
|
||||
const collaborationProcess = computed(() => {
|
||||
return agentsStore.agentRawPlan.data?.['Collaboration Process'] ?? []
|
||||
})
|
||||
|
||||
// 编辑逻辑
|
||||
const editMode = ref(false) //全局编辑开关
|
||||
const editMap = reactive<Record<string, boolean>>({}) //行级编辑状态
|
||||
const editBuffer = reactive<Record<string, string | undefined>>({}) //临时输入
|
||||
|
||||
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 save() {
|
||||
Object.keys(editMap).forEach(key => {
|
||||
if (editMap[key]) {
|
||||
const [stepId, processId] = key.split('-')
|
||||
const value = editBuffer[key]
|
||||
// 确保 value 是字符串类型
|
||||
if (value !== undefined && value !== null) {
|
||||
// @ts-ignore - TypeScript 无法正确推断类型,但运行时是安全的
|
||||
handleSaveEdit(stepId, processId, value)
|
||||
}
|
||||
}
|
||||
})
|
||||
editMode.value = false
|
||||
}
|
||||
|
||||
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
|
||||
ElMessage.success('已保存(前端内存)')
|
||||
}
|
||||
const jsplumb = new Jsplumb('task-results-main', {
|
||||
connector: {
|
||||
type: BezierConnector.type,
|
||||
options: { curviness: 30, stub: 10 },
|
||||
},
|
||||
options: { curviness: 30, stub: 10 }
|
||||
}
|
||||
})
|
||||
|
||||
// 操作折叠面板时要实时的刷新连线
|
||||
let timer: number
|
||||
let timer: ReturnType<typeof setInterval> | null = null
|
||||
function handleCollapse() {
|
||||
if (timer) {
|
||||
clearInterval(timer)
|
||||
@@ -40,11 +87,14 @@ function handleCollapse() {
|
||||
timer = setInterval(() => {
|
||||
jsplumb.repaintEverything()
|
||||
emit('refreshLine')
|
||||
}, 1)
|
||||
}, 1) as ReturnType<typeof setInterval>
|
||||
|
||||
// 默认三秒后已经完全打开
|
||||
const timer1 = setTimeout(() => {
|
||||
clearInterval(timer)
|
||||
if (timer) {
|
||||
clearInterval(timer)
|
||||
timer = null
|
||||
}
|
||||
}, 3000)
|
||||
|
||||
onUnmounted(() => {
|
||||
@@ -61,14 +111,14 @@ function handleCollapse() {
|
||||
function createInternalLine(id?: string) {
|
||||
const arr: ConnectArg[] = []
|
||||
jsplumb.reset()
|
||||
collaborationProcess.value.forEach((item) => {
|
||||
collaborationProcess.value.forEach(item => {
|
||||
// 创建左侧流程与产出的连线
|
||||
arr.push({
|
||||
sourceId: `task-results-${item.Id}-0`,
|
||||
targetId: `task-results-${item.Id}-1`,
|
||||
anchor: [AnchorLocations.Left, AnchorLocations.Left],
|
||||
anchor: [AnchorLocations.Left, AnchorLocations.Left]
|
||||
})
|
||||
collaborationProcess.value.forEach((jitem) => {
|
||||
collaborationProcess.value.forEach(jitem => {
|
||||
// 创建左侧产出与上一步流程的连线
|
||||
if (item.InputObject_List!.includes(jitem.OutputObject ?? '')) {
|
||||
arr.push({
|
||||
@@ -76,12 +126,12 @@ function createInternalLine(id?: string) {
|
||||
targetId: `task-results-${item.Id}-0`,
|
||||
anchor: [AnchorLocations.Left, AnchorLocations.Left],
|
||||
config: {
|
||||
type: 'output',
|
||||
},
|
||||
type: 'output'
|
||||
}
|
||||
})
|
||||
}
|
||||
// 创建右侧任务程序与InputObject字段的连线
|
||||
jitem.TaskProcess.forEach((i) => {
|
||||
jitem.TaskProcess.forEach(i => {
|
||||
if (i.ImportantInput?.includes(`InputObject:${item.OutputObject}`)) {
|
||||
const color = getActionTypeDisplay(i.ActionType)?.color ?? ''
|
||||
const sourceId = `task-results-${item.Id}-1`
|
||||
@@ -93,21 +143,21 @@ function createInternalLine(id?: string) {
|
||||
config: {
|
||||
stops: [
|
||||
[0, color],
|
||||
[1, color],
|
||||
[1, color]
|
||||
],
|
||||
transparent: targetId !== id,
|
||||
},
|
||||
transparent: targetId !== id
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
// 创建右侧TaskProcess内部连线
|
||||
item.TaskProcess?.forEach((i) => {
|
||||
item.TaskProcess?.forEach(i => {
|
||||
if (!i.ImportantInput?.length) {
|
||||
return
|
||||
}
|
||||
item.TaskProcess?.forEach((i2) => {
|
||||
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}`
|
||||
@@ -119,10 +169,10 @@ function createInternalLine(id?: string) {
|
||||
config: {
|
||||
stops: [
|
||||
[0, color],
|
||||
[1, color],
|
||||
[1, color]
|
||||
],
|
||||
transparent: targetId !== id,
|
||||
},
|
||||
transparent: targetId !== id
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
@@ -134,6 +184,14 @@ function createInternalLine(id?: string) {
|
||||
}
|
||||
|
||||
const loading = ref(false)
|
||||
|
||||
// 额外产物编辑状态
|
||||
const editingOutputId = ref<string | null>(null)
|
||||
const editingOutputContent = ref('')
|
||||
|
||||
// 额外产物内容存储
|
||||
const additionalOutputContents = ref<Record<string, string>>({})
|
||||
|
||||
async function handleRun() {
|
||||
try {
|
||||
loading.value = true
|
||||
@@ -144,9 +202,51 @@ async function handleRun() {
|
||||
}
|
||||
}
|
||||
|
||||
// 查看任务过程
|
||||
async function handleTaskProcess() {
|
||||
drawerVisible.value = true
|
||||
}
|
||||
|
||||
// 开始编辑额外产物内容
|
||||
function startOutputEditing(output: string) {
|
||||
editingOutputId.value = output
|
||||
editingOutputContent.value = getAdditionalOutputContent(output) || ''
|
||||
}
|
||||
|
||||
// 保存额外产物内容
|
||||
function saveOutputEditing() {
|
||||
if (editingOutputId.value && editingOutputContent.value.trim()) {
|
||||
additionalOutputContents.value[editingOutputId.value] = editingOutputContent.value.trim()
|
||||
}
|
||||
editingOutputId.value = null
|
||||
editingOutputContent.value = ''
|
||||
}
|
||||
|
||||
// 取消编辑额外产物内容
|
||||
function cancelOutputEditing() {
|
||||
editingOutputId.value = null
|
||||
editingOutputContent.value = ''
|
||||
}
|
||||
|
||||
// 获取额外产物内容
|
||||
function getAdditionalOutputContent(output: string) {
|
||||
return additionalOutputContents.value[output] || ''
|
||||
}
|
||||
|
||||
// 处理额外产物的键盘事件
|
||||
function handleOutputKeydown(event: KeyboardEvent) {
|
||||
if (event.key === 'Enter') {
|
||||
event.preventDefault()
|
||||
saveOutputEditing()
|
||||
} else if (event.key === 'Escape') {
|
||||
editingOutputId.value = null
|
||||
editingOutputContent.value = ''
|
||||
}
|
||||
}
|
||||
|
||||
// 添加滚动状态标识
|
||||
const isScrolling = ref(false)
|
||||
let scrollTimer: number
|
||||
let scrollTimer: ReturnType<typeof setTimeout> | null = null
|
||||
|
||||
// 修改滚动处理函数
|
||||
function handleScroll() {
|
||||
@@ -161,11 +261,11 @@ function handleScroll() {
|
||||
// 设置滚动结束检测
|
||||
scrollTimer = setTimeout(() => {
|
||||
isScrolling.value = false
|
||||
}, 300)
|
||||
}, 300) as ReturnType<typeof setTimeout>
|
||||
}
|
||||
|
||||
// 修改鼠标事件处理函数
|
||||
const handleMouseEnter = throttle((id) => {
|
||||
const handleMouseEnter = throttle(id => {
|
||||
if (!isScrolling.value) {
|
||||
createInternalLine(id)
|
||||
}
|
||||
@@ -181,34 +281,160 @@ function clear() {
|
||||
jsplumb.reset()
|
||||
}
|
||||
|
||||
// ========== 按钮交互状态管理 ==========
|
||||
const buttonHoverState = ref<'process' | 'execute' | 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'
|
||||
}
|
||||
}
|
||||
|
||||
const handleButtonMouseLeave = () => {
|
||||
// 添加防抖,防止快速切换时的抖动
|
||||
if (buttonHoverTimer) {
|
||||
clearTimeout(buttonHoverTimer)
|
||||
}
|
||||
buttonHoverTimer = setTimeout(() => {
|
||||
buttonHoverState.value = null
|
||||
}, 50) // 适当减少延迟时间
|
||||
}
|
||||
|
||||
// 添加离开组件时的清理
|
||||
onUnmounted(() => {
|
||||
if (buttonHoverTimer) {
|
||||
clearTimeout(buttonHoverTimer)
|
||||
}
|
||||
})
|
||||
// 计算按钮类名
|
||||
const processBtnClass = computed(() => {
|
||||
return buttonHoverState.value === 'process' ? 'ellipse' : 'circle'
|
||||
})
|
||||
|
||||
const executeBtnClass = computed(() => {
|
||||
// 鼠标悬停在过程按钮上时,执行按钮变圆形
|
||||
if (buttonHoverState.value === 'process') {
|
||||
return 'circle'
|
||||
}
|
||||
// 其他情况:如果有任务数据就显示椭圆形,否则显示圆形
|
||||
return agentsStore.agentRawPlan.data ? 'ellipse' : 'circle'
|
||||
})
|
||||
|
||||
// 计算按钮是否显示文字
|
||||
const showProcessText = computed(() => {
|
||||
return buttonHoverState.value === 'process'
|
||||
})
|
||||
|
||||
const showExecuteText = computed(() => {
|
||||
// 鼠标悬停在过程按钮上时,执行按钮不显示文字
|
||||
if (buttonHoverState.value === 'process') return false
|
||||
// 其他情况:如果有任务数据就显示文字,否则不显示
|
||||
return agentsStore.agentRawPlan.data
|
||||
})
|
||||
|
||||
// 计算按钮标题
|
||||
const processBtnTitle = computed(() => {
|
||||
return buttonHoverState.value === 'process' ? '任务过程' : '点击查看任务过程'
|
||||
})
|
||||
|
||||
const executeBtnTitle = computed(() => {
|
||||
return showExecuteText.value ? '任务执行' : '点击运行'
|
||||
})
|
||||
|
||||
defineExpose({
|
||||
createInternalLine,
|
||||
clear,
|
||||
clear
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="h-full flex flex-col relative" id="task-results">
|
||||
<div
|
||||
class="h-full flex flex-col relative"
|
||||
id="task-results"
|
||||
:class="{ 'is-running': agentsStore.executePlan.length > 0 }"
|
||||
>
|
||||
<!-- 标题与执行按钮 -->
|
||||
<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" />
|
||||
<span class="text-[var(--color-text-title-header)]">执行结果</span>
|
||||
<div
|
||||
class="flex items-center gap-[14px] task-button-group"
|
||||
@mouseleave="handleButtonMouseLeave"
|
||||
>
|
||||
<!-- 任务过程按钮 -->
|
||||
<el-button
|
||||
:class="processBtnClass"
|
||||
:color="variables.tertiary"
|
||||
:title="processBtnTitle"
|
||||
@mouseenter="handleProcessMouseEnter"
|
||||
@click="handleTaskProcess"
|
||||
>
|
||||
<svg-icon icon-class="process" />
|
||||
<span v-if="showProcessText" class="btn-text">任务过程</span>
|
||||
</el-button>
|
||||
<el-popover :disabled="Boolean(agentsStore.agentRawPlan.data)" title="请先输入要执行的任务">
|
||||
|
||||
<!-- 任务执行按钮 -->
|
||||
<el-popover
|
||||
:disabled="Boolean(agentsStore.agentRawPlan.data)"
|
||||
title="请先输入要执行的任务"
|
||||
:visible="showPopover"
|
||||
@hide="showPopover = false"
|
||||
>
|
||||
<template #reference>
|
||||
<el-button
|
||||
circle
|
||||
:class="executeBtnClass"
|
||||
:color="variables.tertiary"
|
||||
title="点击运行"
|
||||
:title="executeBtnTitle"
|
||||
:disabled="!agentsStore.agentRawPlan.data"
|
||||
@mouseenter="handleExecuteMouseEnter"
|
||||
@click="handleRun"
|
||||
>
|
||||
<svg-icon 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>
|
||||
<!-- 内容 -->
|
||||
@@ -218,11 +444,12 @@ defineExpose({
|
||||
@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"
|
||||
:shadow="true"
|
||||
:id="`task-results-${item.Id}-0`"
|
||||
@click="emit('setCurrentTask', item)"
|
||||
>
|
||||
@@ -244,7 +471,14 @@ defineExpose({
|
||||
<span></span>
|
||||
</template>
|
||||
<template #title>
|
||||
<div class="flex items-center gap-[15px]">
|
||||
<!-- 运行之前背景颜色是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"
|
||||
@@ -256,12 +490,19 @@ defineExpose({
|
||||
>
|
||||
<svg-icon
|
||||
:icon-class="getAgentMapIcon(item1.AgentName).icon"
|
||||
color="var(--color-text)"
|
||||
color="#fff"
|
||||
size="24px"
|
||||
/>
|
||||
</div>
|
||||
<div class="text-[16px]">
|
||||
<span>{{ item1.AgentName }}: </span>
|
||||
<span
|
||||
:class="{
|
||||
'text-[var(--color-text-result-detail)]': !agentsStore.executePlan.length,
|
||||
'text-[var(--color-text-result-detail-run)]':
|
||||
agentsStore.executePlan.length
|
||||
}"
|
||||
>{{ item1.AgentName }}: </span
|
||||
>
|
||||
<span :style="{ color: getActionTypeDisplay(item1.ActionType)?.color }">
|
||||
{{ getActionTypeDisplay(item1.ActionType)?.name }}
|
||||
</span>
|
||||
@@ -279,7 +520,7 @@ defineExpose({
|
||||
|
||||
<el-card
|
||||
class="card-item w-full relative output-object-card"
|
||||
shadow="hover"
|
||||
:shadow="true"
|
||||
:class="agentsStore.currentTask?.StepName === item.StepName ? 'active-card' : ''"
|
||||
:id="`task-results-${item.Id}-1`"
|
||||
@click="emit('setCurrentTask', item)"
|
||||
@@ -297,31 +538,114 @@ defineExpose({
|
||||
<span></span>
|
||||
</template>
|
||||
<template #title>
|
||||
<div class="text-[18px]">{{ item.OutputObject }}</div>
|
||||
<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" />
|
||||
<ExecutePlan
|
||||
:node-id="item.OutputObject"
|
||||
:execute-plans="agentsStore.executePlan"
|
||||
/>
|
||||
</el-collapse-item>
|
||||
</el-collapse>
|
||||
</el-card>
|
||||
</div>
|
||||
|
||||
<!-- 额外产物的编辑卡片 -->
|
||||
<div
|
||||
v-for="(output, index) in agentsStore.additionalOutputs"
|
||||
:key="`additional-output-${index}`"
|
||||
class="card-item"
|
||||
>
|
||||
<!-- 空的流程卡片位置 -->
|
||||
<div class="w-full"></div>
|
||||
|
||||
<!-- 额外产物的编辑卡片 -->
|
||||
<el-card
|
||||
class="card-item w-full relative output-object-card additional-output-card"
|
||||
:shadow="false"
|
||||
:id="`additional-output-results-${index}`"
|
||||
>
|
||||
<!-- 产物名称行 -->
|
||||
<div class="text-[18px] mb-3">
|
||||
{{ output }}
|
||||
</div>
|
||||
|
||||
<!-- 编辑区域行 -->
|
||||
<div class="additional-output-editor">
|
||||
<div v-if="editingOutputId === output" class="w-full">
|
||||
<!-- 编辑状态:输入框 + 按钮 -->
|
||||
<div class="flex flex-col gap-3">
|
||||
<el-input
|
||||
v-model="editingOutputContent"
|
||||
type="textarea"
|
||||
:autosize="{ minRows: 3, maxRows: 6 }"
|
||||
placeholder="请输入产物内容"
|
||||
@keydown="handleOutputKeydown"
|
||||
class="output-editor"
|
||||
size="small"
|
||||
/>
|
||||
<div class="flex justify-end gap-2">
|
||||
<el-button @click="saveOutputEditing" type="primary" size="small" class="px-3">
|
||||
√
|
||||
</el-button>
|
||||
<el-button @click="cancelOutputEditing" size="small" class="px-3">
|
||||
×
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else class="w-full">
|
||||
<!-- 非编辑状态:折叠区域 + 编辑按钮 -->
|
||||
<div
|
||||
class="flex items-center justify-between p-3 bg-[var(--color-bg-quinary)] rounded-[8px]"
|
||||
>
|
||||
<div
|
||||
class="text-[14px] text-[var(--color-text-secondary)] output-content-display"
|
||||
>
|
||||
{{ getAdditionalOutputContent(output) || '暂无内容,点击编辑' }}
|
||||
</div>
|
||||
<el-button
|
||||
@click="startOutputEditing(output)"
|
||||
size="small"
|
||||
type="primary"
|
||||
plain
|
||||
class="flex items-center gap-1"
|
||||
>
|
||||
<svg-icon icon-class="action" size="12px" />
|
||||
<span>编辑</span>
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</el-card>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
#task-results.is-running {
|
||||
--color-bg-detail-list: var(--color-bg-detail-list-run); // 直接指向 100 % 版本
|
||||
}
|
||||
#task-results {
|
||||
:deep(.el-collapse) {
|
||||
border: none;
|
||||
border-radius: 20px;
|
||||
|
||||
.el-collapse-item + .el-collapse-item {
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.el-collapse-item__header {
|
||||
border: none;
|
||||
background: var(--color-bg-secondary);
|
||||
background: var(--color-bg-detail-list-run);
|
||||
min-height: 41px;
|
||||
line-height: 41px;
|
||||
border-radius: 20px;
|
||||
@@ -329,13 +653,16 @@ defineExpose({
|
||||
position: relative;
|
||||
|
||||
.el-collapse-item__title {
|
||||
background: var(--color-bg-secondary);
|
||||
background: var(--color-bg-detail-list);
|
||||
border-radius: 20px;
|
||||
}
|
||||
|
||||
.el-icon {
|
||||
font-size: 20px;
|
||||
font-weight: bold;
|
||||
font-weight: 900;
|
||||
background: var(--color-bg-icon-rotate);
|
||||
border-radius: 50px;
|
||||
color: #d8d8d8;
|
||||
}
|
||||
|
||||
&.is-active {
|
||||
@@ -357,7 +684,7 @@ defineExpose({
|
||||
background: none;
|
||||
|
||||
.card-item {
|
||||
background: var(--color-bg-secondary);
|
||||
background: var(--color-bg-detail);
|
||||
padding: 25px;
|
||||
padding-top: 10px;
|
||||
border-radius: 7px;
|
||||
@@ -367,7 +694,7 @@ defineExpose({
|
||||
|
||||
.el-collapse-item__wrap {
|
||||
border: none;
|
||||
background: var(--color-bg-secondary);
|
||||
background: var(--color-bg-detail-list);
|
||||
border-bottom-left-radius: 20px;
|
||||
border-bottom-right-radius: 20px;
|
||||
}
|
||||
@@ -376,6 +703,10 @@ defineExpose({
|
||||
:deep(.el-card) {
|
||||
.el-card__body {
|
||||
padding-right: 40px;
|
||||
background-color: var(--color-bg-detail);
|
||||
&:hover {
|
||||
background-color: var(--color-card-bg-result-hover);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -388,13 +719,176 @@ defineExpose({
|
||||
}
|
||||
|
||||
.active-card {
|
||||
background:
|
||||
linear-gradient(var(--color-bg-tertiary), var(--color-bg-tertiary)) padding-box,
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 额外产物编辑区域样式
|
||||
.additional-output-editor {
|
||||
.output-editor {
|
||||
:deep(.el-textarea__inner) {
|
||||
font-size: 14px;
|
||||
color: var(--color-text-primary);
|
||||
background: var(--color-bg-detail);
|
||||
border: 1px solid #dcdfe6;
|
||||
resize: none;
|
||||
padding: 12px;
|
||||
}
|
||||
}
|
||||
|
||||
.output-content-display {
|
||||
word-break: break-word;
|
||||
white-space: pre-wrap;
|
||||
transition: all 0.2s ease;
|
||||
line-height: 1.5;
|
||||
min-height: 20px;
|
||||
|
||||
&:hover {
|
||||
background-color: rgba(0, 0, 0, 0.05);
|
||||
border-color: #409eff;
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
// 编辑按钮样式
|
||||
.el-button {
|
||||
font-weight: bold;
|
||||
font-size: 16px;
|
||||
border-radius: 4px;
|
||||
|
||||
&.el-button--small {
|
||||
padding: 4px 12px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ========== 新增:按钮交互样式 ==========
|
||||
.task-button-group {
|
||||
.el-button {
|
||||
display: inline-flex !important;
|
||||
align-items: center !important;
|
||||
justify-content: center !important;
|
||||
transition: all 0.35s cubic-bezier(0.175, 0.885, 0.32, 1.275) !important;
|
||||
overflow: hidden !important;
|
||||
white-space: nowrap !important;
|
||||
border: none !important;
|
||||
color: var(--color-text-primary) !important;
|
||||
position: relative;
|
||||
background-color: var(--color-bg-tertiary);
|
||||
&:hover {
|
||||
transform: translateY(-2px) !important;
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15) !important;
|
||||
filter: brightness(1.1) !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;
|
||||
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;
|
||||
|
||||
.btn-text {
|
||||
display: inline-block !important;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
margin-left: 4px;
|
||||
opacity: 1;
|
||||
animation: fadeIn 0.3s ease forwards;
|
||||
}
|
||||
}
|
||||
|
||||
.btn-text {
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
margin-left: 4px;
|
||||
opacity: 0;
|
||||
animation: fadeIn 0.3s ease forwards;
|
||||
}
|
||||
|
||||
@keyframes fadeIn {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateX(-5px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateX(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,31 +1,26 @@
|
||||
<template>
|
||||
<div class="absolute inset-0 flex items-start gap-[14%]">
|
||||
<!-- 左侧元素 -->
|
||||
<!-- 左侧元素 -->
|
||||
<div class="flex-1 relative h-full flex justify-center">
|
||||
<!-- 背景那一根线 -->
|
||||
<div
|
||||
class="h-full bg-[var(--color-bg-tertiary)] w-[5px]"
|
||||
>
|
||||
<div class="h-full bg-[var(--color-bg-flow)] w-[5px]">
|
||||
<!-- 线底部的小圆球 -->
|
||||
<div
|
||||
class="absolute bottom-0 left-1/2 transform -translate-x-1/2 bg-[var(--color-bg-tertiary)] w-[15px] h-[15px] rounded-full"
|
||||
class="absolute bottom-0 left-1/2 transform -translate-x-1/2 bg-[var(--color-bg-flow)] w-[15px] h-[15px] rounded-full"
|
||||
></div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 右侧元素 -->
|
||||
<!-- 右侧元素 -->
|
||||
<div class="flex-1 relative h-full flex justify-center">
|
||||
<!-- 背景那一根线 -->
|
||||
<div
|
||||
class="h-full bg-[var(--color-bg-tertiary)] w-[5px]"
|
||||
>
|
||||
<div class="h-full bg-[var(--color-bg-flow)] w-[5px]">
|
||||
<!-- 线底部的小圆球 -->
|
||||
<div
|
||||
class="absolute bottom-0 left-1/2 transform -translate-x-1/2 bg-[var(--color-bg-tertiary)] w-[15px] h-[15px] rounded-full"
|
||||
class="absolute bottom-0 left-1/2 transform -translate-x-1/2 bg-[var(--color-bg-flow)] w-[15px] h-[15px] rounded-full"
|
||||
></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
</script>
|
||||
<script setup lang="ts"></script>
|
||||
|
||||
@@ -3,10 +3,9 @@ import SvgIcon from '@/components/SvgIcon/index.vue'
|
||||
import { getAgentMapIcon } from '@/layout/components/config.ts'
|
||||
import { type ConnectArg, Jsplumb } from '@/layout/components/Main/TaskTemplate/utils.ts'
|
||||
import { type IRawStepTask, useAgentsStore } from '@/stores'
|
||||
import { computed } from 'vue'
|
||||
import { computed, ref } from 'vue'
|
||||
import { AnchorLocations } from '@jsplumb/browser-ui'
|
||||
import MultiLineTooltip from '@/components/MultiLineTooltip/index.vue'
|
||||
|
||||
import Bg from './Bg.vue'
|
||||
|
||||
const emit = defineEmits<{
|
||||
@@ -26,6 +25,90 @@ const collaborationProcess = computed(() => {
|
||||
return agentsStore.agentRawPlan.data?.['Collaboration Process'] ?? []
|
||||
})
|
||||
|
||||
// 编辑状态管理
|
||||
const editingTaskId = ref<string | null>(null)
|
||||
const editingContent = ref('')
|
||||
|
||||
// 添加新产物状态管理
|
||||
const showAddOutputForm = ref(false)
|
||||
const newOutputName = ref('')
|
||||
|
||||
// 开始编辑
|
||||
const startEditing = (task: IRawStepTask) => {
|
||||
if (!task.Id) {
|
||||
console.warn('Task ID is missing, cannot start editing')
|
||||
return
|
||||
}
|
||||
editingTaskId.value = task.Id
|
||||
editingContent.value = task.TaskContent || ''
|
||||
}
|
||||
|
||||
// 保存编辑
|
||||
const saveEditing = () => {
|
||||
if (editingTaskId.value && editingContent.value.trim()) {
|
||||
const taskToUpdate = collaborationProcess.value.find(item => item.Id === editingTaskId.value)
|
||||
if (taskToUpdate) {
|
||||
taskToUpdate.TaskContent = editingContent.value.trim()
|
||||
}
|
||||
}
|
||||
editingTaskId.value = null
|
||||
editingContent.value = ''
|
||||
}
|
||||
|
||||
// 取消编辑
|
||||
const cancelEditing = () => {
|
||||
editingTaskId.value = null
|
||||
editingContent.value = ''
|
||||
}
|
||||
|
||||
// 处理键盘事件
|
||||
const handleKeydown = (event: KeyboardEvent) => {
|
||||
if (event.key === 'Enter') {
|
||||
event.preventDefault()
|
||||
saveEditing()
|
||||
} else if (event.key === 'Escape') {
|
||||
cancelEditing()
|
||||
}
|
||||
}
|
||||
|
||||
// 显示添加产物表单
|
||||
const showAddOutputDialog = () => {
|
||||
showAddOutputForm.value = true
|
||||
newOutputName.value = ''
|
||||
}
|
||||
|
||||
// 添加新产物
|
||||
const addNewOutput = () => {
|
||||
if (newOutputName.value.trim()) {
|
||||
const success = agentsStore.addNewOutput(newOutputName.value.trim())
|
||||
if (success) {
|
||||
showAddOutputForm.value = false
|
||||
newOutputName.value = ''
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 取消添加产物
|
||||
const cancelAddOutput = () => {
|
||||
showAddOutputForm.value = false
|
||||
newOutputName.value = ''
|
||||
}
|
||||
|
||||
// 处理添加产物的键盘事件
|
||||
const handleAddOutputKeydown = (event: KeyboardEvent) => {
|
||||
if (event.key === 'Enter') {
|
||||
event.preventDefault()
|
||||
addNewOutput()
|
||||
} else if (event.key === 'Escape') {
|
||||
cancelAddOutput()
|
||||
}
|
||||
}
|
||||
|
||||
// 删除额外产物
|
||||
const removeAdditionalOutput = (output: string) => {
|
||||
agentsStore.removeAdditionalOutput(output)
|
||||
}
|
||||
|
||||
function handleCurrentTask(task: IRawStepTask, transparent: boolean): ConnectArg[] {
|
||||
// 创建当前流程与产出的连线
|
||||
const arr: ConnectArg[] = [
|
||||
@@ -34,14 +117,14 @@ function handleCurrentTask(task: IRawStepTask, transparent: boolean): ConnectArg
|
||||
targetId: `task-syllabus-output-object-${task.Id}`,
|
||||
anchor: [AnchorLocations.Right, AnchorLocations.Left],
|
||||
config: {
|
||||
transparent,
|
||||
},
|
||||
},
|
||||
transparent
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
// 创建当前产出与流程的连线
|
||||
task.InputObject_List?.forEach((item) => {
|
||||
const id = collaborationProcess.value.find((i) => i.OutputObject === item)?.Id
|
||||
task.InputObject_List?.forEach(item => {
|
||||
const id = collaborationProcess.value.find(i => i.OutputObject === item)?.Id
|
||||
if (id) {
|
||||
arr.push({
|
||||
sourceId: `task-syllabus-output-object-${id}`,
|
||||
@@ -49,8 +132,8 @@ function handleCurrentTask(task: IRawStepTask, transparent: boolean): ConnectArg
|
||||
anchor: [AnchorLocations.Left, AnchorLocations.Right],
|
||||
config: {
|
||||
type: 'output',
|
||||
transparent,
|
||||
},
|
||||
transparent
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
@@ -61,7 +144,7 @@ function handleCurrentTask(task: IRawStepTask, transparent: boolean): ConnectArg
|
||||
function changeTask(task?: IRawStepTask, isEmit?: boolean) {
|
||||
jsplumb.reset()
|
||||
const arr: ConnectArg[] = []
|
||||
agentsStore.agentRawPlan.data?.['Collaboration Process']?.forEach((item) => {
|
||||
agentsStore.agentRawPlan.data?.['Collaboration Process']?.forEach(item => {
|
||||
arr.push(...handleCurrentTask(item, item.Id !== task?.Id))
|
||||
})
|
||||
jsplumb.connects(arr)
|
||||
@@ -76,26 +159,32 @@ function clear() {
|
||||
|
||||
defineExpose({
|
||||
changeTask,
|
||||
clear,
|
||||
clear
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="h-full flex flex-col">
|
||||
<div class="text-[18px] font-bold mb-[18px]">任务大纲</div>
|
||||
<div class="text-[18px] font-bold mb-[18px] text-[var(--color-text-title-header)]">
|
||||
任务大纲
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-loading="agentsStore.agentRawPlan.loading"
|
||||
class="flex-1 w-full overflow-y-auto relative"
|
||||
@scroll="handleScroll"
|
||||
>
|
||||
<div v-show="collaborationProcess.length > 0" class="w-full relative min-h-full" id="task-syllabus">
|
||||
<div
|
||||
v-show="collaborationProcess.length > 0"
|
||||
class="w-full relative min-h-full"
|
||||
id="task-syllabus"
|
||||
>
|
||||
<Bg />
|
||||
|
||||
<div class="w-full flex items-center gap-[14%] mb-[35px]">
|
||||
<div class="flex-1 flex justify-center">
|
||||
<div
|
||||
class="card-item w-[45%] h-[41px] flex justify-center relative z-99 items-center rounded-[20px] bg-[var(--color-bg-tertiary)]"
|
||||
class="card-item w-[168px] h-[41px] flex justify-center relative z-99 items-center rounded-[20px] bg-[var(--color-bg-flow)]"
|
||||
>
|
||||
流程
|
||||
</div>
|
||||
@@ -103,7 +192,7 @@ defineExpose({
|
||||
|
||||
<div class="flex-1 flex justify-center">
|
||||
<div
|
||||
class="card-item w-[45%] h-[41px] flex justify-center relative z-99 items-center rounded-[20px] bg-[var(--color-bg-tertiary)]"
|
||||
class="card-item w-[168px] h-[41px] flex justify-center relative z-99 items-center rounded-[20px] bg-[var(--color-bg-flow)]"
|
||||
>
|
||||
产物
|
||||
</div>
|
||||
@@ -113,31 +202,50 @@ defineExpose({
|
||||
<div
|
||||
v-for="item in collaborationProcess"
|
||||
:key="item.Id"
|
||||
class="card-item w-full flex items-center gap-[14%]"
|
||||
class="card-it w-full flex items-center gap-[14%] bg-[var(--color-card-bg)]"
|
||||
>
|
||||
<!-- 流程卡片 -->
|
||||
<el-card
|
||||
class="w-[43%] overflow-y-auto relative z-99 task-syllabus-flow-card"
|
||||
:class="agentsStore.currentTask?.StepName === item.StepName ? 'active-card' : ''"
|
||||
shadow="hover"
|
||||
:shadow="true"
|
||||
:id="`task-syllabus-flow-${item.Id}`"
|
||||
@click="changeTask(item, true)"
|
||||
>
|
||||
<MultiLineTooltip placement="right" :text="item.StepName" :lines="2">
|
||||
<div class="text-[18px] font-bold text-center">
|
||||
{{ item.StepName }}
|
||||
</div>
|
||||
<div class="text-[18px] font-bold text-center">{{ item.StepName }}</div>
|
||||
</MultiLineTooltip>
|
||||
<div class="h-[1px] w-full bg-[#494B51] my-[8px]"></div>
|
||||
<MultiLineTooltip placement="right" :text="item.StepName" :lines="3">
|
||||
<div
|
||||
class="text-[14px] text-[var(--color-text-secondary)]"
|
||||
:title="item.TaskContent"
|
||||
>
|
||||
{{ item.TaskContent }}
|
||||
<div class="h-[1px] w-full bg-[#d6d6d6] my-[8px]"></div>
|
||||
|
||||
<!-- 任务内容区域 - 支持双击编辑 -->
|
||||
<div v-if="editingTaskId === item.Id" class="w-full">
|
||||
<div class="flex flex-col gap-3">
|
||||
<el-input
|
||||
v-model="editingContent"
|
||||
type="textarea"
|
||||
:autosize="{ minRows: 2, maxRows: 4 }"
|
||||
placeholder="请输入任务内容"
|
||||
@keydown="handleKeydown"
|
||||
class="task-content-editor"
|
||||
size="small"
|
||||
/>
|
||||
<div class="flex justify-end gap-2">
|
||||
<el-button @click="saveEditing" type="primary" size="small" class="px-3">
|
||||
√
|
||||
</el-button>
|
||||
<el-button @click="cancelEditing" size="small" class="px-3"> × </el-button>
|
||||
</div>
|
||||
</div>
|
||||
</MultiLineTooltip>
|
||||
<div class="h-[1px] w-full bg-[#494B51] my-[8px]"></div>
|
||||
</div>
|
||||
<div v-else @dblclick="startEditing(item)" class="w-full cursor-pointer">
|
||||
<MultiLineTooltip placement="right" :text="item.TaskContent" :lines="3">
|
||||
<div class="text-[14px] text-[var(--color-text-secondary)] task-content-display">
|
||||
{{ item.TaskContent }}
|
||||
</div>
|
||||
</MultiLineTooltip>
|
||||
</div>
|
||||
|
||||
<div class="h-[1px] w-full bg-[#d6d6d6] my-[8px]"></div>
|
||||
<div
|
||||
class="flex items-center gap-2 overflow-y-auto flex-wrap relative w-full max-h-[72px]"
|
||||
>
|
||||
@@ -157,9 +265,7 @@ defineExpose({
|
||||
<div class="text-[18px] font-bold">{{ agentSelection }}</div>
|
||||
<div class="h-[1px] w-full bg-[#494B51] my-[8px]"></div>
|
||||
<div>
|
||||
{{
|
||||
item.TaskProcess.find((i) => i.AgentName === agentSelection)?.Description
|
||||
}}
|
||||
{{ item.TaskProcess.find(i => i.AgentName === agentSelection)?.Description }}
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -169,29 +275,106 @@ defineExpose({
|
||||
>
|
||||
<svg-icon
|
||||
:icon-class="getAgentMapIcon(agentSelection).icon"
|
||||
color="var(--color-text)"
|
||||
color="#fff"
|
||||
size="24px"
|
||||
/>
|
||||
</div>
|
||||
</el-tooltip>
|
||||
</div>
|
||||
</el-card>
|
||||
<!-- 产物卡片 -->
|
||||
<!-- 产物卡片 -->
|
||||
<el-card
|
||||
class="w-[43%] relative"
|
||||
shadow="hover"
|
||||
class="w-[43%] relative task-syllabus-output-object-card"
|
||||
:shadow="true"
|
||||
:class="agentsStore.currentTask?.StepName === item.StepName ? 'active-card' : ''"
|
||||
:id="`task-syllabus-output-object-${item.Id}`"
|
||||
>
|
||||
<div class="text-[18px] font-bold text-center">{{ item.OutputObject }}</div>
|
||||
</el-card>
|
||||
</div>
|
||||
|
||||
<!-- 额外的产物列表 -->
|
||||
<!-- <div
|
||||
v-for="(output, index) in agentsStore.additionalOutputs"
|
||||
:key="`additional-${index}`"
|
||||
class="card-item w-full flex items-center gap-[14%]"
|
||||
> -->
|
||||
<!-- 空的流程卡片位置 -->
|
||||
<!-- <div class="w-[43%]"></div> -->
|
||||
<!-- 额外产物卡片 -->
|
||||
<!-- <el-card class="w-[43%] relative additional-output-card group" :shadow="false">
|
||||
<div class="flex items-center justify-between">
|
||||
<div class="text-[18px] font-bold text-center flex-1">{{ output }}</div>
|
||||
<button
|
||||
@click="removeAdditionalOutput(output)"
|
||||
class="opacity-0 group-hover:opacity-100 text-red-500 hover:text-red-700 transition-all duration-200 ml-2 text-lg font-bold w-5 h-5 flex items-center justify-center rounded hover:bg-red-50"
|
||||
>
|
||||
×
|
||||
</button>
|
||||
</div>
|
||||
</el-card>
|
||||
</div> -->
|
||||
<!-- 添加新产物按钮 -->
|
||||
<!-- <div class="card-item w-full flex items-center gap-[14%] mt-[20px]"> -->
|
||||
<!-- 空的流程卡片位置 -->
|
||||
<!-- <div class="w-[43%]"></div> -->
|
||||
<!-- 添加新产物按钮 -->
|
||||
<!-- <div class="w-[43%] relative"> -->
|
||||
<!-- <div v-if="!showAddOutputForm" class="add-output-btn">
|
||||
<button
|
||||
@click="showAddOutputDialog"
|
||||
class="w-full h-[50px] border-2 border-dashed border-gray-300 rounded-lg flex items-center justify-center text-gray-500 hover:border-blue-500 hover:text-blue-500 transition-all duration-200 group"
|
||||
>
|
||||
<svg-icon icon-class="plus" size="20px" class="mr-2" />
|
||||
<span class="text-sm font-medium"></span>
|
||||
</button>
|
||||
</div> -->
|
||||
|
||||
<!-- 添加产物表单 -->
|
||||
<!-- <div v-else class="add-output-form">
|
||||
<el-card class="w-full" shadow="hover">
|
||||
<div class="p-3">
|
||||
<el-input
|
||||
v-model="newOutputName"
|
||||
placeholder="输入产物名称"
|
||||
size="small"
|
||||
@keydown="handleAddOutputKeydown"
|
||||
@blur="addNewOutput"
|
||||
class="w-full mb-3"
|
||||
/>
|
||||
<div class="flex gap-2">
|
||||
<el-button @click="addNewOutput" type="primary" size="small" class="flex-1">
|
||||
确定
|
||||
</el-button>
|
||||
<el-button @click="cancelAddOutput" size="small" class="flex-1">
|
||||
取消
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</el-card>
|
||||
</div> -->
|
||||
<!-- </div> -->
|
||||
<!-- </div> -->
|
||||
<!-- <div>
|
||||
<el-button type="info" size="medium" class="flex-1"> branch </el-button>
|
||||
</div> -->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<style lang="scss" scoped>
|
||||
.task-syllabus-flow-card {
|
||||
background-color: var(--color-card-bg-task);
|
||||
border: 1px solid var(--color-card-border-task);
|
||||
box-sizing: border-box;
|
||||
transition: border-color 0.2s ease;
|
||||
// box-shadow: var(--color-card-shadow);
|
||||
margin-bottom: 100px;
|
||||
&:hover {
|
||||
background-color: var(--color-card-bg-task-hover);
|
||||
border-color: var(--color-card-border-hover);
|
||||
box-shadow: var(--color-card-shadow-hover);
|
||||
}
|
||||
:deep(.el-card__body) {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
@@ -201,4 +384,150 @@ defineExpose({
|
||||
overflow: auto;
|
||||
}
|
||||
}
|
||||
|
||||
.task-syllabus-output-object-card {
|
||||
background-color: var(--color-card-bg-task);
|
||||
border: 1px solid var(--color-card-border-task);
|
||||
box-sizing: border-box;
|
||||
transition: border-color 0.2s ease;
|
||||
// box-shadow: var(--color-card-shadow-hover);
|
||||
&:hover {
|
||||
background-color: var(--color-card-bg-task-hover);
|
||||
border-color: var(--color-card-border-hover);
|
||||
box-shadow: var(--color-card-shadow-hover);
|
||||
}
|
||||
:deep(.el-card__body) {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
overflow: auto;
|
||||
}
|
||||
}
|
||||
|
||||
.task-content-editor {
|
||||
:deep(.el-textarea__inner) {
|
||||
font-size: 14px;
|
||||
color: var(--color-text-secondary);
|
||||
background: transparent;
|
||||
border: 1px solid #dcdfe6;
|
||||
border-radius: 4px;
|
||||
resize: none;
|
||||
}
|
||||
}
|
||||
|
||||
.task-content-display {
|
||||
min-height: 40px;
|
||||
word-break: break-word;
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
|
||||
.add-output-btn {
|
||||
opacity: 0.8;
|
||||
transition: opacity 0.2s ease;
|
||||
|
||||
&:hover {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
button {
|
||||
background: transparent;
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
background-color: rgba(59, 130, 246, 0.05);
|
||||
transform: translateY(-1px);
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
&:active {
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.add-output-form {
|
||||
animation: slideDown 0.3s ease-out;
|
||||
}
|
||||
|
||||
@keyframes slideDown {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(-10px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
// 添加产物表单样式
|
||||
:deep(.el-card__body) {
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
// 输入框样式
|
||||
:deep(.el-input__wrapper) {
|
||||
background: transparent;
|
||||
border: 1px solid #dcdfe6;
|
||||
border-radius: 4px;
|
||||
box-shadow: none;
|
||||
transition: all 0.2s ease;
|
||||
|
||||
&:hover {
|
||||
border-color: #c0c4cc;
|
||||
}
|
||||
|
||||
&.is-focus {
|
||||
border-color: #409eff;
|
||||
box-shadow: 0 0 0 2px rgba(64, 158, 255, 0.2);
|
||||
}
|
||||
}
|
||||
|
||||
:deep(.el-input__inner) {
|
||||
color: var(--color-text-primary);
|
||||
font-size: 14px;
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
// 任务内容编辑按钮样式 - 匹配执行结果编辑按钮样式
|
||||
.task-content-editor {
|
||||
.el-button {
|
||||
font-weight: bold;
|
||||
font-size: 16px;
|
||||
border-radius: 4px;
|
||||
|
||||
&.el-button--small {
|
||||
padding: 4px 12px;
|
||||
}
|
||||
}
|
||||
|
||||
// 在深色模式下,按钮背景会自动适配为深色
|
||||
html.dark & {
|
||||
.el-button {
|
||||
&.el-button--primary {
|
||||
background-color: var(--color-bg-detail);
|
||||
border-color: var(--color-border);
|
||||
color: var(--color-text);
|
||||
|
||||
&:hover {
|
||||
background-color: var(--color-bg-hover);
|
||||
border-color: var(--color-text-hover);
|
||||
}
|
||||
}
|
||||
|
||||
&:not(.el-button--primary) {
|
||||
background-color: var(--color-bg-detail);
|
||||
border-color: var(--color-border);
|
||||
color: var(--color-text);
|
||||
|
||||
&:hover {
|
||||
background-color: var(--color-bg-hover);
|
||||
border-color: var(--color-text-hover);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -5,7 +5,7 @@ import TaskResult from './TaskResult/index.vue'
|
||||
import { Jsplumb } from './utils.ts'
|
||||
import { type IRawStepTask, useAgentsStore } from '@/stores'
|
||||
import { BezierConnector } from '@jsplumb/browser-ui'
|
||||
|
||||
import { ref } from 'vue'
|
||||
const agentsStore = useAgentsStore()
|
||||
|
||||
// 智能体库
|
||||
@@ -15,9 +15,9 @@ const agentRepoJsplumb = new Jsplumb('task-template', {
|
||||
options: {
|
||||
curviness: 30, // 曲线弯曲程度
|
||||
stub: 20, // 添加连接点与端点的距离
|
||||
alwaysRespectStubs: true,
|
||||
},
|
||||
},
|
||||
alwaysRespectStubs: true
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
// 任务流程
|
||||
@@ -32,18 +32,16 @@ const taskResultRef = ref<{
|
||||
}>()
|
||||
const taskResultJsplumb = new Jsplumb('task-template')
|
||||
|
||||
|
||||
function scrollToElementTop(elementId: string) {
|
||||
const element = document.getElementById(elementId);
|
||||
const element = document.getElementById(elementId)
|
||||
if (element) {
|
||||
element.scrollIntoView({
|
||||
behavior: 'smooth',
|
||||
block: 'start'
|
||||
});
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function handleTaskSyllabusCurrentTask(task: IRawStepTask) {
|
||||
scrollToElementTop(`task-results-${task.Id}-0`)
|
||||
agentsStore.setCurrentTask(task)
|
||||
@@ -76,13 +74,14 @@ function clear() {
|
||||
defineExpose({
|
||||
changeTask,
|
||||
resetAgentRepoLine,
|
||||
clear,
|
||||
clear
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<!-- 删除overflow-hidden -->
|
||||
<div
|
||||
class="task-template flex gap-6 items-center h-[calc(100%-84px)] relative overflow-hidden"
|
||||
class="task-template flex gap-6 items-center h-[calc(100%-84px)] relative"
|
||||
id="task-template"
|
||||
>
|
||||
<!-- 智能体库 -->
|
||||
@@ -111,10 +110,10 @@ defineExpose({
|
||||
<style scoped lang="scss">
|
||||
.task-template {
|
||||
& > div {
|
||||
box-shadow: 0 0 5px 0 rgba(0, 0, 0, 0.8);
|
||||
box-shadow: var(--color-card-shadow-three);
|
||||
border-radius: 24px;
|
||||
border: 1px solid #414752;
|
||||
background: var(--color-bg-quinary);
|
||||
border: 1px solid var(--color-card-border-three);
|
||||
background: var(--color-bg-three);
|
||||
padding-top: 20px;
|
||||
padding-bottom: 20px;
|
||||
}
|
||||
|
||||
@@ -87,8 +87,8 @@ export class Jsplumb {
|
||||
const stops = _config.stops ?? this.getStops(config.type)
|
||||
// 如果config.transparent为true,则将stops都加一些透明度
|
||||
if (config.transparent) {
|
||||
stops[0][1] = stops[0][1] + '30'
|
||||
stops[1][1] = stops[1][1] + '30'
|
||||
stops[0][1] = stops[0][1] + '80'
|
||||
stops[1][1] = stops[1][1] + '80'
|
||||
}
|
||||
if (targetElement && sourceElement) {
|
||||
this.instance.connect({
|
||||
|
||||
Reference in New Issue
Block a user