"use client"; import { useEffect, useMemo, useState } from "react"; import { FileText, LoaderCircle } from "lucide-react"; import { AssistantMarkdown } from "@/components/chat/assistant-markdown"; import { readSkillResource, type SkillRuntime } from "@/lib/api"; import { cn } from "@/lib/utils"; const DOCUMENT_EXTENSIONS = /\.(?:md|mdx|txt|json|jsonc|ya?ml|toml|xml|html?|css|csv|tsv|ini|cfg|conf|py|js|jsx|ts|tsx|sh|ps1)$/i; function documentPaths(runtime: SkillRuntime) { return runtime.resources .filter((resource) => resource.readable && DOCUMENT_EXTENSIONS.test(resource.path)) .map((resource) => resource.path) .sort((left, right) => { if (left === "SKILL.md") return -1; if (right === "SKILL.md") return 1; return left.localeCompare(right); }); } export function SkillDocumentBrowser({ skillId, runtime }: { skillId: string; runtime: SkillRuntime }) { const paths = useMemo(() => documentPaths(runtime), [runtime]); const [activePath, setActivePath] = useState(paths[0] || ""); const [documents, setDocuments] = useState>({}); const [loading, setLoading] = useState(false); const [error, setError] = useState(""); useEffect(() => { setActivePath(paths[0] || ""); setDocuments({}); setError(""); }, [skillId, paths]); useEffect(() => { if (!activePath || documents[activePath]) return; let cancelled = false; setLoading(true); setError(""); void readSkillResource(skillId, activePath) .then((resource) => { if (!cancelled) setDocuments((current) => ({ ...current, [activePath]: resource.content })); }) .catch((reason) => { if (!cancelled) setError(reason instanceof Error ? reason.message : "读取文档失败"); }) .finally(() => { if (!cancelled) setLoading(false); }); return () => { cancelled = true; }; }, [activePath, documents, skillId]); if (!paths.length) return

这个 Skill 没有可展示的文本文件。

; return (
{loading && !documents[activePath] ?
正在读取 {activePath}
: null} {error ?
{error}
: null} {documents[activePath] ? : null}
); }