import { FileJson, Pencil, Plus, Power, RefreshCw, Sparkles } from "lucide-react"; import { useCallback, useEffect, useState, type FormEvent, type ReactNode, } from "react"; import { toast } from "sonner"; import { adminApi, ApiError } from "../../api/client"; import type { AdminHintPack, CreateOfficialSkillRequest, OfficialSkill, OfficialSkillCatalog, SkillLocalization, UpdateHintPackRequest, } from "../../api/types"; import { Badge, Button, Card, Dialog, EmptyState, ErrorState, Input, LoadingState, PageHeader, Textarea, } from "../../components/primitives"; import { useAuth } from "../auth/auth-context"; import { HintAutoSection } from "./hint-auto-section"; type HintLocale = "zh" | "en"; export function ContentPage() { const { auth } = useAuth(); const canEdit = auth.status === "authenticated" && auth.role === "SUPER_ADMIN"; const [catalog, setCatalog] = useState(); const [loading, setLoading] = useState(true); const [error, setError] = useState(); const [editingSkill, setEditingSkill] = useState(); const [busySkillId, setBusySkillId] = useState(); const [locale, setLocale] = useState("zh"); const [hintText, setHintText] = useState(""); const [hintVersion, setHintVersion] = useState(0); const [hintLoading, setHintLoading] = useState(true); const [hintSaving, setHintSaving] = useState(false); const loadSkills = useCallback(async () => { setError(undefined); try { setCatalog(await adminApi.contentSkills()); } catch (requestError) { setError(requestError); } finally { setLoading(false); } }, []); const loadHint = useCallback(async (nextLocale: HintLocale) => { setHintLoading(true); try { const pack = await adminApi.contentHintPack(nextLocale); setHintVersion(pack.version); setHintText(formatHintPack(pack)); } catch (requestError) { toast.error(errorMessage(requestError, "Hint pack 加载失败")); } finally { setHintLoading(false); } }, []); useEffect(() => { void loadSkills(); }, [loadSkills]); useEffect(() => { void loadHint(locale); }, [loadHint, locale]); async function setSkillEnabled(skill: OfficialSkill) { setBusySkillId(skill.id); try { await adminApi.setContentSkillEnabled(skill.id, !skill.enabled); toast.success(skill.enabled ? "Skill 已停用" : "Skill 已启用"); await loadSkills(); } catch (requestError) { toast.error(errorMessage(requestError, "Skill 状态更新失败")); } finally { setBusySkillId(undefined); } } async function saveHint() { let payload: UpdateHintPackRequest; try { const parsed = JSON.parse(hintText) as Partial; if (!Array.isArray(parsed.cards)) throw new Error("cards 必须是数组"); payload = { generatedAt: optionalString(parsed.generatedAt), expiresAt: optionalString(parsed.expiresAt), intervalHours: parsed.intervalHours === undefined ? undefined : Number(parsed.intervalHours), cards: parsed.cards, }; } catch (parseError) { toast.error(parseError instanceof Error ? parseError.message : "JSON 格式无效"); return; } setHintSaving(true); try { const saved = await adminApi.updateContentHintPack(locale, payload); setHintVersion(saved.version); setHintText(formatHintPack(saved)); toast.success(`${locale} Hint pack 已保存并生效`); } catch (requestError) { toast.error(errorMessage(requestError, "Hint pack 保存失败")); } finally { setHintSaving(false); } } if (error) return void loadSkills()} />; if (loading || !catalog) return ; return (
setEditingSkill("new")}> 新增 Skill ) : ( 只读访问 ) } />

官方 Skill

当前 revision {catalog.revision} · 共 {catalog.skills.length} 项

{catalog.skills.length === 0 ? ( ) : (
{catalog.skills.map((skill) => (

{skill.localizations["zh-Hans"].name}

{skill.enabled ? "已启用" : "已停用"} {skill.thinkingEnabled ? 思考 : null}

{skill.localizations["zh-Hans"].summary}

{skill.id} · {skill.systemImage} · 排序 {skill.sortOrder}

{canEdit ? (
) : null}
))}
)}

AI Hint packs

可查看并编辑当前生效的 AIHintPack;保存后立即生效,服务端自动递增 version。

{(["zh", "en"] as HintLocale[]).map((item) => ( ))} version {hintVersion}
{hintLoading ? ( ) : ( <>