新增预设管理功能

This commit is contained in:
2025-11-11 10:51:02 +08:00
parent 0a93c4f5c0
commit 3d36b8f1c9
6 changed files with 2565 additions and 61 deletions
+19 -5
View File
@@ -2,9 +2,10 @@
import { ref, onMounted } from 'vue' import { ref, onMounted } from 'vue'
import PromptEditor from './components/PromptEditor.vue' import PromptEditor from './components/PromptEditor.vue'
import PromptManager from './components/PromptManager.vue' import PromptManager from './components/PromptManager.vue'
import PresetManager from './components/PresetManager.vue'
import { usePromptStore } from './stores/promptStore' import { usePromptStore } from './stores/promptStore'
const currentView = ref<'editor' | 'manager'>('editor') const currentView = ref<'editor' | 'manager' | 'presets'>('editor')
const isDark = ref(false) const isDark = ref(false)
const store = usePromptStore() const store = usePromptStore()
@@ -27,7 +28,7 @@ function updateTheme() {
document.documentElement.classList.toggle('dark', isDark.value) document.documentElement.classList.toggle('dark', isDark.value)
} }
function switchView(view: 'editor' | 'manager') { function switchView(view: 'editor' | 'manager' | 'presets') {
currentView.value = view currentView.value = view
} }
</script> </script>
@@ -63,7 +64,7 @@ function switchView(view: 'editor' | 'manager') {
<path d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/> <path d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
<path d="m18.5 2.5 3 3L12 15l-4 1 1-4 9.5-9.5z" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/> <path d="m18.5 2.5 3 3L12 15l-4 1 1-4 9.5-9.5z" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</svg> </svg>
编辑器 <span>编辑器</span>
</button> </button>
<button <button
class="nav-btn" class="nav-btn"
@@ -73,7 +74,19 @@ function switchView(view: 'editor' | 'manager') {
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"> <svg width="16" height="16" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M3 3h18v18H3zM9 9h6v6H9z" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/> <path d="M3 3h18v18H3zM9 9h6v6H9z" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</svg> </svg>
词库管理 <span>词库管理</span>
</button>
<button
class="nav-btn"
:class="{ active: currentView === 'presets' }"
@click="switchView('presets')"
>
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M19 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11l5 5v11a2 2 0 0 1-2 2z" stroke="currentColor" stroke-width="2"/>
<polyline points="17,21 17,13 7,13 7,21" stroke="currentColor" stroke-width="2"/>
<polyline points="7,3 7,8 15,8" stroke="currentColor" stroke-width="2"/>
</svg>
<span>预设管理</span>
</button> </button>
</nav> </nav>
@@ -95,7 +108,8 @@ function switchView(view: 'editor' | 'manager') {
<main class="app-main"> <main class="app-main">
<Transition name="view-transition" mode="out-in"> <Transition name="view-transition" mode="out-in">
<PromptEditor v-if="currentView === 'editor'" key="editor" /> <PromptEditor v-if="currentView === 'editor'" key="editor" />
<PromptManager v-else key="manager" /> <PromptManager v-else-if="currentView === 'manager'" key="manager" />
<PresetManager v-else-if="currentView === 'presets'" key="presets" />
</Transition> </Transition>
</main> </main>
</div> </div>
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+54 -55
View File
@@ -3,6 +3,7 @@ import { onMounted, onUnmounted, ref, computed, nextTick, watch } from 'vue';
import { usePromptStore } from '../stores/promptStore'; import { usePromptStore } from '../stores/promptStore';
import type { LangCode } from '../types'; import type { LangCode } from '../types';
import NotificationToast from './NotificationToast.vue'; import NotificationToast from './NotificationToast.vue';
import PresetDropdown from './PresetDropdown.vue';
const store = usePromptStore(); const store = usePromptStore();
const draggingIndex = ref<number | null>(null); const draggingIndex = ref<number | null>(null);
@@ -14,9 +15,6 @@ const editingValue = ref('');
const addingMapIndex = ref<number | null>(null); const addingMapIndex = ref<number | null>(null);
const addingMapValue = ref(''); const addingMapValue = ref('');
const presetName = ref(''); const presetName = ref('');
const presetSearch = ref('');
const renamingPreset = ref<string | null>(null);
const renamingValue = ref('');
const viewMode = ref<'compact' | 'detail'>('compact'); const viewMode = ref<'compact' | 'detail'>('compact');
const showPresetDropdown = ref(false); const showPresetDropdown = ref(false);
const notification = ref<{ message: string; type: 'success' | 'error' | 'info'; show: boolean }>({ const notification = ref<{ message: string; type: 'success' | 'error' | 'info'; show: boolean }>({
@@ -35,7 +33,8 @@ function showNotification(message: string, type: 'success' | 'error' | 'info' =
// 点击外部关闭下拉菜单 // 点击外部关闭下拉菜单
function handleClickOutside(event: Event) { function handleClickOutside(event: Event) {
const target = event.target as HTMLElement; const target = event.target as HTMLElement;
if (!target.closest('.pe-presets')) { // 检查点击是否在预设下拉区域内,包括重命名输入框和按钮
if (!target.closest('.pe-presets') && !target.closest('.pd-dropdown')) {
showPresetDropdown.value = false; showPresetDropdown.value = false;
} }
} }
@@ -55,12 +54,6 @@ const selectedLang = computed({
}); });
const tokens = computed(() => store.tokens); const tokens = computed(() => store.tokens);
const filteredPresets = computed(() => {
const q = presetSearch.value.trim().toLowerCase();
const list = [...store.presets].sort((a, b) => (a.updatedAt < b.updatedAt ? 1 : -1));
if (!q) return list;
return list.filter((p) => p.name.toLowerCase().includes(q));
});
const suggestions = ref<string[]>([]); const suggestions = ref<string[]>([]);
const inputEl = ref<HTMLTextAreaElement | null>(null); const inputEl = ref<HTMLTextAreaElement | null>(null);
@@ -278,31 +271,52 @@ function savePreset() {
showNotification('请输入预设名称', 'error'); showNotification('请输入预设名称', 'error');
return; return;
} }
store.savePreset(presetName.value.trim());
showNotification(`预设「${presetName.value.trim()}」已保存`, 'success'); const name = presetName.value.trim();
// 只保存到新的扩展预设系统
const defaultFolder = store.presetManagement?.settings?.defaultFolder;
store.createExtendedPreset({
name: name,
type: 'positive',
content: store.promptText,
description: '从编辑器快速保存',
folderId: defaultFolder
});
showNotification(`预设「${name}」已保存到预设管理`, 'success');
presetName.value = ''; presetName.value = '';
} }
function loadPreset(name: string) {
store.loadPreset(name); // 预设下拉组件的事件处理
text.value = store.promptText; function handlePresetLoad(name: string) {
// 优先从扩展预设中查找
const extendedPreset = store.extendedPresets.find(p => p.name === name);
if (extendedPreset) {
store.setPromptTextRaw(extendedPreset.content);
text.value = extendedPreset.content;
} else {
// 回退到旧预设系统
store.loadPreset(name);
text.value = store.promptText;
}
showNotification(`已加载预设「${name}`, 'success'); showNotification(`已加载预设「${name}`, 'success');
} }
function deletePreset(name: string) {
if (confirm(`确定删除预设「${name}」吗?`)) { function handlePresetSave(name: string) {
store.deletePreset(name); store.savePreset(name);
showNotification(`预设「${name}」已删除`, 'info'); showNotification(`预设「${name}」已保存`, 'success');
}
} }
function beginRename(name: string) { renamingPreset.value = name; renamingValue.value = name; }
function commitRename() { function handlePresetDelete(name: string) {
if (!renamingPreset.value) return; store.deletePreset(name);
const oldName = renamingPreset.value; showNotification(`预设「${name}」已删除`, 'info');
const newName = renamingValue.value.trim(); }
if (!newName) { alert('预设名称不能为空'); return; }
function handlePresetRename(oldName: string, newName: string) {
store.renamePreset(oldName, newName); store.renamePreset(oldName, newName);
renamingPreset.value = null; renamingValue.value = ''; showNotification(`预设已重命名为「${newName}`, 'success');
} }
function cancelRename() { renamingPreset.value = null; }
async function applySuggestion(s: string) { async function applySuggestion(s: string) {
const el = inputEl.value; const el = inputEl.value;
@@ -355,12 +369,12 @@ function displayTrans(key: string): string {
<button <button
class="pe-preset-toggle" class="pe-preset-toggle"
@click="showPresetDropdown = !showPresetDropdown" @click="showPresetDropdown = !showPresetDropdown"
title="管理预设" title="快速预设"
> >
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"> <svg width="16" height="16" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M3 4h18v2H3V4zm0 7h18v2H3v-2zm0 7h18v2H3v-2z" fill="currentColor"/> <path d="M3 4h18v2H3V4zm0 7h18v2H3v-2zm0 7h18v2H3v-2z" fill="currentColor"/>
</svg> </svg>
预设管理 快速预设
<svg <svg
width="12" height="12" width="12" height="12"
viewBox="0 0 24 24" viewBox="0 0 24 24"
@@ -373,31 +387,14 @@ function displayTrans(key: string): string {
</svg> </svg>
</button> </button>
<Transition name="dropdown"> <PresetDropdown
<div v-if="showPresetDropdown" class="pe-preset-dropdown"> :show="showPresetDropdown"
<div class="pe-preset-search-wrapper"> @close="showPresetDropdown = false"
<input class="pe-preset-search" placeholder="搜索预设..." v-model="presetSearch" /> @load="handlePresetLoad"
</div> @save="handlePresetSave"
<div class="pe-preset-list" v-if="filteredPresets.length"> @delete="handlePresetDelete"
<div v-for="p in filteredPresets" :key="p.name" class="pe-preset-item"> @rename="handlePresetRename"
<template v-if="renamingPreset !== p.name"> />
<button class="pe-preset-load" title="加载" @click="loadPreset(p.name)">{{ p.name }}</button>
<span class="pe-preset-meta">{{ new Date(p.updatedAt).toLocaleString() }}</span>
<button class="pe-preset-rename" title="重命名" @click="beginRename(p.name)"></button>
<button class="pe-preset-delete" title="删除" @click="deletePreset(p.name)">🗑</button>
</template>
<template v-else>
<input class="pe-preset-rename-input" v-model="renamingValue" @keyup.enter="commitRename" />
<button class="pe-preset-rename-ok" @click="commitRename">确定</button>
<button class="pe-preset-rename-cancel" @click="cancelRename">取消</button>
</template>
</div>
</div>
<div v-else class="pe-preset-empty">
<span>{{ presetSearch ? '未找到匹配的预设' : '暂无预设' }}</span>
</div>
</div>
</Transition>
</div> </div>
</div> </div>
</header> </header>
@@ -437,6 +434,7 @@ function displayTrans(key: string): string {
<li v-for="s in suggestions" :key="s" @click="applySuggestion(s)">{{ s }}</li> <li v-for="s in suggestions" :key="s" @click="applySuggestion(s)">{{ s }}</li>
</ul> </ul>
</section> </section>
<section class="pe-right-pane"> <section class="pe-right-pane">
<div class="pe-section-title mode"> <div class="pe-section-title mode">
<span>提示词映射</span> <span>提示词映射</span>
@@ -1703,6 +1701,7 @@ function displayTrans(key: string): string {
background: var(--color-text-tertiary); background: var(--color-text-tertiary);
} }
/* 保证按钮内图标不压缩文本,提升对齐与可读性 */ /* 保证按钮内图标不压缩文本,提升对齐与可读性 */
.pe-left button svg, .pe-left button svg,
.pe-right button svg, .pe-right button svg,
+287 -1
View File
@@ -1,6 +1,6 @@
import { defineStore } from 'pinia'; import { defineStore } from 'pinia';
import { loadInitialDataset } from '../utils/yamlLoader'; import { loadInitialDataset } from '../utils/yamlLoader';
import type { PromptDataset, PromptCategory, PromptGroup, PromptTag, LangCode, ExportBundle, CustomDiff, PromptPreset } from '../types'; import type { PromptDataset, PromptCategory, PromptGroup, PromptTag, LangCode, ExportBundle, CustomDiff, PromptPreset, ExtendedPreset, PresetFolder, PresetManagement, PresetType } from '../types';
const LS_KEY = 'ops.prompt.dataset.v1'; const LS_KEY = 'ops.prompt.dataset.v1';
let saveTimer: number | null = null; // 非响应式计时器,避免递归更新 let saveTimer: number | null = null; // 非响应式计时器,避免递归更新
@@ -20,6 +20,17 @@ export const usePromptStore = defineStore('promptStore', {
// 编辑器相关 // 编辑器相关
promptText: '', promptText: '',
presets: [] as PromptPreset[], presets: [] as PromptPreset[],
// 扩展预设管理
extendedPresets: [] as ExtendedPreset[],
presetFolders: [] as PresetFolder[],
presetManagement: {
folders: [],
presets: [],
settings: {
autoBackup: true,
maxPresets: 1000
}
} as PresetManagement,
}), }),
getters: { getters: {
categories: (s) => s.dataset?.categories ?? [], categories: (s) => s.dataset?.categories ?? [],
@@ -68,6 +79,12 @@ export const usePromptStore = defineStore('promptStore', {
this.dataset = deepClone(baseline!); this.dataset = deepClone(baseline!);
} }
this.presets = bundle.presets || []; this.presets = bundle.presets || [];
// 恢复扩展预设数据
this.extendedPresets = bundle.extendedPresets || [];
this.presetFolders = bundle.presetFolders || [];
if (bundle.presetManagement) {
this.presetManagement = bundle.presetManagement;
}
// 恢复编辑器内容与语言 // 恢复编辑器内容与语言
if (typeof bundle.promptText === 'string') { if (typeof bundle.promptText === 'string') {
this.promptText = bundle.promptText; this.promptText = bundle.promptText;
@@ -86,6 +103,10 @@ export const usePromptStore = defineStore('promptStore', {
const guessLang: LangCode = (this.dataset.languages.includes('zh_CN') ? 'zh_CN' : 'en') as LangCode; const guessLang: LangCode = (this.dataset.languages.includes('zh_CN') ? 'zh_CN' : 'en') as LangCode;
this.selectedLang = guessLang; this.selectedLang = guessLang;
} }
// 初始化扩展预设管理
this.initializeExtendedPresets();
// 自动迁移旧预设到新系统
this.migrateOldPresets();
this.autoPersist(); this.autoPersist();
}, },
autoPersist() { autoPersist() {
@@ -104,6 +125,10 @@ export const usePromptStore = defineStore('promptStore', {
savedAt: new Date().toISOString(), savedAt: new Date().toISOString(),
dataset: deepClone(this.dataset), dataset: deepClone(this.dataset),
presets: deepClone(this.presets), presets: deepClone(this.presets),
// 扩展预设数据
extendedPresets: deepClone(this.extendedPresets),
presetFolders: deepClone(this.presetFolders),
presetManagement: deepClone(this.presetManagement),
promptText: this.promptText, promptText: this.promptText,
selectedLang: this.selectedLang, selectedLang: this.selectedLang,
}; };
@@ -116,7 +141,13 @@ export const usePromptStore = defineStore('promptStore', {
if (!bundle) { if (!bundle) {
if (!baseline) baseline = await loadInitialDataset(); if (!baseline) baseline = await loadInitialDataset();
this.dataset = deepClone(baseline!); this.dataset = deepClone(baseline!);
this.presets = [];
this.extendedPresets = [];
this.presetFolders = [];
} else { } else {
// 确保兼容性
bundle = this.ensureCompatibility(bundle);
if (bundle.dataset) { if (bundle.dataset) {
this.dataset = bundle.dataset; this.dataset = bundle.dataset;
} else if (bundle.customDiff) { } else if (bundle.customDiff) {
@@ -124,9 +155,17 @@ export const usePromptStore = defineStore('promptStore', {
this.dataset = this.applyDiff(deepClone(baseline!), bundle.customDiff); this.dataset = this.applyDiff(deepClone(baseline!), bundle.customDiff);
} }
this.presets = bundle.presets || []; this.presets = bundle.presets || [];
// 导入扩展预设数据
this.extendedPresets = bundle.extendedPresets || [];
this.presetFolders = bundle.presetFolders || [];
if (bundle.presetManagement) {
this.presetManagement = bundle.presetManagement;
}
} }
this.selectedCategoryIndex = 0; this.selectedCategoryIndex = 0;
this.selectedGroupIndex = 0; this.selectedGroupIndex = 0;
// 确保扩展预设管理已初始化
this.initializeExtendedPresets();
this.save(); this.save();
}, },
exportToJson(): string { exportToJson(): string {
@@ -137,6 +176,10 @@ export const usePromptStore = defineStore('promptStore', {
savedAt: new Date().toISOString(), savedAt: new Date().toISOString(),
customDiff: diff, customDiff: diff,
presets: deepClone(this.presets), presets: deepClone(this.presets),
// 导出扩展预设数据
extendedPresets: deepClone(this.extendedPresets),
presetFolders: deepClone(this.presetFolders),
presetManagement: deepClone(this.presetManagement),
}; };
return JSON.stringify(bundle, null, 2); return JSON.stringify(bundle, null, 2);
}, },
@@ -599,6 +642,249 @@ export const usePromptStore = defineStore('promptStore', {
} }
return target; return target;
}, },
// 扩展预设管理方法
initializeExtendedPresets() {
// 如果没有扩展预设数据,初始化默认结构
if (!this.extendedPresets) {
this.extendedPresets = [];
}
if (!this.presetFolders) {
this.presetFolders = [];
}
if (!this.presetManagement) {
this.presetManagement = {
folders: [],
presets: [],
settings: {
autoBackup: true,
maxPresets: 1000
}
};
}
// 确保有默认文件夹
if (this.presetFolders.length === 0) {
const defaultFolder = this.createPresetFolder({
name: '默认文件夹',
description: '系统默认预设文件夹',
color: '#6366f1'
});
// 设置为默认文件夹
if (this.presetManagement.settings) {
this.presetManagement.settings.defaultFolder = defaultFolder.id;
}
}
},
createExtendedPreset(data: Omit<ExtendedPreset, 'id' | 'createdAt' | 'updatedAt'>) {
const now = new Date().toISOString();
const preset: ExtendedPreset = {
id: `preset_${Date.now()}_${Math.random().toString(36).slice(2, 9)}`,
createdAt: now,
updatedAt: now,
...data
};
this.extendedPresets.push(preset);
this.save();
return preset;
},
updateExtendedPreset(id: string, data: Partial<Omit<ExtendedPreset, 'id' | 'createdAt'>>) {
const preset = this.extendedPresets.find(p => p.id === id);
if (!preset) return false;
Object.assign(preset, data, { updatedAt: new Date().toISOString() });
this.save();
return true;
},
deleteExtendedPreset(id: string) {
const index = this.extendedPresets.findIndex(p => p.id === id);
if (index === -1) return false;
this.extendedPresets.splice(index, 1);
this.save();
return true;
},
createPresetFolder(data: Omit<PresetFolder, 'id' | 'createdAt' | 'updatedAt'>) {
const now = new Date().toISOString();
const folder: PresetFolder = {
id: `folder_${Date.now()}_${Math.random().toString(36).slice(2, 9)}`,
createdAt: now,
updatedAt: now,
...data
};
this.presetFolders.push(folder);
this.save();
return folder;
},
updatePresetFolder(id: string, data: Partial<Omit<PresetFolder, 'id' | 'createdAt'>>) {
const folder = this.presetFolders.find(f => f.id === id);
if (!folder) return false;
Object.assign(folder, data, { updatedAt: new Date().toISOString() });
this.save();
return true;
},
deletePresetFolder(id: string) {
const index = this.presetFolders.findIndex(f => f.id === id);
if (index === -1) return false;
// 将该文件夹下的预设移动到未分类
this.extendedPresets.forEach(preset => {
if (preset.folderId === id) {
preset.folderId = undefined;
preset.updatedAt = new Date().toISOString();
}
});
// 删除子文件夹或将其移动到父级
const folder = this.presetFolders[index];
if (folder) {
this.presetFolders.forEach(f => {
if (f.parentId === id) {
f.parentId = folder.parentId;
f.updatedAt = new Date().toISOString();
}
});
}
this.presetFolders.splice(index, 1);
this.save();
return true;
},
importExtendedPresets(data: { folders?: PresetFolder[]; presets?: ExtendedPreset[] }) {
if (data.folders) {
// 合并文件夹,避免ID冲突
data.folders.forEach(folder => {
const existingFolder = this.presetFolders.find(f => f.name === folder.name);
if (existingFolder) {
// 更新现有文件夹
Object.assign(existingFolder, folder, {
id: existingFolder.id,
updatedAt: new Date().toISOString()
});
} else {
// 创建新文件夹,重新生成ID
const newFolder = {
...folder,
id: `folder_${Date.now()}_${Math.random().toString(36).slice(2, 9)}`,
createdAt: new Date().toISOString(),
updatedAt: new Date().toISOString()
};
this.presetFolders.push(newFolder);
}
});
}
if (data.presets) {
// 合并预设,避免ID冲突
data.presets.forEach(preset => {
const existingPreset = this.extendedPresets.find(p => p.name === preset.name && p.type === preset.type);
if (existingPreset) {
// 更新现有预设
Object.assign(existingPreset, preset, {
id: existingPreset.id,
updatedAt: new Date().toISOString()
});
} else {
// 创建新预设,重新生成ID
const newPreset = {
...preset,
id: `preset_${Date.now()}_${Math.random().toString(36).slice(2, 9)}`,
createdAt: new Date().toISOString(),
updatedAt: new Date().toISOString()
};
this.extendedPresets.push(newPreset);
}
});
}
this.save();
},
// 兼容性:从旧预设迁移到新预设系统
migrateOldPresets() {
if (this.presets.length === 0) return; // 没有旧预设需要迁移
console.log(`开始迁移 ${this.presets.length} 个旧预设到新系统...`);
let migratedCount = 0;
const defaultFolder = this.presetManagement?.settings?.defaultFolder;
this.presets.forEach(oldPreset => {
const existingExtended = this.extendedPresets.find(p =>
p.name === oldPreset.name && p.type === 'positive'
);
if (!existingExtended) {
// 创建新的扩展预设,但不触发保存(避免递归)
const now = new Date().toISOString();
const preset: ExtendedPreset = {
id: `migrated_${Date.now()}_${Math.random().toString(36).slice(2, 9)}`,
name: oldPreset.name,
type: 'positive',
content: oldPreset.text,
description: '从旧版快速预设自动迁移',
folderId: defaultFolder,
createdAt: oldPreset.updatedAt,
updatedAt: oldPreset.updatedAt
};
this.extendedPresets.push(preset);
migratedCount++;
}
});
if (migratedCount > 0) {
console.log(`成功迁移 ${migratedCount} 个预设到新系统`);
// 清空旧预设数组,完成迁移
this.presets = [];
console.log('旧预设已清空,迁移完成');
}
},
// 检查并处理版本兼容性
ensureCompatibility(bundle: ExportBundle) {
// 如果是旧版本的导出文件,确保新字段存在
if (!bundle.extendedPresets && bundle.presets && bundle.presets.length > 0) {
// 自动迁移旧预设到新系统
bundle.extendedPresets = bundle.presets.map(preset => ({
id: `migrated_${Date.now()}_${Math.random().toString(36).slice(2, 9)}`,
name: preset.name,
type: 'positive' as PresetType,
content: preset.text,
description: '从旧版本自动迁移',
createdAt: preset.updatedAt,
updatedAt: preset.updatedAt
}));
}
if (!bundle.presetFolders) {
bundle.presetFolders = [];
}
if (!bundle.presetManagement) {
bundle.presetManagement = {
folders: [],
presets: [],
settings: {
autoBackup: true,
maxPresets: 1000
}
};
}
return bundle;
},
}, },
}); });
// —— 工具方法 —— // —— 工具方法 ——
+44
View File
@@ -31,6 +31,10 @@ export interface ExportBundle {
dataset?: PromptDataset; // full snapshot (used for localStorage persistence) dataset?: PromptDataset; // full snapshot (used for localStorage persistence)
customDiff?: CustomDiff; // only user-defined changes for export/import customDiff?: CustomDiff; // only user-defined changes for export/import
presets?: PromptPreset[]; // saved prompt texts by name presets?: PromptPreset[]; // saved prompt texts by name
// 新的扩展预设管理
extendedPresets?: ExtendedPreset[];
presetFolders?: PresetFolder[];
presetManagement?: PresetManagement;
// editor state persistence // editor state persistence
promptText?: string; promptText?: string;
selectedLang?: LangCode; selectedLang?: LangCode;
@@ -65,3 +69,43 @@ export interface PromptPreset {
text: string; text: string;
updatedAt: string; updatedAt: string;
} }
// 新的预设类型枚举
export type PresetType = 'positive' | 'negative' | 'setting' | 'style' | 'character' | 'scene' | 'custom';
// 扩展的预设接口
export interface ExtendedPreset {
id: string;
name: string;
type: PresetType;
content: string;
description?: string;
tags?: string[];
folderId?: string;
createdAt: string;
updatedAt: string;
isPublic?: boolean;
author?: string;
}
// 预设文件夹
export interface PresetFolder {
id: string;
name: string;
description?: string;
color?: string;
parentId?: string;
createdAt: string;
updatedAt: string;
}
// 预设管理数据结构
export interface PresetManagement {
folders: PresetFolder[];
presets: ExtendedPreset[];
settings: {
defaultFolder?: string;
autoBackup: boolean;
maxPresets: number;
};
}