-
-
setQuery(event.target.value)}
- placeholder={t('skillCenter.marketplace.searchPlaceholder')}
- className="h-10 w-full rounded-md border border-[var(--color-border)] bg-[var(--color-surface)] pl-9 pr-20 text-sm text-[var(--color-text-primary)] outline-none transition-colors placeholder:text-[var(--color-text-tertiary)] focus:border-[var(--color-border-focus)] focus:shadow-[var(--shadow-focus-ring)]"
- />
- {query ? (
-
- ) : null}
+
+
+ {FILTER_OPTIONS.map((filter) => (
-
-
-
-
setSource(value as SkillMarketListSource)}
- options={SOURCE_OPTIONS.map((value) => ({
- value,
- label: t(`skillCenter.marketplace.source.${value}`),
- }))}
- />
- setSort(value as SkillMarketSort)}
- options={SORT_OPTIONS.map((value) => ({
- value,
- label: t(`skillCenter.marketplace.sort.${value}`),
- }))}
- />
-
-
-
-
+ ))}
+
+
+
+
{error ? (
-
- {error}
+
+
+
+
{t('skillCenter.marketplace.errorTitle')}
+
+ {formatMarketError(t, error)}
+
+
) : null}
-
+
{isLoading ? (
) : items.length === 0 ? (
-
+
}
+ label={t('skillCenter.marketplace.empty')}
+ hint={t('skillCenter.marketplace.emptyHint')}
+ />
+ ) : filteredItems.length === 0 ? (
+
}
+ label={t('skillCenter.marketplace.filterEmpty')}
+ hint={t('skillCenter.marketplace.filterEmptyHint')}
+ />
) : (
-
- {items.map((item) => (
- void fetchDetail(item.source, item.slug)}
- />
- ))}
-
+ <>
+
+ {filteredItems.map((item) => (
+ void fetchDetail(item.source, item.slug)}
+ />
+ ))}
+
+ {nextCursor ? (
+
+
+
+ ) : null}
+ >
)}
-
+
+ {selectedDetail || isDetailLoading ? (
selectedDetail && setPendingInstallDetail(selectedDetail)}
+ onOpenInstalled={handleOpenInstalled}
onClose={clearDetail}
/>
-
+ ) : null}
setPendingInstallDetail(null)}
@@ -249,15 +368,161 @@ export function SkillCenter() {
/>
) : (
-
+
+
{selectedInstalledSkill ? : }
-
+
)}
)
}
+function MarketplaceStatusLine({
+ t,
+ source,
+ resolvedSource,
+ sourceStatus,
+ statusMessage,
+ total,
+ safeCount,
+ installedCount,
+ totalDownloads,
+ loading,
+}: {
+ t: TFunction
+ source: SkillMarketListSource
+ resolvedSource: SkillMarketSource | null
+ sourceStatus: 'ok' | 'fallback' | 'cached' | null
+ statusMessage: string | null
+ total: number
+ safeCount: number
+ installedCount: number
+ totalDownloads: number
+ loading: boolean
+}) {
+ const sourceName = resolvedSource
+ ? t(`skillCenter.marketplace.source.${resolvedSource}`)
+ : t(`skillCenter.marketplace.source.${source}`)
+ const statusLabel = sourceStatus
+ ? t(`skillCenter.marketplace.sourceStatus.${sourceStatus}`)
+ : t('skillCenter.marketplace.sourceStatus.pending')
+
+ return (
+
+
+
+ {sourceName}
+ {statusLabel}
+
+
+
+
+
+ {loading ? (
+
+
+ {t('skillCenter.marketplace.loading')}
+
+ ) : null}
+ {statusMessage ? (
+ {statusMessage}
+ ) : (
+
+ {t('skillCenter.marketplace.sourcePolicy')} · {t('skillCenter.marketplace.safetyPolicy')}
+
+ )}
+
+ )
+}
+
+function StatusDot({ label }: { label: string }) {
+ return (
+
+
+ {label}
+
+ )
+}
+
+function SourceSwitch({
+ value,
+ onChange,
+}: {
+ value: SkillMarketListSource
+ onChange: (value: SkillMarketListSource) => void
+}) {
+ const t = useTranslation()
+
+ return (
+
+ {SOURCE_OPTIONS.map((sourceOption) => (
+
+ ))}
+
+ )
+}
+
+function InstalledLibraryHeader({
+ t,
+ selected,
+ installedCount,
+}: {
+ t: TFunction
+ selected: boolean
+ installedCount: number
+}) {
+ return (
+
+
+
+
+
+
+
+ {selected ? t('skillCenter.mine.detailTitle') : t('skillCenter.mine.libraryTitle')}
+
+
+ {t('skillCenter.mine.libraryMeta', { count: String(installedCount) })}
+
+
+
+
+
+ {t('skillCenter.mine.localDirectory')}
+
+
+ {t('skillCenter.mine.sources')}
+
+
+
+ )
+}
+
function InstallConfirmBody({ detail }: { detail: SkillMarketDetail }) {
const t = useTranslation()
const target = `~/.claude/skills/${detail.slug}`
@@ -298,6 +563,8 @@ function SkillMarketCard({
}) {
const t = useTranslation()
const stats = formatStats(item, t)
+ const sourceLabel = t(`skillCenter.marketplace.source.${item.source}`)
+ const summary = item.summaryZh || item.summary
return (