import { create } from 'zustand'; import { persist } from 'zustand/middleware'; /** * 动态表格 Store * 管理 SillyTavern 风格的标签数据 */ const useTableStore = create( persist( (set, get) => ({ // ==================== 状态 ==================== // 标签数组 tags: [], // 当前角色和聊天 currentRole: null, currentChat: null, // 加载状态 isLoading: false, // 最后更新时间 lastUpdated: null, // ==================== Actions ==================== /** * 加载标签数据 * @param {string} role - 角色名 * @param {string} chat - 聊天名 */ loadTags: async (role, chat) => { if (!role) { set({ tags: [], currentRole: null, currentChat: null }); return; } set({ isLoading: true, currentRole: role, currentChat: chat }); try { let fetchedTags = []; // 优先级1:从聊天文件读取 if (chat) { console.log('[TableStore] 从聊天文件读取标签数据'); const response = await fetch(`/api/chat/${encodeURIComponent(role)}/${encodeURIComponent(chat)}`); if (!response.ok) throw new Error('获取聊天数据失败'); const chatData = await response.json(); const header = chatData.header || {}; fetchedTags = header.tags || []; if (fetchedTags.length > 0) { set({ tags: fetchedTags, lastUpdated: Date.now(), isLoading: false }); console.log(`[TableStore] ✅ 已加载 ${fetchedTags.length} 个标签`); return; } } // 优先级2:从角色卡读取(降级) console.log('[TableStore] 聊天中无标签,从角色卡读取'); const charResponse = await fetch(`/api/characters/${encodeURIComponent(role)}`); if (!charResponse.ok) throw new Error('获取角色卡失败'); const charData = await charResponse.json(); fetchedTags = charData.tags || []; set({ tags: fetchedTags, lastUpdated: Date.now(), isLoading: false }); console.log(`[TableStore] ✅ 已从角色卡加载 ${fetchedTags.length} 个标签`); } catch (error) { console.error('[TableStore] 获取标签数据失败:', error); set({ tags: [], isLoading: false }); } }, /** * 保存标签到后端(带防抖) * @param {Array} newTags - 新的标签数组 */ saveTags: (newTags) => { const { currentRole, currentChat } = get(); if (!currentRole || !currentChat) { console.warn('[TableStore] 无法保存:缺少角色或聊天信息'); return; } // 清除之前的定时器(如果存在) if (get().saveTimeoutId) { clearTimeout(get().saveTimeoutId); } // 延迟 1.5 秒后保存 const timeoutId = setTimeout(async () => { try { const response = await fetch( `/api/chat/${encodeURIComponent(currentRole)}/${encodeURIComponent(currentChat)}/table`, { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ tags: newTags }) } ); if (!response.ok) throw new Error('保存失败'); set({ lastUpdated: Date.now() }); console.log('[TableStore] ✅ 标签已保存到后端'); } catch (error) { console.error('[TableStore] 保存标签失败:', error); alert('保存失败: ' + error.message); } }, 1500); // 保存定时器 ID set({ saveTimeoutId: timeoutId }); }, /** * 更新标签(乐观更新 + 延迟保存) * @param {Array} newTags - 新的标签数组 */ updateTags: (newTags) => { set({ tags: newTags }); get().saveTags(newTags); }, /** * 添加标签 * @param {string|string[]} tagOrTags - 单个标签或标签数组 */ addTag: (tagOrTags) => { const { tags } = get(); // 支持批量添加 const newTagsList = Array.isArray(tagOrTags) ? tagOrTags : [tagOrTags]; const newTags = [...tags, ...newTagsList]; get().updateTags(newTags); }, /** * 删除标签 * @param {number} index - 标签索引 */ deleteTag: (index) => { const { tags } = get(); const newTags = tags.filter((_, i) => i !== index); get().updateTags(newTags); }, /** * 移动标签 * @param {number} fromIndex - 起始索引 * @param {number} toIndex - 目标索引 */ moveTag: (fromIndex, toIndex) => { const { tags } = get(); if (fromIndex < 0 || toIndex < 0 || fromIndex >= tags.length || toIndex >= tags.length) { return; } const newTags = [...tags]; [newTags[fromIndex], newTags[toIndex]] = [newTags[toIndex], newTags[fromIndex]]; get().updateTags(newTags); }, /** * 左移标签 * @param {number} index - 标签索引 */ moveLeft: (index) => { if (index === 0) return; get().moveTag(index, index - 1); }, /** * 右移标签 * @param {number} index - 标签索引 */ moveRight: (index) => { const { tags } = get(); if (index === tags.length - 1) return; get().moveTag(index, index + 1); }, /** * 编辑标签 * @param {number} index - 标签索引 * @param {string} newValue - 新值 */ editTag: (index, newValue) => { const { tags } = get(); const newTags = [...tags]; newTags[index] = newValue.trim(); get().updateTags(newTags); }, /** * 清空状态 */ clear: () => { // 清除定时器 if (get().saveTimeoutId) { clearTimeout(get().saveTimeoutId); } set({ tags: [], currentRole: null, currentChat: null, isLoading: false, lastUpdated: null, saveTimeoutId: null }); }, /** * 强制刷新(立即从后端重新加载) */ refresh: async () => { const { currentRole, currentChat } = get(); if (!currentRole) return; await get().loadTags(currentRole, currentChat); } }), { name: 'table-storage', // localStorage key partialize: (state) => ({ tags: state.tags, currentRole: state.currentRole, currentChat: state.currentChat, lastUpdated: state.lastUpdated }) } ) ); export default useTableStore;