Files
AgentCoord/frontend/src/layout/components/Main/TaskTemplate/AgentRepo/index.vue
2026-03-03 16:15:11 +08:00

233 lines
6.2 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<script setup lang="ts">
import { ref, computed, onMounted } from 'vue'
import { pick } from 'lodash'
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 { readConfig } from '@/utils/readJson.ts'
import { useNotification } from '@/composables/useNotification.ts'
import AgentRepoList from './AgentRepoList.vue'
const { error, success } = useNotification()
const agentsStore = useAgentsStore()
// 如果agentsStore.agents不存在就读取默认配置的json文件
onMounted(async () => {
if (!agentsStore.agents.length) {
const res = await readConfig<Agent[]>('agent.json')
agentsStore.setAgents(res)
}
await api.setAgents(
agentsStore.agents.map(item => pick(item, ['Name', 'Profile', 'apiUrl', 'apiKey', 'apiModel']))
)
})
// 上传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)
input.value = ''
}
}
// 验证API配置三个字段必须同时存在或同时不存在
const validateApiConfig = (agent: any) => {
const hasApiUrl = 'apiUrl' in agent
const hasApiKey = 'apiKey' in agent
const hasApiModel = 'apiModel' in agent
return hasApiUrl === hasApiKey && hasApiKey === hasApiModel
}
const readFileContent = (file: File) => {
const reader = new FileReader()
reader.onload = e => {
try {
const content = e.target?.result as string
const jsonData = JSON.parse(content)
if (!Array.isArray(jsonData)) {
ElMessage.error('JSON格式错误: 必须为数组格式')
return
}
const validAgents = jsonData.filter(agent => {
// 验证必需字段
if (!agent.Name || typeof agent.Name !== 'string') {
return false
}
if (!agent.Icon || typeof agent.Icon !== 'string') {
return false
}
if (!agent.Profile || typeof agent.Profile !== 'string') {
return false
}
// 验证API配置
if (!validateApiConfig(agent)) {
return false
}
return true
})
// 修改发送到后端的数据
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
}))
agentsStore.setAgents(processedAgents)
// 调用API
api
.setAgents(processedAgents)
.then(() => {
success('智能体上传成功')
})
.catch(() => {
error('智能体上传失败')
})
} catch {
error('JSON解析错误')
}
}
reader.onerror = () => {
error('文件读取错误')
}
reader.readAsText(file)
}
// 根据currentTask排序agent列表
const agentList = computed(() => {
const selected: Agent[] = []
const unselected: {
title: string
data: Agent[]
}[] = []
const obj: Record<string, Agent[]> = {}
// 获取当前任务中参与流程的智能体名称列表
const selectedAgentNames = agentsStore.currentTask?.AgentSelection ?? []
if (!agentsStore.agents.length) {
return {
selected,
unselected
}
}
for (const agent of agentsStore.agents) {
// 如果智能体在当前任务的AgentSelection中则放入selected数组置顶显示
if (selectedAgentNames.includes(agent.Name)) {
selected.push(agent)
continue
}
// 其他智能体按数据空间分类
if (obj[agent.Classification]) {
obj[agent.Classification]!.push(agent)
} else {
const arr = [agent]
obj[agent.Classification] = arr
unselected.push({
title: agent.Classification,
data: arr
})
}
}
return {
selected,
unselected: unselected
}
})
</script>
<template>
<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 text-[var(--color-text-title-header)]">智能体库</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>
<!-- 智能体列表 -->
<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-[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>
</div>
</div>
</template>
<style scoped lang="scss">
.agent-repo {
padding: 0 8px;
.plus-button {
background: var(--color-bg-tertiary);
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: var(--color-bg-quaternary);
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.15);
}
}
}
#agent-repo {
:deep(.agent-repo-item-popover) {
padding: 0;
border-radius: 20px;
background: var(--color-bg-secondary);
}
}
</style>