TauriTavern/docs/API/Migration.md

5.2 KiB
Raw Permalink Blame History

从 SillyTavern 扩展适配到 TauriTavern

本指南帮你把现有 SillyTavern 扩展快速适配到 TauriTavern,解锁 SillyTavern 没有的强大能力。

好消息:大部分适配工作就是把你以前手写的"找消息、搜消息、存数据"逻辑,替换成一行 API 调用。代码通常会变得更短、更清晰。

快速对照表

先看你的代码里有没有这些模式——如果有,TauriTavern 提供了更好的原生替代:

你以前的做法 问题 TauriTavern 替代
chat.slice().reverse().find(...) 手动复制并遍历长数组 ✅ handle.locate.findLastMessage()
手写关键词 filter/includes CJK 不友好,无评分排序 ✅ handle.searchMessages()
数据塞进消息体 + saveChat() 膨胀 payload,数据与消息耦合 ✅ handle.store.setJson()
hack 写入 chat_metadata 容量有限,语义不清 ✅ handle.metadata.setExtension()
反复读取 context.chat.length 已准确,但需要稳定 handle/summary 时可走后端 windowInfo().totalCount 或 handle.summary()
为后台任务复制 context.chat.slice(-N) 会额外复制消息对象引用 ✅ handle.history.tail({ limit: N })

背景:完整历史与增强查询

TauriTavern 与 SillyTavern 1.18.0 一样,保证 getContext().chat 包含当前聊天的完整、有序消息历史。因此,依赖完整数组与绝对索引的上游扩展无需为了数据窗口做兼容分支。

增强 API(findLastMessage、searchMessages、history.*)不是修复不完整数组的旁路,而是可选的高效后端能力:它们适合有界扫描、CJK 检索、分页任务或避免在扩展中建立第二份大数组。

另一个容易踩坑的点:

  • 如果你的扩展会修改聊天消息本身并希望落盘(例如清理/脱敏/批量替换),请调用 await getContext().saveChat()(它会走宿主的统一保存队列)。
  • 不要从 script.js 里直接 import/调用 saveChat():这属于内部实现细节,也会绕过公开 context 边界与调用语义。

第 1 步:初始化 API

await (window.__TAURITAVERN__?.ready ?? window.__TAURITAVERN_MAIN_READY__);
const api = window.__TAURITAVERN__.api.chat;
const handle = api.current.handle();

💡 两端兼容:用 if (window.__TAURITAVERN__) 判断运行环境,让同一个扩展在 SillyTavern 和 TauriTavern 上都能工作。

第 2 步:替换"找最后一条消息"

这是最常见的适配场景——几乎所有记忆/数据库类扩展都在做这件事。

之前:

const hit = context.chat.slice().reverse().find(
  msg => msg.extra?.MyExtData
);

之后:

const hit = await handle.locate.findLastMessage({
  role: 'assistant',
  hasExtraKeys: ['MyExtData'],
  scanLimit: 2000,
});
// hit = { index, message } 或 null

Rust 后端处理,即使万条消息也毫秒响应。

第 3 步:替换关键词搜索

之前:

const results = context.chat.filter(msg =>
  msg.mes.includes('关键词')
);

之后:

const hits = await handle.searchMessages({
  query: '关键词 或 中文短语',
  limit: 20,
  filters: { role: 'assistant', scanLimit: 5000 },
});

内置 CJK 优化,自动分词,带匹配评分和摘要片段。

第 4 步:迁移数据持久化

这是最令人兴奋的改进——SillyTavern 从未提供 标准的扩展数据持久化机制。

大数据 → store.*

之前(常见 hack):

// 把数据塞进最后一条消息...
lastMsg.extra.myData = hugeJsonObject;
saveChat(); // 膨胀聊天文件

之后:

await handle.store.setJson({
  namespace: 'my-ext',
  key: 'index',
  value: hugeJsonObject,
});

独立存储,不影响聊天文件大小。

小配置 → metadata.*

await handle.metadata.setExtension({
  namespace: 'my-ext',
  value: { lastProcessedFloor: 42, enabled: true },
});

存储在 chat_metadata.extensions[namespace],语义清晰。

第 5 步:索引语义

如果你的扩展会持久化"已处理到第几楼"之类的进度,注意使用绝对索引:

const info = await api.current.windowInfo();
const total = info.totalCount;       // 全量消息数
const absIndex = info.windowStartIndex + chatIndex;  // 当前固定等于 chatIndex

⚠️ 持久化时使用 0-based 绝对消息索引;JSONL header 不计入索引。

第 6 步(可选):历史遍历

如果你确实需要遍历历史消息,使用分页读取:

let page = await handle.history.tail({ limit: 100 });
while (page.hasMoreBefore) {
  for (const msg of page.messages) { /* 处理消息 */ }
  page = await handle.history.before(page, { limit: 100 });
}

移动端推荐批量拉取减少 IPC 调用:

const pages = await handle.history.beforePages(page, { limit: 200, pages: 5 });

移动端性能建议

TauriTavern 运行在桌面和移动端,几条简单规则让你的扩展在低端设备上也流畅:

  • ✅ 用 findLastMessage / searchMessages 替代 JS 侧遍历
  • ✅ 用 store.* 存大数据,不塞消息体
  • ✅ 总是设置 scanLimit,避免全量扫描
  • ✅ 用 beforePages() 批量分页,减少 IPC 往返