/** * SkillMarket - Skill browsing, search, and management UI * * Displays available skills (12 built-in + custom), allows users to: * - Browse skills by category * - Search skills by keyword/capability * - View skill details and capabilities * - Install/uninstall skills (with L4 autonomy integration) * * Part of ZCLAW L4 Self-Evolution capability. */ import { useState, useEffect, useMemo, useCallback } from 'react'; import { motion, AnimatePresence } from 'framer-motion'; import { Search, Package, Check, Plus, Minus, Tag, Layers, ChevronDown, ChevronRight, RefreshCw, } from 'lucide-react'; import { useConfigStore } from '../store/configStore'; import { useConnectionStore } from '../store/connectionStore'; import { adaptSkillsCatalog, type SkillDisplay, } from '../lib/skill-adapter'; // === Types === interface SkillMarketProps { className?: string; onSkillInstall?: (skill: SkillDisplay) => void; onSkillUninstall?: (skill: SkillDisplay) => void; } type CategoryFilter = 'all' | 'development' | 'security' | 'analytics' | 'content' | 'ops' | 'management' | 'testing' | 'business' | 'marketing'; // === Category Config === const CATEGORY_CONFIG: Record = { development: { label: '开发', color: 'text-blue-600 dark:text-blue-400', bgColor: 'bg-blue-100 dark:bg-blue-900/30' }, security: { label: '安全', color: 'text-red-600 dark:text-red-400', bgColor: 'bg-red-100 dark:bg-red-900/30' }, analytics: { label: '分析', color: 'text-purple-600 dark:text-purple-400', bgColor: 'bg-purple-100 dark:bg-purple-900/30' }, content: { label: '内容', color: 'text-pink-600 dark:text-pink-400', bgColor: 'bg-pink-100 dark:bg-pink-900/30' }, ops: { label: '运维', color: 'text-orange-600 dark:text-orange-400', bgColor: 'bg-orange-100 dark:bg-orange-900/30' }, management: { label: '管理', color: 'text-cyan-600 dark:text-cyan-400', bgColor: 'bg-cyan-100 dark:bg-cyan-900/30' }, testing: { label: '测试', color: 'text-green-600 dark:text-green-400', bgColor: 'bg-green-100 dark:bg-green-900/30' }, business: { label: '商业', color: 'text-yellow-600 dark:text-yellow-400', bgColor: 'bg-yellow-100 dark:bg-yellow-900/30' }, marketing: { label: '营销', color: 'text-indigo-600 dark:text-indigo-400', bgColor: 'bg-indigo-100 dark:bg-indigo-900/30' }, }; // === Components === function CategoryBadge({ category }: { category?: string }) { if (!category) return null; const config = CATEGORY_CONFIG[category] || { label: category, color: 'text-gray-600 dark:text-gray-400', bgColor: 'bg-gray-100 dark:bg-gray-800', }; return ( {config.label} ); } function SkillCard({ skill, isExpanded, onToggle, onInstall, onUninstall, }: { skill: SkillDisplay; isExpanded: boolean; onToggle: () => void; onInstall: () => void; onUninstall: () => void; }) { const config = CATEGORY_CONFIG[skill.category || ''] || CATEGORY_CONFIG.development; return (
{isExpanded && (
{/* Triggers */}

触发词

{skill.triggers.map((trigger) => ( {trigger} ))}
{/* Capabilities */}

能力

{skill.capabilities.map((cap) => ( {cap} ))}
{/* Tool Dependencies */} {skill.toolDeps.length > 0 && (

工具依赖

{skill.toolDeps.map((dep) => ( {dep} ))}
)} {/* Actions */}
{skill.installed ? ( ) : ( )}
)}
); } // === Main Component === export function SkillMarket({ className = '', onSkillInstall, onSkillUninstall, }: SkillMarketProps) { // Use configStore instead of SkillDiscoveryEngine const skillsCatalog = useConfigStore((s) => s.skillsCatalog); const loadSkillsCatalog = useConfigStore((s) => s.loadSkillsCatalog); const updateSkill = useConfigStore((s) => s.updateSkill); // Watch connection state to reload skills when connected const connectionState = useConnectionStore((s) => s.connectionState); const [searchQuery, setSearchQuery] = useState(''); const [categoryFilter, setCategoryFilter] = useState('all'); const [expandedSkillId, setExpandedSkillId] = useState(null); const [isRefreshing, setIsRefreshing] = useState(false); // Adapt skills to display format const skills = useMemo(() => adaptSkillsCatalog(skillsCatalog), [skillsCatalog]); // Load skills on mount and when connection state changes to 'connected' useEffect(() => { if (connectionState === 'connected') { loadSkillsCatalog(); } }, [loadSkillsCatalog, connectionState]); // Filter skills const filteredSkills = useMemo(() => { let result = skills; // Category filter if (categoryFilter !== 'all') { result = result.filter((s) => s.category === categoryFilter); } // Search filter if (searchQuery.trim()) { const queryLower = searchQuery.toLowerCase(); result = result.filter((s) => s.name.toLowerCase().includes(queryLower) || s.description.toLowerCase().includes(queryLower) || s.triggers.some((t) => t.toLowerCase().includes(queryLower)) || s.capabilities.some((c) => c.toLowerCase().includes(queryLower)) ); } return result; }, [skills, categoryFilter, searchQuery]); // Get categories from skills const categories = useMemo(() => { const cats = new Set(skills.map((s) => s.category).filter(Boolean)); return ['all', ...Array.from(cats)] as CategoryFilter[]; }, [skills]); // Stats const stats = useMemo(() => { const installed = skills.filter((s) => s.installed).length; return { total: skills.length, installed }; }, [skills]); const handleRefresh = useCallback(async () => { setIsRefreshing(true); await loadSkillsCatalog(); setIsRefreshing(false); }, [loadSkillsCatalog]); const handleInstall = useCallback( async (skill: SkillDisplay) => { // Update skill via configStore (persists to backend) await updateSkill(skill.id, { enabled: true }); onSkillInstall?.(skill); }, [updateSkill, onSkillInstall] ); const handleUninstall = useCallback( async (skill: SkillDisplay) => { // Update skill via configStore (persists to backend) await updateSkill(skill.id, { enabled: false }); onSkillUninstall?.(skill); }, [updateSkill, onSkillUninstall] ); const handleSearch = useCallback((query: string) => { setSearchQuery(query); }, []); return (
{/* Header */}

技能市场

{/* Stats Bar */}
总计: {stats.total} 已安装: {stats.installed}
{/* Search */}
handleSearch(e.target.value)} placeholder="搜索技能、能力、触发词..." className="w-full pl-9 pr-3 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100 focus:ring-2 focus:ring-gray-400 focus:border-transparent text-sm" />
{/* AI 智能推荐功能开发中 */}
AI 智能推荐即将推出
{/* Category Filter */}
{categories.map((cat) => ( ))}
{/* Skill List */}
{filteredSkills.length === 0 ? (

{searchQuery ? '未找到匹配的技能' : '暂无技能'}

) : ( filteredSkills.map((skill) => ( setExpandedSkillId((prev) => (prev === skill.id ? null : skill.id))} onInstall={() => handleInstall(skill)} onUninstall={() => handleUninstall(skill)} /> )) )}
); } export default SkillMarket;