新增预设管理功能
This commit is contained in:
+19
-5
@@ -2,9 +2,10 @@
|
||||
import { ref, onMounted } from 'vue'
|
||||
import PromptEditor from './components/PromptEditor.vue'
|
||||
import PromptManager from './components/PromptManager.vue'
|
||||
import PresetManager from './components/PresetManager.vue'
|
||||
import { usePromptStore } from './stores/promptStore'
|
||||
|
||||
const currentView = ref<'editor' | 'manager'>('editor')
|
||||
const currentView = ref<'editor' | 'manager' | 'presets'>('editor')
|
||||
const isDark = ref(false)
|
||||
const store = usePromptStore()
|
||||
|
||||
@@ -27,7 +28,7 @@ function updateTheme() {
|
||||
document.documentElement.classList.toggle('dark', isDark.value)
|
||||
}
|
||||
|
||||
function switchView(view: 'editor' | 'manager') {
|
||||
function switchView(view: 'editor' | 'manager' | 'presets') {
|
||||
currentView.value = view
|
||||
}
|
||||
</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="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>
|
||||
编辑器
|
||||
<span>编辑器</span>
|
||||
</button>
|
||||
<button
|
||||
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">
|
||||
<path d="M3 3h18v18H3zM9 9h6v6H9z" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</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>
|
||||
</nav>
|
||||
|
||||
@@ -95,7 +108,8 @@ function switchView(view: 'editor' | 'manager') {
|
||||
<main class="app-main">
|
||||
<Transition name="view-transition" mode="out-in">
|
||||
<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>
|
||||
</main>
|
||||
</div>
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -3,6 +3,7 @@ import { onMounted, onUnmounted, ref, computed, nextTick, watch } from 'vue';
|
||||
import { usePromptStore } from '../stores/promptStore';
|
||||
import type { LangCode } from '../types';
|
||||
import NotificationToast from './NotificationToast.vue';
|
||||
import PresetDropdown from './PresetDropdown.vue';
|
||||
|
||||
const store = usePromptStore();
|
||||
const draggingIndex = ref<number | null>(null);
|
||||
@@ -14,9 +15,6 @@ const editingValue = ref('');
|
||||
const addingMapIndex = ref<number | null>(null);
|
||||
const addingMapValue = ref('');
|
||||
const presetName = ref('');
|
||||
const presetSearch = ref('');
|
||||
const renamingPreset = ref<string | null>(null);
|
||||
const renamingValue = ref('');
|
||||
const viewMode = ref<'compact' | 'detail'>('compact');
|
||||
const showPresetDropdown = ref(false);
|
||||
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) {
|
||||
const target = event.target as HTMLElement;
|
||||
if (!target.closest('.pe-presets')) {
|
||||
// 检查点击是否在预设下拉区域内,包括重命名输入框和按钮
|
||||
if (!target.closest('.pe-presets') && !target.closest('.pd-dropdown')) {
|
||||
showPresetDropdown.value = false;
|
||||
}
|
||||
}
|
||||
@@ -55,12 +54,6 @@ const selectedLang = computed({
|
||||
});
|
||||
|
||||
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 inputEl = ref<HTMLTextAreaElement | null>(null);
|
||||
@@ -278,31 +271,52 @@ function savePreset() {
|
||||
showNotification('请输入预设名称', 'error');
|
||||
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 = '';
|
||||
}
|
||||
function loadPreset(name: string) {
|
||||
|
||||
// 预设下拉组件的事件处理
|
||||
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');
|
||||
}
|
||||
function deletePreset(name: string) {
|
||||
if (confirm(`确定删除预设「${name}」吗?`)) {
|
||||
|
||||
function handlePresetSave(name: string) {
|
||||
store.savePreset(name);
|
||||
showNotification(`预设「${name}」已保存`, 'success');
|
||||
}
|
||||
|
||||
function handlePresetDelete(name: string) {
|
||||
store.deletePreset(name);
|
||||
showNotification(`预设「${name}」已删除`, 'info');
|
||||
}
|
||||
}
|
||||
function beginRename(name: string) { renamingPreset.value = name; renamingValue.value = name; }
|
||||
function commitRename() {
|
||||
if (!renamingPreset.value) return;
|
||||
const oldName = renamingPreset.value;
|
||||
const newName = renamingValue.value.trim();
|
||||
if (!newName) { alert('预设名称不能为空'); return; }
|
||||
|
||||
function handlePresetRename(oldName: string, newName: string) {
|
||||
store.renamePreset(oldName, newName);
|
||||
renamingPreset.value = null; renamingValue.value = '';
|
||||
showNotification(`预设已重命名为「${newName}」`, 'success');
|
||||
}
|
||||
function cancelRename() { renamingPreset.value = null; }
|
||||
|
||||
async function applySuggestion(s: string) {
|
||||
const el = inputEl.value;
|
||||
@@ -355,12 +369,12 @@ function displayTrans(key: string): string {
|
||||
<button
|
||||
class="pe-preset-toggle"
|
||||
@click="showPresetDropdown = !showPresetDropdown"
|
||||
title="管理预设"
|
||||
title="快速预设"
|
||||
>
|
||||
<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"/>
|
||||
</svg>
|
||||
预设管理
|
||||
快速预设
|
||||
<svg
|
||||
width="12" height="12"
|
||||
viewBox="0 0 24 24"
|
||||
@@ -373,31 +387,14 @@ function displayTrans(key: string): string {
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
<Transition name="dropdown">
|
||||
<div v-if="showPresetDropdown" class="pe-preset-dropdown">
|
||||
<div class="pe-preset-search-wrapper">
|
||||
<input class="pe-preset-search" placeholder="搜索预设..." v-model="presetSearch" />
|
||||
</div>
|
||||
<div class="pe-preset-list" v-if="filteredPresets.length">
|
||||
<div v-for="p in filteredPresets" :key="p.name" class="pe-preset-item">
|
||||
<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>
|
||||
<PresetDropdown
|
||||
:show="showPresetDropdown"
|
||||
@close="showPresetDropdown = false"
|
||||
@load="handlePresetLoad"
|
||||
@save="handlePresetSave"
|
||||
@delete="handlePresetDelete"
|
||||
@rename="handlePresetRename"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
@@ -437,6 +434,7 @@ function displayTrans(key: string): string {
|
||||
<li v-for="s in suggestions" :key="s" @click="applySuggestion(s)">{{ s }}</li>
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
<section class="pe-right-pane">
|
||||
<div class="pe-section-title mode">
|
||||
<span>提示词映射</span>
|
||||
@@ -1703,6 +1701,7 @@ function displayTrans(key: string): string {
|
||||
background: var(--color-text-tertiary);
|
||||
}
|
||||
|
||||
|
||||
/* 保证按钮内图标不压缩文本,提升对齐与可读性 */
|
||||
.pe-left button svg,
|
||||
.pe-right button svg,
|
||||
|
||||
+287
-1
@@ -1,6 +1,6 @@
|
||||
import { defineStore } from 'pinia';
|
||||
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';
|
||||
let saveTimer: number | null = null; // 非响应式计时器,避免递归更新
|
||||
@@ -20,6 +20,17 @@ export const usePromptStore = defineStore('promptStore', {
|
||||
// 编辑器相关
|
||||
promptText: '',
|
||||
presets: [] as PromptPreset[],
|
||||
// 扩展预设管理
|
||||
extendedPresets: [] as ExtendedPreset[],
|
||||
presetFolders: [] as PresetFolder[],
|
||||
presetManagement: {
|
||||
folders: [],
|
||||
presets: [],
|
||||
settings: {
|
||||
autoBackup: true,
|
||||
maxPresets: 1000
|
||||
}
|
||||
} as PresetManagement,
|
||||
}),
|
||||
getters: {
|
||||
categories: (s) => s.dataset?.categories ?? [],
|
||||
@@ -68,6 +79,12 @@ export const usePromptStore = defineStore('promptStore', {
|
||||
this.dataset = deepClone(baseline!);
|
||||
}
|
||||
this.presets = bundle.presets || [];
|
||||
// 恢复扩展预设数据
|
||||
this.extendedPresets = bundle.extendedPresets || [];
|
||||
this.presetFolders = bundle.presetFolders || [];
|
||||
if (bundle.presetManagement) {
|
||||
this.presetManagement = bundle.presetManagement;
|
||||
}
|
||||
// 恢复编辑器内容与语言
|
||||
if (typeof bundle.promptText === 'string') {
|
||||
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;
|
||||
this.selectedLang = guessLang;
|
||||
}
|
||||
// 初始化扩展预设管理
|
||||
this.initializeExtendedPresets();
|
||||
// 自动迁移旧预设到新系统
|
||||
this.migrateOldPresets();
|
||||
this.autoPersist();
|
||||
},
|
||||
autoPersist() {
|
||||
@@ -104,6 +125,10 @@ export const usePromptStore = defineStore('promptStore', {
|
||||
savedAt: new Date().toISOString(),
|
||||
dataset: deepClone(this.dataset),
|
||||
presets: deepClone(this.presets),
|
||||
// 扩展预设数据
|
||||
extendedPresets: deepClone(this.extendedPresets),
|
||||
presetFolders: deepClone(this.presetFolders),
|
||||
presetManagement: deepClone(this.presetManagement),
|
||||
promptText: this.promptText,
|
||||
selectedLang: this.selectedLang,
|
||||
};
|
||||
@@ -116,7 +141,13 @@ export const usePromptStore = defineStore('promptStore', {
|
||||
if (!bundle) {
|
||||
if (!baseline) baseline = await loadInitialDataset();
|
||||
this.dataset = deepClone(baseline!);
|
||||
this.presets = [];
|
||||
this.extendedPresets = [];
|
||||
this.presetFolders = [];
|
||||
} else {
|
||||
// 确保兼容性
|
||||
bundle = this.ensureCompatibility(bundle);
|
||||
|
||||
if (bundle.dataset) {
|
||||
this.dataset = bundle.dataset;
|
||||
} else if (bundle.customDiff) {
|
||||
@@ -124,9 +155,17 @@ export const usePromptStore = defineStore('promptStore', {
|
||||
this.dataset = this.applyDiff(deepClone(baseline!), bundle.customDiff);
|
||||
}
|
||||
this.presets = bundle.presets || [];
|
||||
// 导入扩展预设数据
|
||||
this.extendedPresets = bundle.extendedPresets || [];
|
||||
this.presetFolders = bundle.presetFolders || [];
|
||||
if (bundle.presetManagement) {
|
||||
this.presetManagement = bundle.presetManagement;
|
||||
}
|
||||
}
|
||||
this.selectedCategoryIndex = 0;
|
||||
this.selectedGroupIndex = 0;
|
||||
// 确保扩展预设管理已初始化
|
||||
this.initializeExtendedPresets();
|
||||
this.save();
|
||||
},
|
||||
exportToJson(): string {
|
||||
@@ -137,6 +176,10 @@ export const usePromptStore = defineStore('promptStore', {
|
||||
savedAt: new Date().toISOString(),
|
||||
customDiff: diff,
|
||||
presets: deepClone(this.presets),
|
||||
// 导出扩展预设数据
|
||||
extendedPresets: deepClone(this.extendedPresets),
|
||||
presetFolders: deepClone(this.presetFolders),
|
||||
presetManagement: deepClone(this.presetManagement),
|
||||
};
|
||||
return JSON.stringify(bundle, null, 2);
|
||||
},
|
||||
@@ -599,6 +642,249 @@ export const usePromptStore = defineStore('promptStore', {
|
||||
}
|
||||
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;
|
||||
},
|
||||
},
|
||||
});
|
||||
// —— 工具方法 ——
|
||||
|
||||
@@ -31,6 +31,10 @@ export interface ExportBundle {
|
||||
dataset?: PromptDataset; // full snapshot (used for localStorage persistence)
|
||||
customDiff?: CustomDiff; // only user-defined changes for export/import
|
||||
presets?: PromptPreset[]; // saved prompt texts by name
|
||||
// 新的扩展预设管理
|
||||
extendedPresets?: ExtendedPreset[];
|
||||
presetFolders?: PresetFolder[];
|
||||
presetManagement?: PresetManagement;
|
||||
// editor state persistence
|
||||
promptText?: string;
|
||||
selectedLang?: LangCode;
|
||||
@@ -65,3 +69,43 @@ export interface PromptPreset {
|
||||
text: 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;
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user