TauriTavern/docs/API/Migration.md
Darkatse 5013efde87 chore(sync): finalize SillyTavern 1.19 compatibility
Sync secret inputs, info block animations, and remaining locale and help links.
Declare 1.19.0 compatibility and align architecture docs and localized READMEs.

Validation: pnpm run check; git diff --check.
Packaging and device tests skipped as requested.
2026-09-27 23:17:48 -04:00

156 lines
5.2 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 从 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 一样,保证 **`getContext().chat` 包含当前聊天的完整、有序消息历史**。因此,依赖完整数组与绝对索引的上游扩展无需为了数据窗口做兼容分支。
增强 API(`findLastMessage`、`searchMessages`、`history.*`)不是修复不完整数组的旁路,而是可选的高效后端能力:它们适合有界扫描、CJK 检索、分页任务或避免在扩展中建立第二份大数组。
另一个容易踩坑的点:
- 如果你的扩展会**修改聊天消息本身**并希望落盘(例如清理/脱敏/批量替换),请调用 `await getContext().saveChat()`(它会走宿主的统一保存队列)。
- 不要从 `script.js` 里直接 import/调用 `saveChat()`:这属于内部实现细节,也会绕过公开 context 边界与调用语义。
## 第 1 步:初始化 API
```js
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 步:替换"找最后一条消息"
这是最常见的适配场景——几乎所有记忆/数据库类扩展都在做这件事。
**之前**:
```js
const hit = context.chat.slice().reverse().find(
msg => msg.extra?.MyExtData
);
```
**之后**:
```js
const hit = await handle.locate.findLastMessage({
role: 'assistant',
hasExtraKeys: ['MyExtData'],
scanLimit: 2000,
});
// hit = { index, message } 或 null
```
Rust 后端处理,即使万条消息也毫秒响应。
## 第 3 步:替换关键词搜索
**之前**:
```js
const results = context.chat.filter(msg =>
msg.mes.includes('关键词')
);
```
**之后**:
```js
const hits = await handle.searchMessages({
query: '关键词 或 中文短语',
limit: 20,
filters: { role: 'assistant', scanLimit: 5000 },
});
```
内置 CJK 优化,自动分词,带匹配评分和摘要片段。
## 第 4 步:迁移数据持久化
这是最令人兴奋的改进——SillyTavern **从未提供** 标准的扩展数据持久化机制。
### 大数据 → `store.*`
**之前**(常见 hack):
```js
// 把数据塞进最后一条消息...
lastMsg.extra.myData = hugeJsonObject;
saveChat(); // 膨胀聊天文件
```
**之后**:
```js
await handle.store.setJson({
namespace: 'my-ext',
key: 'index',
value: hugeJsonObject,
});
```
独立存储,不影响聊天文件大小。
### 小配置 → `metadata.*`
```js
await handle.metadata.setExtension({
namespace: 'my-ext',
value: { lastProcessedFloor: 42, enabled: true },
});
```
存储在 `chat_metadata.extensions[namespace]`,语义清晰。
## 第 5 步:索引语义
如果你的扩展会持久化"已处理到第几楼"之类的进度,注意使用**绝对索引**:
```js
const info = await api.current.windowInfo();
const total = info.totalCount; // 全量消息数
const absIndex = info.windowStartIndex + chatIndex; // 当前固定等于 chatIndex
```
> ⚠️ 持久化时使用 0-based 绝对消息索引;JSONL header 不计入索引。
## 第 6 步(可选):历史遍历
如果你确实需要遍历历史消息,使用分页读取:
```js
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 调用:
```js
const pages = await handle.history.beforePages(page, { limit: 200, pages: 5 });
```
## 移动端性能建议
TauriTavern 运行在桌面和移动端,几条简单规则让你的扩展在低端设备上也流畅:
- ✅ 用 `findLastMessage` / `searchMessages` 替代 JS 侧遍历
- ✅ 用 `store.*` 存大数据,不塞消息体
- ✅ 总是设置 `scanLimit`,避免全量扫描
- ✅ 用 `beforePages()` 批量分页,减少 IPC 往返