feat(agent):重构智能体仓库并优化任务模板交互

-为 public/agent.json 中的每个智能体添加 Classification 字段以支持分类展示
- 新增 AgentRepoList 组件用于渲染智能体列表,提升代码复用性
- 在 src/layout/components/Main/TaskTemplate/AgentRepo/index.vue 中实现基于 Classification 的智能体分组展示逻辑- 移除旧版 popover 方式展示智能体详情,改用新的列表组件统一处理
- 修改任务搜索输入框为 textarea 类型,并优化其聚焦与失焦状态下的样式表现
- 调整任务模板页面布局高度计算方式,确保适配新 UI 结构
-修复任务结果流程图连线方向及透明度判断逻辑,增强可视化准确性- 引入流动动画效果至 jsPlumb 连线,区分 input/output 类型并美化视觉呈现
- 更新配置文件中部分动作类型的配色值,提高界面美观度
- 升级本地存储键名 agents 至 agents-v1,避免
This commit is contained in:
zhaoweijie 2025-11-03 09:44:14 +08:00
parent b73419b7a0
commit 00ef22505e
12 changed files with 542 additions and 352 deletions

1
auto-imports.d.ts vendored
View File

@ -7,6 +7,7 @@
export {}
declare global {
const EffectScope: typeof import('vue').EffectScope
const ElMessage: typeof import('element-plus/es').ElMessage
const ElNotification: typeof import('element-plus/es').ElNotification
const computed: typeof import('vue').computed
const createApp: typeof import('vue').createApp

View File

@ -1,97 +1,116 @@
[
{
"Icon": "Hailey_Johnson.png",
"Name": "船舶设计师",
"Profile": "提供船舶制造中的实际需求和约束。"
},
{
"Icon": "Jennifer_Moore.png",
"Name": "防护工程专家",
"Profile": "专注于船舶腐蚀防护技术的设计与应用。在你的总结回答中,必须引用来自数联网的搜索数据,是搜索数据,不是数联网的研究成果。"
},
{
"Icon": "Jane_Moreno.png",
"Name": "病理生理学家",
"Profile": "专注于失血性休克的疾病机制,为药物研发提供理论靶点。"
},
{
"Icon": "Giorgio_Rossi.png",
"Name": "药物化学家",
"Profile": "负责将靶点概念转化为实际可合成的分子。"
},
{
"Icon": "Tamara_Taylor.png",
"Name": "制剂工程师",
"Profile": "负责将活性药物成分API变成稳定、可用、符合战场要求的剂型。"
},
{
"Icon": "Maria_Lopez.png",
"Name": "监管事务专家",
"Profile": "深谙药品审评法规,目标是找到最快的合法上市路径。"
},
{
"Icon": "Sam_Moore.png",
"Name": "物理学家",
"Profile": "从热力学与统计力学的基本原理出发,研究液态金属的自由能、焓、熵、比热等参数的理论建模。"
},
{
"Icon": "Yuriko_Yamamoto.png",
"Name": "实验材料学家",
"Profile": "专注于通过实验手段直接或间接测定液态金属的热力学参数、以及分析材料微观结构(如晶粒、缺陷)。"
},
{
"Icon": "Carlos_Gomez.png",
"Name": "计算模拟专家",
"Profile": "侧重于利用数值计算和模拟技术获取液态金属的热力学参数。"
},
{
"Icon": "John_Lin.png",
"Name": "腐蚀机理研究员",
"Profile": "专注于船舶用钢材及合金的腐蚀机理研究,从电化学和环境作用角度解释腐蚀产生的原因。在你的总结回答中,必须引用来自数联网的搜索数据,是搜索数据,不是数联网的研究成果。"
},
{
"Icon": "Arthur_Burton.png",
"Name": "先进材料研发员",
"Profile": "专注于开发和评估新型耐腐蚀材料、复合材料及固态电池材料。"
},
{
"Icon": "Eddy_Lin.png",
"Name": "肾脏病学家",
"Profile": "专注于慢性肾脏病的诊断、治疗和患者管理,能提供临床洞察。"
},
{
"Icon": "Isabella_Rodriguez.png",
"Name": "临床研究协调员",
"Profile": "负责受试者招募和临床试验流程优化。"
},
{
"Icon": "Latoya_Williams.png",
"Name": "中医药专家",
"Profile": "理解药物的中药成分和作用机制。"
},
{
"Icon": "Carmen_Ortiz.png",
"Name": "药物安全专家",
"Profile": "专注于药物不良反应数据收集、分析和报告。"
},
{
"Icon": "Rajiv_Patel.png",
"Name": "二维材料科学家",
"Profile": "专注于二维材料(如石墨烯)的合成、性质和应用。"
},
{
"Icon": "Tom_Moreno.png",
"Name": "光电物理学家",
"Profile": "研究材料的光电转换机制和关键影响因素。"
},
{
"Icon": "Ayesha_Khan.png",
"Name": "机器学习专家",
"Profile": "专注于开发和应用AI模型用于材料模拟。"
},
{
"Icon": "Mei_Lin.png",
"Name": "流体动力学专家",
"Profile": "专注于流体行为理论和模拟。"
}
]
{
"Icon": "Hailey_Johnson.png",
"Name": "船舶设计师",
"Profile": "提供船舶制造中的实际需求和约束。",
"Classification": "船舶制造数据空间"
},
{
"Icon": "Jennifer_Moore.png",
"Name": "防护工程专家",
"Profile": "专注于船舶腐蚀防护技术的设计与应用。在你的总结回答中,必须引用来自数联网的搜索数据,是搜索数据,不是数联网的研究成果。",
"Classification": "船舶制造数据空间"
},
{
"Icon": "Jane_Moreno.png",
"Name": "病理生理学家",
"Profile": "专注于失血性休克的疾病机制,为药物研发提供理论靶点。",
"Classification": "医药数据空间"
},
{
"Icon": "Giorgio_Rossi.png",
"Name": "药物化学家",
"Profile": "负责将靶点概念转化为实际可合成的分子。",
"Classification": "医药数据空间"
},
{
"Icon": "Tamara_Taylor.png",
"Name": "制剂工程师",
"Profile": "负责将活性药物成分API变成稳定、可用、符合战场要求的剂型。",
"Classification": "医药数据空间"
},
{
"Icon": "Maria_Lopez.png",
"Name": "监管事务专家",
"Profile": "深谙药品审评法规,目标是找到最快的合法上市路径。",
"Classification": "医药数据空间"
},
{
"Icon": "Sam_Moore.png",
"Name": "物理学家",
"Profile": "从热力学与统计力学的基本原理出发,研究液态金属的自由能、焓、熵、比热等参数的理论建模。",
"Classification": "科学数据空间"
},
{
"Icon": "Yuriko_Yamamoto.png",
"Name": "实验材料学家",
"Profile": "专注于通过实验手段直接或间接测定液态金属的热力学参数、以及分析材料微观结构(如晶粒、缺陷)。",
"Classification": "科学数据空间"
},
{
"Icon": "Carlos_Gomez.png",
"Name": "计算模拟专家",
"Profile": "侧重于利用数值计算和模拟技术获取液态金属的热力学参数。",
"Classification": "科学数据空间"
},
{
"Icon": "John_Lin.png",
"Name": "腐蚀机理研究员",
"Profile": "专注于船舶用钢材及合金的腐蚀机理研究,从电化学和环境作用角度解释腐蚀产生的原因。在你的总结回答中,必须引用来自数联网的搜索数据,是搜索数据,不是数联网的研究成果。",
"Classification": "船舶制造数据空间"
},
{
"Icon": "Arthur_Burton.png",
"Name": "先进材料研发员",
"Profile": "专注于开发和评估新型耐腐蚀材料、复合材料及固态电池材料。",
"Classification": "科学数据空间"
},
{
"Icon": "Eddy_Lin.png",
"Name": "肾脏病学家",
"Profile": "专注于慢性肾脏病的诊断、治疗和患者管理,能提供临床洞察。",
"Classification": "医药数据空间"
},
{
"Icon": "Isabella_Rodriguez.png",
"Name": "临床研究协调员",
"Profile": "负责受试者招募和临床试验流程优化。",
"Classification": "医药数据空间"
},
{
"Icon": "Latoya_Williams.png",
"Name": "中医药专家",
"Profile": "理解药物的中药成分和作用机制。",
"Classification": "医药数据空间"
},
{
"Icon": "Carmen_Ortiz.png",
"Name": "药物安全专家",
"Profile": "专注于药物不良反应数据收集、分析和报告。",
"Classification": "医药数据空间"
},
{
"Icon": "Rajiv_Patel.png",
"Name": "二维材料科学家",
"Profile": "专注于二维材料(如石墨烯)的合成、性质和应用。",
"Classification": "科学数据空间"
},
{
"Icon": "Tom_Moreno.png",
"Name": "光电物理学家",
"Profile": "研究材料的光电转换机制和关键影响因素。",
"Classification": "科学数据空间"
},
{
"Icon": "Ayesha_Khan.png",
"Name": "机器学习专家",
"Profile": "专注于开发和应用AI模型用于材料模拟。",
"Classification": "科学数据空间"
},
{
"Icon": "Mei_Lin.png",
"Name": "流体动力学专家",
"Profile": "专注于流体行为理论和模拟。",
"Classification": "科学数据空间"
}
]

