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);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user