2025-10-29 10:22:14 +08:00
|
|
|
|
<script setup lang="ts">
|
2025-12-15 20:46:54 +08:00
|
|
|
|
import { ref, computed, onMounted } from 'vue'
|
2025-10-29 10:22:14 +08:00
|
|
|
|
import { ElNotification } from 'element-plus'
|
|
|
|
|
|
import { pick } from 'lodash'
|
|
|
|
|
|
|
|
|
|
|
|
import api from '@/api/index.ts'
|
|
|
|
|
|
|
|
|
|
|
|
import SvgIcon from '@/components/SvgIcon/index.vue'
|
2025-11-03 09:44:14 +08:00
|
|
|
|
import { agentMapDuty } from '@/layout/components/config.ts'
|
2025-10-31 18:42:31 +08:00
|
|
|
|
import { type Agent, useAgentsStore } from '@/stores'
|
2025-11-02 12:55:13 +08:00
|
|
|
|
import { readConfig } from '@/utils/readJson.ts'
|
2025-11-03 09:44:14 +08:00
|
|
|
|
import AgentRepoList from './AgentRepoList.vue'
|
2025-10-29 10:22:14 +08:00
|
|
|
|
|
|
|
|
|
|
const agentsStore = useAgentsStore()
|
|
|
|
|
|
|
2025-11-02 12:55:13 +08:00
|
|
|
|
// 如果agentsStore.agents不存在就读取默认配置的json文件
|
|
|
|
|
|
onMounted(async () => {
|
|
|
|
|
|
if (!agentsStore.agents.length) {
|
|
|
|
|
|
const res = await readConfig<Agent[]>('agent.json')
|
|
|
|
|
|
agentsStore.setAgents(res)
|
|
|
|
|
|
}
|
2025-12-15 20:46:54 +08:00
|
|
|
|
await api.setAgents(agentsStore.agents.map(item => pick(item, ['Name', 'Profile'])))
|
2025-11-02 12:55:13 +08:00
|
|
|
|
})
|
|
|
|
|
|
|
2025-10-29 10:22:14 +08:00
|
|
|
|
// 上传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)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-15 20:46:54 +08:00
|
|
|
|
// 在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
|
|
|
|
|
|
}
|
|
|
|
|
|
})
|
|
|
|
|
|
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)
|
|
|
|
|
|
|
2025-10-29 10:22:14 +08:00
|
|
|
|
const reader = new FileReader()
|
2025-12-15 20:46:54 +08:00
|
|
|
|
reader.onload = e => {
|
2025-10-29 10:22:14 +08:00
|
|
|
|
try {
|
2025-12-15 20:46:54 +08:00
|
|
|
|
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
|
2025-10-29 10:22:14 +08:00
|
|
|
|
}
|
2025-12-15 20:46:54 +08:00
|
|
|
|
|
|
|
|
|
|
// 验证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('智能体上传成功')
|
2025-10-29 10:22:14 +08:00
|
|
|
|
})
|
2025-12-15 20:46:54 +08:00
|
|
|
|
.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解析错误')
|
2025-10-29 10:22:14 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-12-15 20:46:54 +08:00
|
|
|
|
|
|
|
|
|
|
reader.onerror = error => {
|
|
|
|
|
|
console.error('❌ 文件读取错误:', error)
|
|
|
|
|
|
ElMessage.error('文件读取错误')
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-10-29 10:22:14 +08:00
|
|
|
|
reader.readAsText(file)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-10-31 18:42:31 +08:00
|
|
|
|
// 根据currentTask排序agent列表
|
|
|
|
|
|
const agentList = computed(() => {
|
2025-11-03 09:44:14 +08:00
|
|
|
|
const selected: Agent[] = []
|
|
|
|
|
|
const unselected: {
|
|
|
|
|
|
title: string
|
|
|
|
|
|
data: Agent[]
|
|
|
|
|
|
}[] = []
|
|
|
|
|
|
const obj: Record<string, Agent[]> = {}
|
2025-10-31 18:42:31 +08:00
|
|
|
|
if (!agentsStore.agents.length) {
|
2025-11-03 09:44:14 +08:00
|
|
|
|
return {
|
|
|
|
|
|
selected,
|
2025-12-15 20:46:54 +08:00
|
|
|
|
unselected
|
2025-11-03 09:44:14 +08:00
|
|
|
|
}
|
2025-10-31 18:42:31 +08:00
|
|
|
|
}
|
|
|
|
|
|
for (const agent of agentsStore.agents) {
|
2025-11-03 09:44:14 +08:00
|
|
|
|
// if (agentsStore.currentTask?.AgentSelection?.includes(agent.Name)) {
|
|
|
|
|
|
// selected.push(agent)
|
|
|
|
|
|
// continue
|
|
|
|
|
|
// }
|
|
|
|
|
|
if (obj[agent.Classification]) {
|
|
|
|
|
|
obj[agent.Classification]!.push(agent)
|
2025-10-31 18:42:31 +08:00
|
|
|
|
} else {
|
2025-11-03 09:44:14 +08:00
|
|
|
|
const arr = [agent]
|
|
|
|
|
|
obj[agent.Classification] = arr
|
|
|
|
|
|
unselected.push({
|
|
|
|
|
|
title: agent.Classification,
|
2025-12-15 20:46:54 +08:00
|
|
|
|
data: arr
|
2025-11-03 09:44:14 +08:00
|
|
|
|
})
|
2025-10-31 18:42:31 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-03 09:44:14 +08:00
|
|
|
|
return {
|
|
|
|
|
|
selected,
|
2025-12-15 20:46:54 +08:00
|
|
|
|
unselected: unselected
|
2025-11-03 09:44:14 +08:00
|
|
|
|
}
|
2025-10-31 18:42:31 +08:00
|
|
|
|
})
|
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">
|
2025-12-15 20:46:54 +08:00
|
|
|
|
<span class="text-[18px] font-bold text-[var(--color-text-title-header)]">智能体库</span>
|
2025-10-29 10:22:14 +08:00
|
|
|
|
<!-- 上传文件 -->
|
|
|
|
|
|
<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
|
|
|
|
<!-- 智能体列表 -->
|
2025-11-03 09:44:14 +08:00
|
|
|
|
<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>
|
2025-10-29 10:22:14 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
<!-- 底部提示栏 -->
|
2025-12-15 20:46:54 +08:00
|
|
|
|
<div
|
|
|
|
|
|
class="w-full grid grid-cols-3 gap-x-[10px] bg-[var(--color-bg-indicator)] rounded-[20px] p-[8px] mt-[10px]"
|
|
|
|
|
|
>
|
2025-10-29 10:22:14 +08:00
|
|
|
|
<div
|
|
|
|
|
|
v-for="item in Object.values(agentMapDuty)"
|
|
|
|
|
|
:key="item.key"
|
|
|
|
|
|
class="flex items-center justify-center gap-x-1"
|
|
|
|
|
|
>
|
2025-12-15 20:46:54 +08:00
|
|
|
|
<div
|
|
|
|
|
|
class="w-[8px] h-[8px] rounded-full"
|
|
|
|
|
|
:style="{
|
|
|
|
|
|
background: item.color,
|
|
|
|
|
|
border: `1px solid ${item.border}`
|
|
|
|
|
|
}"
|
|
|
|
|
|
></div>
|
2025-10-29 10:22:14 +08:00
|
|
|
|
<span class="text-[12px]">{{ item.name }}</span>
|
|
|
|
|
|
</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 {
|
2025-12-15 20:46:54 +08:00
|
|
|
|
background: var(--color-bg-tertiary);
|
2025-10-29 10:22:14 +08:00
|
|
|
|
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 {
|
2025-12-15 20:46:54 +08:00
|
|
|
|
background: var(--color-bg-quaternary);
|
2025-10-29 10:22:14 +08:00
|
|
|
|
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.15);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-10-31 18:42:31 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#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>
|