import React, { useState, useEffect } from 'react'; import { adminAPI } from '@/api'; import type { SkillState } from '@/types'; type AdminSkill = { id: string; user_id: string; owner_username?: string; skill_name: string; description?: string; tags: string[]; category?: string; category_label?: string; state: SkillState; use_count: number; version: number; skill_md_path?: string; last_used_at?: string; created_at?: string; updated_at?: string; skill_md_content?: string; publish_records?: PublishRecord[]; }; interface PublishRecord { id: string; price: number; status: string; published_at?: string; } interface SkillVersion { id: string; version: number; description?: string; skill_md_content?: string; created_at?: string; } interface SkillsStats { total_skills: number; by_state: Record; by_category: Record; total_published: number; total_versions: number; } const STATE_CONFIG: Record = { baby: { label: '萌芽期', color: '#6B7280', bg: '#F3F4F6' }, main: { label: '主力期', color: '#10B981', bg: '#D1FAE5' }, staging: { label: '灰度期', color: '#F59E0B', bg: '#FEF3C7' }, frozen: { label: '已冻结', color: '#EF4444', bg: '#FEE2E2' }, }; // 业务场景分类:与前端「应用市场」保持一致的 14 类标准(前台分类为准) const CATEGORIES = [ { id: 'design', label: '🎨 做设计' }, { id: 'frontend', label: '💻 写前端' }, { id: 'mobile', label: '📱 做移动端' }, { id: 'backend', label: '🧪 后端/全栈' }, { id: 'qa', label: '🧪 测试质量' }, { id: 'search', label: '🔍 搜索信息' }, { id: 'docs', label: '✍️ 写文档' }, { id: 'marketing', label: '📢 推广获客' }, { id: 'workflow', label: '🔄 工作流' }, { id: 'secops', label: '🛡️ 安全运维' }, { id: 'learning', label: '🎓 学习教学' }, { id: 'video', label: '🎬 视频多媒体' }, { id: 'collab', label: '🏢 办公协作' }, { id: 'ai', label: '🛠️ AI能力' }, ]; const PAGE_SIZE = 15; const Skills: React.FC = () => { const [skills, setSkills] = useState([]); const [total, setTotal] = useState(0); const [page, setPage] = useState(1); const [loading, setLoading] = useState(false); const [stats, setStats] = useState(null); // Filters const [keyword, setKeyword] = useState(''); const [filterState, setFilterState] = useState(''); const [filterCategory, setFilterCategory] = useState(''); // Create/Edit Modal const [showModal, setShowModal] = useState(false); const [editingSkill, setEditingSkill] = useState(null); const [form, setForm] = useState({ skill_name: '', user_id: '', description: '', tags: '', category: '', skill_md_content: '## 技能定义\n\n### 触发词\n- /skill\n\n### 能力说明\n请描述这个技能的核心能力...\n\n### 使用场景\n1. 场景一\n2. 场景二\n\n### 示例对话\n**用户**: \n**AI**: ', }); // Version Drawer const [showVersions, setShowVersions] = useState(false); const [versions, setVersions] = useState([]); const [versionSkill, setVersionSkill] = useState(null); const [loadingVersions, setLoadingVersions] = useState(false); // Users list (for owner selection) const [users, setUsers] = useState<{id: string; username: string}[]>([]); // Confirm delete const [deleteTarget, setDeleteTarget] = useState(null); // Toast const [toast, setToast] = useState<{msg: string; type: 'success' | 'error'} | null>(null); const showToast = (msg: string, type: 'success' | 'error' = 'success') => { setToast({ msg, type }); setTimeout(() => setToast(null), 3000); }; // Load skills list const loadSkills = async () => { setLoading(true); try { const params: any = { page, page_size: PAGE_SIZE }; if (keyword) params.keyword = keyword; if (filterState) params.state = filterState; if (filterCategory) params.category = filterCategory; const result = await adminAPI.getAdminSkills(params) as any; const data = result.data || result; setSkills(data.items || []); setTotal(data.total || 0); } catch (e: any) { showToast(`加载失败: ${e.message || e}`, 'error'); } finally { setLoading(false); } }; // Load stats const loadStats = async () => { try { const result = await adminAPI.getAdminSkillsStats() as any; const data = result.data || result; setStats(data); } catch {} }; // Load users for owner selection const loadUsers = async () => { try { const result = await adminAPI.getUsers({ page_size: 100 }) as any; const data = result.data || result; setUsers((data.items || data || []).slice(0, 20)); } catch {} }; useEffect(() => { loadSkills(); }, [page, keyword, filterState, filterCategory]); useEffect(() => { loadStats(); loadUsers(); }, []); // Search const handleSearch = () => { setPage(1); loadSkills(); }; // Open create modal const openCreate = () => { setEditingSkill(null); setForm({ skill_name: '', user_id: users[0]?.id || '', description: '', tags: '', category: '', skill_md_content: '## 技能定义\n\n### 触发词\n- /skill\n\n### 能力说明\n请描述这个技能的核心能力...\n\n### 使用场景\n1. 场景一\n2. 场景二\n\n### 示例对话\n**用户**: \n**AI**: ', }); setShowModal(true); }; // Open edit modal const openEdit = (skill: AdminSkill) => { setEditingSkill(skill); setForm({ skill_name: skill.skill_name, user_id: skill.user_id, description: skill.description || '', tags: (skill.tags || []).join(', '), category: skill.category || '', skill_md_content: skill.skill_md_content || '', }); setShowModal(true); }; // Save skill const handleSave = async () => { if (!form.skill_name.trim()) { showToast('技能名称不能为空', 'error'); return; } if (!form.user_id) { showToast('请选择所属用户', 'error'); return; } const tags = form.tags.split(',').map(t => t.trim()).filter(Boolean); const payload: any = { skill_name: form.skill_name, user_id: form.user_id, description: form.description, tags, category: form.category || undefined, skill_md_content: form.skill_md_content, }; try { if (editingSkill) { await adminAPI.updateAdminSkill(editingSkill.id, payload); showToast('技能已更新'); } else { await adminAPI.createAdminSkill(payload); showToast('技能已创建'); } setShowModal(false); loadSkills(); loadStats(); } catch (e: any) { showToast(`保存失败: ${e.message || e}`, 'error'); } }; // Delete skill const handleDelete = async () => { if (!deleteTarget) return; try { await adminAPI.deleteAdminSkill(deleteTarget.id); showToast('技能已删除'); setDeleteTarget(null); loadSkills(); loadStats(); } catch (e: any) { showToast(`删除失败: ${e.message || e}`, 'error'); } }; // Change state const handleChangeState = async (skill: AdminSkill, newState: string) => { try { await adminAPI.changeAdminSkillState(skill.id, { state: newState }); showToast(`状态已变更为 ${STATE_CONFIG[newState as SkillState].label}`); loadSkills(); loadStats(); } catch (e: any) { showToast(`状态修改失败: ${e.message || e}`, 'error'); } }; // Load version history const handleShowVersions = async (skill: AdminSkill) => { setVersionSkill(skill); setShowVersions(true); setLoadingVersions(true); try { const result = await adminAPI.getAdminSkillVersions(skill.id) as any; const data = result.data || result; setVersions(data.versions || []); } catch (e: any) { showToast(`加载版本历史失败: ${e.message || e}`, 'error'); setVersions([]); } finally { setLoadingVersions(false); } }; // Rollback version const handleRollback = async (skill: AdminSkill, targetVersion: number) => { if (!confirm(`确定回滚到版本 ${targetVersion} 吗?`)) return; try { await adminAPI.rollbackAdminSkill(skill.id, { version: targetVersion }); showToast(`已回滚到版本 ${targetVersion}`); setShowVersions(false); loadSkills(); } catch (e: any) { showToast(`回滚失败: ${e.message || e}`, 'error'); } }; // Publish/Unpublish const handlePublish = async (skill: AdminSkill) => { const price = parseFloat(prompt('设置定价(元,0=免费):', '0') || '0'); if (isNaN(price) || price < 0) return; try { await adminAPI.publishAdminSkill(skill.id, { price, market_description: skill.description || '' }); showToast('已发布到应用市场'); loadSkills(); } catch (e: any) { showToast(`发布失败: ${e.message || e}`, 'error'); } }; const handleUnpublish = async (skill: AdminSkill) => { if (!confirm('确定从市场下架吗?')) return; try { await adminAPI.unpublishAdminSkill(skill.id); showToast('已从市场下架'); loadSkills(); } catch (e: any) { showToast(`下架失败: ${e.message || e}`, 'error'); } }; const totalPages = Math.ceil(total / PAGE_SIZE); const isPublished = (skill: AdminSkill) => (skill.publish_records || []).some(r => r.status === 'published'); return (
{/* Toast */} {toast && (
{toast.msg}
)} {/* Header */}

