import { useEffect, useMemo } from 'react' import { useSkillStore } from '../../stores/skillStore' import { useSessionStore } from '../../stores/sessionStore' import { useTranslation } from '../../i18n' import type { SkillMeta, SkillSource } from '../../types/skill' const SOURCE_ORDER: SkillSource[] = ['user', 'project', 'plugin', 'mcp', 'bundled'] const SOURCE_ICONS: Record = { user: 'person', project: 'folder', plugin: 'extension', mcp: 'hub', bundled: 'inventory_2', } const SOURCE_ACCENT_CLASSES: Record = { user: 'bg-[var(--color-primary-fixed)] text-[var(--color-brand)]', project: 'bg-[var(--color-success-container)] text-[var(--color-success)]', plugin: 'bg-[var(--color-warning-container)] text-[var(--color-warning)]', mcp: 'bg-[var(--color-info-container)] text-[var(--color-info)]', bundled: 'bg-[var(--color-surface-container-high)] text-[var(--color-text-tertiary)]', } function estimateTokens(contentLength: number) { return Math.ceil(contentLength / 4) } export function SkillList() { const { skills, isLoading, error, fetchSkills, fetchSkillDetail } = useSkillStore() const sessions = useSessionStore((s) => s.sessions) const activeSessionId = useSessionStore((s) => s.activeSessionId) const t = useTranslation() const activeSession = sessions.find((session) => session.id === activeSessionId) const currentWorkDir = activeSession?.workDir || undefined useEffect(() => { fetchSkills(currentWorkDir) }, [fetchSkills, currentWorkDir]) const grouped = useMemo(() => { const result: Partial> = {} for (const skill of skills) { const src = skill.source as SkillSource ;(result[src] ??= []).push(skill) } return result }, [skills]) const totalTokens = useMemo( () => skills.reduce((sum, skill) => sum + estimateTokens(skill.contentLength), 0), [skills], ) const visibleGroupCount = useMemo( () => SOURCE_ORDER.filter((source) => (grouped[source] ?? []).length > 0).length, [grouped], ) if (isLoading) { return (
) } if (error) { return
{error}
} if (skills.length === 0) { return (
auto_awesome

{t('settings.skills.empty')}

{t('settings.skills.emptyHint')}

) } return (
{t('settings.skills.browserEyebrow')}
auto_awesome

{t('settings.skills.browserTitle')}

{t('settings.skills.browserDescription')}

(grouped[source] ?? []).length > 0) .length, )} icon="layers" />
= 2 ? 'xl:grid-cols-2' : ''}`}> {SOURCE_ORDER.map((source) => { const group = grouped[source] if (!group?.length) return null const sourceLabel = t(`settings.skills.source.${source}`) const sourceTokenCount = group.reduce( (sum, skill) => sum + estimateTokens(skill.contentLength), 0, ) return (
{SOURCE_ICONS[source]}

{sourceLabel}

{group.length}

{t('settings.skills.groupHint', { source: sourceLabel, count: String(group.length), })}

{t('settings.skills.tokenEstimateShort', { count: String(sourceTokenCount) })}
{group.map((skill) => ( ))}
) })}
) } function SummaryCard({ label, value, icon, className = '', }: { label: string value: string icon: string className?: string }) { return (
{icon} {label}
{value}
) }