# 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 的设计规范。