6.0 KiB
6.0 KiB
Swipe & Reroll 机制对比分析
SillyTavern 官方机制
根据 SillyTavern 的设计,swipe 和 reroll 的核心逻辑如下:
1. Swipe(版本切换)
数据结构:
{
"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 字段显示新内容
示例:
// 初始状态
{
floor: 2,
mes: "版本A的内容",
swipes: ["版本A的内容"],
swipe_id: 0
}
// 用户点击 reroll 后
{
floor: 2, // ← 楼层不变
mes: "版本B的内容", // ← 显示新内容
swipes: [
"版本A的内容",
"版本B的内容" // ← 添加新版本
],
swipe_id: 1 // ← 自动切换到新版本
}
我们的实现
✅ 已正确实现的部分
1. Swipe 切换
// 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 控制按钮
{hasSwipes && !isUser && (
<div className="swipe-controls">
<button onClick={() => handleSwipeChange(message.floor, -1)}>◀</button>
<span>{currentSwipeIndex + 1}/{message.swipes.length}</span>
<button onClick={() => handleSwipeChange(message.floor, 1)}>▶</button>
</div>
)}
✅ 符合 SillyTavern:所有有 swipe 的消息都显示控制按钮
3. 键盘快捷键
// 左键:切换到上一个版本
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
{!contextMenu.message.is_user && (
<div className="context-menu-item" onClick={() => handleRerollMessage(contextMenu.message)}>
<span className="menu-icon">🔄</span>
<span className="menu-label">重roll</span>
</div>
)}
✅ 符合 SillyTavern:只有 AI 消息显示重roll选项
✅ 重roll核心逻辑(刚刚修复)
ChatBoxSlice.jsx - sendMessage
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 事件处理
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 官方机制完全一致!
所有核心功能都已正确实现:
- ✅ Swipe 版本切换
- ✅ Reroll 不创建新楼层
- ✅ Reroll 添加到 swipes 数组
- ✅ 自动切换到新版本
- ✅ 键盘快捷键支持
- ✅ 右键菜单支持
唯一的区别是 UI 样式和交互细节,但核心逻辑完全符合 SillyTavern 的设计规范。