import { useState, useEffect } from 'react'; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Card, CardContent, CardHeader, CardTitle, CardDescription } from "@/components/ui/card"; import { Trash2, Edit2, Plus, Terminal, Loader2, FolderOpen, Share2, Download, Eye, ShieldCheck, AlertCircle, Wand2, Upload } from "lucide-react"; import { ScrollArea } from "@/components/ui/scroll-area"; import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogTrigger, DialogFooter } from "@/components/ui/dialog"; import { Label } from "@/components/ui/label"; import { Textarea } from "@/components/ui/textarea"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table"; import { api } from "@/lib/api"; import { useProjectStore } from "@/store/projectStore"; import { useRef } from 'react'; interface Skill { id: string; name: string; description: string; content: string; type: string; project_id?: number; source: string; installation_time: string; status: string; file_path?: string; } export function Skills() { const [skills, setSkills] = useState([]); const [isLoading, setIsLoading] = useState(false); const [isDialogOpen, setIsDialogOpen] = useState(false); const [editingSkill, setEditingSkill] = useState(null); const [newSkill, setNewSkill] = useState>({ type: 'python', content: '', source: '本地导入', status: '安全' }); const { currentProject } = useProjectStore(); const fileInputRef = useRef(null); useEffect(() => { if (currentProject) { fetchSkills(); } }, [currentProject]); const fetchSkills = async () => { if (!currentProject) return; setIsLoading(true); try { const data = await api.get(`/api/v1/skills?project_id=${currentProject.id}`); setSkills(data); } catch (error) { console.error("Failed to fetch skills", error); } finally { setIsLoading(false); } }; const handleFileUpload = async (event: React.ChangeEvent) => { const file = event.target.files?.[0]; if (!file || !currentProject) return; const formData = new FormData(); formData.append('file', file); formData.append('project_id', currentProject.id.toString()); setIsLoading(true); try { await api.post('/api/v1/skills/upload', formData); await fetchSkills(); } catch (error: any) { console.error("Failed to upload skill", error); const errorMessage = error.response?.data?.detail || error.message || "未知错误"; alert("上传失败: " + errorMessage); } finally { setIsLoading(false); if (fileInputRef.current) fileInputRef.current.value = ''; } }; const handleAddSkill = async () => { if (!currentProject) return; if (newSkill.name && newSkill.description && newSkill.content) { try { if (editingSkill) { await api.put(`/api/v1/skills/${editingSkill.id}?project_id=${currentProject.id}`, { ...newSkill, project_id: currentProject.id }); } else { const skillToCreate = { ...newSkill, id: Date.now().toString(), project_id: currentProject.id }; await api.post('/api/v1/skills', skillToCreate); } await fetchSkills(); setNewSkill({ type: 'python', content: '', source: '本地导入', status: '安全' }); setEditingSkill(null); setIsDialogOpen(false); } catch (error) { console.error("Failed to save skill", error); } } }; const handleEditSkill = (skill: Skill) => { setEditingSkill(skill); setNewSkill(skill); setIsDialogOpen(true); }; const handleDeleteSkill = async (id: string) => { if (!currentProject) return; if (!window.confirm("确定要删除这个技能吗?")) return; try { await api.delete(`/api/v1/skills/${id}?project_id=${currentProject.id}`); setSkills(skills.filter(s => s.id !== id)); } catch (error) { console.error("Failed to delete skill", error); } }; if (!currentProject) { return (

请先在顶部选择一个项目以管理其技能

); } return (

< Wand2 className="h-6 w-6 text-indigo-500" /> Skills 仓库 - {currentProject.name}

管理该项目的 AI 技能和工具,支持符合 agentskills.io 标准的文件上传

名称 来源 安装时间 状态 操作 {isLoading ? (
) : ( <> {skills.map((skill) => (

{skill.name}

{skill.type === 'agentskill' && ( Agent )}

{skill.description}

{skill.source}
{skill.installation_time}
{skill.status === '安全' ? ( ) : ( )} {skill.status}
))} {skills.length === 0 && (

该项目尚无技能,点击“导入 Skill”开始

)} )}
{ setIsDialogOpen(open); if (!open) { setEditingSkill(null); setNewSkill({ type: 'python', content: '', source: '本地导入', status: '安全' }); } }}> {editingSkill ? '编辑技能' : '添加新技能'}
setNewSkill({...newSkill, name: e.target.value})} className="rounded-lg border-zinc-200 h-10" />