Files
SillyTavern_replica/FIX_GLOBAL_WORLDBOOK_SYNC.md
2026-05-01 15:44:14 +08:00

5.5 KiB
Raw Blame History

全局世界书数据同步问题修复

🐛 问题描述

现象: 当世界书文件被删除后LocalStorage中仍然保存着该世界书的全局状态导致前端显示一个不存在的"幽灵"世界书。

原因: fetchWorldBooks 函数从LocalStorage加载全局世界书后没有清理那些已经不存在于后端的世界书。


解决方案

修复位置

文件: frontend/src/Store/SideBarLeft/WorldBookSlice.jsx
函数: fetchWorldBooks (第132-165行)

修复逻辑

// 从 LocalStorage 获取全局世界书列表
let globalBooks = loadGlobalWorldBooks();

// 清理 LocalStorage 中已不存在的世界书
const existingWorldBookNames = new Set(data.map(wb => wb.name));
const cleanedGlobalBooks = globalBooks.filter(wb => existingWorldBookNames.has(wb.name));

// 如果有被清理的项,更新 LocalStorage
if (cleanedGlobalBooks.length !== globalBooks.length) {
  console.log(`清理了 ${globalBooks.length - cleanedGlobalBooks.length} 个不存在的全局世界书`);
  saveGlobalWorldBooks(cleanedGlobalBooks);
  globalBooks = cleanedGlobalBooks;
}

工作流程

  1. 获取后端数据: 调用 GET /api/worldbooks/ 获取所有存在的世界书
  2. 加载LocalStorage: 从LocalStorage读取全局世界书列表
  3. 对比清理: 过滤掉LocalStorage中存在但后端不存在的世界书
  4. 更新存储: 如果发现有被清理的项更新LocalStorage
  5. 更新State: 将清理后的列表设置到State中

🧪 测试场景

场景1: 正常情况

步骤:

  1. 创建世界书A和B
  2. 将A和B都设为全局
  3. 刷新页面

预期结果:

  • 全局区域显示A和B
  • LocalStorage中有A和B

场景2: 文件被删除

步骤:

  1. 创建世界书A和B
  2. 将A和B都设为全局
  3. 手动删除世界书A的文件或其他方式删除
  4. 刷新页面

预期结果:

  • 全局区域只显示B
  • LocalStorage中只保留B
  • 控制台输出: "清理了 1 个不存在的全局世界书"

场景3: 通过UI删除

步骤:

  1. 创建世界书A和B
  2. 将A和B都设为全局
  3. 在前端UI中删除世界书A
  4. 刷新页面

预期结果:

  • 全局区域只显示B
  • LocalStorage中只保留B
  • 无错误信息

📊 数据流图

┌─────────────────┐
│  页面加载/切换   │
└────────┬────────┘
         │
         ▼
┌─────────────────┐
│ fetchWorldBooks │
└────────┬────────┘
         │
         ├──► GET /api/worldbooks/ ──► 后端返回现有世界书列表
         │
         ├──► loadGlobalWorldBooks() ──► 从LocalStorage读取
         │
         ├──► 对比两个列表
         │    ├─ 存在于LocalStorage但不存在于后端 → 清理
         │    └─ 存在于两者 → 保留
         │
         ├──► saveGlobalWorldBooks() ──► 更新LocalStorage如有变化
         │
         └──► set state ──► 更新UI

🔍 关键代码说明

1. 使用Set提高查找效率

const existingWorldBookNames = new Set(data.map(wb => wb.name));
  • 将后端返回的世界书名称转换为Set
  • Set的查找时间复杂度为O(1)比数组的O(n)更高效

2. 过滤清理

const cleanedGlobalBooks = globalBooks.filter(wb => 
  existingWorldBookNames.has(wb.name)
);
  • 只保留那些在后端也存在的世界书
  • 自动移除"幽灵"世界书

3. 条件更新

if (cleanedGlobalBooks.length !== globalBooks.length) {
  console.log(`清理了 ${globalBooks.length - cleanedGlobalBooks.length} 个不存在的全局世界书`);
  saveGlobalWorldBooks(cleanedGlobalBooks);
  globalBooks = cleanedGlobalBooks;
}
  • 只有在确实有变化时才更新LocalStorage
  • 避免不必要的写入操作
  • 提供调试信息

优势

  1. 自动清理: 无需手动干预自动同步LocalStorage和后端数据
  2. 性能优化: 使用Set提高查找效率
  3. 用户友好: 静默清理,只在控制台输出日志
  4. 数据一致性: 确保LocalStorage中的数据始终与后端保持一致
  5. 无副作用: 不影响正常的业务流程

📝 相关代码位置

LocalStorage操作函数

// 辅助函数:从 LocalStorage 加载全局世界书
const loadGlobalWorldBooks = () => {
  try {
    const stored = localStorage.getItem(GLOBAL_WORLDBOOKS_KEY);
    return stored ? JSON.parse(stored) : [];
  } catch (error) {
    console.error('加载全局世界书失败:', error);
    return [];
  }
};

// 辅助函数:保存全局世界书到 LocalStorage
const saveGlobalWorldBooks = (globalBooks) => {
  try {
    localStorage.setItem(GLOBAL_WORLDBOOKS_KEY, JSON.stringify(globalBooks));
  } catch (error) {
    console.error('保存全局世界书失败:', error);
  }
};

删除世界书时的清理

// deleteWorldBook 函数中已经有清理逻辑
deleteWorldBook: async (name) => {
  // ...
  const filteredGlobalBooks = state.globalWorldBooks.filter(wb => wb.name !== name);
  saveGlobalWorldBooks(filteredGlobalBooks);
  // ...
}

🎯 总结

问题已修复: 全局世界书现在会自动清理不存在的项
数据同步: LocalStorage与后端数据保持一致
用户体验: 不再显示"幽灵"世界书
代码健壮: 增加了数据一致性检查机制