mirror of
https://github.com/Darkatse/TauriTavern.git
synced 2026-10-21 05:08:06 +00:00
Route full saves and metadata updates through one bounded commit protocol, with session-owned staging and atomic publication that preserves message bytes. Simplify behavioral coverage and keep documentation focused on core contracts.
204 lines
7.2 KiB
Markdown
204 lines
7.2 KiB
Markdown
# `window.__TAURITAVERN__.api.chat` — API 参考
|
||
|
||
TauriTavern 为“记忆/数据库/检索类扩展”扩展开发者提供的增强 API。
|
||
|
||
> **核心理念**:把 SillyTavern 扩展开发中最常见的脏活累活——找消息、搜内容、存数据——变成一行 API 调用。Rust 后端处理重活,JS 侧只管调接口。
|
||
|
||
## 0. 快速上手
|
||
|
||
```js
|
||
await (window.__TAURITAVERN__?.ready ?? window.__TAURITAVERN_MAIN_READY__);
|
||
const api = window.__TAURITAVERN__.api.chat;
|
||
const handle = api.current.handle();
|
||
```
|
||
|
||
> TauriTavern 遵循 SillyTavern 的完整历史契约:`getContext().chat` 是当前聊天的完整、有序消息数组。下面的增强 API 适合在 Rust 后端执行有界读取、定位、检索与独立状态持久化,避免扩展重复实现重扫描或复制大数据。
|
||
|
||
## 1. 核心入口
|
||
|
||
启用[历史滑动按需加载](../CurrentState/ChatPayload.md#21-历史滑动按需加载)时,`getContext().chat` 的历史候选槽位可能为 null;本页后端读取 API 仍返回完整内容。
|
||
|
||
| 方法 | 返回值 | 说明 |
|
||
| --- | --- | --- |
|
||
| `api.chat.open(ref)` | `ChatHandle` | 打开指定聊天 |
|
||
| `api.chat.current.ref()` | `ChatRef` | 当前聊天的引用 |
|
||
| `api.chat.current.handle()` | `ChatHandle` | 当前聊天的操作句柄 |
|
||
| `api.chat.current.windowInfo()` | `Promise<WindowInfo>` | 当前完整历史的兼容状态信息 |
|
||
|
||
**`ChatRef` 类型**:
|
||
|
||
- 角色聊天:`{ kind: 'character', characterId, fileName }`
|
||
- 群聊:`{ kind: 'group', chatId }`
|
||
|
||
**`WindowInfo` 类型**:
|
||
|
||
| 字段 | 说明 |
|
||
| --- | --- |
|
||
| `mode` | 固定为 `'off'` |
|
||
| `chatKind` | `'character' \| 'group'` |
|
||
| `chatRef` | 当前 `ChatRef` |
|
||
| `totalCount` | 聊天总消息数(不含 header) |
|
||
| `windowStartIndex` | 固定为 `0` |
|
||
| `windowLength` | 完整消息数,与 `totalCount` 相同 |
|
||
|
||
`windowInfo()` 保留 Promise 与字段形状是为了稳定公开 ABI;它不触发后端 summary 查询,也不对应可配置加载模式。消息数组索引就是绝对索引:
|
||
|
||
```js
|
||
const info = await api.current.windowInfo();
|
||
const absIndex = info.windowStartIndex + chatIndex; // 等同于 chatIndex
|
||
```
|
||
|
||
## 2. ChatHandle 能力
|
||
|
||
通过 `api.open(ref)` 或 `api.current.handle()` 获取 `ChatHandle`。
|
||
|
||
---
|
||
|
||
### `locate.findLastMessage()` — 精准定位,不再手动遍历
|
||
|
||
> 💡 **解决的痛点**:定位最后一条包含特定数据的消息通常需要 `chat.slice().reverse().find(...)` 手动遍历;后端定位可以限定扫描范围,避免扩展重复复制和扫描长数组。
|
||
|
||
```js
|
||
const hit = await handle.locate.findLastMessage({
|
||
role: 'assistant', // 可选:限定角色
|
||
hasExtraKeys: ['TavernDB_ACU_IsolatedData'], // 可选:消息必须包含的 extra 键
|
||
scanLimit: 2000, // 可选:最多扫描多少条
|
||
});
|
||
```
|
||
|
||
**返回值**:
|
||
- 命中:`{ index, message }` — 绝对索引 + 原始消息对象
|
||
- 未命中:`null`
|
||
|
||
全部由 Rust 后端高效执行——即使 10000 条消息也毫无压力。
|
||
|
||
---
|
||
|
||
### `searchMessages()` — 内置全文检索
|
||
|
||
> 💡 **解决的痛点**:SillyTavern 没有内置的消息搜索 API,扩展开发者只能手写 `filter/includes` 扫描,CJK 内容更是无法正常匹配。
|
||
|
||
```js
|
||
const hits = await handle.searchMessages({
|
||
query: '是啊,吃什么',
|
||
limit: 20,
|
||
filters: {
|
||
role: 'assistant', // 可选:限定角色
|
||
startIndex: 100, // 可选:起始索引
|
||
endIndex: 5000, // 可选:结束索引
|
||
scanLimit: 5000, // 可选:从尾部向前最多扫描条数
|
||
},
|
||
});
|
||
```
|
||
|
||
**返回 `SearchHit[]`**:
|
||
|
||
| 字段 | 说明 |
|
||
| --- | --- |
|
||
| `index` | 绝对索引(0-based) |
|
||
| `score` | 匹配评分(0~1,越大越匹配) |
|
||
| `snippet` | 可直接用于 UI 展示的短片段 |
|
||
| `role` | `'user' \| 'assistant' \| 'system' \| 'tool'` |
|
||
| `text` | 命中消息的完整 `mes` 文本 |
|
||
|
||
**技术特点**:
|
||
- 基于片段命中评分 + TopK 召回(非向量检索,轻量高效)
|
||
- CJK / 无空格文本自动 bigram 分词,大幅提升中日韩文匹配率
|
||
- 纯标点或符号 query 中的连续片段按原始字符序列做字面量子串匹配;混合 query 继续使用既有分词评分语义
|
||
- `scanLimit` 控制性能上限,移动端友好
|
||
|
||
---
|
||
|
||
### `store.*` — 扩展数据持久化
|
||
|
||
> 💡 **解决的痛点**:SillyTavern **从未提供** 扩展数据持久化方案。开发者只能把数据塞进消息体(放大 payload、数据耦合),或 hack 写入 `chat_metadata`(容量有限、语义不清)。
|
||
|
||
TauriTavern 提供每个聊天独立的 KV JSON 存储:
|
||
|
||
```js
|
||
// 写入
|
||
await handle.store.setJson({ namespace: 'my-ext', key: 'index', value: largeData });
|
||
|
||
// 读取
|
||
const data = await handle.store.getJson({ namespace: 'my-ext', key: 'index' });
|
||
|
||
// 更新(对象会深度合并;非对象会直接替换)
|
||
await handle.store.updateJson({
|
||
namespace: 'my-ext',
|
||
key: 'index',
|
||
value: { lastFloor: 42, updatedAt: Date.now() },
|
||
});
|
||
|
||
// 重命名 key
|
||
await handle.store.renameKey({
|
||
namespace: 'my-ext',
|
||
key: 'index',
|
||
newKey: 'index-v2',
|
||
});
|
||
|
||
// 列出所有键
|
||
const keys = await handle.store.listKeys({ namespace: 'my-ext' });
|
||
|
||
// 删除
|
||
await handle.store.deleteJson({ namespace: 'my-ext', key: 'old-key' });
|
||
```
|
||
|
||
兼容:`updateJSON()` 是 `updateJson()` 的别名;`updateKey()` 是 `renameKey()` 的别名。
|
||
|
||
**适用场景**:表格、索引、数据库快照等大 JSON 数据。数据与消息彻底解耦,不会膨胀聊天文件。
|
||
|
||
---
|
||
|
||
### `metadata.*` — 轻量配置存储
|
||
|
||
适合存储进度、配置项、短摘要等小状态:
|
||
|
||
```js
|
||
// 读取 chat 元数据
|
||
const meta = await handle.metadata.get();
|
||
|
||
// 写入扩展配置
|
||
await handle.metadata.setExtension({ namespace: 'my-ext', value: { lastFloor: 42 } });
|
||
|
||
// 删除(value 设为 null)
|
||
await handle.metadata.setExtension({ namespace: 'my-ext', value: null });
|
||
```
|
||
|
||
数据存储在 `chat_metadata.extensions[namespace]` 中,与聊天文件一同迁移。每次调用只修改指定 namespace,不自动合并到当前页面的活 metadata。
|
||
|
||
---
|
||
|
||
### `summary()` / `stableId()`
|
||
|
||
```js
|
||
// 获取聊天摘要(不需要加载完整消息)
|
||
const summary = await handle.summary({ includeMetadata: true });
|
||
|
||
// 获取可持久化的稳定 ID
|
||
const id = await handle.stableId();
|
||
```
|
||
|
||
`summary()` 默认只返回轻量摘要。`includeMetadata: true` 返回完整 metadata,读取成本随返回数据大小增长。
|
||
|
||
---
|
||
|
||
### `history.*` — 按需分页读取
|
||
|
||
扩展希望以有界页面处理历史、避免再创建一份全量数组时使用:
|
||
|
||
```js
|
||
let page = await handle.history.tail({ limit: 100 });
|
||
while (page.hasMoreBefore) {
|
||
// page.messages: ChatMessage[]
|
||
// page.startIndex: 本页首条消息的绝对索引
|
||
page = await handle.history.before(page, { limit: 100 });
|
||
}
|
||
```
|
||
|
||
| 方法 | 说明 |
|
||
| --- | --- |
|
||
| `history.tail({ limit })` | 获取最新的 N 条消息 |
|
||
| `history.before(page, { limit })` | 向前翻一页 |
|
||
| `history.beforePages(page, { limit, pages })` | 一次拉多页(减少 IPC 往返,移动端推荐) |
|
||
|
||
返回值包含 `startIndex`(本页首条消息的绝对索引,0-based)和 `hasMoreBefore`(是否还有更早的消息)。
|