/** * 规则配置管理器 * 负责管理提示词规则配置弹窗和相关功能 */ import { app } from "../../../../scripts/app.js"; import { logger } from '../utils/logger.js'; import { ResourceManager } from "../utils/resourceManager.js"; import { createSettingsDialog, createFormGroup, createInputGroup, createSelectGroup } from "./settings.js"; class RulesConfigManager { constructor() { this.systemPrompts = null; // ---存储提示词列表数据--- this.expandPrompts = []; this.zhVisionPrompts = []; this.enVisionPrompts = []; // ---当前激活的提示词ID--- this.activeExpandPromptId = null; this.activeZhVisionPromptId = null; this.activeEnVisionPromptId = null; this.isDirty = false; this.initialState = null; // 用于存储状态备份 } /** * 备份当前状态 */ _backupState() { this.initialState = { expandPrompts: JSON.parse(JSON.stringify(this.expandPrompts)), zhVisionPrompts: JSON.parse(JSON.stringify(this.zhVisionPrompts)), enVisionPrompts: JSON.parse(JSON.stringify(this.enVisionPrompts)), activeExpandPromptId: this.activeExpandPromptId, activeZhVisionPromptId: this.activeZhVisionPromptId, activeEnVisionPromptId: this.activeEnVisionPromptId, }; this.isDirty = false; // 标记表单未修改 const form = document.querySelector('.rules-config-dialog .rules-config-form'); if (form && form.elements.modified_marker) { form.elements.modified_marker.value = 'initial'; } } /** * 从备份恢复状态 */ _restoreState() { if (this.initialState) { this.expandPrompts = JSON.parse(JSON.stringify(this.initialState.expandPrompts)); this.zhVisionPrompts = JSON.parse(JSON.stringify(this.initialState.zhVisionPrompts)); this.enVisionPrompts = JSON.parse(JSON.stringify(this.initialState.enVisionPrompts)); this.activeExpandPromptId = this.initialState.activeExpandPromptId; this.activeZhVisionPromptId = this.initialState.activeZhVisionPromptId; this.activeEnVisionPromptId = this.initialState.activeEnVisionPromptId; } this.isDirty = false; } /** * 标记配置已修改 */ _setDirty() { this.isDirty = true; // 触发表单修改,以便通用对话框检测到未保存的更改 const form = document.querySelector('.rules-config-dialog .rules-config-form'); if (form && form.elements.modified_marker) { form.elements.modified_marker.value = 'modified_' + Date.now(); form.elements.modified_marker.dispatchEvent(new Event('change', { bubbles: true })); } } /** * 显示规则配置弹窗 */ showRulesConfigModal() { try { logger.debug('打开规则配置弹窗'); createSettingsDialog({ title: '规则管理器', dialogClassName: 'rules-config-dialog', disableBackdropAndCloseOnClickOutside: true, renderContent: (container) => { this._createRulesConfigUI(container); }, onSave: async () => { if (this.isDirty) { try { await this._saveConfigToServer(); this._backupState(); // 保存成功后更新备份 app.extensionManager.toast.add({ severity: "success", summary: "配置已保存", detail: "所有更改已成功保存到服务器", life: 3000 }); } catch (error) { // 错误提示已在 _saveConfigToServer 中处理 return false; // 保存失败时阻止关闭对话框 } } else { app.extensionManager.toast.add({ severity: "info", summary: "无更改", detail: "配置没有未保存的更改", life: 3000 }); } return true; // 关闭对话框 }, onCancel: () => { // createSettingsDialog 的通用逻辑会处理确认 // 如果用户确认取消,我们在这里恢复状态 if (this.isDirty) { this._restoreState(); this._renderPromptLists(); // 重新渲染以显示恢复后的状态 } return true; // 允许关闭 } }); } catch (error) { logger.error(`打开规则配置弹窗失败: ${error.message}`); app.extensionManager.toast.add({ severity: "error", summary: "打开配置失败", detail: error.message || "打开配置弹窗过程中发生错误", life: 3000 }); } } /** * 创建规则配置UI * @param {HTMLElement} container 容器元素 */ _createRulesConfigUI(container) { const form = document.createElement('form'); form.onsubmit = (e) => e.preventDefault(); form.className = 'rules-config-form'; // 添加一个隐藏输入,用于脏检查 const hiddenInput = document.createElement('input'); hiddenInput.type = 'hidden'; hiddenInput.name = 'modified_marker'; hiddenInput.value = 'initial'; form.appendChild(hiddenInput); // 1. 扩写规则配置 const expandSection = this._createExpandSection(); // 2. 反推规则配置 const visionSection = this._createVisionSection(); form.appendChild(expandSection); form.appendChild(visionSection); container.appendChild(form); // 加载系统提示词配置 this._loadSystemPrompts(); } /** * 创建扩写规则配置部分 * @returns {HTMLElement} 扩写规则配置部分的DOM元素 */ _createExpandSection() { // 扩写规则配置 const expandSection = createFormGroup('扩写规则配置'); const expandHeader = expandSection.querySelector('.settings-form-section-header'); // 创建添加按钮 const addButton = document.createElement('div'); addButton.className = 'settings-refresh-button'; addButton.title = '添加新提示词'; const addIcon = document.createElement('span'); addIcon.className = 'pi pi-plus refresh-icon'; addButton.appendChild(addIcon); const addText = document.createElement('span'); addText.textContent = '添加提示词'; addButton.appendChild(addText); expandHeader.appendChild(addButton); // 创建提示词列表容器 const listContainer = document.createElement('div'); listContainer.className = 'prompt-list-container'; // 创建列表头部 const listHeader = document.createElement('div'); listHeader.className = 'prompt-list-header'; listHeader.innerHTML = `
状态
提示词名称
提示词内容
操作
`; // 为头部单元格添加列宽调整手柄 this._addHeaderResizers(listHeader); // 创建滚动列表 const scrollList = document.createElement('div'); scrollList.className = 'prompt-scroll-list'; listContainer.appendChild(listHeader); listContainer.appendChild(scrollList); expandSection.appendChild(listContainer); // 存储列表引用 this.expandScrollList = scrollList; // 添加调试信息 logger.debug('扩写提示词列表容器已创建'); // 处理添加按钮点击 addButton.onclick = () => { this._showPromptEditDialog('expand', null); }; return expandSection; } /** * 创建反推规则配置部分 * @returns {HTMLElement} 反推规则配置部分的DOM元素 */ _createVisionSection() { // 反推规则配置 const visionSection = createFormGroup('反推规则配置'); const visionHeader = visionSection.querySelector('.settings-form-section-header'); // 创建添加按钮并添加到标题栏 const addButton = document.createElement('div'); addButton.className = 'settings-refresh-button'; addButton.title = '添加提示词'; const addIcon = document.createElement('span'); addIcon.className = 'pi pi-plus refresh-icon'; addButton.appendChild(addIcon); const addText = document.createElement('span'); addText.textContent = '添加提示词'; addButton.appendChild(addText); visionHeader.appendChild(addButton); // Tab容器 const tabContainer = document.createElement('div'); tabContainer.className = 'popup_tabs_container'; const tabsScroll = document.createElement('div'); tabsScroll.className = 'popup_tabs_scroll'; const tabs = document.createElement('div'); tabs.className = 'popup_tabs'; const tabZH = document.createElement('div'); tabZH.className = 'popup_tab active'; tabZH.textContent = '中文反推规则'; const tabEN = document.createElement('div'); tabEN.className = 'popup_tab'; tabEN.textContent = '英文反推规则'; tabs.appendChild(tabZH); tabs.appendChild(tabEN); tabsScroll.appendChild(tabs); tabContainer.appendChild(tabsScroll); // 中文反推规则内容 const tabContentZH = document.createElement('div'); tabContentZH.className = 'popup_tab_content active'; // 中文列表容器 const zhListContainer = document.createElement('div'); zhListContainer.className = 'prompt-list-container vision-prompt-list-container'; const zhListHeader = document.createElement('div'); zhListHeader.className = 'prompt-list-header'; zhListHeader.innerHTML = `
状态
提示词名称
提示词内容
操作
`; // 为头部单元格添加列宽调整手柄 this._addHeaderResizers(zhListHeader); const zhScrollList = document.createElement('div'); zhScrollList.className = 'prompt-scroll-list'; zhListContainer.appendChild(zhListHeader); zhListContainer.appendChild(zhScrollList); tabContentZH.appendChild(zhListContainer); // 存储中文列表引用 this.zhVisionScrollList = zhScrollList; // 添加调试信息 logger.debug('中文反推提示词列表容器已创建'); // 英文反推规则内容 const tabContentEN = document.createElement('div'); tabContentEN.className = 'popup_tab_content'; // 英文列表容器 const enListContainer = document.createElement('div'); enListContainer.className = 'prompt-list-container vision-prompt-list-container'; const enListHeader = document.createElement('div'); enListHeader.className = 'prompt-list-header'; enListHeader.innerHTML = `
状态
提示词名称
提示词内容
操作
`; // 为头部单元格添加列宽调整手柄 this._addHeaderResizers(enListHeader); const enScrollList = document.createElement('div'); enScrollList.className = 'prompt-scroll-list'; enListContainer.appendChild(enListHeader); enListContainer.appendChild(enScrollList); tabContentEN.appendChild(enListContainer); // 存储英文列表引用 this.enVisionScrollList = enScrollList; // 添加调试信息 logger.debug('英文反推提示词列表容器已创建'); // 设置Tab切换逻辑 tabZH.onclick = () => { tabZH.classList.add('active'); tabEN.classList.remove('active'); tabContentZH.classList.add('active'); tabContentEN.classList.remove('active'); }; tabEN.onclick = () => { tabEN.classList.add('active'); tabZH.classList.remove('active'); tabContentEN.classList.add('active'); tabContentZH.classList.remove('active'); }; visionSection.appendChild(tabContainer); visionSection.appendChild(tabContentZH); visionSection.appendChild(tabContentEN); // 处理添加按钮点击 - 根据当前激活的tab决定类型 addButton.onclick = () => { const activeTab = tabs.querySelector('.popup_tab.active'); const isZhActive = activeTab && activeTab.textContent === '中文反推规则'; this._showPromptEditDialog(isZhActive ? 'zhVision' : 'enVision', null); }; return visionSection; } /** * 加载系统提示词配置 */ async _loadSystemPrompts() { try { const response = await fetch('/prompt_assistant/api/config/system_prompts'); if (!response.ok) { throw new Error(`加载系统提示词配置失败: ${response.status} ${response.statusText}`); } const data = await response.json(); this.systemPrompts = data; // 获取激活的提示词ID const activePrompts = data.active_prompts || {}; const activeExpandId = activePrompts.expand; const activeZhVisionId = activePrompts.vision_zh; const activeEnVisionId = activePrompts.vision_en; // 输出加载的激活ID logger.debug(`加载的激活提示词ID | expand:${activeExpandId || '无'} | vision_zh:${activeZhVisionId || '无'} | vision_en:${activeEnVisionId || '无'}`); // 转换扩写规则数据 this.expandPrompts = []; if (data.expand_prompts) { // 保存原始顺序,以便在保存时恢复 this.originalExpandOrder = Object.keys(data.expand_prompts); // 使用原始顺序遍历键 this.originalExpandOrder.forEach(key => { const prompt = data.expand_prompts[key]; // 使用配置中的键作为ID,而不是生成新的ID this.expandPrompts.push({ id: key, name: prompt.name || key, // 如果没有名称,使用键作为名称 content: prompt.content, isActive: key === activeExpandId, // 根据active_prompts判断是否激活 order: this.originalExpandOrder.indexOf(key) // 保存原始顺序 }); }); // 如果没有找到激活的提示词,则激活第一个 if (!activeExpandId && this.expandPrompts.length > 0) { this.expandPrompts[0].isActive = true; this.activeExpandPromptId = this.expandPrompts[0].id; } else { this.activeExpandPromptId = activeExpandId; } // 按原始顺序排序 this.expandPrompts.sort((a, b) => a.order - b.order); } // 转换反推规则数据 this.zhVisionPrompts = []; this.enVisionPrompts = []; if (data.vision_prompts) { // 保存原始顺序 this.originalVisionOrder = Object.keys(data.vision_prompts); // 分别存储中文和英文提示词的键 const zhKeys = []; const enKeys = []; // 先分类所有键 this.originalVisionOrder.forEach(key => { const prompt = data.vision_prompts[key]; // 根据ID或内容判断是中文还是英文 const isChinese = key.includes('zh') || /[\u4e00-\u9fa5]/.test(prompt.content); if (isChinese) { zhKeys.push(key); } else { enKeys.push(key); } }); // 处理中文提示词 zhKeys.forEach((key, index) => { const prompt = data.vision_prompts[key]; this.zhVisionPrompts.push({ id: key, name: prompt.name || key, content: prompt.content, isActive: key === activeZhVisionId, order: this.originalVisionOrder.indexOf(key) // 保存原始顺序 }); }); // 处理英文提示词 enKeys.forEach((key, index) => { const prompt = data.vision_prompts[key]; this.enVisionPrompts.push({ id: key, name: prompt.name || key, content: prompt.content, isActive: key === activeEnVisionId, order: this.originalVisionOrder.indexOf(key) // 保存原始顺序 }); }); // 按原始顺序排序 this.zhVisionPrompts.sort((a, b) => a.order - b.order); this.enVisionPrompts.sort((a, b) => a.order - b.order); // 如果没有找到激活的中文提示词,则激活第一个 if (!activeZhVisionId && this.zhVisionPrompts.length > 0) { this.zhVisionPrompts[0].isActive = true; this.activeZhVisionPromptId = this.zhVisionPrompts[0].id; } else { this.activeZhVisionPromptId = activeZhVisionId; } // 如果没有找到激活的英文提示词,则激活第一个 if (!activeEnVisionId && this.enVisionPrompts.length > 0) { this.enVisionPrompts[0].isActive = true; this.activeEnVisionPromptId = this.enVisionPrompts[0].id; } else { this.activeEnVisionPromptId = activeEnVisionId; } } // 备份初始状态 this._backupState(); // 渲染列表 this._renderPromptLists(); } catch (error) { logger.error("加载系统提示词配置失败:", error); app.extensionManager.toast.add({ severity: "error", summary: "加载失败", detail: error.message || "加载系统提示词配置过程中发生错误", life: 3000 }); } } /** * 渲染提示词列表 */ _renderPromptLists() { logger.debug(`开始渲染提示词列表 | 扩写提示词:${this.expandPrompts.length}个 | 中文反推:${this.zhVisionPrompts.length}个 | 英文反推:${this.enVisionPrompts.length}个`); // 渲染扩写规则列表 if (this.expandScrollList) { this._renderPromptList(this.expandScrollList, this.expandPrompts, 'expand'); logger.debug(`渲染扩写提示词列表完成`); } else { logger.error(`扩写提示词列表容器不存在`); } // 渲染中文反推规则列表 if (this.zhVisionScrollList) { this._renderPromptList(this.zhVisionScrollList, this.zhVisionPrompts, 'zhVision'); logger.debug(`渲染中文反推提示词列表完成`); } else { logger.error(`中文反推提示词列表容器不存在`); } // 渲染英文反推规则列表 if (this.enVisionScrollList) { this._renderPromptList(this.enVisionScrollList, this.enVisionPrompts, 'enVision'); logger.debug(`渲染英文反推提示词列表完成`); } else { logger.error(`英文反推提示词列表容器不存在`); } } /** * 渲染单个提示词列表 * @param {HTMLElement} container 列表容器 * @param {Array} prompts 提示词数组 * @param {string} type 类型 */ _renderPromptList(container, prompts, type) { container.innerHTML = ''; prompts.forEach(prompt => { const row = document.createElement('div'); row.className = 'prompt-list-row'; row.dataset.promptId = prompt.id; row.dataset.type = type; row.dataset.order = prompt.order; // 添加顺序属性到DOM元素,方便调试 // 状态列 - 可点击切换状态 const statusCell = document.createElement('div'); statusCell.className = 'prompt-list-cell status-cell'; const statusIcon = document.createElement('span'); statusIcon.className = prompt.isActive ? 'pi pi-check-circle active-status' : 'pi pi-circle-off inactive-status'; statusIcon.title = prompt.isActive ? '当前使用中,点击取消激活' : '未激活,点击激活'; statusCell.appendChild(statusIcon); statusCell.onclick = (e) => { e.stopPropagation(); this._togglePromptActive(type, prompt.id); }; // 名称列 const nameCell = document.createElement('div'); nameCell.className = 'prompt-list-cell name-cell'; // 使用name属性显示名称,而不是id nameCell.textContent = prompt.name; nameCell.title = prompt.name; // 添加列宽调整手柄 const nameResizer = document.createElement('div'); nameResizer.className = 'column-resizer'; nameCell.appendChild(nameResizer); this._addColumnResizer(nameResizer, nameCell); // 内容列 const contentCell = document.createElement('div'); contentCell.className = 'prompt-list-cell content-cell'; const contentPreview = prompt.content.length > 50 ? prompt.content.substring(0, 50) + '...' : prompt.content; contentCell.textContent = contentPreview; contentCell.title = prompt.content; // 添加列宽调整手柄 const contentResizer = document.createElement('div'); contentResizer.className = 'column-resizer'; contentCell.appendChild(contentResizer); this._addColumnResizer(contentResizer, contentCell); // 操作列 const actionCell = document.createElement('div'); actionCell.className = 'prompt-list-cell action-cell'; // 编辑按钮 const editButton = document.createElement('button'); editButton.className = 'prompt-action-btn edit-btn'; editButton.innerHTML = ''; editButton.title = '编辑'; editButton.onclick = (e) => { e.stopPropagation(); this._showPromptEditDialog(type, prompt.id); }; // 删除按钮 const deleteButton = document.createElement('button'); deleteButton.className = 'prompt-action-btn delete-btn'; deleteButton.innerHTML = ''; deleteButton.title = '删除'; deleteButton.onclick = (e) => { e.stopPropagation(); this._deletePrompt(type, prompt.id); }; actionCell.appendChild(editButton); actionCell.appendChild(deleteButton); row.appendChild(statusCell); row.appendChild(nameCell); row.appendChild(contentCell); row.appendChild(actionCell); container.appendChild(row); }); // 初始化拖拽排序 this._initSortable(container, type); } /** * 显示提示词编辑对话框 * @param {string} type 类型 * @param {string|null} promptId 提示词ID,null表示新建 */ _showPromptEditDialog(type, promptId) { this._showPromptConfigDialog(type, promptId); } /** * 创建多行文本输入框组 * @param {string} label 标签文本 * @param {string} placeholder 占位符文本 * @returns {Object} 包含 group 和 textarea 的对象 */ _createTextareaGroup(label, placeholder) { const group = document.createElement('div'); group.className = 'settings-form-group'; const labelElem = document.createElement('label'); labelElem.className = 'settings-form-label'; labelElem.textContent = label; const textarea = document.createElement('textarea'); textarea.className = 'p-inputtext p-component settings-form-textarea'; textarea.placeholder = placeholder; textarea.rows = 8; textarea.style.resize = 'vertical'; textarea.style.minHeight = '150px'; textarea.style.height = 'calc(100% - 30px)'; group.appendChild(labelElem); group.appendChild(textarea); return { group, textarea }; } /** * 显示通用的提示词配置弹窗 * @param {string} defaultType 默认类型 * @param {string|null} promptId 提示词ID,null表示新建 */ _showPromptConfigDialog(defaultType = 'expand', promptId = null) { const isEdit = !!promptId; let editData = null; // 如果是编辑模式,查找对应的数据 if (isEdit) { if (defaultType === 'expand') { editData = this.expandPrompts.find(p => p.id === promptId); } else if (defaultType === 'zhVision') { editData = this.zhVisionPrompts.find(p => p.id === promptId); } else if (defaultType === 'enVision') { editData = this.enVisionPrompts.find(p => p.id === promptId); } } createSettingsDialog({ title: isEdit ? '编辑提示词' : '添加提示词', isConfirmDialog: true, dialogClassName: 'prompt-edit-dialog', disableBackdropAndCloseOnClickOutside: true, saveButtonText: isEdit ? '保存' : '添加', cancelButtonText: '取消', renderContent: (content) => { content.className += ' dialog-form-content'; content.style.padding = '1rem'; content.style.display = 'flex'; content.style.flexDirection = 'column'; content.style.flex = '1'; // 创建提示词名称输入框 const nameInput = createInputGroup('提示词名称', '请输入提示词名称'); nameInput.group.style.marginBottom = '10px'; if (isEdit && editData) { nameInput.input.value = editData.name || ''; } // 创建提示词类型下拉框 const typeOptions = [ { value: 'expand', text: '扩写' }, { value: 'zhVision', text: '反推(中文)' }, { value: 'enVision', text: '反推(英文)' } ]; const typeSelect = createSelectGroup('提示词类型', typeOptions, defaultType); typeSelect.group.style.marginBottom = '10px'; // 创建提示词内容多行输入框 const contentTextarea = this._createTextareaGroup('提示词内容', '请输入提示词内容'); contentTextarea.group.style.marginBottom = '0'; contentTextarea.group.style.flex = '1'; contentTextarea.group.style.display = 'flex'; contentTextarea.group.style.flexDirection = 'column'; if (isEdit && editData) { contentTextarea.textarea.value = editData.content || ''; } content.appendChild(nameInput.group); content.appendChild(typeSelect.group); content.appendChild(contentTextarea.group); content.nameInput = nameInput.input; content.typeSelect = typeSelect.select; content.contentTextarea = contentTextarea.textarea; // 聚焦名称输入框 setTimeout(() => { nameInput.input.focus(); }, 100); }, onSave: (content) => { const name = content.nameInput.value.trim(); const type = content.typeSelect.value; const promptContent = content.contentTextarea.value.trim(); if (!name || !promptContent) { app.extensionManager.toast.add({ severity: "error", summary: isEdit ? "编辑失败" : "添加失败", detail: "提示词名称和内容不能为空", life: 3000 }); return; } // 根据类型获取对应的数组 let targetArray; if (type === 'expand') { targetArray = this.expandPrompts; } else if (type === 'zhVision') { targetArray = this.zhVisionPrompts; } else if (type === 'enVision') { targetArray = this.enVisionPrompts; } else { logger.error(`未知的提示词类型: ${type}`); return; } // 检查名称是否重复(排除编辑时的自身) const isDuplicate = targetArray.some(p => (p.name === name || p.id === name) && (!isEdit || p.id !== promptId) ); if (isDuplicate) { app.extensionManager.toast.add({ severity: "error", summary: isEdit ? "编辑失败" : "添加失败", detail: `提示词名称"${name}"已存在`, life: 3000 }); return; } if (isEdit) { // 编辑模式:更新现有数据 if (editData) { // 如果类型发生变化,需要从原数组中移除,添加到新数组 if (type !== defaultType) { // 从原数组中移除 let originalArray; if (defaultType === 'expand') { originalArray = this.expandPrompts; } else if (defaultType === 'zhVision') { originalArray = this.zhVisionPrompts; } else if (defaultType === 'enVision') { originalArray = this.enVisionPrompts; } const index = originalArray.findIndex(p => p.id === promptId); if (index !== -1) { originalArray.splice(index, 1); } // 添加到新数组 // 为不同类型的提示词设置不同的ID前缀 let newId; if (type === 'expand') { newId = 'expand_' + name.replace(/\s+/g, '_'); } else if (type === 'zhVision') { newId = 'vision_zh_' + name.replace(/\s+/g, '_'); } else if (type === 'enVision') { newId = 'vision_en_' + name.replace(/\s+/g, '_'); } else { newId = this._generateId(); } const newPrompt = { id: newId, name: name, content: promptContent, isActive: false, order: targetArray.length // 设置顺序为当前数组长度 }; targetArray.push(newPrompt); logger.debug(`跨类型编辑提示词 | 原类型:${defaultType} | 新类型:${type} | 新ID:${newId}`); } else { // 同类型编辑,直接更新 const oldName = editData.name; editData.name = name; editData.content = promptContent; // 如果名称发生了变化,则更新ID if (oldName !== name) { const oldId = editData.id; let newId; // 根据类型设置不同的ID前缀 if (type === 'expand') { newId = 'expand_' + name.replace(/\s+/g, '_'); } else if (type === 'zhVision') { newId = 'vision_zh_' + name.replace(/\s+/g, '_'); } else if (type === 'enVision') { newId = 'vision_en_' + name.replace(/\s+/g, '_'); } else { newId = name; } editData.id = newId; logger.debug(`更新提示词ID | 类型:${type} | 旧ID:${oldId} | 新ID:${newId}`); } } } } else { // 添加模式:创建新数据 // 根据类型设置不同的ID前缀 let newId; if (type === 'expand') { newId = 'expand_' + name.replace(/\s+/g, '_'); } else if (type === 'zhVision') { newId = 'vision_zh_' + name.replace(/\s+/g, '_'); } else if (type === 'enVision') { newId = 'vision_en_' + name.replace(/\s+/g, '_'); } else { newId = name; } const newPrompt = { id: newId, name: name, content: promptContent, isActive: false, // 默认不激活 order: targetArray.length // 设置顺序为当前数组长度,确保添加到末尾 }; targetArray.push(newPrompt); // 输出调试信息 logger.debug(`添加新提示词 | 类型:${type} | ID:${newId} | 顺序:${newPrompt.order} | 数组长度:${targetArray.length}`); } // 标记为已修改 this._setDirty(); // 重新渲染对应的列表 this._renderPromptLists(); return true; } }); } /** * 生成唯一ID * @returns {string} 唯一ID */ _generateId() { return 'prompt_' + Date.now() + '_' + Math.random().toString(36).substr(2, 9); } /** * 刷新所有提示词列表 * 注意:为了保持一致性,建议直接使用_renderPromptLists方法 */ _refreshPromptLists() { // 直接调用_renderPromptLists方法,确保一致性 this._renderPromptLists(); } /** * 切换提示词激活状态 * @param {string} type 类型 * @param {string} promptId 提示词ID */ _togglePromptActive(type, promptId) { // 获取对应的数据数组 let dataArray; let activeIdProperty; let configKey; // 用于保存到配置中的键名 switch (type) { case 'expand': dataArray = this.expandPrompts; activeIdProperty = 'activeExpandPromptId'; configKey = 'expand'; break; case 'zhVision': dataArray = this.zhVisionPrompts; activeIdProperty = 'activeZhVisionPromptId'; configKey = 'vision_zh'; break; case 'enVision': dataArray = this.enVisionPrompts; activeIdProperty = 'activeEnVisionPromptId'; configKey = 'vision_en'; break; default: return; } // 找到目标提示词 const targetPrompt = dataArray.find(p => p.id === promptId); if (!targetPrompt) return; // 记录之前的激活状态 const oldActiveId = this[activeIdProperty]; const wasActive = targetPrompt.isActive; // 如果当前提示词已激活,则取消激活 if (targetPrompt.isActive) { targetPrompt.isActive = false; this[activeIdProperty] = null; logger.debug(`取消激活提示词 | 类型:${type} | ID:${promptId} | 配置键:${configKey}`); } else { // 取消其他提示词的激活状态 dataArray.forEach(p => p.isActive = false); // 激活当前提示词 targetPrompt.isActive = true; this[activeIdProperty] = promptId; logger.debug(`激活提示词 | 类型:${type} | ID:${promptId} | 配置键:${configKey} | 旧ID:${oldActiveId}`); } // 标记为已修改 this._setDirty(); // 重新渲染列表 this._renderPromptLists(); // 输出激活状态变化 logger.debug(`激活状态变化 | 类型:${type} | 提示词:${targetPrompt.name} | ID:${promptId} | 旧状态:${wasActive} | 新状态:${targetPrompt.isActive} | 实例属性:${this[activeIdProperty]}`); // 如果系统提示词对象存在,直接更新其中的active_prompts if (this.systemPrompts && this.systemPrompts.active_prompts) { const oldValue = this.systemPrompts.active_prompts[configKey]; this.systemPrompts.active_prompts[configKey] = targetPrompt.isActive ? promptId : null; logger.debug(`更新系统提示词对象 | 配置键:${configKey} | 旧值:${oldValue} | 新值:${this.systemPrompts.active_prompts[configKey]}`); } logger.debug(`${type} 提示词状态切换: ${promptId} -> ${targetPrompt.isActive ? '激活' : '取消激活'}`); } /** * 删除提示词 * @param {string} type 类型 * @param {string} promptId 提示词ID */ _deletePrompt(type, promptId) { // 获取对应的数据数组 let dataArray; switch (type) { case 'expand': dataArray = this.expandPrompts; break; case 'zhVision': dataArray = this.zhVisionPrompts; break; case 'enVision': dataArray = this.enVisionPrompts; break; default: return; } // 找到要删除的提示词索引 const index = dataArray.findIndex(p => p.id === promptId); if (index === -1) return; // 获取提示词名称用于提示 const promptName = dataArray[index].name; // 创建确认对话框 createSettingsDialog({ title: '确认删除', isConfirmDialog: true, disableBackdropAndCloseOnClickOutside: true, saveButtonText: '确认', cancelButtonText: '取消', renderContent: (content) => { content.style.textAlign = 'center'; content.style.padding = '1rem'; const confirmMessage = document.createElement('p'); confirmMessage.textContent = `确定要删除提示词"${promptName}"吗?`; confirmMessage.style.margin = '0'; confirmMessage.style.fontSize = '1rem'; content.appendChild(confirmMessage); }, onSave: () => { // 从数组中删除提示词 dataArray.splice(index, 1); // 标记为已修改 this._setDirty(); // 重新渲染列表 this._renderPromptLists(); return true; } }); } /** * 将当前配置保存到服务器 * @returns {Promise} 保存操作的Promise */ async _saveConfigToServer() { try { // 构建系统提示词配置 const systemPrompts = { expand_prompts: {}, translate_prompts: { ZH: { role: "system", content: "你是一名AI绘画领域的提示词翻译专家,负责将用户提供的文本内容由{src_lang}准确地翻译成{dst_lang}。要求:1.完整翻译用户提供的所有文本,不要遗漏;2.保持格式,不要改变原文的书写结构、标点符号、权重标记格式【如(文本内容:1.2)】等;2.准确原文,使用准确、地道的{dst_lang}表达词汇和AI绘画领域的专业的术语;5.直接输出翻译结果,无需注释、说明。" } }, vision_prompts: {}, active_prompts: { expand: null, vision_zh: null, vision_en: null } }; // 创建一个有序的提示词列表 const orderedExpandPrompts = [...this.expandPrompts].sort((a, b) => a.order - b.order); // 添加扩写提示词,并记录激活的提示词ID orderedExpandPrompts.forEach(prompt => { // 使用提示词ID作为键,而不是名称 systemPrompts.expand_prompts[prompt.id] = { name: prompt.name, role: "system", content: prompt.content }; // 记录激活的提示词ID if (prompt.isActive) { systemPrompts.active_prompts.expand = prompt.id; logger.debug(`保存激活的扩写提示词ID: ${prompt.id}`); } }); // 如果没有找到激活的扩写提示词,使用实例属性中保存的ID if (!systemPrompts.active_prompts.expand && this.activeExpandPromptId) { systemPrompts.active_prompts.expand = this.activeExpandPromptId; logger.debug(`使用实例属性中的扩写提示词ID: ${this.activeExpandPromptId}`); } // 创建有序的中文和英文反推提示词列表 const orderedZhVisionPrompts = [...this.zhVisionPrompts].sort((a, b) => a.order - b.order); const orderedEnVisionPrompts = [...this.enVisionPrompts].sort((a, b) => a.order - b.order); // 添加中文反推提示词 orderedZhVisionPrompts.forEach(prompt => { systemPrompts.vision_prompts[prompt.id] = { name: prompt.name, role: "system", content: prompt.content }; // 记录激活的提示词ID if (prompt.isActive) { systemPrompts.active_prompts.vision_zh = prompt.id; logger.debug(`保存激活的中文反推提示词ID: ${prompt.id}`); } }); // 如果没有找到激活的中文反推提示词,使用实例属性中保存的ID if (!systemPrompts.active_prompts.vision_zh && this.activeZhVisionPromptId) { systemPrompts.active_prompts.vision_zh = this.activeZhVisionPromptId; logger.debug(`使用实例属性中的中文反推提示词ID: ${this.activeZhVisionPromptId}`); } // 添加英文反推提示词 orderedEnVisionPrompts.forEach(prompt => { systemPrompts.vision_prompts[prompt.id] = { name: prompt.name, role: "system", content: prompt.content }; // 记录激活的提示词ID if (prompt.isActive) { systemPrompts.active_prompts.vision_en = prompt.id; logger.debug(`保存激活的英文反推提示词ID: ${prompt.id}`); } }); // 如果没有找到激活的英文反推提示词,使用实例属性中保存的ID if (!systemPrompts.active_prompts.vision_en && this.activeEnVisionPromptId) { systemPrompts.active_prompts.vision_en = this.activeEnVisionPromptId; logger.debug(`使用实例属性中的英文反推提示词ID: ${this.activeEnVisionPromptId}`); } // 输出最终的激活状态 logger.debug(`最终激活的提示词ID: expand=${systemPrompts.active_prompts.expand}, vision_zh=${systemPrompts.active_prompts.vision_zh}, vision_en=${systemPrompts.active_prompts.vision_en}`); const response = await fetch('/prompt_assistant/api/config/system_prompts', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(systemPrompts) }); if (!response.ok) { throw new Error(`保存失败: ${response.status} ${response.statusText}`); } logger.debug('配置已成功保存到服务器'); return true; } catch (error) { logger.error(`保存系统提示词配置失败: ${error.message}`); throw error; } } /** * 为头部单元格添加列宽调整手柄 * @param {HTMLElement} headerRow 头部行元素 */ _addHeaderResizers(headerRow) { const cells = headerRow.querySelectorAll('.prompt-list-cell'); // 为前三列添加调整手柄(最后一列操作列不需要) for (let i = 0; i < cells.length - 1; i++) { const cell = cells[i]; const resizer = document.createElement('div'); resizer.className = 'column-resizer'; cell.appendChild(resizer); this._addColumnResizer(resizer, cell); } } /** * 添加列宽调整功能 * @param {HTMLElement} resizer 调整手柄元素 * @param {HTMLElement} column 列元素 */ _addColumnResizer(resizer, column) { let isResizing = false; let startX = 0; let startWidth = 0; resizer.addEventListener('mousedown', (e) => { isResizing = true; startX = e.clientX; startWidth = parseInt(document.defaultView.getComputedStyle(column).width, 10); // 添加拖拽状态样式 resizer.classList.add('resizing'); document.body.classList.add('column-resizing'); // 添加事件监听器 document.addEventListener('mousemove', handleMouseMove, { capture: true }); document.addEventListener('mouseup', handleMouseUp, { capture: true }); e.preventDefault(); e.stopPropagation(); }); const handleMouseMove = (e) => { if (!isResizing) return; e.preventDefault(); e.stopPropagation(); const deltaX = e.clientX - startX; const newWidth = startWidth + deltaX; // 设置最小和最大宽度限制 const minWidth = 80; const maxWidth = 500; if (newWidth >= minWidth && newWidth <= maxWidth) { // 获取列的类名以确定是哪一列 const columnClass = Array.from(column.classList).find(cls => cls.includes('-cell') && cls !== 'prompt-list-cell' ); if (columnClass) { // 同时调整所有相同类型的列 const allColumns = document.querySelectorAll(`.${columnClass}`); allColumns.forEach(col => { col.style.width = newWidth + 'px'; col.style.flexShrink = '0'; col.style.flexGrow = '0'; col.style.flexBasis = newWidth + 'px'; }); } else { // 如果没有找到特定类名,只调整当前列 column.style.width = newWidth + 'px'; column.style.flexShrink = '0'; column.style.flexGrow = '0'; column.style.flexBasis = newWidth + 'px'; } } }; const handleMouseUp = (e) => { if (!isResizing) return; isResizing = false; // 获取列的类名以确定是哪一列 const columnClass = Array.from(column.classList).find(cls => cls.includes('-cell') && cls !== 'prompt-list-cell' ); // 重置 flex 属性,允许下次调整 if (columnClass) { const allColumns = document.querySelectorAll(`.${columnClass}`); allColumns.forEach(col => { col.style.flexShrink = ''; col.style.flexGrow = ''; col.style.flexBasis = ''; }); } else { column.style.flexShrink = ''; column.style.flexGrow = ''; column.style.flexBasis = ''; } // 移除拖拽状态样式 resizer.classList.remove('resizing'); document.body.classList.remove('column-resizing'); // 移除事件监听器 document.removeEventListener('mousemove', handleMouseMove, { capture: true }); document.removeEventListener('mouseup', handleMouseUp, { capture: true }); e.preventDefault(); e.stopPropagation(); }; // 添加鼠标进入和离开事件以增强视觉反馈 resizer.addEventListener('mouseenter', () => { if (!isResizing) { resizer.style.opacity = '0.6'; } }); resizer.addEventListener('mouseleave', () => { if (!isResizing) { resizer.style.opacity = ''; } }); } /** * 初始化拖拽排序功能 * @param {HTMLElement} container 列表容器 * @param {string} type 类型 */ _initSortable(container, type) { // 检查是否已加载Sortable库 if (typeof Sortable === 'undefined') { this._loadSortableLibrary().then(() => { this._createSortableInstance(container, type); }).catch(error => { logger.error('加载Sortable库失败:', error); }); } else { this._createSortableInstance(container, type); } } /** * 加载Sortable库 * @returns {Promise} */ _loadSortableLibrary() { return new Promise((resolve, reject) => { if (typeof Sortable !== 'undefined') { resolve(); return; } const script = document.createElement('script'); script.src = '/custom_nodes/comfyui_prompt_assistant/js/lib/Sortable.min.js'; script.onload = () => resolve(); script.onerror = () => reject(new Error('Failed to load Sortable library')); document.head.appendChild(script); }); } /** * 创建Sortable实例 * @param {HTMLElement} container 列表容器 * @param {string} type 类型 */ _createSortableInstance(container, type) { new Sortable(container, { animation: 150, ghostClass: 'sortable-ghost', chosenClass: 'sortable-chosen', dragClass: 'sortable-drag', handle: '.prompt-list-row', // 移除 handle 限制,允许整行拖拽 // handle: '.prompt-list-row', // 禁用固定项目,确保所有项目都可以拖拽 filter: '', preventOnFilter: false, onStart: (evt) => { logger.debug(`开始拖拽: 类型=${type}, 索引=${evt.oldIndex}`); }, onEnd: (evt) => { logger.debug(`结束拖拽: 类型=${type}, 旧索引=${evt.oldIndex}, 新索引=${evt.newIndex}`); this._handleSortEnd(evt, type); } }); } /** * 处理拖拽排序结束事件 * @param {Event} evt 排序事件 * @param {string} type 类型 */ _handleSortEnd(evt, type) { const { oldIndex, newIndex } = evt; if (oldIndex === newIndex) return; // 获取对应的数据数组 let dataArray; switch (type) { case 'expand': dataArray = this.expandPrompts; break; case 'zhVision': dataArray = this.zhVisionPrompts; break; case 'enVision': dataArray = this.enVisionPrompts; break; default: return; } // 输出排序前的顺序信息 logger.debug(`排序前数据: ${JSON.stringify(dataArray.map(item => ({ id: item.id, order: item.order })))}`); // 重新排序数据 const movedItem = dataArray.splice(oldIndex, 1)[0]; dataArray.splice(newIndex, 0, movedItem); // 更新所有项目的顺序属性 dataArray.forEach((item, index) => { item.order = index; }); // 输出排序后的顺序信息 logger.debug(`排序后数据: ${JSON.stringify(dataArray.map(item => ({ id: item.id, order: item.order })))}`); logger.debug(`${type} 列表排序已更新: ${oldIndex} -> ${newIndex} | 移动项ID: ${movedItem.id}`); // 标记为已修改 this._setDirty(); } } // 导出规则配置管理器实例 export const rulesConfigManager = new RulesConfigManager();