Skills技能管理

管理全平台 AI 技能,支持版本历史与市场发布

{/* Stats Cards */} {stats && (
{stats.total_skills}
技能总数
{Object.entries(STATE_CONFIG).map(([state, cfg]) => (
{stats.by_state[state] || 0}
{cfg.label}
))}
{stats.total_published}
已发布市场
{stats.total_versions}
版本记录
)} {/* Filters */}
setKeyword(e.target.value)} onKeyDown={e => e.key === 'Enter' && handleSearch()} style={{ maxWidth: 280 }} /> {(keyword || filterState || filterCategory) && ( )}
{/* Table */}
{loading ? ( ) : skills.length === 0 ? ( ) : skills.map(skill => { const cfg = STATE_CONFIG[skill.state as SkillState] || STATE_CONFIG.baby; const published = isPublished(skill); return ( ); })}
技能名称 状态 分类 标签 所有者 版本 使用次数 市场 更新时间 操作
加载中...
暂无技能
{skill.skill_name}
{skill.description && (
{skill.description}
)} {skill.tags && skill.tags.length > 0 && (
{skill.tags.slice(0, 3).map(tag => ( {tag} ))} {skill.tags.length > 3 && +{skill.tags.length - 3}}
)}
{cfg.label} {skill.category_label || '-'} {skill.tags && skill.tags.length > 0 ? (
{skill.tags.slice(0, 3).map(tag => ( {tag} ))} {skill.tags.length > 3 && +{skill.tags.length - 3}}
) : -}
{skill.owner_username || skill.user_id} v{skill.version} {skill.use_count} {published ? ( ✓ 已发布 ) : ( 未发布 )} {skill.updated_at ? skill.updated_at.slice(0, 16).replace('T', ' ') : '-'}
{published ? ( ) : ( )} {/* State change */}
{/* Pagination */} {totalPages > 1 && (
共 {total} 条,第 {page}/{totalPages} 页
{[...Array(Math.min(5, totalPages))].map((_, i) => { const p = Math.max(1, Math.min(page - 2, totalPages - 4)) + i; return p <= totalPages && ( ); })}
)} {/* Create/Edit Modal */} {showModal && (
setShowModal(false)}>
e.stopPropagation()}>

{editingSkill ? '编辑技能' : '创建技能'}

setForm(f => ({ ...f, skill_name: e.target.value }))} placeholder="例如: 代码审查助手" />
{!editingSkill && (
)}
setForm(f => ({ ...f, tags: e.target.value }))} placeholder="例如: 代码, 审查, 自动化" />