Add Database page with new connection feature
- Reorganize project structure: move frontend to web/ directory - Add Database page with connection list (name, type, subtables, status, created, actions) - Integrate Element Plus for UI components with dark theme support - Add Quicksand font for rounded UI design - Configure root package.json to run frontend from web/ directory Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
654
web/src/views/Agents.vue
Normal file
654
web/src/views/Agents.vue
Normal file
@@ -0,0 +1,654 @@
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
|
||||
interface Agent {
|
||||
id: number
|
||||
name: string
|
||||
framework: string
|
||||
status: 'running' | 'stopped' | 'error'
|
||||
mcpServers: number
|
||||
model: string
|
||||
createdAt: string
|
||||
description: string
|
||||
}
|
||||
|
||||
// Agents 数据
|
||||
const agents = ref<Agent[]>([
|
||||
{ id: 1, name: 'template-google-adk-api', framework: 'Google ADK', status: 'running', mcpServers: 2, model: 'gemini-2.0-flash', createdAt: '2025-04-10', description: 'Google ADK template for agent deployment' },
|
||||
{ id: 2, name: 'mcp-google-adk-api', framework: 'Google ADK', status: 'error', mcpServers: 1, model: 'gemini-2.0-flash', createdAt: '2025-04-08', description: 'MCP-enabled Google ADK agent' },
|
||||
{ id: 3, name: 'template-openai-api', framework: 'OpenAI', status: 'stopped', mcpServers: 3, model: 'gpt-4o', createdAt: '2025-04-05', description: 'OpenAI API template agent' },
|
||||
{ id: 4, name: 'pydantic-ai-agent', framework: 'PydanticAI', status: 'running', mcpServers: 2, model: 'gpt-4o-mini', createdAt: '2025-04-12', description: 'PydanticAI framework agent' },
|
||||
{ id: 5, name: 'langchain-agent', framework: 'LangChain', status: 'running', mcpServers: 4, model: 'claude-3-5-sonnet', createdAt: '2025-04-11', description: 'LangChain based agent with tools' },
|
||||
])
|
||||
|
||||
// 编辑状态
|
||||
const editingAgent = ref<Agent | null>(null)
|
||||
const isEditing = ref(false)
|
||||
const isCreating = ref(false)
|
||||
const searchQuery = ref('')
|
||||
const filterStatus = ref<string>('all')
|
||||
|
||||
// 新建 Agent 表单
|
||||
const newAgentForm = ref({
|
||||
name: '',
|
||||
framework: 'Google ADK',
|
||||
model: 'gemini-2.0-flash',
|
||||
description: '',
|
||||
mcpServers: [] as string[],
|
||||
})
|
||||
|
||||
const frameworks = [
|
||||
{ name: 'Google ADK', icon: 'fa-google', color: 'from-blue-500 to-blue-600' },
|
||||
{ name: 'OpenAI', icon: 'fa-openai', color: 'from-green-500 to-green-600' },
|
||||
{ name: 'PydanticAI', icon: 'fa-robot', color: 'from-purple-500 to-purple-600' },
|
||||
{ name: 'LangChain', icon: 'fa-link', color: 'from-orange-500 to-orange-600' },
|
||||
]
|
||||
|
||||
const models = [
|
||||
{ name: 'Google ADK', models: ['gemini-2.0-flash', 'gemini-1.5-pro', 'gemini-pro'] },
|
||||
{ name: 'OpenAI', models: ['gpt-4o', 'gpt-4o-mini', 'gpt-4-turbo'] },
|
||||
{ name: 'PydanticAI', models: ['gpt-4o', 'gpt-4o-mini', 'claude-3-5-sonnet'] },
|
||||
{ name: 'LangChain', models: ['claude-3-5-sonnet', 'gpt-4o', 'gpt-4o-mini'] },
|
||||
]
|
||||
|
||||
const availableMCPServers = [
|
||||
{ name: 'linear-demo', icon: 'fa-check-circle', status: 'connected' },
|
||||
{ name: 'google-maps', icon: 'fa-map-marker-alt', status: 'connected' },
|
||||
{ name: 'explorer-mcp', icon: 'fa-folder', status: 'connected' },
|
||||
{ name: 'postgres-mcp', icon: 'fa-database', status: 'disconnected' },
|
||||
{ name: 'github-mcp', icon: 'fa-github', status: 'disconnected' },
|
||||
]
|
||||
|
||||
// 打开新建弹窗
|
||||
const openCreate = () => {
|
||||
newAgentForm.value = {
|
||||
name: '',
|
||||
framework: 'Google ADK',
|
||||
model: 'gemini-2.0-flash',
|
||||
description: '',
|
||||
mcpServers: [],
|
||||
}
|
||||
isCreating.value = true
|
||||
}
|
||||
|
||||
// 关闭新建弹窗
|
||||
const closeCreate = () => {
|
||||
isCreating.value = false
|
||||
}
|
||||
|
||||
// 保存新建
|
||||
const saveNewAgent = () => {
|
||||
const newId = Math.max(...agents.value.map(a => a.id)) + 1
|
||||
agents.value.push({
|
||||
id: newId,
|
||||
name: newAgentForm.value.name || 'Untitled Agent',
|
||||
framework: newAgentForm.value.framework,
|
||||
status: 'stopped',
|
||||
mcpServers: newAgentForm.value.mcpServers.length,
|
||||
model: newAgentForm.value.model,
|
||||
createdAt: new Date().toISOString().split('T')[0],
|
||||
description: newAgentForm.value.description,
|
||||
})
|
||||
isCreating.value = false
|
||||
}
|
||||
|
||||
// 切换 MCP 服务器
|
||||
const toggleMCPServer = (serverName: string) => {
|
||||
const index = newAgentForm.value.mcpServers.indexOf(serverName)
|
||||
if (index === -1) {
|
||||
newAgentForm.value.mcpServers.push(serverName)
|
||||
} else {
|
||||
newAgentForm.value.mcpServers.splice(index, 1)
|
||||
}
|
||||
}
|
||||
|
||||
// 编辑表单数据
|
||||
const editForm = ref({
|
||||
name: '',
|
||||
framework: '',
|
||||
model: '',
|
||||
description: '',
|
||||
})
|
||||
|
||||
// 打开编辑弹窗
|
||||
const openEdit = (agent: Agent) => {
|
||||
editingAgent.value = agent
|
||||
editForm.value = {
|
||||
name: agent.name,
|
||||
framework: agent.framework,
|
||||
model: agent.model,
|
||||
description: agent.description,
|
||||
}
|
||||
isEditing.value = true
|
||||
}
|
||||
|
||||
// 保存编辑
|
||||
const saveEdit = () => {
|
||||
if (editingAgent.value) {
|
||||
const index = agents.value.findIndex(a => a.id === editingAgent.value!.id)
|
||||
if (index !== -1) {
|
||||
agents.value[index] = {
|
||||
...agents.value[index],
|
||||
...editForm.value,
|
||||
}
|
||||
}
|
||||
}
|
||||
isEditing.value = false
|
||||
}
|
||||
|
||||
// 取消编辑
|
||||
const cancelEdit = () => {
|
||||
isEditing.value = false
|
||||
editingAgent.value = null
|
||||
}
|
||||
|
||||
// 切换状态
|
||||
const toggleStatus = (agent: Agent) => {
|
||||
if (agent.status === 'running') {
|
||||
agent.status = 'stopped'
|
||||
} else if (agent.status === 'stopped') {
|
||||
agent.status = 'running'
|
||||
}
|
||||
}
|
||||
|
||||
// 删除 Agent
|
||||
const deleteAgent = (id: number) => {
|
||||
agents.value = agents.value.filter(a => a.id !== id)
|
||||
}
|
||||
|
||||
// 过滤后的 Agents
|
||||
const filteredAgents = () => {
|
||||
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 statusClass = (status: string) => {
|
||||
switch (status) {
|
||||
case 'running': return 'bg-primary-success'
|
||||
case 'stopped': return 'bg-gray-500'
|
||||
case 'error': return 'bg-primary-danger'
|
||||
default: return 'bg-gray-500'
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
/* 模态框进入动画 */
|
||||
@keyframes modal-in {
|
||||
0% {
|
||||
opacity: 0;
|
||||
transform: scale(0.95) translateY(20px);
|
||||
}
|
||||
100% {
|
||||
opacity: 1;
|
||||
transform: scale(1) translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes fade-in {
|
||||
0% { opacity: 0; transform: translateY(-5px); }
|
||||
100% { opacity: 1; transform: translateY(0); }
|
||||
}
|
||||
|
||||
@keyframes float {
|
||||
0%, 100% { transform: translateY(0); }
|
||||
50% { transform: translateY(-8px); }
|
||||
}
|
||||
|
||||
@keyframes scale-in {
|
||||
0% { opacity: 0; transform: scale(0.9); }
|
||||
100% { opacity: 1; transform: scale(1); }
|
||||
}
|
||||
|
||||
.animate-modal-in {
|
||||
animation: modal-in 0.4s cubic-bezier(0.16, 1, 0.3, 1) forwards;
|
||||
}
|
||||
|
||||
.animate-fade-in {
|
||||
animation: fade-in 0.3s ease-out forwards;
|
||||
}
|
||||
|
||||
.animate-float {
|
||||
animation: float 3s ease-in-out infinite;
|
||||
}
|
||||
|
||||
.animate-scale-in {
|
||||
animation: scale-in 0.5s ease-out forwards;
|
||||
}
|
||||
</style>
|
||||
|
||||
<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 @click="openCreate" class="bg-gradient-to-r from-primary-orange to-red-500 hover:from-orange-500 hover:to-red-600 text-white px-4 py-2 rounded-lg font-medium flex items-center gap-2 transition-all">
|
||||
<i class="fa-solid fa-plus"></i>
|
||||
New Agent
|
||||
</button>
|
||||
</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..."
|
||||
class="w-full bg-dark-600 border border-dark-500 rounded-lg py-2 pl-10 pr-4 text-white placeholder-gray-500 focus:outline-none focus:border-primary-orange"
|
||||
>
|
||||
</div>
|
||||
<select
|
||||
v-model="filterStatus"
|
||||
class="bg-dark-600 border border-dark-500 rounded-lg px-4 py-2 text-white focus:outline-none focus:border-primary-orange"
|
||||
>
|
||||
<option value="all">All Status</option>
|
||||
<option value="running">Running</option>
|
||||
<option value="stopped">Stopped</option>
|
||||
<option value="error">Error</option>
|
||||
</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-left px-5 py-3 text-sm font-medium text-gray-400">MCP Servers</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="border-t border-dark-600 hover:bg-dark-600/50 transition-colors">
|
||||
<td class="px-5 py-4">
|
||||
<div class="font-medium">{{ agent.name }}</div>
|
||||
<div class="text-sm text-gray-500">{{ agent.description }}</div>
|
||||
</td>
|
||||
<td class="px-5 py-4">
|
||||
<span class="bg-dark-500 px-2 py-1 rounded text-sm">{{ agent.framework }}</span>
|
||||
</td>
|
||||
<td class="px-5 py-4 text-gray-300">{{ agent.model }}</td>
|
||||
<td class="px-5 py-4">
|
||||
<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">{{ 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="p-2 rounded-lg hover:bg-dark-500 transition-colors"
|
||||
:title="agent.status === 'running' ? 'Stop' : 'Start'"
|
||||
>
|
||||
<i :class="['fa-solid', agent.status === 'running' ? 'fa-stop' : 'fa-play', 'text-gray-400 hover:text-white']"></i>
|
||||
</button>
|
||||
<button
|
||||
@click="openEdit(agent)"
|
||||
class="p-2 rounded-lg hover:bg-dark-500 transition-colors"
|
||||
title="Edit"
|
||||
>
|
||||
<i class="fa-solid fa-pen text-gray-400 hover:text-white"></i>
|
||||
</button>
|
||||
<button
|
||||
@click="deleteAgent(agent.id)"
|
||||
class="p-2 rounded-lg hover:bg-dark-500 transition-colors"
|
||||
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>
|
||||
|
||||
<!-- 编辑弹窗 -->
|
||||
<Teleport to="body">
|
||||
<div v-if="isEditing" class="fixed inset-0 bg-black/60 flex items-center justify-center z-50" @click.self="cancelEdit">
|
||||
<div class="bg-dark-700 rounded-2xl w-full max-w-lg border border-dark-500 shadow-2xl">
|
||||
<!-- 弹窗头部 -->
|
||||
<div class="flex items-center justify-between p-5 border-b border-dark-500">
|
||||
<h3 class="text-lg font-semibold">Edit Agent</h3>
|
||||
<button @click="cancelEdit" class="text-gray-400 hover:text-white transition-colors">
|
||||
<i class="fa-solid fa-xmark text-xl"></i>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- 弹窗内容 -->
|
||||
<div class="p-5 space-y-4">
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-300 mb-2">Agent Name</label>
|
||||
<input
|
||||
v-model="editForm.name"
|
||||
type="text"
|
||||
class="w-full bg-dark-600 border border-dark-500 rounded-lg px-4 py-2.5 text-white focus:outline-none focus:border-primary-orange"
|
||||
>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-300 mb-2">Framework</label>
|
||||
<select
|
||||
v-model="editForm.framework"
|
||||
class="w-full bg-dark-600 border border-dark-500 rounded-lg px-4 py-2.5 text-white focus:outline-none focus:border-primary-orange"
|
||||
>
|
||||
<option value="Google ADK">Google ADK</option>
|
||||
<option value="OpenAI">OpenAI</option>
|
||||
<option value="PydanticAI">PydanticAI</option>
|
||||
<option value="LangChain">LangChain</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-300 mb-2">Model</label>
|
||||
<select
|
||||
v-model="editForm.model"
|
||||
class="w-full bg-dark-600 border border-dark-500 rounded-lg px-4 py-2.5 text-white focus:outline-none focus:border-primary-orange"
|
||||
>
|
||||
<option value="gemini-2.0-flash">gemini-2.0-flash</option>
|
||||
<option value="gpt-4o">gpt-4o</option>
|
||||
<option value="gpt-4o-mini">gpt-4o-mini</option>
|
||||
<option value="claude-3-5-sonnet">claude-3-5-sonnet</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-300 mb-2">Description</label>
|
||||
<textarea
|
||||
v-model="editForm.description"
|
||||
rows="3"
|
||||
class="w-full bg-dark-600 border border-dark-500 rounded-lg px-4 py-2.5 text-white focus:outline-none focus:border-primary-orange resize-none"
|
||||
></textarea>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 弹窗底部 -->
|
||||
<div class="flex items-center justify-end gap-3 p-5 border-t border-dark-500">
|
||||
<button
|
||||
@click="cancelEdit"
|
||||
class="px-4 py-2 rounded-lg bg-dark-600 text-gray-300 hover:bg-dark-500 transition-colors"
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
<button
|
||||
@click="saveEdit"
|
||||
class="px-4 py-2 rounded-lg bg-gradient-to-r from-primary-orange to-red-500 text-white hover:from-orange-500 hover:to-red-600 transition-all"
|
||||
>
|
||||
Save Changes
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Teleport>
|
||||
|
||||
<!-- 新建 Agent 模态框 -->
|
||||
<Teleport to="body">
|
||||
<div v-if="isCreating" class="fixed inset-0 bg-black/80 flex items-center justify-center z-50 p-4" @click.self="closeCreate">
|
||||
<div class="bg-dark-800 rounded-2xl w-full max-w-6xl h-[85vh] border border-dark-600 shadow-2xl overflow-hidden flex flex-col animate-modal-in">
|
||||
<!-- 模态框头部 -->
|
||||
<div class="flex items-center justify-between p-5 border-b border-dark-600 bg-dark-700/50">
|
||||
<div class="flex items-center gap-3">
|
||||
<div class="w-10 h-10 rounded-xl bg-gradient-to-br from-primary-orange to-red-500 flex items-center justify-center animate-pulse">
|
||||
<i class="fa-solid fa-robot text-white"></i>
|
||||
</div>
|
||||
<div>
|
||||
<h3 class="text-xl font-semibold text-white">Create New Agent</h3>
|
||||
<p class="text-sm text-gray-400">Configure your agent workflow</p>
|
||||
</div>
|
||||
</div>
|
||||
<button @click="closeCreate" class="text-gray-400 hover:text-white transition-all p-2 hover:bg-dark-600 rounded-lg">
|
||||
<i class="fa-solid fa-xmark text-xl"></i>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- 三栏布局主体 -->
|
||||
<div class="flex-1 flex overflow-hidden">
|
||||
<!-- 左侧:框架选择 -->
|
||||
<div class="w-72 bg-dark-700/50 border-r border-dark-600 p-5 overflow-y-auto">
|
||||
<div class="flex items-center gap-2 mb-4">
|
||||
<i class="fa-solid fa-layer-group text-primary-orange"></i>
|
||||
<h4 class="font-medium text-white">Framework</h4>
|
||||
</div>
|
||||
<div class="space-y-3">
|
||||
<div
|
||||
v-for="fw in frameworks"
|
||||
:key="fw.name"
|
||||
@click="newAgentForm.framework = fw.name; newAgentForm.model = models.find(m => m.name === fw.name)?.models[0] || ''"
|
||||
class="p-4 rounded-xl border-2 cursor-pointer transition-all duration-300 hover:scale-105"
|
||||
:class="newAgentForm.framework === fw.name
|
||||
? 'border-primary-orange bg-dark-600 shadow-lg shadow-primary-orange/20'
|
||||
: 'border-dark-500 bg-dark-700 hover:border-gray-500'"
|
||||
>
|
||||
<div class="flex items-center gap-3">
|
||||
<div :class="['w-10 h-10 rounded-lg bg-gradient-to-br flex items-center justify-center', fw.color]">
|
||||
<i :class="['fa-solid', fw.icon, 'text-white text-lg']"></i>
|
||||
</div>
|
||||
<span class="font-medium text-white">{{ fw.name }}</span>
|
||||
</div>
|
||||
<div v-if="newAgentForm.framework === fw.name" class="mt-2 flex items-center gap-1 text-primary-orange text-sm animate-fade-in">
|
||||
<i class="fa-solid fa-check-circle"></i>
|
||||
<span>Selected</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 模型选择 -->
|
||||
<div class="mt-6">
|
||||
<div class="flex items-center gap-2 mb-3">
|
||||
<i class="fa-solid fa-brain text-primary-cyan"></i>
|
||||
<h4 class="font-medium text-white">Model</h4>
|
||||
</div>
|
||||
<select
|
||||
v-model="newAgentForm.model"
|
||||
class="w-full bg-dark-600 border border-dark-500 rounded-lg px-4 py-3 text-white focus:outline-none focus:border-primary-orange transition-colors cursor-pointer"
|
||||
>
|
||||
<option v-for="model in models.find(m => m.name === newAgentForm.framework)?.models" :key="model" :value="model">
|
||||
{{ model }}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<!-- Agent 名称 -->
|
||||
<div class="mt-6">
|
||||
<div class="flex items-center gap-2 mb-3">
|
||||
<i class="fa-solid fa-tag text-primary-purple"></i>
|
||||
<h4 class="font-medium text-white">Agent Name</h4>
|
||||
</div>
|
||||
<input
|
||||
v-model="newAgentForm.name"
|
||||
type="text"
|
||||
placeholder="Enter agent name..."
|
||||
class="w-full bg-dark-600 border border-dark-500 rounded-lg px-4 py-3 text-white placeholder-gray-500 focus:outline-none focus:border-primary-orange transition-colors"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 中间:流程画布 -->
|
||||
<div class="flex-1 bg-dark-900 relative overflow-hidden">
|
||||
<!-- 背景网格 -->
|
||||
<div class="absolute inset-0 opacity-10" style="background-image: radial-gradient(circle, #1E6BF9 1px, transparent 1px); background-size: 30px 30px;"></div>
|
||||
|
||||
<!-- 流程节点 -->
|
||||
<div class="h-full flex flex-col items-center justify-center p-8 relative z-10">
|
||||
<!-- 开始节点 -->
|
||||
<div class="node bg-gradient-to-r from-blue-500 to-blue-600 rounded-xl w-64 p-4 shadow-lg shadow-blue-500/30 animate-float">
|
||||
<div class="flex items-center gap-3">
|
||||
<div class="w-8 h-8 rounded-lg bg-white/20 flex items-center justify-center">
|
||||
<i class="fa-solid fa-play text-white text-sm"></i>
|
||||
</div>
|
||||
<div>
|
||||
<div class="font-medium text-white">Start</div>
|
||||
<div class="text-xs text-blue-200">Agent begins</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 连接线 -->
|
||||
<div class="h-8 w-0.5 bg-gradient-to-b from-blue-500 to-primary-orange animate-pulse"></div>
|
||||
|
||||
<!-- 框架节点 -->
|
||||
<div class="node bg-dark-700 border-2 border-primary-orange rounded-xl w-64 p-4 shadow-lg shadow-primary-orange/20 animate-scale-in">
|
||||
<div class="flex items-center gap-3">
|
||||
<div class="w-10 h-10 rounded-lg bg-gradient-to-br from-primary-orange to-red-500 flex items-center justify-center">
|
||||
<i :class="['fa-solid', frameworks.find(f => f.name === newAgentForm.framework)?.icon || 'fa-robot', 'text-white']"></i>
|
||||
</div>
|
||||
<div>
|
||||
<div class="font-medium text-white">{{ newAgentForm.framework }}</div>
|
||||
<div class="text-xs text-gray-400">{{ newAgentForm.model }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 连接线 -->
|
||||
<div class="h-8 w-0.5 bg-gradient-to-b from-primary-orange to-purple-500 animate-pulse"></div>
|
||||
|
||||
<!-- MCP 服务器节点 -->
|
||||
<div class="node bg-dark-700 border-2 border-purple-500 rounded-xl w-64 p-4 shadow-lg shadow-purple-500/20 animate-scale-in" style="animation-delay: 0.2s">
|
||||
<div class="flex items-center justify-between mb-2">
|
||||
<div class="flex items-center gap-2">
|
||||
<i class="fa-solid fa-server text-purple-400"></i>
|
||||
<span class="font-medium text-white">MCP Servers</span>
|
||||
</div>
|
||||
<span class="bg-purple-500/30 text-purple-300 text-xs px-2 py-0.5 rounded">{{ newAgentForm.mcpServers.length }} connected</span>
|
||||
</div>
|
||||
<div class="flex flex-wrap gap-2">
|
||||
<span v-for="mcp in newAgentForm.mcpServers" :key="mcp" class="bg-dark-600 text-gray-300 text-xs px-2 py-1 rounded flex items-center gap-1">
|
||||
<i class="fa-solid fa-check-circle text-green-400"></i>
|
||||
{{ mcp }}
|
||||
</span>
|
||||
<span v-if="newAgentForm.mcpServers.length === 0" class="text-gray-500 text-xs">No servers selected</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 连接线 -->
|
||||
<div class="h-8 w-0.5 bg-gradient-to-b from-purple-500 to-green-500 animate-pulse"></div>
|
||||
|
||||
<!-- 结束节点 -->
|
||||
<div class="node bg-gradient-to-r from-green-500 to-emerald-600 rounded-xl w-64 p-4 shadow-lg shadow-green-500/30 animate-float" style="animation-delay: 0.5s">
|
||||
<div class="flex items-center gap-3">
|
||||
<div class="w-8 h-8 rounded-lg bg-white/20 flex items-center justify-center">
|
||||
<i class="fa-solid fa-check text-white text-sm"></i>
|
||||
</div>
|
||||
<div>
|
||||
<div class="font-medium text-white">Ready</div>
|
||||
<div class="text-xs text-green-200">Agent configured</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 装饰性光效 -->
|
||||
<div class="absolute top-1/4 -left-20 w-40 h-40 bg-primary-orange/10 rounded-full blur-3xl animate-pulse"></div>
|
||||
<div class="absolute bottom-1/4 -right-20 w-40 h-40 bg-purple-500/10 rounded-full blur-3xl animate-pulse" style="animation-delay: 0.5s"></div>
|
||||
</div>
|
||||
|
||||
<!-- 右侧:MCP 服务器选择 -->
|
||||
<div class="w-80 bg-dark-700/50 border-l border-dark-600 p-5 overflow-y-auto">
|
||||
<div class="flex items-center gap-2 mb-4">
|
||||
<i class="fa-solid fa-plug text-primary-success"></i>
|
||||
<h4 class="font-medium text-white">MCP Servers</h4>
|
||||
<span class="text-xs text-gray-500">({{ newAgentForm.mcpServers.length }} selected)</span>
|
||||
</div>
|
||||
|
||||
<div class="space-y-3">
|
||||
<div
|
||||
v-for="server in availableMCPServers"
|
||||
:key="server.name"
|
||||
@click="toggleMCPServer(server.name)"
|
||||
class="p-4 rounded-xl border-2 cursor-pointer transition-all duration-300 hover:scale-105"
|
||||
:class="newAgentForm.mcpServers.includes(server.name)
|
||||
? 'border-green-500 bg-dark-600 shadow-lg shadow-green-500/20'
|
||||
: 'border-dark-500 bg-dark-700 hover:border-gray-500'"
|
||||
>
|
||||
<div class="flex items-center justify-between">
|
||||
<div class="flex items-center gap-3">
|
||||
<div class="w-10 h-10 rounded-lg bg-dark-500 flex items-center justify-center">
|
||||
<i :class="['fa-solid', server.icon, server.status === 'connected' ? 'text-green-400' : 'text-gray-500']"></i>
|
||||
</div>
|
||||
<div>
|
||||
<div class="font-medium text-white">{{ server.name }}</div>
|
||||
<div class="text-xs flex items-center gap-1" :class="server.status === 'connected' ? 'text-green-400' : 'text-gray-500'">
|
||||
<span class="w-1.5 h-1.5 rounded-full" :class="server.status === 'connected' ? 'bg-green-400' : 'bg-gray-500'"></span>
|
||||
{{ server.status }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="w-6 h-6 rounded-md flex items-center justify-center transition-all"
|
||||
:class="newAgentForm.mcpServers.includes(server.name) ? 'bg-green-500' : 'bg-dark-500'"
|
||||
>
|
||||
<i v-if="newAgentForm.mcpServers.includes(server.name)" class="fa-solid fa-check text-white text-xs"></i>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 描述 -->
|
||||
<div class="mt-6">
|
||||
<div class="flex items-center gap-2 mb-3">
|
||||
<i class="fa-solid fa-align-left text-gray-400"></i>
|
||||
<h4 class="font-medium text-white">Description</h4>
|
||||
</div>
|
||||
<textarea
|
||||
v-model="newAgentForm.description"
|
||||
rows="4"
|
||||
placeholder="Describe your agent's purpose..."
|
||||
class="w-full bg-dark-600 border border-dark-500 rounded-lg px-4 py-3 text-white placeholder-gray-500 focus:outline-none focus:border-primary-orange transition-colors resize-none"
|
||||
></textarea>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 底部操作栏 -->
|
||||
<div class="flex items-center justify-between p-5 border-t border-dark-600 bg-dark-700/50">
|
||||
<div class="flex items-center gap-2 text-sm text-gray-400">
|
||||
<i class="fa-solid fa-circle-info"></i>
|
||||
<span>Configure your agent settings</span>
|
||||
</div>
|
||||
<div class="flex items-center gap-3">
|
||||
<button
|
||||
@click="closeCreate"
|
||||
class="px-6 py-2.5 rounded-xl bg-dark-600 text-gray-300 hover:bg-dark-500 border border-dark-500 transition-all hover:scale-105"
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
<button
|
||||
@click="saveNewAgent"
|
||||
class="px-6 py-2.5 rounded-xl bg-gradient-to-r from-primary-orange to-red-500 text-white hover:from-orange-500 hover:to-red-600 transition-all hover:scale-105 shadow-lg shadow-primary-orange/30 flex items-center gap-2"
|
||||
>
|
||||
<i class="fa-solid fa-plus"></i>
|
||||
Create Agent
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Teleport>
|
||||
</div>
|
||||
</template>
|
||||
Reference in New Issue
Block a user