refactor: Agents页面重构为独立模块
- 新增 web/src/views/agents/useAgents.ts: Agents页面的组合式函数 - 新增 web/src/views/agents/agents.css: Agents页面样式文件 - 精简 web/src/views/Agents.vue: 保留主入口,引用新的模块 将Agents页面的逻辑拆分为独立的TS文件和CSS文件,提升代码可维护性 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
591
web/src/views/agents/useAgents.ts
Normal file
591
web/src/views/agents/useAgents.ts
Normal file
@@ -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<Agent[]>([])
|
||||
const skillsList = ref<Skill[]>([])
|
||||
const modelsList = ref<ModelOption[]>([])
|
||||
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<AgentFormData>({
|
||||
name: '',
|
||||
description: '',
|
||||
avatar: '🤖',
|
||||
skillsMode: 'all',
|
||||
selectedSkills: [],
|
||||
knowledge: '',
|
||||
prompt: '',
|
||||
modelId: ''
|
||||
})
|
||||
|
||||
// 编辑弹窗
|
||||
const showEditModal = ref(false)
|
||||
const isEditing = ref(false)
|
||||
const editingAgent = ref<AgentFormData & { id: string }>({
|
||||
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
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user