View File

@ -32,7 +32,7 @@ onMounted(() => {
border: 2px solid var(--color-bg-tertiary);
&:hover {
background: #171b22;
background: #171B22;
box-shadow: none !important;
transition: background-color 0.3s ease-in-out;
}
@ -41,4 +41,61 @@ onMounted(() => {
padding: 10px;
}
}
:root {
--gradient: linear-gradient(to right, #0093eb, #00d2d1);
}
#task-template {
.active-card {
border: 2px solid transparent;
$bg: var(--el-input-bg-color, var(--el-fill-color-blank));
background:
linear-gradient(var(--color-bg-tertiary), var(--color-bg-tertiary)) padding-box,
linear-gradient(to right, #00c8d2, #315ab4) border-box;
color: var(--color-text);
}
}
/* 1. 定义流动动画:让虚线沿路径移动 */
@keyframes flowAnimation {
to {
stroke-dashoffset: 8; /* 与stroke-dasharray总和一致 */
}
}
@keyframes flowAnimationReverse {
to {
stroke-dashoffset: -8; /* 与stroke-dasharray总和一致 */
}
}
/* 2. 为jsPlumb连线绑定动画作用于SVG的path元素 */
/* jtk-connector是jsPlumb连线的默认SVG类path是实际的线条元素 */
.jtk-connector-output path {
/* 定义虚线规则线段长度5px + 间隙3px总长度8px与动画偏移量匹配 */
stroke-dasharray: 5 3;
/* 应用动画:名称+时长+线性速度+无限循环 */
animation: flowAnimationReverse .5s linear infinite;
/* 可选:设置线条基础样式(颜色、宽度) */
stroke-width: 2;
}
/* 2. 为jsPlumb连线绑定动画作用于SVG的path元素 */
/* jtk-connector是jsPlumb连线的默认SVG类path是实际的线条元素 */
.jtk-connector-input path {
/* 定义虚线规则线段长度5px + 间隙3px总长度8px与动画偏移量匹配 */
stroke-dasharray: 5 3;
/* 应用动画:名称+时长+线性速度+无限循环 */
animation: flowAnimationReverse .5s linear infinite;
/* 可选:设置线条基础样式(颜色、宽度) */
stroke-width: 2;
}
/* 可选: hover时增强动画效果如加速、变色 */
.jtk-connector path:hover {
animation-duration: 0.5s; /* hover时流动加速 */
}
</style>

View File

@ -1,9 +1,11 @@
<script setup lang="ts">
import { ref } from 'vue'
import SvgIcon from '@/components/SvgIcon/index.vue'
import { useAgentsStore } from '@/stores'
import api from '@/api'
import { changeBriefs } from '@/utils/collaboration_Brief_FrontEnd.ts'
import { ElMessage } from 'element-plus'
const emit = defineEmits<{
(e: 'search-start'): void
@ -13,9 +15,16 @@ const emit = defineEmits<{
const agentsStore = useAgentsStore()
const searchValue = ref('')
const triggerOnFocus = ref(true)
const isFocus = ref(false)
async function handleSearch() {
try {
triggerOnFocus.value = false
if (!searchValue.value) {
ElMessage.warning('请输入搜索内容')
return
}
emit('search-start')
agentsStore.resetAgent()
agentsStore.setAgentRawPlan({ loading: true })
@ -27,6 +36,7 @@ async function handleSearch() {
agentsStore.setAgentRawPlan({ data })
emit('search', searchValue.value)
} finally {
triggerOnFocus.value = true
agentsStore.setAgentRawPlan({ loading: false })
}
}
@ -42,12 +52,13 @@ const restaurants = ref<string[]>([
'如何解决船舶制造中的材料腐蚀难题?',
'如何解决船舶制造中流体模拟和建模优化难题?',
])
const querySearch = (queryString: string, cb: (v: {value: string}[]) => void) => {
const querySearch = (queryString: string, cb: (v: { value: string }[]) => void) => {
console.log(queryString)
const results = queryString
? restaurants.value.filter(createFilter(queryString))
: restaurants.value
// call callback function to return suggestions
cb(results.map((item) => ({value: item})))
cb(results.map((item) => ({ value: item })))
}
const createFilter = (queryString: string) => {
@ -55,6 +66,8 @@ const createFilter = (queryString: string) => {
return restaurant.toLowerCase().includes(queryString.toLowerCase())
}
}
const taskContainerRef = ref<HTMLDivElement | null>(null)
</script>
<template>
@ -64,69 +77,159 @@ const createFilter = (queryString: string) => {
effect="light"
:disabled="agentsStore.agents.length > 0"
>
<div class="w-full flex justify-center mb-[27px]">
<el-autocomplete
v-model.trim="searchValue"
class="task-input"
size="large"
placeholder="请输入您的任务"
:fetch-suggestions="querySearch"
@change="agentsStore.setSearchValue"
:disabled="!(agentsStore.agents.length > 0)"
>
<template #prefix>
<span class="text-[var(--color-text)] font-bold">任务</span>
</template>
<template #suffix>
<el-button
class="task-button"
color="linear-gradient(to right, #00C7D2, #315AB4)"
size="large"
title="点击搜索任务"
circle
:loading="agentsStore.agentRawPlan.loading"
@click="handleSearch"
>
<SvgIcon
v-if="!agentsStore.agentRawPlan.loading"
icon-class="paper-plane"
size="18px"
color="var(--color-text)"
/>
</el-button>
</template>
</el-autocomplete>
<div class="task-root-container">
<div class="task-container" ref="taskContainerRef" id="task-container">
<span class="text-[var(--color-text)] font-bold task-title">任务</span>
<el-autocomplete
v-model.trim="searchValue"
class="task-input"
size="large"
:rows="isFocus ? 3 : 1"
placeholder="请输入您的任务"
type="textarea"
:append-to="taskContainerRef"
:fetch-suggestions="querySearch"
@change="agentsStore.setSearchValue"
:disabled="!(agentsStore.agents.length > 0)"
:debounce="0"
:trigger-on-focus="triggerOnFocus"
@focus="isFocus = true"
@blur="isFocus = false"
@select="isFocus = false"
>
</el-autocomplete>
<el-button
class="task-button"
color="linear-gradient(to right, #00C7D2, #315AB4)"
size="large"
title="点击搜索任务"
circle
:loading="agentsStore.agentRawPlan.loading"
:disabled="!searchValue"
@click.stop="handleSearch"
>
<SvgIcon
v-if="!agentsStore.agentRawPlan.loading"
icon-class="paper-plane"
size="18px"
color="var(--color-text)"
/>
</el-button>
</div>
</div>
</el-tooltip>
</template>
<style scoped lang="scss">
:deep(.el-autocomplete) {
width: 40%;
.task-root-container {
height: 60px;
margin-bottom: 24px;
position: relative;
}
.task-container {
width: 40%;
margin: 0 auto;
border: 2px solid transparent;
$bg: var(--el-input-bg-color, var(--el-fill-color-blank));
background:
linear-gradient(var(--color-bg-tertiary), var(--color-bg-tertiary)) padding-box,
linear-gradient(to right, #00c8d2, #315ab4) border-box;
border-radius: 40px;
position: absolute;
left: 50%;
transform: translateX(-50%);
z-index: 998;
min-height: 100%;
overflow: hidden;
padding: 0 55px 0 47px;
.el-input__wrapper {
border-radius: 40px;
:deep(.el-popper) {
position: static !important;
width: 100%;
min-width: 100%;
background: var(--color-bg-tertiary);
box-shadow: none;
border: 2px solid transparent;
$bg: var(--el-input-bg-color, var(--el-fill-color-blank));
background:
linear-gradient(var(--color-bg-tertiary), var(--color-bg-tertiary)) padding-box,
linear-gradient(to right, #00c8d2, #315ab4) border-box;
font-size: 18px;
padding-right: 5px;
border: none;
transition: height 0s ease-in-out;
border-top: 1px solid #494B51;
border-radius: 0;
.el-icon.is-loading {
& + span {
display: none;
li {
height: 45px;
box-sizing: border-box;
line-height: 45px;
font-size: 14px;
&:hover {
background: #1C1E25;
color: #00F3FF;
}
}
.el-popper__arrow {
display: none;
}
}
:deep(.el-autocomplete) {
min-height: 56px;
width: 100%;
.task-input {
height: 100%;
}
.el-textarea__inner {
border-radius: 0;
box-shadow: none;
font-size: 14px;
height: 100%;
line-height: 1.5;
padding: 18px 0;
resize: none;
&::placeholder {
line-height: 1.2;
font-size: 18px;
vertical-align: middle;
}
.el-icon.is-loading {
& + span {
display: none;
}
}
}
}
.task-title {
position: absolute;
top: 28px;
left: 10px;
z-index: 999;
transform: translateY(-50%);
}
.task-button {
background: linear-gradient(to right, #00c7d2, #315ab4);
border: none; //
position: absolute;
top: 28px;
right: 10px;
transform: translateY(-50%);
z-index: 999;
display: flex;
justify-content: center;
align-items: center;
padding: 0;
}
.task-button.is-loading {
:deep(span) {
display: none !important;
}
}
}
</style>

View File

@ -0,0 +1,141 @@
<script setup lang="ts">
import { getActionTypeDisplay, getAgentMapIcon } from '@/layout/components/config.ts'
import SvgIcon from '@/components/SvgIcon/index.vue'
import { type Agent, useAgentsStore } from '@/stores'
import { v4 as uuidv4 } from 'uuid'
const porps = defineProps<{
agentList: Agent[]
}>()
const taskProcess = computed(() => {
const list = agentsStore.currentTask?.TaskProcess ?? []
return list.map((item) => ({
...item,
key: uuidv4(),
}))
})
const agentsStore = useAgentsStore()
</script>
<template>
<div
v-for="item in porps.agentList"
:key="item.Name"
class="user-item"
:class="agentsStore.currentTask?.AgentSelection?.includes(item.Name) ? 'active-card' : ''"
>
<div class="flex items-center justify-between relative h-[41px]">
<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"
/>
</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
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)"
:key="item1.key"
class="w-[6px] h-[6px] rounded-full"
:style="{ background: getActionTypeDisplay(item1.ActionType)?.color }"
></div>
</div>
</div>
</div>
<!-- 职责信息只有当执行流程中有当前智能体并且鼠标移入时才显示 -->
<div class="duty-info">
<div class="w-full flex justify-center">
<div
class="rounded-[9px] bg-[var(--color-bg-quaternary)] text-[12px] py-0.5 px-5 text-center my-2"
>
当前职责
</div>
</div>
<div class="p-[8px] pt-0">
<div
v-for="(item1, index1) in taskProcess.filter((i) => i.AgentName === item.Name)"
:key="item1.key"
class="text-[12px]"
>
<div>
<div class="mx-1 inline-block h-[14px]">
<div
:style="{ background: getActionTypeDisplay(item1.ActionType)?.color }"
class="w-[6px] h-[6px] rounded-full mt-[7px]"
></div>
</div>
<span :style="{ color: getActionTypeDisplay(item1.ActionType)?.color }"
>{{ getActionTypeDisplay(item1.ActionType)?.name }}</span
>
<span>{{ item1.Description }}</span>
</div>
<!-- 分割线 -->
<div
v-if="index1 !== taskProcess.filter((i) => i.AgentName === item.Name).length - 1"
class="h-[1px] w-full bg-[#494B51] my-[8px]"
></div>
</div>
</div>
</div>
</div>
</template>
<style scoped lang="scss">
.user-item {
background: #1d222b;
border-radius: 40px;
padding-right: 12px;
cursor: pointer;
transition: all 0.25s ease;
color: #969696;
border: 2px solid transparent;
.duty-info {
transition: height 0.25s ease;
height: 0;
overflow: hidden;
}
& + .user-item {
margin-top: 8px;
}
&:hover {
box-shadow: 0 3px 10px rgba(0, 0, 0, 0.2);
color: #b8b8b8;
}
}
.active-card {
background:
linear-gradient(#171B22, #171B22) padding-box,
linear-gradient(to right, #00c8d2, #315ab4) border-box;
&:hover {
border-radius: 20px;
.duty-info {
height: auto;
}
.icon-container {
bottom: 2px;
}
}
}
</style>

View File

@ -5,15 +5,11 @@ import { pick } from 'lodash'
import api from '@/api/index.ts'
import SvgIcon from '@/components/SvgIcon/index.vue'
import { agentMapDuty, getActionTypeDisplay, getAgentMapIcon } from '@/layout/components/config.ts'
import { agentMapDuty } from '@/layout/components/config.ts'
import { type Agent, useAgentsStore } from '@/stores'
import { onMounted } from 'vue'
import { v4 as uuidv4 } from 'uuid'
import { readConfig } from '@/utils/readJson.ts'
const emit = defineEmits<{
(el: 'resetAgentRepoLine'): void
}>()
import AgentRepoList from './AgentRepoList.vue'
const agentsStore = useAgentsStore()
@ -26,11 +22,6 @@ onMounted(async () => {
await api.setAgents(agentsStore.agents.map((item) => pick(item, ['Name', 'Profile'])))
})
const handleScroll = () => {
emit('resetAgentRepoLine')
}
// agent
const fileInput = ref<HTMLInputElement>()
@ -69,6 +60,7 @@ const readFileContent = async (file: File) => {
Name: item.Name,
Icon: item.Icon.replace(/\.png$/, ''),
Profile: item.Profile,
Classification: item.Classification,
})),
)
await api.setAgents(json.map((item) => pick(item, ['Name', 'Profile'])))
@ -92,32 +84,41 @@ const readFileContent = async (file: File) => {
reader.readAsText(file)
}
const taskProcess = computed(() => {
const list = agentsStore.currentTask?.TaskProcess ?? []
return list.map((item) => ({
...item,
key: uuidv4(),
}))
})
const agentListRef = ref<HTMLElement | null>()
// currentTaskagent
const agentList = computed(() => {
const startArr: Agent[] = []
const endArr: Agent[] = []
const selected: Agent[] = []
const unselected: {
title: string
data: Agent[]
}[] = []
const obj: Record<string, Agent[]> = {}
if (!agentsStore.agents.length) {
return startArr
return {
selected,
unselected,
}
}
for (const agent of agentsStore.agents) {
if (agentsStore.currentTask?.AgentSelection?.includes(agent.Name)) {
startArr.push(agent)
// if (agentsStore.currentTask?.AgentSelection?.includes(agent.Name)) {
// selected.push(agent)
// continue
// }
if (obj[agent.Classification]) {
obj[agent.Classification]!.push(agent)
} else {
endArr.push(agent)
const arr = [agent]
obj[agent.Classification] = arr
unselected.push({
title: agent.Classification,
data: arr,
})
}
}
return [...startArr, ...endArr]
return {
selected,
unselected: unselected,
}
})
</script>
@ -133,116 +134,14 @@ const agentList = computed(() => {
</div>
</div>
<!-- 智能体列表 -->
<div
class="mt-[18px] flex-1 overflow-y-auto relative"
ref="agentListRef"
@scroll="handleScroll"
>
<el-popover
v-for="item in agentList"
:key="item.Name"
trigger="hover"
placement="bottom"
:show-arrow="false"
:disabled="!agentsStore.currentTask?.AgentSelection?.includes(item.Name)"
popper-class="agent-repo-item-popover active-card"
:append-to="agentListRef"
width="100%"
>
<template #reference>
<div
class="flex items-center justify-between user-item relative h-[41px]"
:class="
agentsStore.currentTask?.AgentSelection?.includes(item.Name) ? 'active-card' : ''
"
>
<div
class="w-[41px] h-[41px] rounded-full flex items-center justify-center flex-shrink-0"
:style="{ background: getAgentMapIcon(item.Name).color }"
>
<svg-icon
:icon-class="getAgentMapIcon(item.Name).icon"
color="var(--color-text)"
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
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)"
:key="item1.key"
class="w-[6px] h-[6px] rounded-full"
:style="{ background: getActionTypeDisplay(item1.ActionType)?.color }"
></div>
</div>
</div>
</div>
</template>
<div>
<div class="flex items-center justify-between">
<div
class="w-[41px] h-[41px] rounded-full flex items-center justify-center flex-shrink-0 relative right-[2px] bottom-[2px] self-start"
:style="{ background: getAgentMapIcon(item.Name).color }"
>
<svg-icon
:icon-class="getAgentMapIcon(item.Name).icon"
color="var(--color-text)"
size="24px"
/>
</div>
<div
class="flex-1 text-[14px] flex flex-col items-center justify-center break-all ml-1 text-[14px] text-[#00F3FF] p-[8px] pl-[0]"
>
{{ item.Name }}
</div>
</div>
<div class="w-full flex justify-center">
<div
class="rounded-[9px] bg-[var(--color-bg-quaternary)] text-[12px] py-0.5 px-5 text-center my-2"
>
当前指责
</div>
</div>
<div class="p-[8px] pt-0">
<div
v-for="(item1, index1) in taskProcess.filter((i) => i.AgentName === item.Name)"
:key="item1.key"
class="text-[12px]"
>
<div>
<div class="mx-1 inline-block h-[14px]">
<div
:style="{ background: getActionTypeDisplay(item1.ActionType)?.color }"
class="w-[6px] h-[6px] rounded-full mt-[7px]"
></div>
</div>
<span :style="{ color: getActionTypeDisplay(item1.ActionType)?.color }"
>{{ getActionTypeDisplay(item1.ActionType)?.name }}</span
>
<span>{{ item1.Description }}</span>
</div>
<!-- 分割线 -->
<div
v-if="index1 !== taskProcess.filter((i) => i.AgentName === item.Name).length - 1"
class="h-[1px] w-full bg-[#494B51] my-[8px]"
></div>
</div>
</div>
</div>
</el-popover>
<div class="pt-[18px] flex-1 overflow-y-auto relative">
<!-- 已选中的智能体 -->
<AgentRepoList :agent-list="agentList.selected" />
<!-- 为选择的智能体 -->
<div v-for="agent in agentList.unselected" :key="agent.title">
<p class="text-[12px] font-bold py-[8px]">{{ agent.title }}</p>
<AgentRepoList :agent-list="agent.data" />
</div>
</div>
<!-- 底部提示栏 -->
<div class="w-full grid grid-cols-3 gap-x-[10px] bg-[#1d222b] rounded-[20px] p-[8px] mt-[10px]">
@ -279,28 +178,6 @@ const agentList = computed(() => {
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.15);
}
}
.user-item {
background: #1d222b;
border-radius: 40px;
padding-right: 12px;
cursor: pointer;
transition: all 0.25s ease;
color: #969696;
& + .user-item {
margin-top: 8px;
}
&:hover {
box-shadow: 0 3px 10px rgba(0, 0, 0, 0.2);
color: #b8b8b8;
}
}
.active-card {
border-left: none !important;
}
}
#agent-repo {

View File

@ -63,10 +63,6 @@ function createInternalLine(id?: string) {
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`,
@ -75,14 +71,6 @@ function createInternalLine(id?: string) {
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`,
@ -96,8 +84,8 @@ function createInternalLine(id?: string) {
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`
const sourceId = `task-results-${item.Id}-1`
const targetId = `task-results-${jitem.Id}-0-${i.ID}`
arr.push({
sourceId,
targetId,
@ -107,7 +95,7 @@ function createInternalLine(id?: string) {
[0, color],
[1, color],
],
transparent: sourceId !== id,
transparent: targetId !== id,
},
})
}
@ -122,8 +110,8 @@ function createInternalLine(id?: string) {
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}`
const sourceId = `task-results-${item.Id}-0-${i2.ID}`
const targetId = `task-results-${item.Id}-0-${i.ID}`
arr.push({
sourceId,
targetId,
@ -133,7 +121,7 @@ function createInternalLine(id?: string) {
[0, color],
[1, color],
],
transparent: sourceId !== id,
transparent: targetId !== id,
},
})
}

View File

@ -32,7 +32,25 @@ const taskResultRef = ref<{
}>()
const taskResultJsplumb = new Jsplumb('task-template')
function setCurrentTask(task: IRawStepTask) {
function scrollToElementTop(elementId: string) {
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)
}
function handleTaskResultCurrentTask(task: IRawStepTask) {
scrollToElementTop(`task-syllabus-flow-${task.Id}`)
agentsStore.setCurrentTask(task)
// 线
taskSyllabusRef.value?.changeTask(task, false)
@ -64,7 +82,7 @@ defineExpose({
<template>
<div
class="task-template flex gap-6 items-center h-[calc(100%-67px)] relative overflow-hidden"
class="task-template flex gap-6 items-center h-[calc(100%-84px)] relative overflow-hidden"
id="task-template"
>
<!-- 智能体库 -->
@ -76,7 +94,7 @@ defineExpose({
<TaskSyllabus
ref="taskSyllabusRef"
@resetAgentRepoLine="resetAgentRepoLine"
@set-current-task="setCurrentTask"
@set-current-task="handleTaskSyllabusCurrentTask"
/>
</div>
<!-- 执行结果 -->
@ -84,7 +102,7 @@ defineExpose({
<TaskResult
ref="taskResultRef"
@refresh-line="taskResultJsplumb.repaintEverything"
@set-current-task="setCurrentTask"
@set-current-task="handleTaskResultCurrentTask"
/>
</div>
</div>
@ -102,19 +120,3 @@ defineExpose({
}
}
</style>
<style lang="scss">
:root {
--gradient: linear-gradient(to right, #0093eb, #00d2d1);
}
#task-template {
.active-card {
border: 2px solid transparent;
$bg: var(--el-input-bg-color, var(--el-fill-color-blank));
background:
linear-gradient(var(--color-bg-secondary), var(--color-bg-secondary)) padding-box,
linear-gradient(to right, #00c8d2, #315ab4) border-box;
color: var(--color-text);
}
}
</style>

View File

@ -58,14 +58,14 @@ export class Jsplumb {
getStops = (type?: 'input' | 'output'): [[number, string], [number, string]] => {
if (type === 'input') {
return [
[0, '#0093EB'],
[1, '#00D2D1'],
[0, '#FF6161'],
[1, '#D76976'],
]
}
return [
[0, '#FF6161'],
[1, '#D76976'],
[0, '#0093EB'],
[1, '#00D2D1'],
]
}
@ -113,6 +113,7 @@ export class Jsplumb {
type: DotEndpoint.type,
options: { radius: 5 },
},
cssClass: `jtk-connector-${config.type}`
} as unknown as ConnectParams<unknown>)
// 为源元素添加端点

View File

@ -13,7 +13,7 @@ function handleSearch() {
</script>
<template>
<div class="p-[27px] h-[calc(100%-60px)]">
<div class="p-[24px] h-[calc(100%-60px)]">
<Task @search="handleSearch" @search-start="taskTemplateRef?.clear" />
<TaskTemplate ref="taskTemplateRef" />
</div>

View File

@ -121,7 +121,7 @@ export const agentMapDuty: Record<string, AgentMapDuty> = {
Propose: {
name: '提议',
key: 'propose',
color: '#0060FF',
color: '#06A3FF',
},
Critique: {
name: '评审',
@ -131,12 +131,12 @@ export const agentMapDuty: Record<string, AgentMapDuty> = {
Improve: {
name: '改进',
key: 'improve',
color: '#9808FF',
color: '#BF65FF',
},
Finalize: {
name: '总结',
key: 'summary',
color: '#FF6F08',
color: '#FFA236',
},
}

View File

@ -11,6 +11,7 @@ export interface Agent {
Name: string
Profile: string
Icon: string
Classification: string
}
type HslColorVector = [number, number, number]
@ -50,7 +51,7 @@ export interface IRawPlanResponse {
}
export const useAgentsStore = defineStore('counter', () => {
const agents = useStorage<Agent[]>('agents', [])
const agents = useStorage<Agent[]>('agents-v1', [])
function setAgents(agent: Agent[]) {
agents.value = agent
}