2025-10-29 10:22:14 +08:00
|
|
|
|
<script setup lang="ts">
|
|
|
|
|
|
import { ElNotification } from 'element-plus'
|
|
|
|
|
|
import { pick } from 'lodash'
|
|
|
|
|
|
|
|
|
|
|
|
import api from '@/api/index.ts'
|
|
|
|
|
|
|
|
|
|
|
|
import SvgIcon from '@/components/SvgIcon/index.vue'
|
2025-10-31 18:42:31 +08:00
|
|
|
|
import { agentMapDuty, getActionTypeDisplay, getAgentMapIcon } from '@/layout/components/config.ts'
|
|
|
|
|
|
import { type Agent, useAgentsStore } from '@/stores'
|
2025-10-29 10:22:14 +08:00
|
|
|
|
import { onMounted } from 'vue'
|
|
|
|
|
|
import { v4 as uuidv4 } from 'uuid'
|
|
|
|
|
|
|
|
|
|
|
|
const emit = defineEmits<{
|
|
|
|
|
|
(el: 'resetAgentRepoLine'): void
|
|
|
|
|
|
}>()
|
|
|
|
|
|
|
|
|
|
|
|
const agentsStore = useAgentsStore()
|
|
|
|
|
|
|
|
|
|
|
|
const handleScroll = () => {
|
|
|
|
|
|
emit('resetAgentRepoLine')
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
onMounted(() => {
|
|
|
|
|
|
// 如果已上传就重新发送给后端
|
|
|
|
|
|
if (agentsStore.agents.length) {
|
|
|
|
|
|
void api.setAgents(agentsStore.agents.map((item) => pick(item, ['Name', 'Profile'])))
|
|
|
|
|
|
}
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
// 上传agent文件
|
|
|
|
|
|
const fileInput = ref<HTMLInputElement>()
|
|
|
|
|
|
|
|
|
|
|
|
const triggerFileSelect = () => {
|
|
|
|
|
|
fileInput.value?.click()
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const handleFileSelect = (event: Event) => {
|
|
|
|
|
|
const input = event.target as HTMLInputElement
|
|
|
|
|
|
if (input.files && input.files[0]) {
|
|
|
|
|
|
const file = input.files[0]
|
|
|
|
|
|
readFileContent(file)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
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,
|
|
|
|
|
|
})),
|
|
|
|
|
|
)
|
|
|
|
|
|
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 格式错误',
|
|
|
|
|
|
})
|
|
|
|
|
|
}
|
|
|
|
|
|
} catch (e) {
|
|
|
|
|
|
console.error(e)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
reader.readAsText(file)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const taskProcess = computed(() => {
|
|
|
|
|
|
const list = agentsStore.currentTask?.TaskProcess ?? []
|
|
|
|
|
|
return list.map((item) => ({
|
|
|
|
|
|
...item,
|
|
|
|
|
|
key: uuidv4(),
|
|
|
|
|
|
}))
|
|
|
|
|
|
})
|
2025-10-31 18:42:31 +08:00
|
|
|
|
|
|
|
|
|
|
const agentListRef = ref<HTMLElement | null>()
|
|
|
|
|
|
|
|
|
|
|
|
// 根据currentTask排序agent列表
|
|
|
|
|
|
const agentList = computed(() => {
|
|
|
|
|
|
const startArr: Agent[] = []
|
|
|
|
|
|
const endArr: Agent[] = []
|
|
|
|
|
|
if (!agentsStore.agents.length) {
|
|
|
|
|
|
return startArr
|
|
|
|
|
|
}
|
|
|
|
|
|
for (const agent of agentsStore.agents) {
|
|
|
|
|
|
if (agentsStore.currentTask?.AgentSelection?.includes(agent.Name)) {
|
|
|
|
|
|
startArr.push(agent)
|
|
|
|
|
|
} else {
|
|
|
|
|
|
endArr.push(agent)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return [...startArr, ...endArr]
|
|
|
|
|
|
})
|
2025-10-29 10:22:14 +08:00
|
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
|
|
<template>
|
2025-10-31 18:42:31 +08:00
|
|
|
|
<div class="agent-repo h-full flex flex-col" id="agent-repo">
|
2025-10-29 10:22:14 +08:00
|
|
|
|
<!-- 头部 -->
|
|
|
|
|
|
<div class="flex items-center justify-between">
|
|
|
|
|
|
<span class="text-[18px] font-bold">智能体库</span>
|
|
|
|
|
|
<!-- 上传文件 -->
|
|
|
|
|
|
<input type="file" accept=".json" @change="handleFileSelect" class="hidden" ref="fileInput" />
|
|
|
|
|
|
<div class="plus-button" @click="triggerFileSelect">
|
|
|
|
|
|
<svg-icon icon-class="plus" color="var(--color-text)" size="18px" />
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
2025-10-31 18:42:31 +08:00
|
|
|
|
<!-- 智能体列表 -->
|
|
|
|
|
|
<div
|
|
|
|
|
|
class="mt-[18px] flex-1 overflow-y-auto relative"
|
|
|
|
|
|
ref="agentListRef"
|
|
|
|
|
|
@scroll="handleScroll"
|
|
|
|
|
|
>
|
|
|
|
|
|
<el-popover
|
|
|
|
|
|
v-for="item in agentList"
|
2025-10-29 10:22:14 +08:00
|
|
|
|
:key="item.Name"
|
2025-10-31 18:42:31 +08:00
|
|
|
|
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%"
|
2025-10-29 10:22:14 +08:00
|
|
|
|
>
|
2025-10-31 18:42:31 +08:00
|
|
|
|
<template #reference>
|
|
|
|
|
|
<div
|
|
|
|
|
|
class="flex items-center justify-between user-item relative h-[41px]"
|
|
|
|
|
|
:class="
|
|
|
|
|
|
agentsStore.currentTask?.AgentSelection?.includes(item.Name) ? 'active-card' : ''
|
|
|
|
|
|
"
|
|
|
|
|
|
>
|
2025-10-29 10:22:14 +08:00
|
|
|
|
<div
|
2025-10-31 18:42:31 +08:00
|
|
|
|
class="w-[41px] h-[41px] rounded-full flex items-center justify-center flex-shrink-0"
|
|
|
|
|
|
:style="{ background: getAgentMapIcon(item.Name).color }"
|
2025-10-29 10:22:14 +08:00
|
|
|
|
>
|
2025-10-31 18:42:31 +08:00
|
|
|
|
<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"
|
2025-10-29 10:22:14 +08:00
|
|
|
|
>
|
2025-10-31 18:42:31 +08:00
|
|
|
|
<!-- 小圆点 -->
|
|
|
|
|
|
<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>
|
2025-10-29 10:22:14 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
2025-10-31 18:42:31 +08:00
|
|
|
|
</el-popover>
|
2025-10-29 10:22:14 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
<!-- 底部提示栏 -->
|
|
|
|
|
|
<div class="w-full grid grid-cols-3 gap-x-[10px] bg-[#1d222b] 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"
|
|
|
|
|
|
>
|
|
|
|
|
|
<span class="text-[12px]">{{ item.name }}</span>
|
|
|
|
|
|
<div class="w-[8px] h-[8px] rounded-full" :style="{ background: item.color }"></div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
|
|
<style scoped lang="scss">
|
|
|
|
|
|
.agent-repo {
|
|
|
|
|
|
padding: 0 8px;
|
2025-10-31 18:42:31 +08:00
|
|
|
|
|
2025-10-29 10:22:14 +08:00
|
|
|
|
.plus-button {
|
|
|
|
|
|
background: #1d2128;
|
|
|
|
|
|
width: 24px;
|
|
|
|
|
|
height: 24px;
|
|
|
|
|
|
padding: 0;
|
|
|
|
|
|
border-radius: 50%;
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
justify-content: center;
|
|
|
|
|
|
cursor: pointer;
|
|
|
|
|
|
transition: all 0.3s ease;
|
|
|
|
|
|
|
|
|
|
|
|
&:hover {
|
|
|
|
|
|
background: #374151;
|
|
|
|
|
|
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.15);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.user-item {
|
|
|
|
|
|
background: #1d222b;
|
2025-10-31 18:42:31 +08:00
|
|
|
|
border-radius: 40px;
|
2025-10-29 10:22:14 +08:00
|
|
|
|
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;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-10-31 18:42:31 +08:00
|
|
|
|
|
|
|
|
|
|
.active-card {
|
|
|
|
|
|
border-left: none !important;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#agent-repo {
|
|
|
|
|
|
:deep(.agent-repo-item-popover) {
|
|
|
|
|
|
padding: 0;
|
|
|
|
|
|
border-radius: 20px;
|
|
|
|
|
|
background: var(--color-bg-secondary);
|
|
|
|
|
|
}
|
2025-10-29 10:22:14 +08:00
|
|
|
|
}
|
|
|
|
|
|
</style>
|