mirror of
https://github.com/Darkatse/TauriTavern.git
synced 2026-11-06 06:03:17 +00:00
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.
156 lines
5.2 KiB
Markdown
156 lines
5.2 KiB
Markdown
# 从 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 往返
|