بنقرة واحدة
write-fullfront-card
设计完全前端化的 SillyTavern 角色卡。当用户要求设计完全前端卡、全前端卡、前端游戏卡、自定义界面卡、HTML 卡时使用。
التثبيت باستخدام Codex أو Claude انسخ هذا Prompt والصقه في Codex أو Claude أو مساعد آخر ليراجع صفحة Skill ويثبّتها لك.
القائمة
设计完全前端化的 SillyTavern 角色卡。当用户要求设计完全前端卡、全前端卡、前端游戏卡、自定义界面卡、HTML 卡时使用。
التثبيت باستخدام Codex أو Claude انسخ هذا Prompt والصقه في Codex أو Claude أو مساعد آخر ليراجع صفحة Skill ويثبّتها لك.
استنادا إلى تصنيف SOC المهني
编写 SillyTavern 卡内世界书条目的完整格式规范。当用户要求编写世界书、lorebook 条目、character_book entries 时使用。
通过 Chrome DevTools MCP + ST REST API 端到端调试角色卡。当用户要求调试卡片、测试卡片、导入卡到 ST、查看卡渲染效果、ST 端到端测试时使用。
编写在 SillyTavern 原生交互框架内嵌入前端 UI 的角色卡。当用户要求写嵌入式前端卡、MVU 变量卡、状态栏卡、EJS 动态提示词卡时使用。
| name | write-fullfront-card |
| description | 设计完全前端化的 SillyTavern 角色卡。当用户要求设计完全前端卡、全前端卡、前端游戏卡、自定义界面卡、HTML 卡时使用。 |
设计完全前端化的 SillyTavern 角色卡——放弃 ST 原生对话交互,用自定义 HTML/CSS/JS 构建完整的游戏/交互界面。
用户要求设计/编写:完全前端卡、全前端卡、前端游戏卡、自定义界面卡、HTML 卡、独立前端卡。
完全前端卡使用 Vite 项目化开发 + st-card-toolkit 共享工具包:
st-card-toolkit 导入,卡只写 schema + prompt + 游戏逻辑npm run build → Vite 内联所有资源 → build.cjs 提取片段 + 合并 lorebook → 输出 card.jsonlorebook-editor) 保证条目格式正确AI 调用细节见 docs/toolkit-api.md。叙事类调用优先用 generate() 适配 ST 预设(见「AI 调用模式」章节)。只有 toolkit 不覆盖的场景(流式事件、世界书操作、ordered_prompts / overrides 编排)才需要查阅 docs/tavernhelper-api.md 或 docs/prompt-orchestration.md。
完全前端卡利用 SillyTavern 的正则脚本机制,将用户输入的触发词替换为完整的 HTML 应用。核心结构:
first_mes → 开场白/引导界面(HTML 页面)
alternate_greetings[0] → 触发关键词(如 "start_game")
regex_scripts → findRegex 匹配触发词 → replaceString 注入完整 HTML/JS 应用
character_book → 世界规则、AI 行为指令、变量操作规范
description → 可留空或简短说明(前端卡主要靠世界书驱动)
用户输入触发词 → 正则匹配 → 替换为 HTML 应用 → 应用内用 JS 调用 AI API
→ 应用内用 IndexedDB (Dexie) 存储状态
→ AI 回复带结构化数据(通常经 json_schema 约束)
→ 前端解析数据 → 更新数据库 → 刷新 UI
完全前端卡完全脱离了 SillyTavern 的原生对话/存档机制。ST 在这种卡里几乎只负责两件事:(1)渲染 ```html 代码块为 iframe;(2)通过 TavernHelper 转发 AI 请求(以及在 generate() 模式下应用用户预设)。除此之外的一切——对话状态、提示词组装、上下文压缩、存档读档、设置持久化——都必须由卡自己实现。
设计一张完全前端卡前,先确认下面四个系统都已经规划好:
ST 的 Prompt Manager 在 generate() 模式下只负责注入用户预设和 lorebook 的静态部分,每一轮的动态 prompt 拼装完全在卡手中。卡需要决定:
callAI() 前要拼哪些层(系统规则 / 长期记忆 / 近期回顾 / 当前状态 / 本轮输入)generateRaw 通道的 ordered_prompts 顺序、role 分布generate 通道的 overrides 槽位用法(chat_history / world_info / char_description 等)src/prompt.js 是卡的提示词中枢,不要把 prompt 拼装散落在调用点。
ST 自己的「聊天历史 → token」机制对完全前端卡不生效——卡用 max_chat_history: 0 把它清空了,所有「记得多少」都由卡自己决定。卡需要规划:
每个使用 callAI() 的通道都要单独考虑压缩策略,不能只管主叙事。
完全前端卡的所有运行时状态都在浏览器内存和 IndexedDB 里,ST 的对话保存机制覆盖不到这些数据。用户刷新页面、切换角色、重开聊天时,卡必须能从持久化存储里把整个游戏世界重建出来。卡需要规划:
判断标准:关闭浏览器后重新进入这张卡,能不能 1:1 恢复到之前的状态和 AI 上下文。如果做不到,存档系统就没设计完。
API 配置、用户偏好、各通道开关等都属于设置层。createSettingsController() 负责面板渲染和 localStorage 持久化,但面板上有哪些项、设置项之间的依赖、默认值、迁移是卡作者的事。常见漏项:
简而言之:ST 给你一个 iframe 和一根 AI 调用通道,剩下的整个「游戏运行时」都得自己造。本 skill 后续章节会分别给出 history、压缩、多通道等子系统的设计模式,但这些模式只是可选实现,责任本身始终在卡作者手里。
{
"name": "卡名",
"description": "",
"personality": "",
"scenario": "",
"first_mes": "<完整 HTML 引导页面>",
"mes_example": "",
"creator_notes": "使用说明:推荐模型、触发方式等",
"system_prompt": "",
"post_history_instructions": "",
"alternate_greetings": ["触发关键词"],
"tags": ["前端卡", "游戏"],
"creator": "作者名",
"character_version": "1.0.0",
"character_book": {
"name": "卡名_worldbook",
"entries": [
// 世界规则、AI 行为指令、变量操作规范等
]
},
"extensions": {
"regex_scripts": [
{
"scriptName": "卡名 (作者)",
"findRegex": "触发关键词",
"replaceString": "提示文字\n```html\n<!DOCTYPE html>...\n```",
"trimStrings": [],
"placement": [2],
"disabled": false,
"markdownOnly": true,
"promptOnly": false,
"runOnEdit": true,
"substituteRegex": true,
"minDepth": null,
"maxDepth": null
}
]
}
}
| 参数 | 完全前端卡设置 | 说明 |
|---|---|---|
findRegex | 触发关键词字符串 | 用户输入此词时触发界面注入 |
replaceString | 完整 HTML 应用代码 | 必须包裹在 ```html 代码块中(见踩坑清单) |
placement | [2] | 匹配用户输入 |
markdownOnly | true | 仅在显示时替换,不影响发送给 AI 的内容 |
promptOnly | false | 显示侧需要渲染 |
完全前端卡依赖 SillyTavern 的代码块渲染器(需用户在 ST 设置中开启)。渲染器会将 ```html 代码块放进 iframe 执行,从而绕过 DOMPurify 的 <script> 剥离。
重要前置条件: 用户必须在 ST 设置中开启「启用渲染器 - 启用后,符合条件的代码块将被渲染」。
cards/my-card/
├── package.json # dependencies: st-card-toolkit
├── vite.config.js # vite-plugin-singlefile
├── index.html # Vite 入口 HTML(模板)
├── build.cjs # 后处理:Vite 产物 → card.json
├── card.meta.json # 卡元数据
├── src/
│ ├── main.js # 入口 + 将交互函数暴露到 window
│ ├── style.css # CSS
│ ├── schema.js # json_schema 定义
│ ├── prompt.js # 每轮提示词构建
│ ├── state.js # 卡状态管理(config / history / 运行时状态)
│ ├── settings.js # 设置面板(通常基于 createSettingsController)
│ ├── ... # 卡特有模块(游戏逻辑、UI、存档、数据库等)
│ └── first_mes.html # 首条消息
├── lorebook/ # 世界书条目(V2 Spec JSON)
└── dist/card.json # 构建产物
这只是一个典型布局,不是规范。文件拆分粒度视卡的复杂度调整——可以把 state/config/history 合并到一个文件,也可以把游戏逻辑拆成多个模块(动作处理、存档系统、数据库访问等);toolkit 的 callAI 可以直接用,也可以做一层卡内封装(如果需要定制 history 管理或响应解析)。
构建产物是 HTML 片段(不含 <!DOCTYPE>/<html>/<head> 外壳),用 <body> 标签包裹以通过 isFrontend() 检测。JS-Slash-Runner 的 createSrcContent() 会自动包一层完整 HTML 文档并注入 FontAwesome、jQuery、Vue 等。
卡不需要自己引入 FontAwesome / jQuery / Vue(框架已注入)。只需引入框架未提供的依赖(如 Dexie)。
以下是在 SillyTavern 中实际部署完全前端卡时的已知坑点:
问题: 如果 replaceString 不使用 ```html 代码块包裹,而是直接输出 HTML 片段,SillyTavern 的 DOMPurify 净化器会:
<script> 标签onclick、onchange 等事件处理器属性<iframe srcdoc> 属性表现: HTML/CSS 正常渲染(布局、样式、图标都在),但所有交互完全失效——点击按钮无任何反应,浏览器控制台也无报错(因为事件处理器根本不存在)。
规则: 完全前端卡必须使用 ```html 代码块包裹,依赖 ST 的代码块渲染器(在 iframe 中执行脚本,绕过 DOMPurify)。
调试方法: F12 → Elements 面板,搜索 <script> 或 onclick。如果搜不到,说明被净化器剥离了。
问题: first_mes 中的 HTML 同样经过 DOMPurify。
规则: 如果 first_mes 需要 JS 交互,必须用 ```html 代码块包裹完整 HTML 文档。纯静态展示页(无 JS)可以直接用 HTML 片段:
// 静态展示(无 JS)—— 直接 HTML 片段即可:
<style>.intro { ... }</style>
<div class="intro">...</div>
// 需要 JS 交互 —— 必须代码块包裹:
` ` `html
<!DOCTYPE html>
<html>...<script>...</script>...</html>
` ` `
原则: 完全前端卡的 first_mes 不应包含任何实际文字内容。它的唯一职责是提供一个正则匹配的上下文,让 regex_scripts 将其替换为前端应用。所有展示内容(封面、介绍文字、按钮等)都在 replaceString 的 HTML/JS 中实现。
原因: 正则脚本会扫描所有渲染的消息内容。如果 first_mes 中包含任何文字(包括说明文字中提到的触发词),可能被正则意外匹配,导致封面页同时渲染出游戏界面。
推荐做法: first_mes 直接填入触发关键词本身,让正则将其完整替换为封面页 HTML:
{
"first_mes": "start_game", // 触发词本身,会被正则替换为封面 HTML
"alternate_greetings": ["start_game"], // 同一个触发词,替换为游戏主界面
"regex_scripts": [{
"findRegex": "start_game",
"replaceString": "```html\n<!DOCTYPE html>...\n```" // 前端应用
}]
}
如果封面页和游戏主界面不同,可以用两个正则分别匹配不同的触发词:
{
"first_mes": "show_intro",
"alternate_greetings": ["start_game"],
"regex_scripts": [
{ "findRegex": "show_intro", "replaceString": "```html\n...封面HTML...\n```" },
{ "findRegex": "start_game", "replaceString": "```html\n...游戏HTML...\n```" }
]
}
前置条件: 用户必须在 ST 设置中开启「启用渲染器」(启用后,符合条件的代码块将被渲染)。未开启时,```html 代码块只会显示为语法高亮的源码,不会执行。
建议在 creator_notes 中注明此要求。
完全前端卡的所有运行时数据都要自己存——ST 不会帮你保存游戏内的角色、物品、任务、存档快照。主流选择是 IndexedDB(通常通过 Dexie.js 封装):容量大、原生异步、iframe 环境稳定、有 schema 版本迁移机制。轻量的启动配置(API key、主题偏好等)可以直接放 localStorage;其余长期数据走 IndexedDB。
db.version(N).stores({...}).upgrade(tx => ...) 写迁移函数,老存档才能正确升级;没有迁移策略就不要发版本更新Dexie 的表定义语法、索引、版本迁移等 API 参考 dexie.org。
完全前端卡里,AI 的一次回复通常要带两层信息:叙事文本(给玩家看)+ 结构化数据(给前端解析、更新 UI/DB)。让 AI 稳定返回可解析的数据是这一层的核心需求。
通过 callAI() 的 json_schema 参数约束 AI 输出格式。把叙事正文也放进 schema 的一个字段(通常叫 narrative),一轮调用就完成「叙事 + 结构化数据」混合输出。
const SCENE_SCHEMA = {
name: 'scene_response',
value: {
type: 'object',
properties: {
narrative: { type: 'string', description: '本轮叙事正文' },
// ...其他结构化字段(状态快照、决策标记、事件等)
},
required: ['narrative' /* , ... */],
additionalProperties: false,
},
strict: true,
};
const raw = await callAI(prompt, { config, history, json_schema: SCENE_SCHEMA });
const { text, data } = parseNarrativeAndData(raw, 'narrative');
renderNarrative(text);
updateState(data); // CRUD 时机由前端代码决定
这种做法的三个好处:
parseNarrativeAndData() 由 st-card-toolkit 提供,一行拆出 text 和 dataschema 设计和 json_schema 完整语法参考 docs/structured-output.md。
json_schema 普及之前,早期完全前端卡让 AI 在叙事文本里嵌入自定义数据操作指令(y_insert({...}) / y_update("ID", {...}) / y_delete("ID") / y_add_json(...)),前端用正则扫描回复、解析指令、执行数据库操作。
这种做法的痛点:
什么时候还用得到:
json_schema(某些自定义端点、老 API)完整的 parseCommands / executeCommands / sanitize / 自动修复参考实现在 docs/data-ops.md。
如果 AI 需要显式调用外部工具或触发副作用(例如"搜索网页"、"发送通知"、"执行脚本"),用 callAI() 的 tools / tool_choice 参数走 function calling 协议。和 json_schema 的区别是:json_schema 约束的是 AI 的输出格式,function calling 是"AI 自主决定要不要调用某个具体函数"。详见 docs/structured-output.md。
默认用 json_schema——稳定、简单、工具链原生。只有明确需要 "AI 动态决定 CRUD" 或 "API 不支持 json_schema" 时,才考虑退回到 y_xxx 指令流。
完全前端卡有两种 AI 调用方式,选择取决于是否需要 ST 预设参与提示词编排。
SillyTavern 的预设(Prompt Manager)为 AI 编排一套完整的提示词上下文:系统提示(Main Prompt)→ 世界书 → 角色描述 → 聊天历史 → 最终指令(Post-History Instructions)。用户通过预设来丰富 AI 的故事表现、限定文风、设置破限指令。这些都是用户侧的调教,卡作者不需要也不应该替用户做这些事。
用 generate() | 用 generateRaw() |
|---|---|
| 这次调用需要用户的预设 / 文风 / 破限介入 | 这次调用不希望用户预设参与编排 |
| 输出是面向玩家的叙事 / 故事 / 角色扮演 | 输出是卡内自控的数据、子对话、翻译、事件生成等 |
| 卡的静态规则放 lorebook 里,让 ST 预设注入 | 卡完全自控上下文,不依赖 ST 的任何内容 |
| 温度、模型等参数由用户预设控制 | 温度、模型等参数由代码精确控制 |
同一张卡可以混用两种模式。典型做法:主叙事通道用 generate()(用户预设生效),辅助通道(摘要生成、数据提取)用 generateRaw()(不需要预设干扰)。
ST 预设 + lorebook → 静态系统规则(世界观、格式规范、认知隔离、文风、破限)
代码(prompt.js) → 每次调用的动态内容(随机种子、状态变量、玩家行动)
overrides → 自管的多轮对话历史(注入到预设的 chat_history 槽位)
lorebook 条目设为 constant: true,ST 预设会自动按 Prompt Manager 顺序注入。代码中的 prompt 不重复写 lorebook 已有的规则——否则 AI 会看到两份相同的指令。
多轮对话通过 overrides.chat_history.prompts 注入,配合 max_chat_history: 0 清空 ST 聊天记录,实现正规的 user/assistant 交替格式。API 细节见 docs/prompt-orchestration.md。
完全前端卡的 AI 调用由前端 JS 控制,对话历史(history)的管理完全在卡手中。核心问题:随着交互次数增长,原始对话记录会撑爆 token 上限。解决思路是小总结 + 大总结的两级压缩。
| 概念 | 存储位置 | 内容 | 生命周期 |
|---|---|---|---|
| history | 内存(createHistory()) | 原始对话记录(user/assistant 逐条) | 每次 callAI() 自动追加,压缩后清空 |
| 小总结 | AI 每次回复的结构化字段 | 本轮发生了什么(一两句话) | 随回复返回,存入 state 或 DB |
| 大总结 | IndexedDB | 多轮小总结的压缩合并 | 定期生成,长期保留 |
| 上下文块 | prompt 拼接时构建 | 大总结 + 近期小总结 + 当前状态 | 每次 callAI() 前临时组装 |
在结构化输出的 schema 中加一个 recap 字段,要求 AI 每次回复时顺带生成一句话总结本轮要点。零额外请求——AI 生成叙事的同时就完成了压缩。
前端收到回复后把 recap 存入数组,下次调用时将近期 recap 列表拼入 prompt 作为「近期回顾」。
适用: 所有卡。即使不做大总结,光靠小总结队列就能覆盖大多数短中期交互。
每 N 轮(或 history 达到 token 阈值时),发起一次独立的 AI 调用,将累积的小总结压缩为一段精炼的摘要。压缩完成后清空 history 和小总结队列。
关键点:
createHistory()),不污染主对话流history.reset() 释放主对话的 token 占用触发时机根据卡的交互模式选择:
| 触发方式 | 适用场景 |
|---|---|
| 固定轮次(每 N 轮) | 节奏稳定的卡 |
| token 估算 | 对话长度波动大的卡 |
| 阶段切换 | 有章节/关卡的卡,切换时压缩上一阶段 |
| 结案/存档点 | 有明确结束事件的卡 |
每次调用 AI 时,按层级拼装 prompt:
[系统规则] ← 世界观、行为规则、输出格式等
[故事至今] ← 大总结(长期记忆,可选)
[近期回顾] ← 小总结队列,最近 N 条(近期记忆)
[当前状态] ← DB 数据快照(角色属性、物品、任务等)
[当前场景/玩家动作] ← 本轮输入
信息密度从上到下递增:大总结高度压缩,小总结保留细节,当前状态是实时快照。
### 异步压缩时机
大总结的触发方式可以根据卡的交互模式选择:
| 触发方式 | 适用场景 | 实现 |
|---|---|---|
| **固定轮次** | 节奏稳定的卡(每 N 轮压缩) | `if (state.recaps.length >= N)` |
| **token 估算** | 对话长度波动大的卡 | 估算 history token 数,超阈值时压缩 |
| **阶段切换** | 有明确章节/关卡的卡 | 切换阶段时压缩上一阶段 |
| **结案/存档点** | 有明确结束事件的卡 | 事件结束时压缩并归档到 DB |
异步压缩可以在不阻塞 UI 的情况下执行——用 `Promise` 发起压缩请求,压缩期间玩家仍可阅读叙事:
```javascript
// 不阻塞:叙事渲染后台压缩
addMessage('narrator', narrative);
maybeCompress(); // 不 await,后台执行
原始对话 history(临时,callAI 自动管理)
↓ 每轮回复附带 recap 字段(小总结,零额外请求)
↓ 累积 N 轮后,独立 AI 调用压缩(大总结,一次额外请求)
↓ 压缩后清空 history + recaps,大总结存入 state/DB
↓ 下次调用:大总结 + 近期 recaps + 当前状态 → prompt
关键原则:
history.reset() 释放 token,大总结接管长期记忆完全前端卡的核心优势:前端 JS 可以同时发起多个独立 AI 调用,每个通道按用途选择 generate 还是 generateRaw,并使用独立的 history。
每个通道创建独立的 createConfig() + createHistory():
// 叙事通道:走 ST 预设,采样参数由用户预设决定(config 里设 temperature 也不会生效)
const narrativeConfig = createConfig('mycard_narrative');
const narrativeHistory = createHistory();
// 逻辑通道:完全自控,采样参数由代码精确控制
const logicConfig = createConfig('mycard_logic', { temperature: /* 偏低 */ });
const logicHistory = createHistory();
// 并行调用:generate + generateRaw 可以同时跑
const [narrativeResult, logicResult] = await Promise.all([
callAI(narrativePrompt, {
config: narrativeConfig, history: narrativeHistory,
mode: 'generate',
}),
callAI(logicPrompt, {
config: logicConfig, history: logicHistory,
mode: 'generateRaw', json_schema: LOGIC_SCHEMA,
}),
]);
| 通道类型 | 模式 | 温度倾向 | 典型用途 |
|---|---|---|---|
| 主叙事 | generate | 由用户预设决定 | 故事推进、角色扮演 |
| 数据/逻辑 | generateRaw | 偏低(代码控) | 数据操作指令、分类、提取、判定 |
| 压缩/总结 | generateRaw | 中(代码控) | 记忆压缩、大总结 |
| 其他自控通道 | generateRaw | 视用途而定 | 任何不希望 ST 预设介入的场景 |
generateRaw的适用范围:不只是「数据/逻辑」。只要你不希望用户的预设、文风、破限、聊天历史等参与编排——例如卡内的固定 NPC 对话、自定义的子角色推理、固定模板的翻译/润色、独立的世界事件生成、对外部素材的纯加工等——都应该用generateRaw自己拼ordered_prompts。判断标准只有一个:这一次调用是否需要用户预设介入。需要 →generate;不需要 →generateRaw。
在 parent +
generate模式下,createConfig()的temperature不会被读取——温度、模型、break-jail 等参数全部由用户的 ST 预设接管。卡只负责提供 prompt 和(可选的)overrides。
generate() 是单实例并发:同一时刻只能跑一个 generate() 调用,第二个会排队等待。generateRaw() 可任意并行。generate + N 个 generateRaw 同时跑(上面的示例就是这种)。generate 然后 Promise.all——它们会被强制串行,得不到并行收益。如果叙事侧也需要并发,把次要的通道改成 generateRaw + 自己拼 prompt。用 createSettingsController() 为每个通道绑定独立设置面板。叙事通道在 generate 模式下,设置面板里的温度/采样参数仅对自定义端点(非 parent)生效,可在面板上加注释提醒用户。
完全前端卡的世界书主要用来注入静态的 AI 行为规则——那些每轮都要见效、但不随游戏状态变化的内容。动态内容(当前状态快照、玩家行动、近期记忆)由代码每轮重新拼接,见「AI 调用模式 → generate() 模式下的分工」。
每轮都自动注入的条目。典型内容:
按关键词触发的条目——只有 AI 或玩家提到特定词时才注入。适合:
用关键词触发避免把所有细节塞进常驻条目占用 token 预算。
条目的字段格式(V2 Spec)见 write-lorebook-entry skill。
first_mes 只放触发占位符,不放任何实际文字内容。封面/引导界面的所有展示内容(世界观介绍、角色创建表单、动画效果等)都在 replaceString 的 HTML/JS 中实现,由正则脚本将 first_mes 替换为封面页。
如果封面页和游戏主界面是同一个应用(由 JS 内部切换状态),只需一个正则和一个触发词。如果是独立的两个页面,用两个正则分别匹配(见踩坑清单第 4 条)。
height: 800px)——100vh 在部分 ST 环境下会导致 iframe 无限扩展(消息区域不断撑高、输入栏被推出视口)。需要全屏效果用 Fullscreen API 按钮.myapp .btn)影响有限但可读性更好npm install st-card-toolkitsrc/schema.js — AI 输出的 JSON Schemasrc/prompt.js — 提示词构建逻辑src/actions.js — 用 callAI() + parseNarrativeAndData() 驱动lorebook-editor MCP 创建条目(格式自动正确)index.html + src/style.css,设置面板用 createSettingsController()npm run build → dist/card.jsonnpm run build 产出完整 card.json,可直接导入 SillyTavern800px),避免 100vh 导致 iframe 无限扩展