Files
SillyTavern_replica/frontend/src/Store/SideBarLeft/CharacterSlice.jsx
2026-05-01 15:44:14 +08:00

243 lines
7.1 KiB
JavaScript

import { create } from 'zustand';
import { persist } from 'zustand/middleware';
const useCharacterStore = create(
persist(
(set, get) => ({
// 状态
characters: [], // 所有角色卡列表
selectedCharacter: null, // 当前选中的角色
isLoading: false,
error: null,
currentPage: 1, // 当前页码
pageSize: 12, // 每页显示数量
characterChats: {}, // 存储每个角色的聊天记录 {characterName: [chats]}
// 获取所有角色卡
fetchCharacters: async () => {
set({ isLoading: true, error: null });
try {
const response = await fetch('/api/characters/');
if (!response.ok) throw new Error('Failed to fetch characters');
const data = await response.json();
set({
characters: data,
isLoading: false,
currentPage: 1 // 重置到第一页
});
} catch (error) {
set({
error: error.message,
isLoading: false
});
}
},
// 设置当前页码
setCurrentPage: (page) => {
set({ currentPage: page });
},
// 设置每页显示数量
setPageSize: (size) => {
set({ pageSize: size, currentPage: 1 }); // 重置到第一页
},
// 获取当前页的角色数据
getCurrentPageCharacters: () => {
const { characters, currentPage, pageSize } = get();
const startIndex = (currentPage - 1) * pageSize;
const endIndex = startIndex + pageSize;
return characters.slice(startIndex, endIndex);
},
// 获取总页数
getTotalPages: () => {
const { characters, pageSize } = get();
return Math.ceil(characters.length / pageSize);
},
// 获取角色的聊天记录
fetchCharacterChats: async (characterName) => {
const { characterChats } = get();
// 如果已经缓存了,直接返回
if (characterChats[characterName]) {
return characterChats[characterName];
}
try {
const response = await fetch(`/api/characters/${encodeURIComponent(characterName)}/chats`);
if (!response.ok) throw new Error('Failed to fetch chats');
const data = await response.json();
// 更新缓存
set((state) => ({
characterChats: {
...state.characterChats,
[characterName]: data.chats || []
}
}));
return data.chats || [];
} catch (error) {
console.error('获取聊天记录失败:', error);
return [];
}
},
// 选择角色
selectCharacter: (character) => {
// 使用函数式更新避免不必要的重渲染
set((state) => {
// 如果选中的是同一个角色,不更新状态
if (state.selectedCharacter?.id === character.id) {
return state;
}
return { selectedCharacter: character };
});
},
// 创建角色
createCharacter: async (characterData) => {
try {
const response = await fetch('/api/characters/', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(characterData)
});
if (!response.ok) throw new Error('Failed to create character');
const data = await response.json();
// 刷新列表
await get().fetchCharacters();
return data.character;
} catch (error) {
set({ error: error.message });
throw error;
}
},
// 更新角色
updateCharacter: async (name, updates) => {
try {
const response = await fetch(`/api/characters/${encodeURIComponent(name)}`, {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(updates)
});
if (!response.ok) throw new Error('Failed to update character');
// 刷新列表
await get().fetchCharacters();
} catch (error) {
set({ error: error.message });
throw error;
}
},
// 删除角色
deleteCharacter: async (name) => {
try {
const response = await fetch(`/api/characters/${encodeURIComponent(name)}`, {
method: 'DELETE'
});
if (!response.ok) throw new Error('Failed to delete character');
// 刷新列表
await get().fetchCharacters();
} catch (error) {
set({ error: error.message });
throw error;
}
},
// 上传头像
uploadAvatar: async (name, file) => {
try {
const formData = new FormData();
formData.append('file', file);
const response = await fetch(`/api/characters/${encodeURIComponent(name)}/avatar`, {
method: 'POST',
body: formData
});
if (!response.ok) throw new Error('Failed to upload avatar');
// 刷新列表以更新头像路径
await get().fetchCharacters();
} catch (error) {
set({ error: error.message });
throw error;
}
},
// 导入角色卡
importCharacter: async (file) => {
try {
const formData = new FormData();
formData.append('file', file);
const response = await fetch('/api/characters/import', {
method: 'POST',
body: formData
});
if (!response.ok) throw new Error('Failed to import character');
const data = await response.json();
// 刷新列表
await get().fetchCharacters();
return data.character;
} catch (error) {
set({ error: error.message });
throw error;
}
},
// 导出角色卡为 PNG
exportCharacterAsPng: async (name) => {
try {
const response = await fetch(`/api/characters/${encodeURIComponent(name)}/export/png`, {
method: 'POST'
});
if (!response.ok) throw new Error('Failed to export character');
// 下载文件
const blob = await response.blob();
const url = window.URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = `${name}.png`;
document.body.appendChild(a);
a.click();
window.URL.revokeObjectURL(url);
document.body.removeChild(a);
} catch (error) {
set({ error: error.message });
throw error;
}
}
}),
{
name: 'character-storage',
partialize: (state) => ({
selectedCharacter: state.selectedCharacter
})
}
)
);
export default useCharacterStore;