485 lines
15 KiB
JavaScript
485 lines
15 KiB
JavaScript
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;
|