Files
SillyTavern_replica/SWIPE_REROLL_COMPARISON.md
2026-05-05 21:51:34 +08:00

6.0 KiB
Raw Blame History

Swipe & Reroll 机制对比分析

SillyTavern 官方机制

根据 SillyTavern 的设计swipe 和 reroll 的核心逻辑如下:

1. Swipe版本切换

数据结构:

{
  "floor": 2,
  "mes": "当前显示的内容",
  "swipes": [
    "第一个版本的回复",
    "第二个版本的回复",
    "第三个版本的回复"
  ],
  "swipe_id": 1  // 当前显示的版本索引从0开始
}

行为规则:

  • mes 字段始终显示当前选中的版本内容
  • swipes 数组存储所有历史版本
  • swipe_id 指向当前在 swipes 数组中的索引
  • 切换 swipe 时只更新 swipe_idmes,不创建新楼层

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 官方机制完全一致!

所有核心功能都已正确实现:

  1. Swipe 版本切换
  2. Reroll 不创建新楼层
  3. Reroll 添加到 swipes 数组
  4. 自动切换到新版本
  5. 键盘快捷键支持
  6. 右键菜单支持

唯一的区别是 UI 样式和交互细节,但核心逻辑完全符合 SillyTavern 的设计规范。