diff --git a/web/src/views/Agents.vue b/web/src/views/Agents.vue
index 3bb4dbb..fd7bfd9 100644
--- a/web/src/views/Agents.vue
+++ b/web/src/views/Agents.vue
@@ -1,549 +1,60 @@
@@ -1096,426 +607,3 @@ const saveEdit = async () => {
-
-
diff --git a/web/src/views/agents/agents.css b/web/src/views/agents/agents.css
new file mode 100644
index 0000000..04e83ca
--- /dev/null
+++ b/web/src/views/agents/agents.css
@@ -0,0 +1,370 @@
+/* Skills Selector Styles */
+.skills-selector {
+ position: relative;
+}
+
+/* Skills Mode Options */
+.skills-mode-options {
+ padding: 8px;
+ border-bottom: 1px solid #2a2c36;
+}
+
+.skills-mode-item {
+ display: flex;
+ align-items: flex-start;
+ gap: 12px;
+ padding: 12px;
+ border-radius: 8px;
+ cursor: pointer;
+ transition: all 0.2s ease;
+}
+
+.skills-mode-item:hover {
+ background: #1a1c25;
+}
+
+.skills-mode-item.active {
+ background: rgba(249, 115, 22, 0.1);
+}
+
+.mode-radio {
+ width: 18px;
+ height: 18px;
+ border: 2px solid #6b7280;
+ border-radius: 50%;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ flex-shrink: 0;
+ margin-top: 2px;
+}
+
+.skills-mode-item.active .mode-radio {
+ border-color: #f97316;
+}
+
+.radio-dot {
+ width: 10px;
+ height: 10px;
+ background: #f97316;
+ border-radius: 50%;
+}
+
+.mode-content {
+ display: flex;
+ flex-direction: column;
+ gap: 2px;
+}
+
+.mode-label {
+ font-size: 14px;
+ font-weight: 500;
+ color: #f3f4f6;
+}
+
+.mode-desc {
+ font-size: 12px;
+ color: #9ca3af;
+}
+
+/* 子下拉框箭头 */
+.sub-arrow {
+ margin-left: auto;
+ color: #9ca3af;
+ transition: transform 0.2s ease;
+}
+
+.skills-mode-item:hover .sub-arrow {
+ color: #f97316;
+}
+
+/* 子下拉面板 */
+.sub-dropdown-panel {
+ position: absolute;
+ top: 0;
+ left: 100%;
+ margin-left: 8px;
+ width: 280px;
+ background: #171922;
+ border: 1px solid #2a2c36;
+ border-radius: 12px;
+ box-shadow: 0 10px 40px rgba(0, 0, 0, 0.4);
+ z-index: 100;
+ overflow: hidden;
+}
+
+.sub-dropdown-header {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ padding: 12px 16px;
+ border-bottom: 1px solid #2a2c36;
+ background: #1a1c25;
+}
+
+.sub-dropdown-title {
+ font-size: 13px;
+ font-weight: 500;
+ color: #f3f4f6;
+}
+
+.sub-dropdown-close {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ width: 24px;
+ height: 24px;
+ border-radius: 6px;
+ color: #9ca3af;
+ transition: all 0.2s ease;
+}
+
+.sub-dropdown-close:hover {
+ background: #374151;
+ color: #f3f4f6;
+}
+
+/* 无结果样式 */
+.no-results {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ padding: 24px;
+ color: #6b7280;
+ gap: 8px;
+}
+
+.no-results-icon {
+ opacity: 0.5;
+}
+
+.selected-tags {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ min-height: 44px;
+ padding: 0 12px;
+ background: #171922;
+ border: 1px solid #2a2c36;
+ border-radius: 8px;
+ cursor: pointer;
+ transition: all 0.2s ease;
+}
+
+.selected-tags:hover {
+ border-color: #f97316;
+}
+
+.selected-tags .placeholder {
+ color: #9ca3af;
+ font-size: 14px;
+}
+
+.tags-container {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 6px;
+ flex: 1;
+ overflow: hidden;
+}
+
+.selected-tag {
+ display: inline-flex;
+ align-items: center;
+ padding: 2px 8px;
+ background: linear-gradient(135deg, #f97316 0%, #ea580c 100%);
+ color: white;
+ font-size: 12px;
+ font-weight: 500;
+ border-radius: 4px;
+ white-space: nowrap;
+}
+
+.more-tag {
+ display: inline-flex;
+ align-items: center;
+ padding: 2px 8px;
+ background: #374151;
+ color: #d1d5db;
+ font-size: 12px;
+ border-radius: 4px;
+}
+
+.dropdown-icon {
+ color: #9ca3af;
+ transition: transform 0.2s ease;
+ flex-shrink: 0;
+}
+
+.dropdown-icon.rotate-180 {
+ transform: rotate(180deg);
+}
+
+/* Dropdown Panel */
+.dropdown-panel {
+ position: absolute;
+ top: calc(100% + 8px);
+ left: 0;
+ right: 0;
+ background: #171922;
+ border: 1px solid #2a2c36;
+ border-radius: 12px;
+ box-shadow: 0 20px 40px rgba(0, 0, 0, 0.4);
+ z-index: 100;
+ overflow: hidden;
+}
+
+/* Search Box */
+.search-box {
+ position: relative;
+ padding: 12px;
+ border-bottom: 1px solid #2a2c36;
+}
+
+.search-icon {
+ position: absolute;
+ left: 24px;
+ top: 50%;
+ transform: translateY(-50%);
+ color: #9ca3af;
+}
+
+.search-input {
+ width: 100%;
+ padding: 8px 12px 8px 36px;
+ background: #111827;
+ border: 1px solid #374151;
+ border-radius: 6px;
+ color: white;
+ font-size: 14px;
+ outline: none;
+ transition: border-color 0.2s ease;
+}
+
+.search-input:focus {
+ border-color: #f97316;
+}
+
+.search-input::placeholder {
+ color: #6b7280;
+}
+
+/* Action Bar */
+.action-bar {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ padding: 10px 12px;
+ background: #111827;
+ border-bottom: 1px solid #374151;
+}
+
+.checkbox-label {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ cursor: pointer;
+}
+
+.checkbox {
+ width: 16px;
+ height: 16px;
+ accent-color: #f97316;
+ cursor: pointer;
+}
+
+.checkbox-text {
+ font-size: 13px;
+ font-weight: 500;
+ color: #f97316;
+}
+
+.clear-btn {
+ padding: 4px 10px;
+ background: transparent;
+ border: 1px solid #4b5563;
+ border-radius: 4px;
+ color: #9ca3af;
+ font-size: 12px;
+ cursor: pointer;
+ transition: all 0.2s ease;
+}
+
+.clear-btn:hover {
+ background: #374151;
+ color: white;
+}
+
+/* Options List */
+.options-list {
+ max-height: 240px;
+ overflow-y: auto;
+ padding: 8px;
+}
+
+.options-list::-webkit-scrollbar {
+ width: 6px;
+}
+
+.options-list::-webkit-scrollbar-track {
+ background: #171922;
+}
+
+.options-list::-webkit-scrollbar-thumb {
+ background: #4b5563;
+ border-radius: 3px;
+}
+
+.option-item {
+ display: flex;
+ align-items: flex-start;
+ gap: 10px;
+ padding: 10px 12px;
+ border-radius: 8px;
+ cursor: pointer;
+ transition: background 0.15s ease;
+}
+
+.option-item:hover {
+ background: #374151;
+}
+
+.option-item .checkbox {
+ margin-top: 2px;
+ flex-shrink: 0;
+}
+
+.option-content {
+ display: flex;
+ flex-direction: column;
+ gap: 2px;
+ min-width: 0;
+ flex: 1;
+}
+
+.option-label {
+ font-size: 14px;
+ font-weight: 500;
+ color: white;
+}
+
+.option-desc {
+ font-size: 12px;
+ color: #9ca3af;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+}
+
+/* Dropdown Transition */
+.dropdown-enter-active,
+.dropdown-leave-active {
+ transition: all 0.2s ease;
+}
+
+.dropdown-enter-from,
+.dropdown-leave-to {
+ opacity: 0;
+ transform: translateY(-8px);
+}
diff --git a/web/src/views/agents/useAgents.ts b/web/src/views/agents/useAgents.ts
new file mode 100644
index 0000000..e9b95f4
--- /dev/null
+++ b/web/src/views/agents/useAgents.ts
@@ -0,0 +1,591 @@
+// Agent API 调用和状态管理
+
+import { ref, computed } from 'vue'
+import { ElMessage } from 'element-plus'
+import { formatDate } from '@/utils/format'
+
+const API_BASE = 'http://localhost:8082'
+
+// 类型定义
+export interface Skill {
+ id: string
+ skill_name: string
+ skill_type: string
+ skill_desc: string
+ path: string
+ status: string
+}
+
+export interface ModelOption {
+ id: string
+ name: string
+ provider: string
+ model: string
+}
+
+export interface Agent {
+ id: string
+ name: string
+ avatar: string
+ description: string
+ accentColor: string
+ gradient: string
+ status: string
+ skills: string
+ model: string
+ createdAt: string
+}
+
+export interface AgentFormData {
+ name: string
+ description: string
+ avatar: string
+ skillsMode: 'all' | 'include' | 'exclude'
+ selectedSkills: string[]
+ knowledge: string
+ prompt: string
+ modelId: string
+}
+
+// 状态
+const agents = ref([])
+const skillsList = ref([])
+const modelsList = ref([])
+const searchQuery = ref('')
+const filterStatus = ref('all')
+const isLoading = ref(false)
+const skillsLoading = ref(false)
+const modelsLoading = ref(false)
+
+// 创建弹窗
+const showCreateModal = ref(false)
+const isCreating = ref(false)
+const newAgent = ref({
+ name: '',
+ description: '',
+ avatar: '🤖',
+ skillsMode: 'all',
+ selectedSkills: [],
+ knowledge: '',
+ prompt: '',
+ modelId: ''
+})
+
+// 编辑弹窗
+const showEditModal = ref(false)
+const isEditing = ref(false)
+const editingAgent = ref({
+ id: '',
+ name: '',
+ description: '',
+ avatar: '🤖',
+ skillsMode: 'all',
+ selectedSkills: [],
+ knowledge: '',
+ prompt: '',
+ modelId: ''
+})
+
+// Skills 选择器
+const showSkillsDropdown = ref(false)
+const showSubSkillsDropdown = ref(false)
+const skillsSearch = ref('')
+
+// 选项
+const skillsModeOptions = [
+ { value: 'all', label: 'All Skills', desc: 'Use all available skills' },
+ { value: 'include', label: 'Include Skills', desc: 'Select specific skills to use' },
+ { value: 'exclude', label: 'Exclude Skills', desc: 'Select skills to exclude' },
+]
+
+const avatarOptions = [
+ '🤖', '🧠', '💻', '📊', '🔬', '🎧', '✨', '💬', '🔮', '🌙',
+ '🐉', '☁️', '🎨', '🎯', '🚀', '⚡', '🔥', '💡', '🎭', '🎪'
+]
+
+const knowledgeOptions = [
+ { value: 'general', label: 'General Knowledge' },
+ { value: 'codebase', label: 'Codebase' },
+ { value: 'docs', label: 'Documentation' },
+ { value: 'api', label: 'API Reference' },
+]
+
+// 计算属性
+const skillsOptions = computed(() => {
+ return skillsList.value.map(skill => ({
+ value: skill.id,
+ label: skill.skill_name,
+ desc: skill.skill_desc
+ }))
+})
+
+const filteredSkills = computed(() => {
+ if (!skillsSearch.value) return skillsOptions.value
+ const search = skillsSearch.value.toLowerCase()
+ return skillsOptions.value.filter(s =>
+ s.label.toLowerCase().includes(search) ||
+ (s.desc && s.desc.toLowerCase().includes(search))
+ )
+})
+
+const filteredAgents = computed(() => {
+ return agents.value.filter(agent => {
+ const matchSearch = agent.name.toLowerCase().includes(searchQuery.value.toLowerCase()) ||
+ agent.skills.toLowerCase().includes(searchQuery.value.toLowerCase())
+ const matchStatus = filterStatus.value === 'all' || agent.status === filterStatus.value
+ return matchSearch && matchStatus
+ })
+})
+
+const stats = computed(() => ({
+ total: agents.value.length,
+ active: agents.value.filter(a => a.status === 'active').length,
+ inactive: agents.value.filter(a => a.status === 'inactive').length,
+}))
+
+const isAllSelected = computed(() => {
+ return skillsOptions.value.length > 0 && newAgent.value.selectedSkills.length === skillsOptions.value.length
+})
+
+const isIndeterminate = computed(() => {
+ return newAgent.value.selectedSkills.length > 0 && newAgent.value.selectedSkills.length < skillsOptions.value.length
+})
+
+// 方法
+async function fetchAgents() {
+ try {
+ const response = await fetch(`${API_BASE}/api/agent/list`)
+ if (!response.ok) throw new Error('Failed to fetch agents')
+ const data = await response.json()
+ agents.value = (data.agents || []).map((agent: any) => ({
+ id: agent.id,
+ name: agent.name,
+ avatar: agent.avatar || '🤖',
+ description: agent.description || '',
+ accentColor: '#f97316',
+ gradient: 'from-orange-500/20 to-amber-500/20',
+ status: agent.is_active ? 'active' : 'inactive',
+ skills: agent.skills?.length > 0 ? agent.skills.join(', ') : 'None',
+ model: agent.model_name || 'None',
+ createdAt: agent.created_at ? formatDate(agent.created_at, 'YYYY/MM/DD HH:mm') : formatDate(new Date(), 'YYYY/MM/DD HH:mm'),
+ }))
+ } catch (error) {
+ console.error('Failed to fetch agents:', error)
+ }
+}
+
+async function fetchSkills() {
+ skillsLoading.value = true
+ try {
+ const response = await fetch(`${API_BASE}/skill/list`)
+ if (!response.ok) {
+ console.error('Failed to fetch skills:', response.status, response.statusText)
+ return
+ }
+ const result = await response.json()
+ if (result.list) {
+ skillsList.value = result.list
+ }
+ } catch (error) {
+ console.error('Failed to fetch skills:', error)
+ } finally {
+ skillsLoading.value = false
+ }
+}
+
+async function fetchModels() {
+ modelsLoading.value = true
+ try {
+ const response = await fetch(`${API_BASE}/model/list`)
+ if (!response.ok) {
+ console.error('Failed to fetch models:', response.status, response.statusText)
+ return
+ }
+ const result = await response.json()
+ if (result.list) {
+ modelsList.value = result.list.map((m: any) => ({
+ id: m.id,
+ name: m.name,
+ provider: m.provider,
+ model: m.model
+ }))
+ }
+ } catch (error) {
+ console.error('Failed to fetch models:', error)
+ } finally {
+ modelsLoading.value = false
+ }
+}
+
+function getSkillLabel(id: string) {
+ return skillsOptions.value.find(s => s.value === id)?.label || id
+}
+
+function openCreateModal() {
+ newAgent.value = {
+ name: '',
+ description: '',
+ avatar: '🤖',
+ skillsMode: 'all',
+ selectedSkills: [],
+ knowledge: '',
+ prompt: '',
+ modelId: ''
+ }
+ showCreateModal.value = true
+}
+
+async function createAgent() {
+ if (!newAgent.value.name || (newAgent.value.skillsMode !== 'all' && newAgent.value.selectedSkills.length === 0)) {
+ return
+ }
+
+ const selectedModel = modelsList.value.find(m => m.id === newAgent.value.modelId)
+ isCreating.value = true
+
+ try {
+ const skills = newAgent.value.skillsMode === 'all' ? ['*'] : newAgent.value.selectedSkills
+
+ const response = await fetch(`${API_BASE}/api/agent/create`, {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({
+ name: newAgent.value.name,
+ description: newAgent.value.description,
+ avatar: newAgent.value.avatar,
+ skillsMode: newAgent.value.skillsMode,
+ skills: skills,
+ knowledge: newAgent.value.knowledge,
+ prompt: newAgent.value.prompt,
+ model_provider: selectedModel?.provider,
+ model_name: selectedModel?.name
+ })
+ })
+
+ if (!response.ok) throw new Error('Failed to create agent')
+
+ const result = await response.json()
+ const skillsLabels = newAgent.value.selectedSkills.map(id => getSkillLabel(id)).join(', ')
+
+ agents.value.unshift({
+ id: result.agent_id,
+ name: newAgent.value.name,
+ avatar: newAgent.value.avatar,
+ description: newAgent.value.description,
+ accentColor: '#f97316',
+ gradient: 'from-orange-500/20 to-amber-500/20',
+ status: 'inactive',
+ skills: skillsLabels || 'None',
+ model: selectedModel?.name || 'None',
+ createdAt: formatDate(new Date(), 'YYYY/MM/DD HH:mm')
+ })
+
+ showCreateModal.value = false
+ ElMessage.success('Agent created successfully')
+ } catch (error) {
+ console.error('Failed to create agent:', error)
+ ElMessage.error('Failed to create agent')
+ } finally {
+ isCreating.value = false
+ }
+}
+
+function openEdit(agent: Agent) {
+ let selectedSkills: string[] = []
+ let skillsMode: 'all' | 'include' | 'exclude' = 'all'
+
+ if (agent.skills === '*') {
+ skillsMode = 'all'
+ } else if (agent.skills && agent.skills !== 'None') {
+ skillsMode = 'include'
+ selectedSkills = agent.skills.split(',').map((s: string) => s.trim())
+ }
+
+ const model = modelsList.value.find(m => m.name === agent.model)
+
+ editingAgent.value = {
+ id: agent.id,
+ name: agent.name,
+ description: agent.description || '',
+ avatar: agent.avatar || '🤖',
+ skillsMode,
+ selectedSkills,
+ modelId: model?.id || '',
+ prompt: '',
+ knowledge: ''
+ }
+ showEditModal.value = true
+}
+
+async function saveEdit() {
+ if (!editingAgent.value.name) return
+
+ const skills = editingAgent.value.skillsMode === 'all' ? ['*'] : editingAgent.value.selectedSkills
+ const selectedModel = modelsList.value.find(m => m.id === editingAgent.value.modelId)
+ isEditing.value = true
+
+ try {
+ const response = await fetch(`${API_BASE}/api/agent/${editingAgent.value.id}`, {
+ method: 'PUT',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({
+ name: editingAgent.value.name,
+ description: editingAgent.value.description,
+ skills: skills,
+ role_description: editingAgent.value.prompt,
+ model_provider: selectedModel?.provider || '',
+ model_name: selectedModel?.name || ''
+ })
+ })
+
+ if (response.ok) {
+ const agent = agents.value.find(a => a.id === editingAgent.value.id)
+ if (agent) {
+ agent.name = editingAgent.value.name
+ agent.description = editingAgent.value.description
+ agent.skills = editingAgent.value.skillsMode === 'all' ? '*' : editingAgent.value.selectedSkills.join(', ')
+ agent.model = selectedModel?.name || ''
+ }
+ showEditModal.value = false
+ ElMessage.success('Agent updated successfully')
+ } else {
+ ElMessage.error('Failed to update agent')
+ }
+ } catch (error) {
+ console.error('Failed to update agent:', error)
+ ElMessage.error('Failed to update agent')
+ } finally {
+ isEditing.value = false
+ }
+}
+
+async function toggleStatus(agent: Agent) {
+ const newStatus = agent.status === 'active' ? false : true
+ try {
+ const response = await fetch(`${API_BASE}/api/agent/${agent.id}/status`, {
+ method: 'PUT',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({ is_active: newStatus })
+ })
+ if (response.ok) {
+ agent.status = newStatus ? 'active' : 'inactive'
+ } else {
+ ElMessage.error('Failed to update status')
+ }
+ } catch (error) {
+ console.error('Failed to update status:', error)
+ ElMessage.error('Failed to update status')
+ }
+}
+
+async function deleteAgent(id: string) {
+ try {
+ const response = await fetch(`${API_BASE}/api/agent/${id}`, { method: 'DELETE' })
+ if (response.ok) {
+ agents.value = agents.value.filter(a => a.id !== id)
+ ElMessage.success('Agent deleted successfully')
+ } else {
+ ElMessage.error('Failed to delete agent')
+ }
+ } catch (error) {
+ console.error('Failed to delete agent:', error)
+ ElMessage.error('Failed to delete agent')
+ }
+}
+
+function toggleSkillsDropdown() {
+ showSkillsDropdown.value = !showSkillsDropdown.value
+ if (!showSkillsDropdown.value) {
+ skillsSearch.value = ''
+ }
+}
+
+function closeSkillsDropdown() {
+ showSkillsDropdown.value = false
+ skillsSearch.value = ''
+}
+
+function handleSkillsModeClick(mode: string) {
+ if (mode === 'all' || mode === 'include' || mode === 'exclude') {
+ newAgent.value.skillsMode = mode
+ if (mode === 'all') {
+ newAgent.value.selectedSkills = []
+ }
+ showSkillsDropdown.value = false
+ if (mode === 'include' || mode === 'exclude') {
+ showSubSkillsDropdown.value = true
+ }
+ }
+}
+
+function handleSkillsModeClickEdit(mode: string) {
+ if (mode === 'all' || mode === 'include' || mode === 'exclude') {
+ editingAgent.value.skillsMode = mode
+ if (mode === 'all') {
+ editingAgent.value.selectedSkills = []
+ }
+ showSkillsDropdown.value = false
+ if (mode === 'include' || mode === 'exclude') {
+ showSubSkillsDropdown.value = true
+ }
+ }
+}
+
+function toggleSelectAll() {
+ if (isAllSelected.value) {
+ newAgent.value.selectedSkills = []
+ } else {
+ newAgent.value.selectedSkills = skillsOptions.value.map(s => s.value)
+ }
+}
+
+function clearSkills() {
+ newAgent.value.selectedSkills = []
+}
+
+// 切换技能模式下拉框
+function toggleSkillsMode() {
+ showSkillsDropdown.value = !showSkillsDropdown.value
+ showSubSkillsDropdown.value = false
+}
+
+// 选择技能模式
+function selectSkillsMode(mode: 'all' | 'include' | 'exclude') {
+ newAgent.value.skillsMode = mode
+ if (mode === 'all') {
+ newAgent.value.selectedSkills = []
+ }
+ showSkillsDropdown.value = false
+ if (mode === 'include' || mode === 'exclude') {
+ showSubSkillsDropdown.value = true
+ }
+}
+
+// 切换子下拉框
+function toggleSubSkillsDropdown() {
+ showSubSkillsDropdown.value = !showSubSkillsDropdown.value
+}
+
+// 关闭所有下拉框
+function closeAllDropdowns() {
+ showSkillsDropdown.value = false
+ showSubSkillsDropdown.value = false
+}
+
+// 获取显示文本
+function getSkillsDisplayText() {
+ if (newAgent.value.skillsMode === 'all') {
+ return 'All Skills'
+ }
+ const count = newAgent.value.selectedSkills.length
+ if (count === 0) {
+ return newAgent.value.skillsMode === 'include' ? 'Select skills to include...' : 'Select skills to exclude...'
+ }
+ return `${count} skill${count > 1 ? 's' : ''} ${newAgent.value.skillsMode === 'include' ? 'included' : 'excluded'}`
+}
+
+// 切换子下拉框中的技能选择
+function toggleSkillSelection(skillId: string) {
+ const index = newAgent.value.selectedSkills.indexOf(skillId)
+ if (index > -1) {
+ newAgent.value.selectedSkills.splice(index, 1)
+ } else {
+ newAgent.value.selectedSkills.push(skillId)
+ }
+}
+
+// 全选 skills
+function selectAllSkills() {
+ newAgent.value.selectedSkills = skillsOptions.value.map(s => s.value)
+}
+
+// 状态颜色
+function statusClass(status: string) {
+ switch (status) {
+ case 'active': return 'bg-green-500'
+ case 'inactive': return 'bg-gray-500'
+ default: return 'bg-gray-500'
+ }
+}
+
+function handleClickOutside(e: MouseEvent) {
+ const target = e.target as HTMLElement
+ if (!target.closest('.skills-selector')) {
+ closeSkillsDropdown()
+ }
+}
+
+// 初始化 - 在 useAgents 函数中导出初始化函数
+function init() {
+ fetchSkills()
+ fetchModels()
+ fetchAgents()
+ document.addEventListener('click', handleClickOutside)
+}
+
+function cleanup() {
+ document.removeEventListener('click', handleClickOutside)
+}
+
+// 导出
+export function useAgents() {
+ // 在 useAgents 被调用时初始化(组件挂载时)
+ init()
+
+ return {
+ // 状态
+ 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,
+ // 方法
+ fetchAgents,
+ fetchSkills,
+ fetchModels,
+ getSkillLabel,
+ openCreateModal,
+ createAgent,
+ openEdit,
+ saveEdit,
+ toggleStatus,
+ deleteAgent,
+ toggleSkillsDropdown,
+ closeSkillsDropdown,
+ handleSkillsModeClick,
+ handleSkillsModeClickEdit,
+ toggleSelectAll,
+ clearSkills,
+ handleClickOutside,
+ toggleSkillsMode,
+ selectSkillsMode,
+ toggleSubSkillsDropdown,
+ closeAllDropdowns,
+ getSkillsDisplayText,
+ toggleSkillSelection,
+ selectAllSkills,
+ statusClass,
+ cleanup
+ }
+}