完成大量美化,zustand迁移,动态表格修复
This commit is contained in:
@@ -0,0 +1,255 @@
|
||||
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;
|
||||
Reference in New Issue
Block a user