Files
SillyTavern_replica/frontend/src/Store/SideBarLeft/PresetSlice.jsx
2026-05-06 00:40:18 +08:00

485 lines
15 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { create } from 'zustand';
import { persist } from 'zustand/middleware'; // ✅ 添加持久化支持
const usePresetStore = create(
persist(
(set, get) => ({
// 预设选择
selectedPreset: '',
// 核心参数
parameters: {
temperature: 1.0,
frequency_penalty: 0.0,
presence_penalty: 0.0,
top_p: 1.0,
top_k: 0,
max_context: 1000000,
max_tokens: 30000,
max_context_unlocked: false,
stream_openai: true,
seed: -1,
n: 1
},
// 可用的预设列表 - 初始为空,将从后端加载
presets: [],
// 是否正在加载预设列表
isLoadingPresets: false,
// 参数设置折叠状态
isParametersExpanded: true,
// 分页状态
currentPage: 1,
pageSize: 8,
// 预设组件列表
promptComponents: [
{
identifier: "dialogueExamples",
name: "Chat Examples",
system_prompt: true,
marker: true,
enabled: true,
role: 0
},
{
identifier: "chatHistory",
name: "Chat History",
system_prompt: true,
marker: true,
enabled: true,
role: 0
},
{
identifier: "worldInfoAfter",
name: "World Info (after)",
system_prompt: true,
marker: true,
enabled: true,
role: 0
},
{
identifier: "worldInfoBefore",
name: "World Info (before)",
system_prompt: true,
marker: true,
enabled: true,
role: 0
},
{
identifier: "charDescription",
name: "Char Description",
system_prompt: true,
marker: true,
enabled: true,
role: 0
},
{
identifier: "charPersonality",
name: "Char Personality",
system_prompt: true,
marker: true,
enabled: true,
role: 0
},
{
identifier: "scenario",
name: "Scenario",
system_prompt: true,
marker: true,
enabled: true,
role: 0
},
{
identifier: "personaDescription",
name: "Persona Description",
system_prompt: true,
marker: true,
enabled: true,
role: 0
}
],
// 从后端加载预设列表
fetchPresets: async () => {
set({ isLoadingPresets: true });
try {
const response = await fetch('/api/presets');
const data = await response.json();
// 转换为预设对象数组
const presetList = data.presets.map(preset => ({
id: preset.name,
name: preset.name,
description: preset.description,
component_count: preset.component_count,
temperature: preset.temperature
}));
set({ presets: presetList, isLoadingPresets: false });
} catch (error) {
// console.error('Failed to fetch presets:', error);
set({ isLoadingPresets: false });
}
},
// 设置选中的预设
setSelectedPreset: async (presetId) => {
try {
// 从后端获取预设的完整内容
const response = await fetch(`/api/presets/${presetId}`);
const presetData = await response.json();
// 提取参数并更新状态支持内部结构和SillyTavern结构
const parameters = {
temperature: presetData.temperature !== undefined ? presetData.temperature : 1.0,
frequency_penalty: presetData.frequency_penalty !== undefined ? presetData.frequency_penalty :
(presetData.frequencyPenalty !== undefined ? presetData.frequencyPenalty : 0.0),
presence_penalty: presetData.presence_penalty !== undefined ? presetData.presence_penalty :
(presetData.presencePenalty !== undefined ? presetData.presencePenalty : 0.0),
top_p: presetData.top_p !== undefined ? presetData.top_p :
(presetData.topP !== undefined ? presetData.topP : 1.0),
top_k: presetData.top_k !== undefined ? presetData.top_k :
(presetData.topK !== undefined ? presetData.topK : 0),
max_context: presetData.openai_max_context !== undefined ? presetData.openai_max_context :
(presetData.max_context !== undefined ? presetData.max_context : 1000000),
max_tokens: presetData.openai_max_tokens !== undefined ? presetData.openai_max_tokens :
(presetData.max_tokens !== undefined ? presetData.max_tokens :
(presetData.maxLength !== undefined ? presetData.maxLength : 30000)),
max_context_unlocked: presetData.max_context_unlocked !== undefined ? presetData.max_context_unlocked : false,
stream_openai: presetData.stream_openai !== undefined ? presetData.stream_openai : true,
seed: presetData.seed !== undefined ? presetData.seed : -1,
n: presetData.n !== undefined ? presetData.n : 1
};
// 处理预设组件 - 支持内部结构和SillyTavern结构
let components = [];
// 优先使用内部结构的 entries
if (presetData.entries && Array.isArray(presetData.entries)) {
components = presetData.entries.map(entry => ({
identifier: entry.identifier,
name: entry.name,
content: entry.content || '',
enabled: entry.enabled !== false,
role: entry.role === 'system' ? 0 : entry.role === 'user' ? 1 : 2,
system_prompt: entry.role === 'system',
marker: entry.isSystemNode || false
}));
// 按 order 排序
components.sort((a, b) => {
const orderA = presetData.entries.find(e => e.identifier === a.identifier)?.order || 0;
const orderB = presetData.entries.find(e => e.identifier === b.identifier)?.order || 0;
return orderA - orderB;
});
}
// 兼容SillyTavern结构的 prompts
else if (presetData.prompts && Array.isArray(presetData.prompts)) {
// 获取当前角色的prompt_order添加更严格的检查
const currentOrder = (presetData.prompt_order &&
Array.isArray(presetData.prompt_order) &&
presetData.prompt_order.length > 0 &&
presetData.prompt_order[0] &&
presetData.prompt_order[0].order &&
Array.isArray(presetData.prompt_order[0].order))
? presetData.prompt_order[0].order
: [];
// 根据prompt_order排序组件
components = presetData.prompts.map(prompt => {
const orderItem = currentOrder.find(item => item && item.identifier === prompt.identifier);
return {
...prompt,
enabled: orderItem ? orderItem.enabled : true,
role: prompt.role !== undefined ? prompt.role : (prompt.system_prompt ? 0 : 1)
};
});
// 如果有prompt_order按照它排序
if (currentOrder.length > 0) {
components.sort((a, b) => {
const indexA = currentOrder.findIndex(item => item && item.identifier === a.identifier);
const indexB = currentOrder.findIndex(item => item && item.identifier === b.identifier);
return indexA - indexB;
});
}
}
// 更新状态,确保参数容器展开
set({
selectedPreset: presetId,
parameters,
promptComponents: components,
isParametersExpanded: true // 确保参数容器展开
});
} catch (error) {
// console.error('Failed to load preset:', error);
}
},
// 更新参数
updateParameter: ({ name, value }) => set((state) => ({
parameters: { ...state.parameters, [name]: value }
})),
// 添加预设
addPreset: (preset) => set((state) => ({
presets: [...state.presets, preset]
})),
// 保存当前设置为预设 - 使用 SillyTavern 标准格式
saveCurrentAsPreset: async ({ name }) => {
const state = get();
try {
// 构建 SillyTavern 标准格式的预设数据
const presetData = {
// 基本参数 - 使用 SillyTavern 标准字段名
name: name,
temperature: state.parameters.temperature,
frequency_penalty: state.parameters.frequency_penalty,
presence_penalty: state.parameters.presence_penalty,
top_p: state.parameters.top_p,
top_k: state.parameters.top_k,
max_tokens: state.parameters.max_tokens,
request_timeout: state.parameters.request_timeout || 60,
// SillyTavern 标准的 prompts 数组
prompts: state.promptComponents.map((component) => ({
identifier: component.identifier,
name: component.name,
content: component.content || '',
system_prompt: component.role === 0,
role: component.role === 0 ? 'system' : component.role === 1 ? 'user' : 'assistant',
enabled: component.enabled !== false
})),
// prompt_order - SillyTavern 用于管理顺序和启用状态
prompt_order: [{
character_id: 'global',
order: state.promptComponents.map(component => ({
identifier: component.identifier,
enabled: component.enabled !== false
}))
}]
};
// 发送到后端
const response = await fetch('/api/presets', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(presetData)
});
if (!response.ok) {
throw new Error('Failed to save preset');
}
const result = await response.json();
// 添加到本地预设列表
const newPreset = {
id: name, // 使用名称作为 ID
name,
description: '',
component_count: state.promptComponents.length,
temperature: state.parameters.temperature
};
set((state) => ({
presets: [...state.presets, newPreset],
selectedPreset: name
}));
return result;
} catch (error) {
// console.error('Failed to save preset:', error);
throw error;
}
},
// 编辑预设名称
editPresetName: async (presetId, newName) => {
try {
const response = await fetch(`/api/presets/${presetId}`, {
method: 'PUT',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
name: newName
})
});
if (!response.ok) {
throw new Error('Failed to update preset name');
}
// 更新本地状态
set((state) => ({
presets: state.presets.map(preset =>
preset.id === presetId ? { ...preset, name: newName } : preset
)
}));
return await response.json();
} catch (error) {
// console.error('Failed to update preset name:', error);
throw error;
}
},
// 切换参数设置折叠状态
toggleParametersExpanded: () => set((state) => ({
isParametersExpanded: !state.isParametersExpanded
})),
// 设置预设组件列表
setPromptComponents: (components) => set({ promptComponents: components }),
// 更新组件
updateComponent: (index, updatedComponent) => set((state) => {
const newComponents = [...state.promptComponents];
newComponents[index] = { ...newComponents[index], ...updatedComponent };
return { promptComponents: newComponents };
}),
// 切换组件启用状态
toggleComponentEnabled: (index) => set((state) => {
const newComponents = [...state.promptComponents];
newComponents[index] = {
...newComponents[index],
enabled: !newComponents[index].enabled
};
return { promptComponents: newComponents };
}),
// 添加新组件
addComponent: (component) => set((state) => ({
promptComponents: [...state.promptComponents, component]
})),
// 删除组件
removeComponent: (index) => set((state) => {
const newComponents = [...state.promptComponents];
newComponents.splice(index, 1);
return { promptComponents: newComponents };
}),
// 移动组件位置
moveComponent: (fromIndex, toIndex) => set((state) => {
const newComponents = [...state.promptComponents];
const [movedComponent] = newComponents.splice(fromIndex, 1);
newComponents.splice(toIndex, 0, movedComponent);
return { promptComponents: newComponents };
}),
// 保存组件排序到后端
saveComponentOrder: async () => {
const state = get();
if (!state.selectedPreset) {
// console.warn('No preset selected, cannot save order');
return;
}
try {
// 提取组件 identifier 列表,按当前顺序
const componentOrder = state.promptComponents.map(component => component.identifier);
const response = await fetch(`/api/presets/${state.selectedPreset}/reorder`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
component_order: componentOrder
})
});
if (!response.ok) {
throw new Error('Failed to save component order');
}
const result = await response.json();
// console.log('Component order saved successfully:', result);
return result;
} catch (error) {
// console.error('Failed to save component order:', error);
throw error;
}
},
// 获取当前预设的prompt_order
getPromptOrder: () => {
const { promptComponents } = get();
return promptComponents.map(component => ({
identifier: component.identifier,
enabled: component.enabled !== false
}));
},
// 设置当前页
setCurrentPage: (page) => set({ currentPage: page }),
// 设置每页数量
setPageSize: (size) => set({ pageSize: size, currentPage: 1 }),
// 获取当前页的预设列表
getCurrentPagePresets: () => {
const { presets, currentPage, pageSize } = get();
const startIndex = (currentPage - 1) * pageSize;
const endIndex = startIndex + pageSize;
return presets.slice(startIndex, endIndex);
},
// 获取总页数
getTotalPages: () => {
const { presets, pageSize } = get();
return Math.ceil(presets.length / pageSize);
}
}), // ✅ 闭合 (set, get) => ({...})
{
name: 'PresetStore', // localStorage 中的键名
partialize: (state) => ({
// ✅ 持久化选中的预设名称
selectedPreset: state.selectedPreset,
// ✅ 持久化参数设置
parameters: state.parameters,
// ✅ 持久化提示词组件配置
promptComponents: state.promptComponents,
// ✅ 持久化折叠状态
isParametersExpanded: state.isParametersExpanded
}),
// ✅ 恢复时的回调
onRehydrateStorage: () => {
return (state, error) => {
if (error) {
console.error('[PresetStore] 恢复状态失败:', error);
return;
}
// 异步加载预设详情
setTimeout(() => {
usePresetStore.getState().fetchPresets().then(() => {
// 加载完列表后,重新选择之前选中的预设以加载其详细配置
if (state.selectedPreset) {
usePresetStore.getState().setSelectedPreset(state.selectedPreset).catch(err => {
console.error('[PresetStore] 加载预设详情失败:', err);
});
}
}).catch(err => {
console.error('[PresetStore] 加载预设列表失败:', err);
});
}, 0);
};
}
}
) // ✅ 闭合 persist(
);
export default usePresetStore;