Files
SillyTavern_replica/frontend/src/Store/SideBarLeft/PresetSlice.jsx

455 lines
14 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();
// 记录原始数据用于调试
console.log('从后端获取的预设数据:', presetData);
// 提取参数并更新状态支持内部结构和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
};
// 记录映射后的参数用于调试
console.log('映射后的参数:', parameters);
// 处理预设组件 - 支持内部结构和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]
})),
// 保存当前设置为预设
saveCurrentAsPreset: async ({ name }) => {
const state = get();
try {
// 构建预设数据 - 使用内部专有结构
const presetData = {
// GenerationPreset 部分 - 采样参数
id: `preset_${Date.now()}`,
name: name,
temperature: state.parameters.temperature,
topP: state.parameters.top_p,
topK: state.parameters.top_k,
frequencyPenalty: state.parameters.frequency_penalty,
presencePenalty: state.parameters.presence_penalty,
maxLength: state.parameters.max_tokens,
isDefault: false,
// PromptPresetView 部分 - prompt组件列表
characterId: 'global', // 全局预设,不绑定特定角色
entries: state.promptComponents.map((component, index) => ({
identifier: component.identifier,
name: component.name,
enabled: component.enabled !== false,
content: component.content || '',
order: index,
role: component.role === 0 ? 'system' : component.role === 1 ? 'user' : 'ai',
tokenCount: component.content ? component.content.length : 0,
isSystemNode: component.marker || 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: presetData.id,
name,
description: '',
component_count: state.promptComponents.length,
temperature: state.parameters.temperature
};
set((state) => ({
presets: [...state.presets, newPreset],
selectedPreset: presetData.id
}));
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 };
}),
// 获取当前预设的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;
}
if (state?.selectedPreset) {
console.log(`[PresetStore] 🔄 恢复上次选中的预设: ${state.selectedPreset}`);
// 异步加载预设详情
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;