con un clic
write-fullfront-card
设计完全前端化的 SillyTavern 角色卡。当用户要求设计完全前端卡、全前端卡、前端游戏卡、自定义界面卡、HTML 卡时使用。
Instalar con Codex o Claude Copia este prompt, pégalo en Codex, Claude u otro asistente, y deja que revise la página de la skill y la instale por ti.
Menú
设计完全前端化的 SillyTavern 角色卡。当用户要求设计完全前端卡、全前端卡、前端游戏卡、自定义界面卡、HTML 卡时使用。
Instalar con Codex o Claude Copia este prompt, pégalo en Codex, Claude u otro asistente, y deja que revise la página de la skill y la instale por ti.
Basado en la clasificación ocupacional 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 无限扩展