'use client'; import React, { useCallback, useEffect, useMemo, useState } from 'react'; import { AlertCircle, ArrowLeft, Check, Download, Loader2, Search, Star } from 'lucide-react'; import { getSkillHubDetail, getSkillHubVersion, installSkillHubSkill, searchSkillHubSkills, } from '@/lib/api'; import { Badge } from '@/components/ui/badge'; import { Button } from '@/components/ui/button'; import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; import { Input } from '@/components/ui/input'; import type { SkillHubSearchItem, SkillHubVersionResponse } from '@/types'; import { pickAppText } from '@/lib/i18n/core'; import { useAppI18n } from '@/lib/i18n/provider'; type SortMode = 'relevance' | 'downloads' | 'newest'; function publishedVersion(skill: SkillHubSearchItem | null): string { return skill?.publishedVersion?.version || skill?.headlineVersion?.version || ''; } export default function MarketplacePage() { const { locale } = useAppI18n(); const t = useCallback((zh: string, en: string) => pickAppText(locale, zh, en), [locale]); const [query, setQuery] = useState(''); const [sort, setSort] = useState('newest'); const [starredOnly, setStarredOnly] = useState(false); const [page, setPage] = useState(0); const [items, setItems] = useState([]); const [total, setTotal] = useState(0); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); const [selected, setSelected] = useState(null); const [versionDetail, setVersionDetail] = useState(null); const [detailLoading, setDetailLoading] = useState(false); const [installing, setInstalling] = useState(false); const load = useCallback(async () => { setLoading(true); setError(null); try { const result = await searchSkillHubSkills({ q: query, sort, page, size: 12 }); const nextItems = Array.isArray(result.items) ? result.items : []; setItems(starredOnly ? nextItems.filter((item) => (item.starCount || 0) > 0) : nextItems); setTotal(result.total || 0); } catch (err: any) { setError(err.message || t('加载 SkillHub 失败', 'Failed to load SkillHub')); } finally { setLoading(false); } }, [page, query, sort, starredOnly, t]); useEffect(() => { void load(); }, [load]); const openDetail = async (item: SkillHubSearchItem) => { setSelected(item); setVersionDetail(null); setDetailLoading(true); setError(null); try { const detail = await getSkillHubDetail(item.namespace, item.slug); setSelected(detail); const version = publishedVersion(detail); if (version) { setVersionDetail(await getSkillHubVersion(detail.namespace, detail.slug, version)); } } catch (err: any) { setError(err.message || t('加载技能详情失败', 'Failed to load skill details')); } finally { setDetailLoading(false); } }; const installSelected = async () => { if (!selected) return; setInstalling(true); setError(null); try { const result = await installSkillHubSkill(selected.namespace, selected.slug, publishedVersion(selected)); setSelected({ ...selected, installed: true, installed_version: result.version }); await load(); } catch (err: any) { setError(err.message || t('安装技能失败', 'Failed to install skill')); } finally { setInstalling(false); } }; const totalPages = useMemo(() => Math.max(1, Math.ceil(total / 12)), [total]); return (
{ event.preventDefault(); setPage(0); void load(); }} >
setQuery(event.target.value)} placeholder={t('搜索技能...', 'Search skills...')} className="h-14 rounded-2xl pl-12 text-base" />
{error && ( {error} )} {selected ? (
@{selected.namespace} {selected.installed && ( {t('已安装', 'Installed')} )}
{selected.displayName || selected.slug}

{selected.summary}

{detailLoading ? (
) : ( <>
v{publishedVersion(selected) || '-'} {t('下载', 'Downloads')}: {selected.downloadCount || 0} {t('收藏', 'Stars')}: {selected.starCount || 0}
SKILL.md
                        {versionDetail?.detail?.parsedMetadataJson || t('暂无预览', 'No preview available')}
                      
{t('版本文件', 'Version files')}
{(versionDetail?.files || []).map((file) => (
{file.filePath} {file.fileSize} B
))}
)}
) : (
{t('排序:', 'Sort:')} {([ ['relevance', t('相关性', 'Relevance')], ['downloads', t('下载量', 'Downloads')], ['newest', t('最新', 'Newest')], ] as Array<[SortMode, string]>).map(([value, label]) => ( ))} {t('筛选:', 'Filter:')}
{loading ? (
) : (
{items.map((item) => ( void openDetail(item)}>
{item.displayName || item.slug} @{item.namespace}

{item.summary}

v{publishedVersion(item) || '-'} {item.downloadCount || 0} {item.starCount || 0} {item.installed && {t('已安装', 'Installed')}}
))}
)}
{page + 1} / {totalPages}
)}
); }