2026-03-05 10:49:46 +08:00
|
|
|
|
<script setup lang="ts">
|
2026-03-12 23:19:04 +08:00
|
|
|
|
import { Play, Pause, Edit, Trash2 } from 'lucide-vue-next'
|
2026-03-13 08:32:08 +08:00
|
|
|
|
import { useAgents } from './agents/useAgents'
|
|
|
|
|
|
import './agents/agents.css'
|
|
|
|
|
|
|
|
|
|
|
|
const {
|
|
|
|
|
|
agents,
|
|
|
|
|
|
skillsList,
|
|
|
|
|
|
modelsList,
|
|
|
|
|
|
searchQuery,
|
|
|
|
|
|
filterStatus,
|
|
|
|
|
|
isLoading,
|
|
|
|
|
|
skillsLoading,
|
|
|
|
|
|
modelsLoading,
|
|
|
|
|
|
showCreateModal,
|
|
|
|
|
|
isCreating,
|
|
|
|
|
|
newAgent,
|
|
|
|
|
|
showEditModal,
|
|
|
|
|
|
isEditing,
|
|
|
|
|
|
editingAgent,
|
|
|
|
|
|
showSkillsDropdown,
|
|
|
|
|
|
showSubSkillsDropdown,
|
|
|
|
|
|
skillsSearch,
|
|
|
|
|
|
skillsModeOptions,
|
|
|
|
|
|
avatarOptions,
|
|
|
|
|
|
knowledgeOptions,
|
|
|
|
|
|
skillsOptions,
|
|
|
|
|
|
filteredSkills,
|
|
|
|
|
|
filteredAgents,
|
|
|
|
|
|
stats,
|
|
|
|
|
|
isAllSelected,
|
|
|
|
|
|
isIndeterminate,
|
2026-03-13 21:29:01 +08:00
|
|
|
|
isAllSelectedEdit,
|
|
|
|
|
|
isIndeterminateEdit,
|
2026-03-13 08:32:08 +08:00
|
|
|
|
fetchAgents,
|
|
|
|
|
|
fetchSkills,
|
|
|
|
|
|
fetchModels,
|
|
|
|
|
|
getSkillLabel,
|
|
|
|
|
|
openCreateModal,
|
|
|
|
|
|
createAgent,
|
|
|
|
|
|
openEdit,
|
|
|
|
|
|
saveEdit,
|
|
|
|
|
|
toggleStatus,
|
|
|
|
|
|
deleteAgent,
|
|
|
|
|
|
toggleSkillsDropdown,
|
|
|
|
|
|
closeSkillsDropdown,
|
|
|
|
|
|
handleSkillsModeClick,
|
|
|
|
|
|
handleSkillsModeClickEdit,
|
|
|
|
|
|
toggleSelectAll,
|
|
|
|
|
|
clearSkills,
|
2026-03-13 21:29:01 +08:00
|
|
|
|
toggleSelectAllEdit,
|
|
|
|
|
|
clearSkillsEdit,
|
2026-03-13 08:32:08 +08:00
|
|
|
|
toggleSkillsMode,
|
|
|
|
|
|
selectSkillsMode,
|
|
|
|
|
|
toggleSubSkillsDropdown,
|
|
|
|
|
|
closeAllDropdowns,
|
|
|
|
|
|
getSkillsDisplayText,
|
|
|
|
|
|
toggleSkillSelection,
|
|
|
|
|
|
selectAllSkills,
|
|
|
|
|
|
statusClass
|
|
|
|
|
|
} = useAgents()
|
2026-03-05 10:49:46 +08:00
|
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
|
|
<template>
|
2026-03-10 16:09:09 +08:00
|
|
|
|
<!-- 主内容区域 -->
|
|
|
|
|
|
<div class="p-6 min-h-screen">
|
|
|
|
|
|
<!-- 顶部导航 -->
|
|
|
|
|
|
<div class="flex justify-between items-center mb-6">
|
2026-03-13 21:29:01 +08:00
|
|
|
|
<div class="flex items-center gap-4">
|
|
|
|
|
|
<div class="flex items-center gap-2">
|
|
|
|
|
|
<i class="fa-solid fa-robot text-orange-500 text-xl"></i>
|
|
|
|
|
|
<span class="text-xl font-semibold text-white">Agents</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="flex items-center gap-3 text-sm">
|
|
|
|
|
|
<span class="text-gray-400">Total:</span>
|
|
|
|
|
|
<span class="text-white font-medium">{{ stats.total }}</span>
|
|
|
|
|
|
<span class="w-1 h-1 rounded-full bg-gray-500"></span>
|
|
|
|
|
|
<span class="text-gray-400">Active:</span>
|
|
|
|
|
|
<span class="text-green-400 font-medium">{{ stats.active }}</span>
|
|
|
|
|
|
<span class="w-1 h-1 rounded-full bg-gray-500"></span>
|
|
|
|
|
|
<span class="text-gray-400">Inactive:</span>
|
|
|
|
|
|
<span class="text-gray-400 font-medium">{{ stats.inactive }}</span>
|
|
|
|
|
|
</div>
|
2026-03-10 16:09:09 +08:00
|
|
|
|
</div>
|
2026-03-11 14:26:25 +08:00
|
|
|
|
<button @click="openCreateModal" class="btn-primary">
|
2026-03-10 16:09:09 +08:00
|
|
|
|
<i class="fa-solid fa-plus"></i>
|
|
|
|
|
|
New Agent
|
|
|
|
|
|
</button>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 搜索和筛选 -->
|
|
|
|
|
|
<div class="flex gap-4 mb-6">
|
|
|
|
|
|
<div class="flex-1 relative">
|
2026-03-13 21:29:01 +08:00
|
|
|
|
<i class="fa-solid fa-search absolute left-3 top-1/2 -translate-y-1/2 text-gray-500"></i>
|
2026-03-10 16:09:09 +08:00
|
|
|
|
<input
|
|
|
|
|
|
v-model="searchQuery"
|
|
|
|
|
|
type="text"
|
2026-03-12 23:19:04 +08:00
|
|
|
|
placeholder="Search agents by name or skills..."
|
2026-03-10 16:09:09 +08:00
|
|
|
|
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" />
|
2026-03-12 23:19:04 +08:00
|
|
|
|
<el-option label="Active" value="active" />
|
|
|
|
|
|
<el-option label="Inactive" value="inactive" />
|
2026-03-10 16:09:09 +08:00
|
|
|
|
</el-select>
|
|
|
|
|
|
</div>
|
2026-03-05 10:49:46 +08:00
|
|
|
|
|
2026-03-10 16:09:09 +08:00
|
|
|
|
<!-- Agents 列表 -->
|
|
|
|
|
|
<div class="bg-dark-700 rounded-xl overflow-hidden">
|
2026-03-13 21:29:01 +08:00
|
|
|
|
<!-- 加载状态 -->
|
|
|
|
|
|
<div v-if="isLoading" class="flex items-center justify-center py-20">
|
|
|
|
|
|
<div class="flex flex-col items-center gap-3">
|
|
|
|
|
|
<i class="fa-solid fa-circle-notch fa-spin text-3xl text-orange-500"></i>
|
|
|
|
|
|
<span class="text-gray-400">Loading agents...</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<table v-else-if="filteredAgents.length > 0" class="w-full">
|
2026-03-10 16:09:09 +08:00
|
|
|
|
<thead class="bg-dark-600">
|
|
|
|
|
|
<tr>
|
2026-03-12 23:19:04 +08:00
|
|
|
|
<th class="text-left px-4 py-3 text-sm font-medium text-gray-400">Agent Name</th>
|
|
|
|
|
|
<th class="text-center px-4 py-3 text-sm font-medium text-gray-400">Skills</th>
|
|
|
|
|
|
<th class="text-center px-4 py-3 text-sm font-medium text-gray-400">Model</th>
|
|
|
|
|
|
<th class="text-center px-4 py-3 text-sm font-medium text-gray-400">Status</th>
|
|
|
|
|
|
<th class="text-center px-4 py-3 text-sm font-medium text-gray-400">Created</th>
|
|
|
|
|
|
<th class="text-center px-4 py-3 text-sm font-medium text-gray-400">Actions</th>
|
2026-03-10 16:09:09 +08:00
|
|
|
|
</tr>
|
|
|
|
|
|
</thead>
|
|
|
|
|
|
<tbody>
|
|
|
|
|
|
<tr v-for="agent in filteredAgents" :key="agent.id" class="table-row">
|
2026-03-12 23:19:04 +08:00
|
|
|
|
<td class="px-4 py-3">
|
2026-03-10 15:42:21 +08:00
|
|
|
|
<div class="flex items-center gap-3">
|
2026-03-05 10:49:46 +08:00
|
|
|
|
<div
|
2026-03-10 16:09:09 +08:00
|
|
|
|
class="w-10 h-10 rounded-lg flex items-center justify-center text-lg"
|
2026-03-10 15:42:21 +08:00
|
|
|
|
:style="{ backgroundColor: agent.accentColor + '20', color: agent.accentColor }"
|
2026-03-05 10:49:46 +08:00
|
|
|
|
>
|
2026-03-10 15:42:21 +08:00
|
|
|
|
{{ agent.avatar }}
|
2026-03-05 10:49:46 +08:00
|
|
|
|
</div>
|
2026-03-10 16:09:09 +08:00
|
|
|
|
<div>
|
|
|
|
|
|
<div class="font-medium text-white">{{ agent.name }}</div>
|
|
|
|
|
|
<div class="text-xs text-gray-500">{{ agent.description }}</div>
|
2026-03-05 10:49:46 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
2026-03-10 16:09:09 +08:00
|
|
|
|
</td>
|
2026-03-12 23:19:04 +08:00
|
|
|
|
<td class="px-4 py-3 text-center">
|
|
|
|
|
|
<span class="bg-dark-500 px-2 py-1 rounded text-sm text-gray-300">{{ agent.skills }}</span>
|
2026-03-10 16:09:09 +08:00
|
|
|
|
</td>
|
2026-03-12 23:19:04 +08:00
|
|
|
|
<td class="px-4 py-3 text-center">
|
|
|
|
|
|
<span class="bg-dark-500 px-2 py-1 rounded text-sm text-gray-300">{{ agent.model }}</span>
|
2026-03-10 16:09:09 +08:00
|
|
|
|
</td>
|
2026-03-12 23:19:04 +08:00
|
|
|
|
<td class="px-4 py-3 text-center">
|
2026-03-13 21:29:01 +08:00
|
|
|
|
<span class="inline-flex items-center gap-1.5 px-2 py-1 rounded-full text-xs transition-all duration-200" :class="agent.status === 'active' ? 'bg-green-500/20 text-green-400' : 'bg-gray-500/20 text-gray-400'">
|
|
|
|
|
|
<span class="w-1.5 h-1.5 rounded-full animate-pulse" :class="agent.status === 'active' ? 'bg-green-500' : 'bg-gray-400'"></span>
|
2026-03-12 23:19:04 +08:00
|
|
|
|
<span class="capitalize">{{ agent.status }}</span>
|
|
|
|
|
|
</span>
|
2026-03-10 16:09:09 +08:00
|
|
|
|
</td>
|
2026-03-12 23:19:04 +08:00
|
|
|
|
<td class="px-4 py-3 text-center text-gray-400 text-sm">{{ agent.createdAt }}</td>
|
|
|
|
|
|
<td class="px-4 py-3">
|
|
|
|
|
|
<div class="flex items-center justify-center gap-2">
|
2026-03-10 16:09:09 +08:00
|
|
|
|
<button
|
|
|
|
|
|
@click="toggleStatus(agent)"
|
|
|
|
|
|
class="btn-icon"
|
2026-03-12 23:19:04 +08:00
|
|
|
|
:title="agent.status === 'active' ? 'Deactivate' : 'Activate'"
|
2026-03-10 16:09:09 +08:00
|
|
|
|
>
|
2026-03-13 21:29:01 +08:00
|
|
|
|
<Pause v-if="agent.status === 'active'" class="w-4 h-4 text-gray-500 hover:text-yellow-400 transition-colors" />
|
|
|
|
|
|
<Play v-else class="w-4 h-4 text-gray-500 hover:text-green-400 transition-colors" />
|
2026-03-10 16:09:09 +08:00
|
|
|
|
</button>
|
2026-03-12 23:19:04 +08:00
|
|
|
|
<button @click="openEdit(agent)" class="btn-icon" title="Edit">
|
2026-03-13 21:29:01 +08:00
|
|
|
|
<Edit class="w-4 h-4 text-gray-500 hover:text-white transition-colors" />
|
2026-03-10 16:09:09 +08:00
|
|
|
|
</button>
|
|
|
|
|
|
<button
|
2026-03-13 21:29:01 +08:00
|
|
|
|
@click.stop="deleteAgent(agent.id)"
|
2026-03-10 16:09:09 +08:00
|
|
|
|
class="btn-icon"
|
|
|
|
|
|
title="Delete"
|
|
|
|
|
|
>
|
2026-03-13 21:29:01 +08:00
|
|
|
|
<Trash2 class="w-4 h-4 text-gray-500 hover:text-red-400 transition-colors" />
|
2026-03-10 16:09:09 +08:00
|
|
|
|
</button>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</td>
|
|
|
|
|
|
</tr>
|
|
|
|
|
|
</tbody>
|
|
|
|
|
|
</table>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 空状态 -->
|
2026-03-13 21:29:01 +08:00
|
|
|
|
<div v-if="filteredAgents.length === 0 && !isLoading" class="empty-box">
|
2026-03-12 17:17:08 +08:00
|
|
|
|
<div class="empty-icon">
|
|
|
|
|
|
<i class="fa-solid fa-robot"></i>
|
|
|
|
|
|
</div>
|
2026-03-13 21:29:01 +08:00
|
|
|
|
<p class="empty-text">{{ searchQuery || filterStatus !== 'all' ? 'No matching agents found' : 'No agents found' }}</p>
|
|
|
|
|
|
<p class="empty-tip">{{ searchQuery || filterStatus !== 'all' ? 'Try adjusting your search or filter' : 'Click "New Agent" to create one' }}</p>
|
2026-03-05 10:49:46 +08:00
|
|
|
|
</div>
|
2026-03-10 16:09:09 +08:00
|
|
|
|
</div>
|
2026-03-05 10:49:46 +08:00
|
|
|
|
</div>
|
2026-03-11 14:26:25 +08:00
|
|
|
|
|
|
|
|
|
|
<!-- 创建智能体弹窗 -->
|
|
|
|
|
|
<Teleport to="body">
|
2026-03-13 21:29:01 +08:00
|
|
|
|
<div v-if="showCreateModal" class="fixed inset-0 bg-black/60 flex items-center justify-center z-50" @click.self="showCreateModal = false">
|
2026-03-11 14:26:25 +08:00
|
|
|
|
<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">Create New Agent</h3>
|
|
|
|
|
|
<button @click="showCreateModal = false" 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="newAgent.name"
|
|
|
|
|
|
type="text"
|
|
|
|
|
|
placeholder="Enter agent name..."
|
|
|
|
|
|
class="w-full bg-dark-600 border border-dark-500 rounded-lg px-4 py-2.5 text-white placeholder-gray-500 focus:outline-none focus:border-primary-orange"
|
|
|
|
|
|
>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<div>
|
|
|
|
|
|
<label class="block text-sm font-medium text-gray-300 mb-2">Description</label>
|
|
|
|
|
|
<textarea
|
|
|
|
|
|
v-model="newAgent.description"
|
|
|
|
|
|
rows="3"
|
|
|
|
|
|
placeholder="Describe what this agent does..."
|
|
|
|
|
|
class="w-full bg-dark-600 border border-dark-500 rounded-lg px-4 py-2.5 text-white placeholder-gray-500 focus:outline-none focus:border-primary-orange resize-none"
|
|
|
|
|
|
></textarea>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
2026-03-12 10:49:44 +08:00
|
|
|
|
<div>
|
|
|
|
|
|
<label class="block text-sm font-medium text-gray-300 mb-2">Avatar</label>
|
|
|
|
|
|
<div class="flex flex-wrap gap-2">
|
|
|
|
|
|
<button
|
|
|
|
|
|
v-for="avatar in avatarOptions"
|
|
|
|
|
|
:key="avatar"
|
|
|
|
|
|
type="button"
|
|
|
|
|
|
@click="newAgent.avatar = avatar"
|
|
|
|
|
|
class="w-10 h-10 rounded-lg flex items-center justify-center text-lg transition-all"
|
|
|
|
|
|
:class="newAgent.avatar === avatar ? 'bg-primary-orange text-white ring-2 ring-orange-400' : 'bg-dark-600 text-gray-300 hover:bg-dark-500'"
|
|
|
|
|
|
>
|
|
|
|
|
|
{{ avatar }}
|
|
|
|
|
|
</button>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
2026-03-12 23:19:04 +08:00
|
|
|
|
<div>
|
|
|
|
|
|
<label class="block text-sm font-medium text-gray-300 mb-2">Preferred Model</label>
|
|
|
|
|
|
<el-select
|
|
|
|
|
|
v-model="newAgent.modelId"
|
|
|
|
|
|
placeholder="Select a model..."
|
|
|
|
|
|
class="w-full"
|
|
|
|
|
|
size="large"
|
|
|
|
|
|
clearable
|
|
|
|
|
|
>
|
|
|
|
|
|
<el-option
|
|
|
|
|
|
v-for="model in modelsList"
|
|
|
|
|
|
:key="model.id"
|
|
|
|
|
|
:label="model.name"
|
|
|
|
|
|
:value="model.id"
|
|
|
|
|
|
>
|
|
|
|
|
|
<div class="flex items-center justify-between">
|
|
|
|
|
|
<span>{{ model.name }}</span>
|
|
|
|
|
|
<span class="text-xs text-gray-500">{{ model.provider }} - {{ model.model }}</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</el-option>
|
|
|
|
|
|
</el-select>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
2026-03-11 14:26:25 +08:00
|
|
|
|
<div>
|
|
|
|
|
|
<label class="block text-sm font-medium text-gray-300 mb-2">Skills *</label>
|
2026-03-12 15:23:13 +08:00
|
|
|
|
<!-- 技能模式选择 - 两级下拉框 -->
|
|
|
|
|
|
<div class="skills-selector">
|
|
|
|
|
|
<!-- 已选 tags 显示区域 -->
|
|
|
|
|
|
<div class="selected-tags" @click="toggleSkillsDropdown">
|
|
|
|
|
|
<div v-if="newAgent.skillsMode === 'all'" class="placeholder">
|
|
|
|
|
|
All Skills
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div v-else class="tags-container">
|
|
|
|
|
|
<span
|
|
|
|
|
|
v-for="skillId in newAgent.selectedSkills.slice(0, 3)"
|
|
|
|
|
|
:key="skillId"
|
|
|
|
|
|
class="selected-tag"
|
|
|
|
|
|
>
|
|
|
|
|
|
{{ getSkillLabel(skillId) }}
|
|
|
|
|
|
</span>
|
|
|
|
|
|
<span v-if="newAgent.selectedSkills.length > 3" class="more-tag">
|
|
|
|
|
|
+{{ newAgent.selectedSkills.length - 3 }}
|
|
|
|
|
|
</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<svg class="dropdown-icon" :class="{ 'rotate-180': showSkillsDropdown }" xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
|
|
|
|
<path d="m6 9 6 6 6-6"/>
|
|
|
|
|
|
</svg>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 第一级下拉面板:模式选择 -->
|
|
|
|
|
|
<Transition name="dropdown">
|
|
|
|
|
|
<div v-if="showSkillsDropdown" class="dropdown-panel">
|
|
|
|
|
|
<div class="skills-mode-options">
|
|
|
|
|
|
<div
|
|
|
|
|
|
v-for="option in skillsModeOptions"
|
|
|
|
|
|
:key="option.value"
|
|
|
|
|
|
class="skills-mode-item"
|
|
|
|
|
|
:class="{ 'active': newAgent.skillsMode === option.value }"
|
2026-03-12 17:17:08 +08:00
|
|
|
|
@click="handleSkillsModeClick(option.value)"
|
2026-03-12 15:23:13 +08:00
|
|
|
|
>
|
|
|
|
|
|
<div class="mode-radio">
|
|
|
|
|
|
<div v-if="newAgent.skillsMode === option.value" class="radio-dot"></div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="mode-content">
|
|
|
|
|
|
<span class="mode-label">{{ option.label }}</span>
|
|
|
|
|
|
<span class="mode-desc">{{ option.desc }}</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<!-- 显示展开图标 -->
|
|
|
|
|
|
<svg v-if="option.value !== 'all'" class="sub-arrow" xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
|
|
|
|
<path d="m9 18 6-6-6-6"/>
|
|
|
|
|
|
</svg>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</Transition>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 第二级下拉面板:技能列表(悬浮窗形式) -->
|
|
|
|
|
|
<Transition name="dropdown">
|
|
|
|
|
|
<div v-if="showSubSkillsDropdown" class="sub-dropdown-panel" @click.stop>
|
|
|
|
|
|
<!-- 标题 -->
|
|
|
|
|
|
<div class="sub-dropdown-header">
|
|
|
|
|
|
<span class="sub-dropdown-title">
|
|
|
|
|
|
{{ newAgent.skillsMode === 'include' ? 'Select skills to include' : 'Select skills to exclude' }}
|
|
|
|
|
|
</span>
|
|
|
|
|
|
<button @click="showSubSkillsDropdown = false" class="sub-dropdown-close">
|
|
|
|
|
|
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
|
|
|
|
<path d="M18 6 6 18"/><path d="m6 6 12 12"/>
|
|
|
|
|
|
</svg>
|
|
|
|
|
|
</button>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 搜索框 -->
|
|
|
|
|
|
<div class="search-box">
|
|
|
|
|
|
<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="search-icon">
|
|
|
|
|
|
<circle cx="11" cy="11" r="8"/><path d="m21 21-4.3-4.3"/>
|
|
|
|
|
|
</svg>
|
|
|
|
|
|
<input
|
|
|
|
|
|
v-model="skillsSearch"
|
|
|
|
|
|
type="text"
|
|
|
|
|
|
placeholder="Search skills..."
|
|
|
|
|
|
class="search-input"
|
|
|
|
|
|
>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 操作按钮 -->
|
|
|
|
|
|
<div class="action-bar">
|
|
|
|
|
|
<label class="checkbox-label cursor-pointer">
|
|
|
|
|
|
<input
|
|
|
|
|
|
type="checkbox"
|
|
|
|
|
|
:checked="isAllSelected"
|
|
|
|
|
|
:indeterminate="isIndeterminate"
|
|
|
|
|
|
@change="toggleSelectAll"
|
|
|
|
|
|
class="checkbox"
|
|
|
|
|
|
>
|
|
|
|
|
|
<span class="checkbox-text">Select All</span>
|
|
|
|
|
|
</label>
|
|
|
|
|
|
<button
|
|
|
|
|
|
v-if="newAgent.selectedSkills.length > 0"
|
|
|
|
|
|
type="button"
|
|
|
|
|
|
@click="clearSkills"
|
|
|
|
|
|
class="clear-btn"
|
|
|
|
|
|
>
|
|
|
|
|
|
Clear
|
|
|
|
|
|
</button>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 技能列表 -->
|
|
|
|
|
|
<div class="options-list">
|
|
|
|
|
|
<template v-if="filteredSkills.length > 0">
|
|
|
|
|
|
<label
|
|
|
|
|
|
v-for="skill in filteredSkills"
|
|
|
|
|
|
:key="skill.value"
|
|
|
|
|
|
class="option-item"
|
|
|
|
|
|
>
|
|
|
|
|
|
<input
|
|
|
|
|
|
type="checkbox"
|
|
|
|
|
|
:value="skill.value"
|
|
|
|
|
|
v-model="newAgent.selectedSkills"
|
|
|
|
|
|
class="checkbox"
|
|
|
|
|
|
>
|
|
|
|
|
|
<div class="option-content">
|
|
|
|
|
|
<span class="option-label">{{ skill.label }}</span>
|
|
|
|
|
|
<span v-if="skill.desc" class="option-desc">{{ skill.desc }}</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</label>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
<div v-else class="no-results">
|
|
|
|
|
|
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="no-results-icon">
|
|
|
|
|
|
<path d="M21 12a9 9 0 1 1-9-9c2.52 0 4.93 1 6.74 2.74L21 8"/>
|
|
|
|
|
|
<path d="M21 3v5h-5"/>
|
|
|
|
|
|
</svg>
|
|
|
|
|
|
<span>No skills available</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</Transition>
|
|
|
|
|
|
</div>
|
2026-03-11 14:26:25 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<div>
|
|
|
|
|
|
<label class="block text-sm font-medium text-gray-300 mb-2">Custom Prompt</label>
|
|
|
|
|
|
<textarea
|
|
|
|
|
|
v-model="newAgent.prompt"
|
|
|
|
|
|
rows="4"
|
|
|
|
|
|
placeholder="Define the agent's behavior and instructions..."
|
|
|
|
|
|
class="w-full bg-dark-600 border border-dark-500 rounded-lg px-4 py-2.5 text-white placeholder-gray-500 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="showCreateModal = false"
|
|
|
|
|
|
class="px-4 py-2 rounded-lg bg-dark-600 text-gray-300 hover:bg-dark-500 transition-colors"
|
|
|
|
|
|
>
|
|
|
|
|
|
Cancel
|
|
|
|
|
|
</button>
|
|
|
|
|
|
<button
|
|
|
|
|
|
@click="createAgent"
|
2026-03-12 23:19:04 +08:00
|
|
|
|
:disabled="isCreating || !newAgent.name || (newAgent.skillsMode !== 'all' && newAgent.selectedSkills.length === 0)"
|
2026-03-11 14:26:25 +08:00
|
|
|
|
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 disabled:opacity-50 disabled:cursor-not-allowed flex items-center gap-2"
|
|
|
|
|
|
>
|
|
|
|
|
|
<i v-if="isCreating" class="fa-solid fa-circle-notch fa-spin"></i>
|
|
|
|
|
|
{{ isCreating ? 'Creating...' : 'Create Agent' }}
|
|
|
|
|
|
</button>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</Teleport>
|
2026-03-12 23:19:04 +08:00
|
|
|
|
|
|
|
|
|
|
<!-- 编辑智能体弹窗 -->
|
|
|
|
|
|
<Teleport to="body">
|
2026-03-13 21:29:01 +08:00
|
|
|
|
<div v-if="showEditModal" class="fixed inset-0 bg-black/60 flex items-center justify-center z-50" @click.self="showEditModal = false">
|
2026-03-12 23:19:04 +08:00
|
|
|
|
<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="showEditModal = false" 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="editingAgent.name"
|
|
|
|
|
|
type="text"
|
|
|
|
|
|
placeholder="Enter agent name..."
|
|
|
|
|
|
class="w-full bg-dark-600 border border-dark-500 rounded-lg px-4 py-2.5 text-white placeholder-gray-500 focus:outline-none focus:border-primary-orange"
|
|
|
|
|
|
/>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<div>
|
|
|
|
|
|
<label class="block text-sm font-medium text-gray-300 mb-2">Description</label>
|
|
|
|
|
|
<textarea
|
|
|
|
|
|
v-model="editingAgent.description"
|
|
|
|
|
|
rows="3"
|
|
|
|
|
|
placeholder="Describe what this agent does..."
|
|
|
|
|
|
class="w-full bg-dark-600 border border-dark-500 rounded-lg px-4 py-2.5 text-white placeholder-gray-500 focus:outline-none focus:border-primary-orange resize-none"
|
|
|
|
|
|
></textarea>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<div>
|
|
|
|
|
|
<label class="block text-sm font-medium text-gray-300 mb-2">Avatar</label>
|
|
|
|
|
|
<div class="flex flex-wrap gap-2">
|
|
|
|
|
|
<button
|
|
|
|
|
|
v-for="avatar in avatarOptions"
|
|
|
|
|
|
:key="avatar"
|
|
|
|
|
|
type="button"
|
|
|
|
|
|
@click="editingAgent.avatar = avatar"
|
|
|
|
|
|
class="w-10 h-10 rounded-lg flex items-center justify-center text-lg transition-all"
|
|
|
|
|
|
:class="editingAgent.avatar === avatar ? 'bg-primary-orange text-white ring-2 ring-orange-400' : 'bg-dark-600 text-gray-300 hover:bg-dark-500'"
|
|
|
|
|
|
>
|
|
|
|
|
|
{{ avatar }}
|
|
|
|
|
|
</button>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<div>
|
|
|
|
|
|
<label class="block text-sm font-medium text-gray-300 mb-2">Preferred Model</label>
|
|
|
|
|
|
<el-select
|
|
|
|
|
|
v-model="editingAgent.modelId"
|
|
|
|
|
|
placeholder="Select a model..."
|
|
|
|
|
|
class="w-full"
|
|
|
|
|
|
size="large"
|
|
|
|
|
|
clearable
|
|
|
|
|
|
>
|
|
|
|
|
|
<el-option
|
|
|
|
|
|
v-for="model in modelsList"
|
|
|
|
|
|
:key="model.id"
|
|
|
|
|
|
:label="model.name"
|
|
|
|
|
|
:value="model.id"
|
|
|
|
|
|
>
|
|
|
|
|
|
<div class="flex items-center justify-between">
|
|
|
|
|
|
<span>{{ model.name }}</span>
|
|
|
|
|
|
<span class="text-xs text-gray-500">{{ model.provider }} - {{ model.model }}</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</el-option>
|
|
|
|
|
|
</el-select>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<div>
|
|
|
|
|
|
<label class="block text-sm font-medium text-gray-300 mb-2">Skills *</label>
|
|
|
|
|
|
<!-- 技能模式选择 -->
|
|
|
|
|
|
<div class="skills-selector">
|
|
|
|
|
|
<div class="selected-tags" @click="toggleSkillsDropdown">
|
|
|
|
|
|
<div v-if="editingAgent.skillsMode === 'all'" class="placeholder">
|
|
|
|
|
|
All Skills
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div v-else class="tags-container">
|
|
|
|
|
|
<span
|
|
|
|
|
|
v-for="skillId in editingAgent.selectedSkills.slice(0, 3)"
|
|
|
|
|
|
:key="skillId"
|
|
|
|
|
|
class="selected-tag"
|
|
|
|
|
|
>
|
|
|
|
|
|
{{ getSkillLabel(skillId) }}
|
|
|
|
|
|
</span>
|
|
|
|
|
|
<span v-if="editingAgent.selectedSkills.length > 3" class="more-tag">
|
|
|
|
|
|
+{{ editingAgent.selectedSkills.length - 3 }}
|
|
|
|
|
|
</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<svg class="dropdown-icon" :class="{ 'rotate-180': showSkillsDropdown }" xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
|
|
|
|
<path d="m6 9 6 6 6-6"/>
|
|
|
|
|
|
</svg>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<Transition name="dropdown">
|
|
|
|
|
|
<div v-if="showSkillsDropdown" class="dropdown-panel">
|
|
|
|
|
|
<div class="skills-mode-options">
|
|
|
|
|
|
<div
|
|
|
|
|
|
v-for="option in skillsModeOptions"
|
|
|
|
|
|
:key="option.value"
|
|
|
|
|
|
class="skills-mode-item"
|
|
|
|
|
|
:class="{ 'active': editingAgent.skillsMode === option.value }"
|
|
|
|
|
|
@click="handleSkillsModeClickEdit(option.value)"
|
|
|
|
|
|
>
|
|
|
|
|
|
<div class="mode-radio">
|
|
|
|
|
|
<div v-if="editingAgent.skillsMode === option.value" class="radio-dot"></div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="mode-content">
|
|
|
|
|
|
<span class="mode-label">{{ option.label }}</span>
|
|
|
|
|
|
<span class="mode-desc">{{ option.desc }}</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<svg v-if="option.value !== 'all'" class="sub-arrow" xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
|
|
|
|
<path d="m9 18 6-6-6-6"/>
|
|
|
|
|
|
</svg>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</Transition>
|
|
|
|
|
|
|
|
|
|
|
|
<Transition name="dropdown">
|
|
|
|
|
|
<div v-if="showSubSkillsDropdown" class="sub-dropdown-panel" @click.stop>
|
|
|
|
|
|
<div class="sub-dropdown-header">
|
|
|
|
|
|
<span class="sub-dropdown-title">
|
|
|
|
|
|
{{ editingAgent.skillsMode === 'include' ? 'Select skills to include' : 'Select skills to exclude' }}
|
|
|
|
|
|
</span>
|
|
|
|
|
|
<button @click="showSubSkillsDropdown = false" class="sub-dropdown-close">
|
|
|
|
|
|
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
|
|
|
|
<path d="M18 6 6 18"/><path d="m6 6 12 12"/>
|
|
|
|
|
|
</svg>
|
|
|
|
|
|
</button>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="search-box">
|
|
|
|
|
|
<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="search-icon">
|
|
|
|
|
|
<circle cx="11" cy="11" r="8"/><path d="m21 21-4.3-4.3"/>
|
|
|
|
|
|
</svg>
|
|
|
|
|
|
<input v-model="skillsSearch" type="text" placeholder="Search skills..." class="search-input">
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="action-bar">
|
|
|
|
|
|
<label class="checkbox-label cursor-pointer">
|
2026-03-13 21:29:01 +08:00
|
|
|
|
<input type="checkbox" :checked="isAllSelectedEdit" :indeterminate="isIndeterminateEdit" @change="toggleSelectAllEdit" class="checkbox">
|
2026-03-12 23:19:04 +08:00
|
|
|
|
<span class="checkbox-text">Select All</span>
|
|
|
|
|
|
</label>
|
2026-03-13 21:29:01 +08:00
|
|
|
|
<button v-if="editingAgent.selectedSkills.length > 0" type="button" @click="clearSkillsEdit" class="clear-btn">Clear</button>
|
2026-03-12 23:19:04 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
<div class="options-list">
|
|
|
|
|
|
<template v-if="filteredSkills.length > 0">
|
|
|
|
|
|
<label v-for="skill in filteredSkills" :key="skill.value" class="option-item">
|
|
|
|
|
|
<input type="checkbox" :value="skill.value" v-model="editingAgent.selectedSkills" class="checkbox">
|
|
|
|
|
|
<div class="option-content">
|
|
|
|
|
|
<span class="option-label">{{ skill.label }}</span>
|
|
|
|
|
|
<span v-if="skill.desc" class="option-desc">{{ skill.desc }}</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</label>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
<div v-else class="no-results">
|
|
|
|
|
|
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="no-results-icon">
|
|
|
|
|
|
<path d="M21 12a9 9 0 1 1-9-9c2.52 0 4.93 1 6.74 2.74L21 8"/>
|
|
|
|
|
|
<path d="M21 3v5h-5"/>
|
|
|
|
|
|
</svg>
|
|
|
|
|
|
<span>No skills available</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</Transition>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<div>
|
|
|
|
|
|
<label class="block text-sm font-medium text-gray-300 mb-2">Prompt</label>
|
|
|
|
|
|
<textarea
|
|
|
|
|
|
v-model="editingAgent.prompt"
|
|
|
|
|
|
rows="4"
|
|
|
|
|
|
placeholder="Define the agent's behavior and instructions..."
|
|
|
|
|
|
class="w-full bg-dark-600 border border-dark-500 rounded-lg px-4 py-2.5 text-white placeholder-gray-500 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="showEditModal = false"
|
|
|
|
|
|
class="px-4 py-2 rounded-lg bg-dark-600 text-gray-300 hover:bg-dark-500 transition-colors"
|
|
|
|
|
|
>
|
|
|
|
|
|
Cancel
|
|
|
|
|
|
</button>
|
|
|
|
|
|
<button
|
|
|
|
|
|
@click="saveEdit"
|
|
|
|
|
|
:disabled="isEditing || !editingAgent.name"
|
|
|
|
|
|
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 disabled:opacity-50 disabled:cursor-not-allowed flex items-center gap-2"
|
|
|
|
|
|
>
|
|
|
|
|
|
<i v-if="isEditing" class="fa-solid fa-circle-notch fa-spin"></i>
|
|
|
|
|
|
{{ isEditing ? 'Saving...' : 'Save' }}
|
|
|
|
|
|
</button>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</Teleport>
|
2026-03-05 10:49:46 +08:00
|
|
|
|
</template>
|