|
- {{ skill.name }}
- {{ skill.description }}
+
+
+
+
+
+ {{ skill.skill_name }}
+ {{ skill.skill_desc || '-' }}
+
+
|
- {{ skill.type }}
+ {{ skill.skill_type === 'system' ? 'System' : 'User' }}
|
- {{ skill.category }}
- |
- {{ skill.port }} |
-
- {{ skill.tools }}
- |
-
-
-
+
+
{{ skill.status }}
|
- {{ skill.createdAt }} |
+ {{ skill.created_at ? new Date(skill.created_at).toLocaleDateString() : '-' }} |
-
@@ -116,7 +114,7 @@ const {
-
+
No skills found
Click "New Skill" to add a skill
@@ -130,39 +128,27 @@ const {
Edit Skill
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
-
-
+
+
-
+
@@ -181,7 +167,7 @@ const {
-
+
Create New Skill
@@ -189,30 +175,33 @@ const {
-
+
+
+
+
+
+
-
-
-
-
-
-
+
-
+
diff --git a/web/src/views/skill/useSkills.ts b/web/src/views/skill/useSkills.ts
index 8dcf293..a8e4ae6 100644
--- a/web/src/views/skill/useSkills.ts
+++ b/web/src/views/skill/useSkills.ts
@@ -1,60 +1,144 @@
-import { ref, computed, onMounted } from 'vue'
+import { ref, computed } from 'vue'
import { ElMessageBox, ElMessage } from 'element-plus'
const API_BASE = 'http://localhost:8082'
export interface Skill {
id: string
- name: string
- description: string
- description_cn?: string
- command: string
- args?: string
- env?: string
- category: string
+ skill_name: string
+ skill_type: string
+ skill_desc: string
+ path: string
status: string
created_at?: string
+ updated_at?: string
}
export function useSkills() {
- // 从 API 获取 MCP 列表
- const fetchSkills = async () => {
+ const skills = ref([])
+ const skillsLoading = ref(false)
+
+ // 获取技能列表
+ const fetchSkills = async (type?: string) => {
+ skillsLoading.value = true
try {
- const response = await fetch(`${API_BASE}/mcp/list`)
+ let url = `${API_BASE}/skill/list`
+ if (type) {
+ url += `?type=${type}`
+ }
+
+ const response = await fetch(url)
const result = await response.json()
- if (result && Array.isArray(result)) {
- skills.value = result.map((mcp: any) => ({
- id: mcp.id,
- name: mcp.name,
- description: mcp.description || '',
- description_cn: mcp.description_cn || '',
- command: mcp.command || '',
- args: mcp.args || '',
- env: mcp.env || '',
- category: mcp.category || 'custom',
- status: mcp.status || 'stopped',
- created_at: mcp.created_at,
+
+ if (result.list) {
+ skills.value = result.list.map((skill: any) => ({
+ id: skill.id,
+ skill_name: skill.skill_name,
+ skill_type: skill.skill_type,
+ skill_desc: skill.skill_desc || '',
+ path: skill.path || '',
+ status: skill.status || 'active',
+ created_at: skill.created_at,
+ updated_at: skill.updated_at,
}))
}
+ return result.list || []
} catch (error) {
- console.error('Failed to fetch MCP list:', error)
+ console.error('Failed to fetch skills:', error)
+ return []
+ } finally {
+ skillsLoading.value = false
}
}
- onMounted(() => {
- fetchSkills()
+ // 同步技能
+ const syncSkills = async () => {
+ try {
+ const response = await fetch(`${API_BASE}/skill/sync`)
+ const data = await response.json()
+ await fetchSkills()
+ return data
+ } catch (error) {
+ console.error('Failed to sync skills:', error)
+ throw error
+ }
+ }
+
+ // 获取技能详情
+ const fetchSkillById = async (id: string) => {
+ try {
+ const response = await fetch(`${API_BASE}/skill/${id}`)
+ const data = await response.json()
+ return data.skill
+ } catch (error) {
+ console.error('Failed to fetch skill:', error)
+ throw error
+ }
+ }
+
+ // 创建技能
+ const createSkill = async (skill: Partial) => {
+ try {
+ const response = await fetch(`${API_BASE}/skill/add`, {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ body: JSON.stringify(skill),
+ })
+ const data = await response.json()
+ await fetchSkills()
+ return data
+ } catch (error) {
+ console.error('Failed to create skill:', error)
+ throw error
+ }
+ }
+
+ // 更新技能
+ const updateSkill = async (id: string, skill: Partial) => {
+ try {
+ const response = await fetch(`${API_BASE}/skill/${id}`, {
+ method: 'PUT',
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ body: JSON.stringify(skill),
+ })
+ const data = await response.json()
+ await fetchSkills()
+ return data
+ } catch (error) {
+ console.error('Failed to update skill:', error)
+ throw error
+ }
+ }
+
+ // 删除技能
+ const deleteSkill = async (id: string) => {
+ try {
+ const response = await fetch(`${API_BASE}/skill/${id}`, {
+ method: 'DELETE',
+ })
+ const data = await response.json()
+ await fetchSkills()
+ return data
+ } catch (error) {
+ console.error('Failed to delete skill:', error)
+ throw error
+ }
+ }
+
+ // 按类型获取技能
+ const getSkillsByType = (type: string) => {
+ return skills.value.filter(skill => skill.skill_type === type)
+ }
+
+ // 获取所有分类
+ const categories = computed(() => {
+ const cats = new Set(skills.value.map(skill => skill.skill_type))
+ return Array.from(cats)
})
- // Skill 数据
- const skills = ref([
- { id: 1, name: 'Linear', description: 'Linear API integration for project management', type: 'API', category: 'api', status: 'running', port: 3001, createdAt: '2025-04-10', tools: 12 },
- { id: 2, name: 'Google Maps', description: 'Google Maps API for location services', type: 'Google Maps', category: 'api', status: 'running', port: 3002, createdAt: '2025-04-08', tools: 8 },
- { id: 3, name: 'File Explorer', description: 'File system explorer and editor', type: 'File System', category: 'filesystem', status: 'error', port: 3003, createdAt: '2025-04-05', tools: 15 },
- { id: 4, name: 'PostgreSQL', description: 'PostgreSQL database operations', type: 'Database', category: 'database', status: 'running', port: 3004, createdAt: '2025-04-12', tools: 10 },
- { id: 5, name: 'GitHub', description: 'GitHub API integration', type: 'GitHub', category: 'communication', status: 'stopped', port: 3005, createdAt: '2025-04-11', tools: 20 },
- { id: 6, name: 'Slack', description: 'Slack messaging integration', type: 'Communication', category: 'communication', status: 'running', port: 3006, createdAt: '2025-04-09', tools: 6 },
- { id: 7, name: 'OpenAI', description: 'OpenAI GPT models for AI conversations', type: 'AI/ML', category: 'ai', status: 'running', port: 3007, createdAt: '2025-04-07', tools: 5 },
- { id: 8, name: 'MySQL', description: 'MySQL database operations', type: 'Database', category: 'database', status: 'stopped', port: 3008, createdAt: '2025-04-06', tools: 10 },
- ])
// 搜索和筛选
const searchQuery = ref('')
@@ -67,35 +151,22 @@ export function useSkills() {
// 表单
const editForm = ref({
- name: '',
- type: '',
- category: '',
- port: 3000,
- description: '',
+ skill_name: '',
+ skill_desc: '',
+ skill_type: 'user',
})
const newSkillForm = ref({
- name: '',
- description: '',
- markdown: '',
+ skill_name: '',
+ skill_desc: '',
+ skill_type: 'user',
})
- // 分类选项
- const categories = [
- { value: 'api', label: 'API' },
- { value: 'database', label: 'Database' },
- { value: 'filesystem', label: 'File System' },
- { value: 'communication', label: 'Communication' },
- { value: 'ai', label: 'AI/ML' },
- ]
-
- const types = ['API', 'Database', 'File System', 'Communication', 'AI/ML']
-
// 筛选
const filteredSkills = computed(() => {
return skills.value.filter(skill => {
- const matchSearch = skill.name.toLowerCase().includes(searchQuery.value.toLowerCase()) ||
- skill.description.toLowerCase().includes(searchQuery.value.toLowerCase())
+ const matchSearch = skill.skill_name.toLowerCase().includes(searchQuery.value.toLowerCase()) ||
+ (skill.skill_desc && skill.skill_desc.toLowerCase().includes(searchQuery.value.toLowerCase()))
const matchStatus = filterStatus.value === 'all' || skill.status === filterStatus.value
return matchSearch && matchStatus
})
@@ -104,8 +175,8 @@ export function useSkills() {
// 状态样式
const statusClass = (status: string) => {
switch (status) {
- case 'running': return 'bg-green-500'
- case 'stopped': return 'bg-gray-500'
+ case 'active': return 'bg-green-500'
+ case 'inactive': return 'bg-gray-500'
case 'error': return 'bg-red-500'
default: return 'bg-gray-500'
}
@@ -113,7 +184,7 @@ export function useSkills() {
// 打开创建弹窗
const openCreate = () => {
- newSkillForm.value = { name: '', description: '', markdown: '' }
+ newSkillForm.value = { skill_name: '', skill_desc: '', skill_type: 'user' }
isCreating.value = true
}
@@ -123,31 +194,28 @@ export function useSkills() {
}
// 保存新技能
- const saveNewSkill = () => {
- const newId = Math.max(...skills.value.map(s => s.id)) + 1
- skills.value.push({
- id: newId,
- name: newSkillForm.value.name,
- description: newSkillForm.value.description,
- type: 'Custom',
- category: 'custom',
- status: 'stopped',
- port: 3000,
- createdAt: new Date().toISOString().split('T')[0],
- tools: 0,
- })
- isCreating.value = false
+ const saveNewSkill = async () => {
+ try {
+ await createSkill({
+ skill_name: newSkillForm.value.skill_name,
+ skill_desc: newSkillForm.value.skill_desc,
+ skill_type: newSkillForm.value.skill_type,
+ status: 'active',
+ })
+ ElMessage.success('Skill created successfully')
+ isCreating.value = false
+ } catch (error) {
+ ElMessage.error('Failed to create skill')
+ }
}
// 打开编辑弹窗
const openEdit = (skill: Skill) => {
editingSkill.value = skill
editForm.value = {
- name: skill.name,
- type: skill.type,
- category: skill.category,
- port: skill.port,
- description: skill.description,
+ skill_name: skill.skill_name,
+ skill_desc: skill.skill_desc,
+ skill_type: skill.skill_type,
}
isEditing.value = true
}
@@ -159,41 +227,54 @@ export function useSkills() {
}
// 保存编辑
- const saveEdit = () => {
- const index = skills.value.findIndex(s => s.id === editingSkill.value!.id)
- if (index !== -1) {
- skills.value[index] = {
- ...skills.value[index],
- name: editForm.value.name,
- type: editForm.value.type,
- category: editForm.value.category,
- port: editForm.value.port,
- description: editForm.value.description,
- }
+ const saveEdit = async () => {
+ try {
+ await updateSkill(editingSkill.value!.id, {
+ skill_name: editForm.value.skill_name,
+ skill_desc: editForm.value.skill_desc,
+ skill_type: editForm.value.skill_type,
+ })
+ ElMessage.success('Skill updated successfully')
+ isEditing.value = false
+ } catch (error) {
+ ElMessage.error('Failed to update skill')
}
- isEditing.value = false
}
// 切换状态
- const toggleStatus = (skill: Skill) => {
- if (skill.status === 'running') skill.status = 'stopped'
- else if (skill.status === 'stopped') skill.status = 'running'
+ const toggleStatus = async (skill: Skill) => {
+ const newStatus = skill.status === 'active' ? 'inactive' : 'active'
+ try {
+ await updateSkill(skill.id, { status: newStatus })
+ skill.status = newStatus
+ } catch (error) {
+ ElMessage.error('Failed to update status')
+ }
}
// 删除技能
- const deleteSkill = (id: number) => {
- ElMessageBox.confirm('Are you sure you want to delete this skill?', 'Confirm Delete', {
- confirmButtonText: 'Delete',
- cancelButtonText: 'Cancel',
- type: 'warning',
- }).then(() => {
- skills.value = skills.value.filter(s => s.id !== id)
- }).catch(() => {})
+ const handleDeleteSkill = async (id: string) => {
+ try {
+ await ElMessageBox.confirm('Are you sure you want to delete this skill?', 'Confirm Delete', {
+ confirmButtonText: 'Delete',
+ cancelButtonText: 'Cancel',
+ type: 'warning',
+ })
+
+ await deleteSkill(id)
+ ElMessage.success('Skill deleted successfully')
+ } catch (error: any) {
+ if (error !== 'cancel') {
+ console.error('Failed to delete skill:', error)
+ ElMessage.error('Failed to delete skill')
+ }
+ }
}
return {
// State
skills,
+ skillsLoading,
searchQuery,
filterStatus,
isEditing,
@@ -202,10 +283,11 @@ export function useSkills() {
editForm,
newSkillForm,
categories,
- types,
// Computed
filteredSkills,
// Methods
+ fetchSkills,
+ syncSkills,
statusClass,
openCreate,
closeCreate,
@@ -214,6 +296,6 @@ export function useSkills() {
closeEdit,
saveEdit,
toggleStatus,
- deleteSkill,
+ deleteSkill: handleDeleteSkill,
}
}
|