# Swipe & Reroll 机制对比分析
## SillyTavern 官方机制
根据 SillyTavern 的设计,swipe 和 reroll 的核心逻辑如下:
### 1. **Swipe(版本切换)**
**数据结构:**
```json
{
"floor": 2,
"mes": "当前显示的内容",
"swipes": [
"第一个版本的回复",
"第二个版本的回复",
"第三个版本的回复"
],
"swipe_id": 1 // 当前显示的版本索引(从0开始)
}
```
**行为规则:**
- ✅ `mes` 字段始终显示当前选中的版本内容
- ✅ `swipes` 数组存储所有历史版本
- ✅ `swipe_id` 指向当前在 `swipes` 数组中的索引
- ✅ 切换 swipe 时只更新 `swipe_id` 和 `mes`,不创建新楼层
### 2. **Reroll(重新生成)**
**核心原则:**
- ❌ **不创建新的消息楼层**
- ✅ **在当前 AI 消息的 swipes 数组中添加新版本**
- ✅ **使用上一条用户消息作为上下文重新生成**
- ✅ **生成完成后自动切换到新版本**
**流程:**
```
1. 用户触发 reroll(右键菜单或键盘快捷键)
2. 系统找到该 AI 消息的上一条用户消息
3. 发送相同的用户输入到后端
4. 后端生成新的回复
5. 前端将新回复添加到 swipes 数组末尾
6. 自动更新 swipe_id 指向新版本
7. 更新 mes 字段显示新内容
```
**示例:**
```javascript
// 初始状态
{
floor: 2,
mes: "版本A的内容",
swipes: ["版本A的内容"],
swipe_id: 0
}
// 用户点击 reroll 后
{
floor: 2, // ← 楼层不变
mes: "版本B的内容", // ← 显示新内容
swipes: [
"版本A的内容",
"版本B的内容" // ← 添加新版本
],
swipe_id: 1 // ← 自动切换到新版本
}
```
---
## 我们的实现
### ✅ 已正确实现的部分
#### 1. **Swipe 切换**
```javascript
// ChatBox.jsx - handleSwipeChange
const handleSwipeChange = (messageId, direction) => {
const message = messages.find(m => m.floor === messageId);
if (message && message.swipes && message.swipes.length > 0) {
const currentIndex = currentSwipeId[messageId] !== undefined
? currentSwipeId[messageId]
: message.swipe_id;
const newIndex = currentIndex + direction;
if (newIndex >= 0 && newIndex < message.swipes.length) {
setCurrentSwipeId({ [messageId]: newIndex });
}
}
};
```
✅ **符合 SillyTavern**:只更新索引,不修改数据结构
#### 2. **Swipe 控制按钮**
```jsx
{hasSwipes && !isUser && (
{currentSwipeIndex + 1}/{message.swipes.length}
)}
```
✅ **符合 SillyTavern**:所有有 swipe 的消息都显示控制按钮
#### 3. **键盘快捷键**
```javascript
// 左键:切换到上一个版本
if (e.key === 'ArrowLeft' && currentIndex > 0) {
handleSwipeChange(lastAiMessage.floor, -1);
}
// 右键:如果在最后一个版本,触发重roll;否则切换到下一个版本
if (e.key === 'ArrowRight') {
if (currentIndex >= lastAiMessage.swipes.length - 1) {
handleRerollMessage(lastAiMessage); // 触发重roll
} else {
handleSwipeChange(lastAiMessage.floor, 1);
}
}
```
✅ **符合 SillyTavern**:智能判断是否触发重roll
#### 4. **右键菜单重roll**
```jsx
{!contextMenu.message.is_user && (
handleRerollMessage(contextMenu.message)}>
🔄
重roll
)}
```
✅ **符合 SillyTavern**:只有 AI 消息显示重roll选项
### ✅ 重roll核心逻辑(刚刚修复)
#### ChatBoxSlice.jsx - sendMessage
```javascript
sendMessage: async (content, targetFloor = null) => {
const isReroll = targetFloor !== null;
if (isReroll) {
// 重roll模式
assistantFloor = targetFloor;
nextFloor = targetFloor; // ✅ 修复:设置 nextFloor
// 不添加新用户消息
} else {
// 正常模式
nextFloor = get().getNextFloor(messages);
userFloor = nextFloor;
assistantFloor = nextFloor + 1;
// 添加新用户消息和AI消息
}
}
```
#### WebSocket Complete 事件处理
```javascript
if (data.type === 'complete' && isReroll) {
// 获取现有的 swipes 数组
const existingSwipes = msg.swipes || [];
const currentMes = msg.mes;
// 如果当前内容不在 swipes 中,先添加它
let updatedSwipes = [...existingSwipes];
if (!updatedSwipes.includes(currentMes)) {
updatedSwipes.push(currentMes);
}
// 添加新生成的内容
updatedSwipes.push(assistantMessage);
// 更新消息
return {
...msg,
mes: assistantMessage, // 显示新内容
swipes: updatedSwipes, // 更新数组
swipe_id: updatedSwipes.length - 1 // 自动切换到新版本
};
}
```
✅ **完全符合 SillyTavern**:
- ❌ 不创建新楼层
- ✅ 在 swipes 数组中添加新版本
- ✅ 自动切换到新生成的版本
- ✅ 保留历史版本
---
## 对比总结
| 功能 | SillyTavern | 我们的实现 | 状态 |
|------|-------------|-----------|------|
| Swipe 数据结构 | `swipes` 数组 + `swipe_id` | ✅ 相同 | ✅ |
| Swipe 切换 | 只更新索引 | ✅ 相同 | ✅ |
| Swipe 按钮显示 | 所有有 swipe 的消息 | ✅ 相同 | ✅ |
| 键盘左右键切换 | 支持 | ✅ 相同 | ✅ |
| Reroll 不创建新楼层 | ✅ | ✅ | ✅ |
| Reroll 添加到 swipes | ✅ | ✅ | ✅ |
| Reroll 自动切换 | ✅ | ✅ | ✅ |
| Reroll 使用上文 | ✅ | ✅ | ✅ |
| 右键菜单重roll | ✅ | ✅ | ✅ |
| 最后一个swipe按右键重roll | ✅ | ✅ | ✅ |
## 结论
✅ **我们的实现与 SillyTavern 官方机制完全一致!**
所有核心功能都已正确实现:
1. ✅ Swipe 版本切换
2. ✅ Reroll 不创建新楼层
3. ✅ Reroll 添加到 swipes 数组
4. ✅ 自动切换到新版本
5. ✅ 键盘快捷键支持
6. ✅ 右键菜单支持
唯一的区别是 UI 样式和交互细节,但核心逻辑完全符合 SillyTavern 的设计规范。