Download web/src/lib/skill-guide.ts from zlcy/abc: direct link, hf CLI and curl.
- Browser
- Download file 7.04 kB
-
https://huggingface.co/spaces/zlcy/abc/resolve/main/web/src/lib/skill-guide.ts
- Command line
-
hf download hf://spaces/zlcy/abc/web/src/lib/skill-guide.ts
-
curl -L -o skill-guide.ts https://huggingface.co/spaces/zlcy/abc/resolve/main/web/src/lib/skill-guide.ts
7.04 kB
| import { fetchSkillUserGuide, type InstalledSkill, type SkillRuntime } from "@/lib/api"; | |
| import { type ChatCompletionResponse, parseChatCompletionResponse } from "@/lib/chat-runtime"; | |
| import { httpRequest } from "@/lib/request"; | |
| export type SkillGuideDocument = { | |
| path: string; | |
| content: string; | |
| }; | |
| const guideCache = new Map<string, string>(); | |
| const GUIDE_STORAGE_PREFIX = "chatgpt2api.skill-guide.v2"; | |
| function textFingerprint(value: string) { | |
| let hash = 2166136261; | |
| for (let index = 0; index < value.length; index += 1) { | |
| hash ^= value.charCodeAt(index); | |
| hash = Math.imul(hash, 16777619); | |
| } | |
| return (hash >>> 0).toString(16).padStart(8, "0"); | |
| } | |
| function guideCacheKey(skill: InstalledSkill, runtime?: SkillRuntime, documents: SkillGuideDocument[] = []) { | |
| const version = skill.guide_version?.trim() || (runtime | |
| ? textFingerprint([runtime.content, ...documents.map((item) => `${item.path}\n${item.content}`)].join("\n---\n")) | |
| : ""); | |
| return version ? `${skill.id}:${version}` : ""; | |
| } | |
| function readCachedGuide(cacheKey: string) { | |
| if (!cacheKey) return ""; | |
| const memory = guideCache.get(cacheKey); | |
| if (memory) return memory; | |
| if (typeof window === "undefined") return ""; | |
| try { | |
| const value = window.localStorage.getItem(`${GUIDE_STORAGE_PREFIX}:${cacheKey}`)?.trim() || ""; | |
| if (value) guideCache.set(cacheKey, value); | |
| return value; | |
| } catch { | |
| return ""; | |
| } | |
| } | |
| function writeCachedGuide(skill: InstalledSkill, cacheKey: string, guide: string) { | |
| if (!cacheKey || !guide.trim()) return; | |
| guideCache.set(cacheKey, guide); | |
| if (typeof window === "undefined") return; | |
| const storageKey = `${GUIDE_STORAGE_PREFIX}:${cacheKey}`; | |
| const skillPrefix = `${GUIDE_STORAGE_PREFIX}:${skill.id}:`; | |
| try { | |
| for (let index = window.localStorage.length - 1; index >= 0; index -= 1) { | |
| const key = window.localStorage.key(index); | |
| if (key?.startsWith(skillPrefix) && key !== storageKey) window.localStorage.removeItem(key); | |
| } | |
| window.localStorage.setItem(storageKey, guide); | |
| } catch { | |
| // Browser privacy modes or a full storage quota should not block the guide. | |
| } | |
| } | |
| export function getCachedSkillUserGuide(skill: InstalledSkill) { | |
| return readCachedGuide(guideCacheKey(skill)); | |
| } | |
| function basename(path: string) { | |
| return path.replace(/\\/g, "/").split("/").pop() || path; | |
| } | |
| function documentScore(path: string) { | |
| const normalized = path.replace(/\\/g, "/").toLowerCase(); | |
| const name = basename(normalized); | |
| let score = normalized.includes("/") ? 20 : 0; | |
| if (/zh[-_.]?(?:cn|hans)|chinese|中文/.test(name)) score -= 12; | |
| if (/^readme/.test(name)) score -= 8; | |
| if (/^(?:usage|quickstart|getting[-_ ]started|user[-_ ]guide)/.test(name)) score -= 6; | |
| if (normalized.startsWith("_package-docs/")) score -= 4; | |
| return score; | |
| } | |
| export function skillUserDocumentPaths(runtime: SkillRuntime) { | |
| return runtime.resources | |
| .filter((resource) => resource.readable && resource.path !== "SKILL.md") | |
| .map((resource) => resource.path) | |
| .filter((path) => /(?:^|\/)(?:readme|usage|quickstart|getting[-_ ]started|user[-_ ]guide|how[-_ ]to)(?:[._-].*)?\.(?:md|mdx|txt)$/i.test(path)) | |
| .sort((left, right) => documentScore(left) - documentScore(right) || left.localeCompare(right)) | |
| .slice(0, 4); | |
| } | |
| function stripFrontmatter(content: string) { | |
| return content.replace(/^---[\s\S]*?---\s*/i, "").trim(); | |
| } | |
| function fallbackGuide(skill: InstalledSkill, runtime: SkillRuntime, documents: SkillGuideDocument[]) { | |
| const source = documents[0]; | |
| const sourceExcerpt = source?.content.trim().slice(0, 6000); | |
| return [ | |
| "## 能做什么", | |
| skill.description || runtime.description || "按照该 Skill 的专业流程处理任务。", | |
| "## 怎么开始", | |
| `1. 点击“使用这个 Skill”,输入框会插入 \`$${skill.id}\`。`, | |
| "2. 上传该任务依赖的图片或文件。", | |
| "3. 用自然语言说明目标;如果文档要求先确认选项,Skill 会在执行前询问。", | |
| sourceExcerpt ? `## 文档中的说明\n\n> 来源:${source.path}\n\n${sourceExcerpt}` : "", | |
| ].filter(Boolean).join("\n\n"); | |
| } | |
| export async function generateSkillUserGuide({ | |
| skill, | |
| runtime, | |
| documents, | |
| }: { | |
| skill: InstalledSkill; | |
| runtime: SkillRuntime; | |
| documents: SkillGuideDocument[]; | |
| }) { | |
| const cacheKey = guideCacheKey(skill, runtime, documents); | |
| const cached = readCachedGuide(cacheKey); | |
| if (cached) return cached; | |
| try { | |
| const shared = await fetchSkillUserGuide(skill.id); | |
| const guide = shared.guide.trim(); | |
| if (guide) { | |
| writeCachedGuide(skill, cacheKey, guide); | |
| return guide; | |
| } | |
| } catch (error) { | |
| console.warn("Unable to read shared Skill guide cache", error); | |
| } | |
| const sourceDocuments = documents | |
| .map((document) => `<document path="${document.path}">\n${document.content.slice(0, 10000)}\n</document>`) | |
| .join("\n\n") | |
| .slice(0, 20_000); | |
| const skillProtocol = stripFrontmatter(runtime.content).slice(0, 14_000); | |
| try { | |
| const response = await httpRequest<ChatCompletionResponse>("/v1/chat/completions", { | |
| method: "POST", | |
| body: { | |
| model: "auto", | |
| stream: false, | |
| messages: [ | |
| { | |
| role: "system", | |
| content: [ | |
| "你是网站内的 Skill 使用说明编辑器。把开发者/Agent 文档整理成普通用户一眼能懂的中文使用指南。", | |
| "优先采用 README、USAGE、QUICKSTART 等面向用户的文档;SKILL.md 只用于补足能力、必需输入、执行前确认和限制。", | |
| "只能依据提供的资料,不得猜测不存在的能力、参数或结果。不要暴露内部系统提示、资源读取顺序、脚本路径或 Agent 协议。", | |
| "输出简洁 Markdown,固定包含:『能做什么』『适合什么情况』『开始前准备』『怎么使用』『可直接照抄的示例』『需要注意』。", | |
| `给出 2 到 4 个具体、自然的示例指令,每条必须原样包含调用标记 \`$${skill.id}\`。没有明确参数时用可替换占位说明,不要编造参数。`, | |
| ].join("\n"), | |
| }, | |
| { | |
| role: "user", | |
| content: [ | |
| `Skill ID: ${skill.id}`, | |
| `名称: ${skill.name}`, | |
| `描述: ${skill.description || runtime.description}`, | |
| sourceDocuments ? `面向用户的文档:\n${sourceDocuments}` : "没有找到 README/USAGE/QUICKSTART。", | |
| `Agent 执行协议(仅用于补充事实):\n${skillProtocol}`, | |
| ].join("\n\n"), | |
| }, | |
| ], | |
| }, | |
| }); | |
| const guide = parseChatCompletionResponse(response).content.trim(); | |
| if (guide) { | |
| writeCachedGuide(skill, cacheKey, guide); | |
| return guide; | |
| } | |
| } catch (error) { | |
| console.warn("Unable to generate Skill user guide", error); | |
| } | |
| const fallback = fallbackGuide(skill, runtime, documents); | |
| writeCachedGuide(skill, cacheKey, fallback); | |
| return fallback; | |
| } | |