7 KiB
TauriTavern Extension Development Guide
本文档面向 SillyTavern 扩展开发者,介绍 TauriTavern 提供的专属 API——让你的扩展更强大、更优雅。
一句话总结:TauriTavern 为扩展提供了
findLastMessage()精准定位、searchMessages()全文检索、以及store.*状态持久化——这些都是 SillyTavern 从未内置,而开发者一直在手动造轮子的能力。
为什么值得适配?
在 SillyTavern 中,扩展开发者长期面临这些痛点:
| 痛点 | 传统做法 | TauriTavern 方案 |
|---|---|---|
| 找最后一条特定消息 | chat.slice().reverse().find(...) 手动遍历 |
✅ findLastMessage() 一行搞定,后端高效定位 |
| 搜索历史消息 | 手写关键词扫描,CJK 断词全靠运气 | ✅ searchMessages() 内置全文检索,CJK 优化 |
| 扩展数据持久化 | 塞进消息体、手动 saveChat(),数据与消息耦合 |
✅ store.* 独立 KV 存储,干净可靠 |
| 小配置/进度保存 | 各种 hack 写入 chat_metadata |
✅ metadata.* 标准化接口,语义清晰 |
** TauriTavern,让你的扩展制作更简单。**
关于窗口化聊天
TauriTavern 采用窗口化加载(windowed payload)的设计:前端只保留最近 N 条消息在内存中,而非一次性加载全部聊天记录。这让长对话和移动端体验更流畅,同时也意味着 getContext().chat 只反映当前窗口内的消息。
你不需要关心底层细节——上面提到的 findLastMessage()、searchMessages()、history.* 等 API 已经帮你透明地处理了窗口边界,后端会扫描完整历史。只需调用 API,其余交给 Rust。
1. 快速开始
1.1 扩展目录
TauriTavern 的 third-party 扩展目录与 SillyTavern 兼容:
- local:
data/default-user/extensions/<folder> - global:
data/extensions/third-party/<folder> - 同名时 local 优先
资源端点:/scripts/extensions/third-party/<folder>/<path>
1.2 等待宿主就绪
await (window.__TAURITAVERN__?.ready ?? window.__TAURITAVERN_MAIN_READY__);
const api = window.__TAURITAVERN__.api.chat;
TauriTavern 在宿主层暴露 window.__TAURITAVERN__,你只需等待 ready 即可使用所有 API。
1.3 判断是否在 TauriTavern 中运行
if (window.__TAURITAVERN__) {
// TauriTavern 环境,可以使用增强 API
} else {
// 标准 SillyTavern 环境,走原有逻辑
}
这样你的扩展可以轻松做到两端兼容。
2. 核心能力一览
TauriTavern 专属 API 的唯一入口:
window.__TAURITAVERN__.api.chat
findLastMessage() — 再也不用手动遍历
以前为了找最后一条包含特定扩展数据的消息,你得:
// ❌ 以前的做法
const hit = context.chat.slice().reverse().find(
msg => msg.extra?.TavernDB_ACU_IsolatedData
);
现在只需要:
// ✅ TauriTavern
const hit = await handle.locate.findLastMessage({
role: 'assistant',
hasExtraKeys: ['TavernDB_ACU_IsolatedData'],
scanLimit: 2000,
});
// hit = { index, message } 或 null
后端 Rust 处理,快速、高效、准确。
searchMessages() — 内置全文检索,告别手写搜索
以前你要在聊天记录里搜关键词?只能自己 filter/includes,CJK 用户更是一言难尽。
现在:
// ✅ TauriTavern
const hits = await handle.searchMessages({
query: 'AUV,您吉祥',
limit: 20,
filters: { role: 'assistant' },
});
// hits = [{ index, score, snippet, role, text }, ...]
- 内置 CJK bigram 分词优化
- 支持按角色、索引范围过滤
scanLimit控制性能开销
store.* — 真正的扩展数据持久化
这是 SillyTavern 从未实现 的功能。以前存数据的"标准做法"是塞进消息体:
// ❌ 以前的做法:把数据塞进最后一条消息
lastMsg.extra.myExtensionData = hugeJsonObject;
saveChat(); // 放大 payload,容易出问题
现在你有独立的 per-chat KV 存储:
// ✅ TauriTavern
await handle.store.setJson({ namespace: 'my-ext', key: 'index', value: data });
const data = await handle.store.getJson({ namespace: 'my-ext', key: 'index' });
const keys = await handle.store.listKeys({ namespace: 'my-ext' });
await handle.store.deleteJson({ namespace: 'my-ext', key: 'old-key' });
数据与消息彻底解耦,干净、可靠、不膨胀 payload。
metadata.* — 轻量配置存储
适合存储进度、配置项、短摘要等小状态:
await handle.metadata.setExtension({ namespace: 'my-ext', value: { lastFloor: 42 } });
const meta = await handle.metadata.get();
3. 完整 API 参考
详细参数与返回值请查阅:
4. 移动端性能建议
TauriTavern 同时运行在桌面和移动端(Android/iOS),以下建议让你的扩展在移动端也流畅运行:
- 检索和定位交给后端 API(
findLastMessage/searchMessages),避免在 JS 侧做 O(N) 扫描 - 大状态用
store.setJson()持久化,不要塞进消息体,小状态优先metadata.* - 使用
scanLimit控制扫描上限,保护低端设备性能
5. 移动端布局适配(Safe‑Area / Viewport / IME)
如果你的扩展包含悬浮球、全屏面板、弹窗、同源 iframe 小应用等 UI,推荐接入 TauriTavern 的 Layout ABI:
- ✅ 推荐(DX):直接使用
/scripts/tauritavern/layout-kit.js(减少样板代码、避免字符串 typo) - ✅ 硬 ABI(长期稳定):
data-tt-mobile-surface+--tt-inset-* / --tt-base-viewport-height / --tt-ime-bottom - ✅ 稳定 API:
window.__TAURITAVERN__.api.layout(读取/订阅 safe-area、viewport、IME 快照;layout-kit 内部也会用它)
5.1 几行接入:把你的 UI 归类为 surface
全屏面板:
import { waitForHostReady, SURFACE, applySurface } from '/scripts/tauritavern/layout-kit.js';
await waitForHostReady();
applySurface(panelEl, SURFACE.FullscreenWindow);
applySurface(backdropEl, SURFACE.Backdrop);
悬浮球/小窗:
applySurface(bubbleEl, SURFACE.FreeWindow);
同源 iframe(只承诺 same-origin):
applySurface(iframe, SURFACE.ViewportHost);
iframe.src = '/scripts/extensions/third-party/my-ext/ui/index.html';
提示:宿主的 overlay classifier 会保持克制(避免全局 subtree observer)。框架型扩展请优先显式写入
data-tt-mobile-surface,不要依赖宿主“猜中”你的结构。
5.2 订阅布局快照(用于 clamp / 自定义动画等)
import { subscribeLayout } from '/scripts/tauritavern/layout-kit.js';
const unsubscribe = await subscribeLayout((snap) => {
// snap.safeInsets / snap.safeFrame / snap.ime.keyboardOffset
});
完整参考:
- 📖 Layout API —
api.layout+ mobile surface contract 说明