import React, { useMemo, useState } from "react"; import { useTranslation } from "react-i18next"; import { Sparkles, Trash2, ExternalLink } from "lucide-react"; import { Button } from "@/components/ui/button"; import { Switch } from "@/components/ui/switch"; import { useInstalledSkills, useToggleSkillApp, useUninstallSkill, useScanUnmanagedSkills, useImportSkillsFromApps, type InstalledSkill, type AppType, } from "@/hooks/useSkills"; import { ConfirmDialog } from "@/components/ConfirmDialog"; import { settingsApi } from "@/lib/api"; import { toast } from "sonner"; interface UnifiedSkillsPanelProps { onOpenDiscovery: () => void; } /** * 统一 Skills 管理面板 * v3.10.0 新架构:所有 Skills 统一管理,每个 Skill 通过开关控制应用到哪些客户端 */ export interface UnifiedSkillsPanelHandle { openDiscovery: () => void; openImport: () => void; } const UnifiedSkillsPanel = React.forwardRef< UnifiedSkillsPanelHandle, UnifiedSkillsPanelProps >(({ onOpenDiscovery }, ref) => { const { t } = useTranslation(); const [confirmDialog, setConfirmDialog] = useState<{ isOpen: boolean; title: string; message: string; onConfirm: () => void; } | null>(null); const [importDialogOpen, setImportDialogOpen] = useState(false); // Queries and Mutations const { data: skills, isLoading } = useInstalledSkills(); const toggleAppMutation = useToggleSkillApp(); const uninstallMutation = useUninstallSkill(); const { data: unmanagedSkills, refetch: scanUnmanaged } = useScanUnmanagedSkills(); const importMutation = useImportSkillsFromApps(); // Count enabled skills per app const enabledCounts = useMemo(() => { const counts = { claude: 0, codex: 0, gemini: 0, opencode: 0 }; if (!skills) return counts; skills.forEach((skill) => { if (skill.apps.claude) counts.claude++; if (skill.apps.codex) counts.codex++; if (skill.apps.gemini) counts.gemini++; if (skill.apps.opencode) counts.opencode++; }); return counts; }, [skills]); const handleToggleApp = async ( id: string, app: AppType, enabled: boolean, ) => { try { await toggleAppMutation.mutateAsync({ id, app, enabled }); } catch (error) { toast.error(t("common.error"), { description: String(error), }); } }; const handleUninstall = (skill: InstalledSkill) => { setConfirmDialog({ isOpen: true, title: t("skills.uninstall"), message: t("skills.uninstallConfirm", { name: skill.name }), onConfirm: async () => { try { await uninstallMutation.mutateAsync(skill.id); setConfirmDialog(null); toast.success(t("skills.uninstallSuccess", { name: skill.name }), { closeButton: true, }); } catch (error) { toast.error(t("common.error"), { description: String(error), }); } }, }); }; const handleOpenImport = async () => { try { const result = await scanUnmanaged(); if (!result.data || result.data.length === 0) { toast.success(t("skills.noUnmanagedFound"), { closeButton: true }); return; } setImportDialogOpen(true); } catch (error) { toast.error(t("common.error"), { description: String(error), }); } }; const handleImport = async (directories: string[]) => { try { const imported = await importMutation.mutateAsync(directories); setImportDialogOpen(false); toast.success(t("skills.importSuccess", { count: imported.length }), { closeButton: true, }); } catch (error) { toast.error(t("common.error"), { description: String(error), }); } }; React.useImperativeHandle(ref, () => ({ openDiscovery: onOpenDiscovery, openImport: handleOpenImport, })); return (
{t("skills.noInstalledDescription")}
{skill.description}
)}{sourceLabel}
{t("skills.importDescription")}