import { useEffect, useState, type ReactNode } from "react"; import type { TFunction } from "i18next"; import { Brain, Check, CircleAlert, KeyRound, Loader2, Terminal } from "lucide-react"; import { useTranslation } from "react-i18next"; import { Sheet, SheetContent, SheetDescription, SheetTitle } from "@/components/ui/sheet"; import { fetchSkillDetail } from "@/lib/api"; import type { SkillDetail, SkillSummary } from "@/lib/types"; import { cn } from "@/lib/utils"; import { useClient } from "@/providers/ClientProvider"; export function SkillsCatalogSettings({ skills }: { skills: SkillSummary[] }) { const { t } = useTranslation(); const availableCount = skills.filter((skill) => skill.available).length; const [selectedSkill, setSelectedSkill] = useState(null); return (

{t("settings.skills.description", { defaultValue: "Review the instruction skills this agent can load during a conversation.", })}

{t("settings.skills.caption", { available: availableCount, total: skills.length, defaultValue: "{{available}} available ยท {{total}} total", })}

{t("settings.skills.featured", { defaultValue: "Agent skills" })}

{skills.length}
{skills.length ? (
{skills.map((skill) => ( ))}
) : (
{t("settings.skills.empty", { defaultValue: "No skills are available." })}
)}
{ if (!open) setSelectedSkill(null); }} />
); } function SkillCatalogRow({ skill, onSelect, }: { skill: SkillSummary; onSelect: (skill: SkillSummary) => void; }) { const { t } = useTranslation(); const sourceLabel = skillSourceLabel(skill.source, t); const StatusIcon = skill.available ? Check : CircleAlert; const statusLabel = skill.available ? t("settings.skills.statusAvailable", { defaultValue: "Available" }) : t("settings.skills.statusUnavailable", { defaultValue: "Unavailable" }); return ( ); } function SkillDetailSheet({ skill, open, onOpenChange, }: { skill: SkillSummary | null; open: boolean; onOpenChange: (open: boolean) => void; }) { const { token } = useClient(); const { t } = useTranslation(); const [detail, setDetail] = useState(null); const [loading, setLoading] = useState(false); const [loadFailed, setLoadFailed] = useState(false); useEffect(() => { if (!open || !skill) return; let cancelled = false; setDetail(null); setLoading(true); setLoadFailed(false); fetchSkillDetail(token, skill.name) .then((payload) => { if (!cancelled) setDetail(payload); }) .catch(() => { if (!cancelled) setLoadFailed(true); }) .finally(() => { if (!cancelled) setLoading(false); }); return () => { cancelled = true; }; }, [open, skill, token]); if (!skill) return null; const activeSkill = detail ?? skill; const sourceLabel = skillSourceLabel(activeSkill.source, t); const statusLabel = activeSkill.available ? t("settings.skills.statusAvailable", { defaultValue: "Available" }) : t("settings.skills.statusUnavailable", { defaultValue: "Unavailable" }); return (
{activeSkill.name} {t("settings.skills.detailDescription", { name: activeSkill.name, defaultValue: "Details for {{name}}.", })}
{sourceLabel} {statusLabel}
{loading ? (
{t("settings.skills.loadingDetail", { defaultValue: "Loading skill details..." })}
) : loadFailed ? (
{t("settings.skills.loadFailed", { defaultValue: "Could not load skill details." })}
) : (

{activeSkill.description}

{!activeSkill.available && activeSkill.unavailable_reason ? (

{activeSkill.unavailable_reason}

) : null} {detail ? : null} {detail ? : null}
)}
); } function RawInstructionsBlock({ markdown }: { markdown: string }) { const { t } = useTranslation(); const content = markdown || t("settings.skills.rawInstructionsEmpty", { defaultValue: "No raw instructions.", }); return (
{t("settings.skills.rawInstructions", { defaultValue: "Raw SKILL.md" })}
          {content}
        
); } function MetaItem({ label, value }: { label: string; value: string }) { return (
{label}
{value}
); } function RequirementsSection({ detail }: { detail: SkillDetail }) { const { t } = useTranslation(); const { bins, env, missing_bins, missing_env } = detail.requirements; const hasRequirements = bins.length > 0 || env.length > 0; return ( {hasRequirements ? (
{missing_bins.length ? ( } /> ) : null} {missing_env.length ? ( } /> ) : null} {bins.length ? ( } /> ) : null} {env.length ? ( } /> ) : null}
) : (

{t("settings.skills.noRequirements", { defaultValue: "No explicit requirements." })}

)}
); } function DetailSection({ title, children }: { title: string; children: ReactNode }) { return (

{title}

{children}
); } function RequirementLine({ title, items, icon, tone = "muted", }: { title: string; items: string[]; icon: ReactNode; tone?: "muted" | "danger"; }) { return (
{icon} {title}
{items.map((item) => ( {item} ))}
); } function Pill({ children, tone = "muted", }: { children: ReactNode; tone?: "muted" | "success"; }) { return ( {children} ); } function skillSourceLabel(source: string, t: TFunction): string { if (source === "workspace") { return t("settings.skills.sourceWorkspace", { defaultValue: "Custom" }); } if (source === "builtin") { return t("settings.skills.sourceBuiltin", { defaultValue: "Built-in" }); } return source; }