- 新增 Chat.vue 聊天页面组件 - 重构 Agents.vue 页面代码 - 更新侧边栏和路由配置 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
228 lines
9.6 KiB
Vue
228 lines
9.6 KiB
Vue
<script setup lang="ts">
|
|
import { ref, computed } from 'vue'
|
|
|
|
interface Agent {
|
|
id: number
|
|
name: string
|
|
avatar: string
|
|
description: string
|
|
accentColor: string
|
|
gradient: string
|
|
status: 'running' | 'stopped'
|
|
framework: string
|
|
model: string
|
|
mcpServers: number
|
|
createdAt: string
|
|
}
|
|
|
|
// 管理页面的 agents
|
|
const agents = ref<Agent[]>([
|
|
{ id: 1, name: 'Claude Agent', avatar: '🧠', description: 'General purpose AI assistant', accentColor: '#f97316', gradient: 'from-orange-500/20 to-amber-500/20', status: 'running', framework: 'Google ADK', model: 'gemini-2.0-flash', mcpServers: 2, createdAt: '2025-04-10' },
|
|
{ id: 2, name: 'Code Assistant', avatar: '💻', description: 'Specialized in code generation', accentColor: '#3b82f6', gradient: 'from-blue-500/20 to-cyan-500/20', status: 'running', framework: 'OpenAI', model: 'gpt-4o', mcpServers: 1, createdAt: '2025-04-08' },
|
|
{ id: 3, name: 'Data Analyst', avatar: '📊', description: 'Data analysis and visualization', accentColor: '#10b981', gradient: 'from-emerald-500/20 to-green-500/20', status: 'stopped', framework: 'PydanticAI', model: 'gpt-4o-mini', mcpServers: 3, createdAt: '2025-04-05' },
|
|
{ id: 4, name: 'Research Bot', avatar: '🔬', description: 'Academic research assistant', accentColor: '#8b5cf6', gradient: 'from-violet-500/20 to-purple-500/20', status: 'running', framework: 'LangChain', model: 'claude-3-5-sonnet', mcpServers: 2, createdAt: '2025-04-12' },
|
|
{ id: 5, name: '客服助手', avatar: '🎧', description: 'Customer support agent', accentColor: '#ec4899', gradient: 'from-pink-500/20 to-rose-500/20', status: 'running', framework: 'Google ADK', model: 'gemini-1.5-pro', mcpServers: 4, createdAt: '2025-04-11' },
|
|
])
|
|
|
|
const searchQuery = ref('')
|
|
const filterStatus = ref('all')
|
|
|
|
// 过滤后的 agents
|
|
const filteredAgents = computed(() => {
|
|
return agents.value.filter(agent => {
|
|
const matchSearch = agent.name.toLowerCase().includes(searchQuery.value.toLowerCase()) ||
|
|
agent.framework.toLowerCase().includes(searchQuery.value.toLowerCase())
|
|
const matchStatus = filterStatus.value === 'all' || agent.status === filterStatus.value
|
|
return matchSearch && matchStatus
|
|
})
|
|
})
|
|
|
|
// 统计数据
|
|
const stats = computed(() => ({
|
|
total: agents.value.length,
|
|
running: agents.value.filter(a => a.status === 'running').length,
|
|
stopped: agents.value.filter(a => a.status === 'stopped').length,
|
|
}))
|
|
|
|
// 状态颜色
|
|
const statusClass = (status: string) => {
|
|
switch (status) {
|
|
case 'running': return 'bg-primary-success'
|
|
case 'stopped': return 'bg-gray-500'
|
|
default: return 'bg-gray-500'
|
|
}
|
|
}
|
|
|
|
// 切换状态
|
|
const toggleStatus = (agent: Agent) => {
|
|
agent.status = agent.status === 'running' ? 'stopped' : 'running'
|
|
}
|
|
|
|
// 删除 Agent
|
|
const deleteAgent = (id: number) => {
|
|
agents.value = agents.value.filter(a => a.id !== id)
|
|
}
|
|
</script>
|
|
|
|
<template>
|
|
<!-- 主内容区域 -->
|
|
<div class="p-6 min-h-screen">
|
|
<!-- 顶部导航 -->
|
|
<div class="flex justify-between items-center mb-6">
|
|
<div class="flex items-center gap-2">
|
|
<i class="fa-solid fa-robot text-gray-400"></i>
|
|
<span class="font-medium">Agents</span>
|
|
</div>
|
|
<button class="btn-primary">
|
|
<i class="fa-solid fa-plus"></i>
|
|
New Agent
|
|
</button>
|
|
</div>
|
|
|
|
<!-- Stats Cards -->
|
|
<div class="grid grid-cols-4 gap-4 mb-6">
|
|
<div class="bg-dark-700 rounded-xl p-4 border border-dark-500">
|
|
<div class="flex items-center gap-3">
|
|
<div class="w-10 h-10 rounded-lg bg-dark-600 flex items-center justify-center">
|
|
<i class="fa-solid fa-robot text-gray-400"></i>
|
|
</div>
|
|
<div>
|
|
<div class="text-2xl font-bold text-white">{{ stats.total }}</div>
|
|
<div class="text-xs text-gray-400">Total Agents</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="bg-dark-700 rounded-xl p-4 border border-dark-500">
|
|
<div class="flex items-center gap-3">
|
|
<div class="w-10 h-10 rounded-lg bg-primary-success/20 flex items-center justify-center">
|
|
<i class="fa-solid fa-circle-check text-primary-success"></i>
|
|
</div>
|
|
<div>
|
|
<div class="text-2xl font-bold text-primary-success">{{ stats.running }}</div>
|
|
<div class="text-xs text-gray-400">Running</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="bg-dark-700 rounded-xl p-4 border border-dark-500">
|
|
<div class="flex items-center gap-3">
|
|
<div class="w-10 h-10 rounded-lg bg-gray-500/20 flex items-center justify-center">
|
|
<i class="fa-solid fa-circle-stop text-gray-400"></i>
|
|
</div>
|
|
<div>
|
|
<div class="text-2xl font-bold text-gray-400">{{ stats.stopped }}</div>
|
|
<div class="text-xs text-gray-400">Stopped</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="bg-dark-700 rounded-xl p-4 border border-dark-500">
|
|
<div class="flex items-center gap-3">
|
|
<div class="w-10 h-10 rounded-lg bg-primary-cyan/20 flex items-center justify-center">
|
|
<i class="fa-solid fa-plug text-primary-cyan"></i>
|
|
</div>
|
|
<div>
|
|
<div class="text-2xl font-bold text-primary-cyan">{{ agents.reduce((sum, a) => sum + a.mcpServers, 0) }}</div>
|
|
<div class="text-xs text-gray-400">MCP Servers</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 搜索和筛选 -->
|
|
<div class="flex gap-4 mb-6">
|
|
<div class="flex-1 relative">
|
|
<i class="fa-solid fa-search absolute left-3 top-1/2 -translate-y-1/2 text-gray-400"></i>
|
|
<input
|
|
v-model="searchQuery"
|
|
type="text"
|
|
placeholder="Search agents by name or framework..."
|
|
class="search-input w-full"
|
|
>
|
|
</div>
|
|
<el-select v-model="filterStatus" placeholder="Select" class="w-40" size="large">
|
|
<el-option label="All Status" value="all" />
|
|
<el-option label="Running" value="running" />
|
|
<el-option label="Stopped" value="stopped" />
|
|
</el-select>
|
|
</div>
|
|
|
|
<!-- Agents 列表 -->
|
|
<div class="bg-dark-700 rounded-xl overflow-hidden">
|
|
<table class="w-full">
|
|
<thead class="bg-dark-600">
|
|
<tr>
|
|
<th class="text-left px-5 py-3 text-sm font-medium text-gray-400">Agent Name</th>
|
|
<th class="text-left px-5 py-3 text-sm font-medium text-gray-400">Framework</th>
|
|
<th class="text-left px-5 py-3 text-sm font-medium text-gray-400">Model</th>
|
|
<th class="text-center px-5 py-3 text-sm font-medium text-gray-400">MCP</th>
|
|
<th class="text-left px-5 py-3 text-sm font-medium text-gray-400">Status</th>
|
|
<th class="text-left px-5 py-3 text-sm font-medium text-gray-400">Created</th>
|
|
<th class="text-right px-5 py-3 text-sm font-medium text-gray-400">Actions</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<tr v-for="agent in filteredAgents" :key="agent.id" class="table-row">
|
|
<td class="px-5 py-4">
|
|
<div class="flex items-center gap-3">
|
|
<div
|
|
class="w-10 h-10 rounded-lg flex items-center justify-center text-lg"
|
|
:style="{ backgroundColor: agent.accentColor + '20', color: agent.accentColor }"
|
|
>
|
|
{{ agent.avatar }}
|
|
</div>
|
|
<div>
|
|
<div class="font-medium text-white">{{ agent.name }}</div>
|
|
<div class="text-xs text-gray-500">{{ agent.description }}</div>
|
|
</div>
|
|
</div>
|
|
</td>
|
|
<td class="px-5 py-4">
|
|
<span class="bg-dark-500 px-2 py-1 rounded text-sm text-gray-300">{{ agent.framework }}</span>
|
|
</td>
|
|
<td class="px-5 py-4 text-gray-300">{{ agent.model }}</td>
|
|
<td class="px-5 py-4 text-center">
|
|
<span class="text-primary-cyan">{{ agent.mcpServers }}</span>
|
|
</td>
|
|
<td class="px-5 py-4">
|
|
<div class="flex items-center gap-2">
|
|
<span class="w-2 h-2 rounded-full" :class="statusClass(agent.status)"></span>
|
|
<span class="capitalize text-sm text-gray-300">{{ agent.status }}</span>
|
|
</div>
|
|
</td>
|
|
<td class="px-5 py-4 text-gray-400 text-sm">{{ agent.createdAt }}</td>
|
|
<td class="px-5 py-4">
|
|
<div class="flex items-center justify-end gap-2">
|
|
<button
|
|
@click="toggleStatus(agent)"
|
|
class="btn-icon"
|
|
:title="agent.status === 'running' ? 'Stop' : 'Start'"
|
|
>
|
|
<i :class="['fa-solid', agent.status === 'running' ? 'fa-stop' : 'fa-play', 'text-gray-400']"></i>
|
|
</button>
|
|
<button class="btn-icon" title="Edit">
|
|
<i class="fa-solid fa-pen text-gray-400"></i>
|
|
</button>
|
|
<button class="btn-icon" title="Settings">
|
|
<i class="fa-solid fa-gear text-gray-400"></i>
|
|
</button>
|
|
<button
|
|
@click="deleteAgent(agent.id)"
|
|
class="btn-icon"
|
|
title="Delete"
|
|
>
|
|
<i class="fa-solid fa-trash text-gray-400 hover:text-primary-danger"></i>
|
|
</button>
|
|
</div>
|
|
</td>
|
|
</tr>
|
|
</tbody>
|
|
</table>
|
|
|
|
<!-- 空状态 -->
|
|
<div v-if="filteredAgents.length === 0" class="py-12 text-center text-gray-500">
|
|
<i class="fa-solid fa-robot text-4xl mb-3"></i>
|
|
<p>No agents found</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</template>
|