// 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 } }