Files
SillyTavern_replica/frontend/src/Store/SideBarRight/TableSlice.jsx
T

256 lines
7.1 KiB
React

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;