/** * 规则配置管理器 * 负责管理规则规则配置弹窗和相关功能 */ import { app } from "../../../../scripts/app.js"; import { logger } from '../utils/logger.js'; import { ResourceManager } from "../utils/resourceManager.js"; import { createSettingsDialog, createFormGroup, createInputGroup, createSelectGroup, createTextareaGroup, createComboBoxGroup, createSelectButtonGroup } from "./uiComponents.js"; import { APIService } from "../services/api.js"; import { tUI } from "../utils/uiI18n.js"; class RulesConfigManager { constructor() { this.systemPrompts = null; // ---存储规则列表数据--- this.expandPrompts = []; this.zhVisionPrompts = []; this.enVisionPrompts = []; this.videoPrompts = []; // ---当前激活的规则ID--- this.activeExpandPromptId = null; this.activeZhVisionPromptId = null; this.activeEnVisionPromptId = null; this.activeVideoPromptId = null; this.isDirty = false; this.initialState = null; // 用于存储状态备份 } /** * 备份当前状态 */ _backupState() { this.initialState = { expandPrompts: JSON.parse(JSON.stringify(this.expandPrompts)), translatePrompts: JSON.parse(JSON.stringify(this.translatePrompts || [])), zhVisionPrompts: JSON.parse(JSON.stringify(this.zhVisionPrompts)), enVisionPrompts: JSON.parse(JSON.stringify(this.enVisionPrompts)), videoPrompts: JSON.parse(JSON.stringify(this.videoPrompts || [])), activeExpandPromptId: this.activeExpandPromptId, activeZhVisionPromptId: this.activeZhVisionPromptId, activeEnVisionPromptId: this.activeEnVisionPromptId, activeVideoPromptId: this.activeVideoPromptId, }; 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.translatePrompts = JSON.parse(JSON.stringify(this.initialState.translatePrompts || [])); this.zhVisionPrompts = JSON.parse(JSON.stringify(this.initialState.zhVisionPrompts)); this.enVisionPrompts = JSON.parse(JSON.stringify(this.initialState.enVisionPrompts)); this.videoPrompts = JSON.parse(JSON.stringify(this.initialState.videoPrompts || [])); this.activeExpandPromptId = this.initialState.activeExpandPromptId; this.activeZhVisionPromptId = this.initialState.activeZhVisionPromptId; this.activeEnVisionPromptId = this.initialState.activeEnVisionPromptId; this.activeVideoPromptId = this.initialState.activeVideoPromptId; } 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: `${tUI('规则管理器')}`, 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: tUI("打开配置失败"), 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); // ---创建标签页容器--- const tabContainer = document.createElement('div'); tabContainer.className = 'rules-config-tabs'; // ---创建标签页头部包装容器(包含标签按钮和添加按钮)--- const tabHeaderWrapper = document.createElement('div'); tabHeaderWrapper.className = 'tab-header-wrapper'; // ---创建标签页头部--- const tabHeader = document.createElement('div'); tabHeader.className = 'tab-header'; // ---创建添加按钮容器--- const addButtonContainer = document.createElement('div'); addButtonContainer.className = 'tab-header-actions'; // 定义标签页配置(包含是否有添加按钮) const tabs = [ { id: 'expand', title: tUI('提示词优化规则'), subtitle: tUI('提示词优化润色提示词'), addLabel: tUI('添加提示词优化规则') }, { id: 'zhVision', title: tUI('中文反推'), subtitle: tUI('图像反推中文提示词'), addLabel: tUI('添加中文反推规则') }, { id: 'enVision', title: tUI('英文反推'), subtitle: tUI('图像反推英文提示词'), addLabel: tUI('添加英文反推规则') }, { id: 'video', title: tUI('视频反推'), subtitle: tUI('将视频反推提示词'), addLabel: tUI('添加视频反推规则') }, { id: 'translate', title: tUI('翻译规则'), subtitle: tUI('大模型翻译规则'), addLabel: null } ]; // 创建标签按钮 tabs.forEach((tab, index) => { const button = this._createRuleTabButton(tab.id, tab.title, tab.subtitle); if (index === 0) { button.classList.add('active'); } tabHeader.appendChild(button); }); // 创建各标签页对应的添加按钮(初始只显示第一个) tabs.forEach((tab, index) => { if (tab.addLabel) { const addButton = document.createElement('button'); addButton.className = 'p-button p-component p-button-sm tab-add-button'; addButton.type = 'button'; addButton.dataset.tab = tab.id; addButton.innerHTML = `${tab.addLabel}`; addButton.onclick = () => { this._showPromptEditDialog(tab.id, null); }; // 只显示第一个标签页的按钮 addButton.style.display = index === 0 ? 'inline-flex' : 'none'; addButtonContainer.appendChild(addButton); } }); tabHeaderWrapper.appendChild(tabHeader); tabHeaderWrapper.appendChild(addButtonContainer); tabContainer.appendChild(tabHeaderWrapper); // ---创建标签页内容容器--- const tabContent = document.createElement('div'); tabContent.className = 'tab-content'; // 创建各个标签页内容 const expandPane = this._createExpandTabPane(); const zhVisionPane = this._createVisionTabPane('zhVision', '中文反推规则'); const enVisionPane = this._createVisionTabPane('enVision', '英文反推规则'); const videoPane = this._createVideoTabPane(); const translatePane = this._createTranslateTabPane(); tabContent.appendChild(expandPane); tabContent.appendChild(zhVisionPane); tabContent.appendChild(enVisionPane); tabContent.appendChild(videoPane); tabContent.appendChild(translatePane); tabContainer.appendChild(tabContent); form.appendChild(tabContainer); container.appendChild(form); // 默认显示第一个标签页 this._switchRuleTab('expand', tabHeader, tabContent); // 加载系统规则配置 this._loadSystemPrompts(); } /** * 创建规则标签按钮 * @param {string} tabId 标签ID * @param {string} title 标签标题 * @param {string} subtitle 标签副标题 * @returns {HTMLElement} 标签按钮元素 */ _createRuleTabButton(tabId, title, subtitle) { const button = document.createElement('button'); button.className = 'tab-button'; button.dataset.tab = tabId; button.type = 'button'; const titleEl = document.createElement('div'); titleEl.className = 'tab-title'; titleEl.textContent = title; button.appendChild(titleEl); if (subtitle) { const subtitleEl = document.createElement('div'); subtitleEl.className = 'tab-subtitle'; subtitleEl.textContent = subtitle; button.appendChild(subtitleEl); } // 点击切换标签 button.addEventListener('click', () => { const tabHeader = button.parentElement; // tabHeader 在 tab-header-wrapper 内,tabContent 是 wrapper 的下一个兄弟元素 const tabHeaderWrapper = tabHeader.parentElement; const tabContent = tabHeaderWrapper.nextElementSibling; this._switchRuleTab(tabId, tabHeader, tabContent); }); return button; } /** * 切换规则标签页 * @param {string} tabId 标签ID * @param {HTMLElement} header 标签头部容器 * @param {HTMLElement} contentContainer 内容容器 */ _switchRuleTab(tabId, header, contentContainer) { // 更新标签按钮状态 header.querySelectorAll('.tab-button').forEach(btn => { if (btn.dataset.tab === tabId) { btn.classList.add('active'); } else { btn.classList.remove('active'); } }); // 显示对应内容 contentContainer.querySelectorAll('.tab-pane').forEach(pane => { pane.style.display = pane.dataset.tab === tabId ? 'block' : 'none'; }); // 切换添加按钮的显示状态 const actionsContainer = header.parentElement?.querySelector('.tab-header-actions'); if (actionsContainer) { actionsContainer.querySelectorAll('.tab-add-button').forEach(btn => { btn.style.display = btn.dataset.tab === tabId ? 'inline-flex' : 'none'; }); } } /** * 创建扩写规则标签页内容 * @returns {HTMLElement} 标签页内容元素 */ _createExpandTabPane() { const pane = document.createElement('div'); pane.className = 'tab-pane'; pane.dataset.tab = 'expand'; pane.style.display = 'none'; pane.style.padding = '16px'; // 创建规则列表容器 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); pane.appendChild(listContainer); // 存储列表引用 this.expandScrollList = scrollList; return pane; } /** * 创建翻译规则标签页内容(只读编辑模式) * @returns {HTMLElement} 标签页内容元素 */ _createTranslateTabPane() { const pane = document.createElement('div'); pane.className = 'tab-pane'; pane.dataset.tab = 'translate'; pane.style.display = 'none'; pane.style.padding = '16px'; // 创建翻译规则列表容器 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); pane.appendChild(listContainer); // 创建提示信息 const notice = document.createElement('div'); notice.className = 'rule-pane-notice'; notice.innerHTML = ` ${tUI('翻译规则仅支持编辑,不支持新增和删除')}`; pane.appendChild(notice); // 存储列表引用 this.translateScrollList = scrollList; return pane; } /** * 创建反推规则标签页内容 * @param {string} type 类型 (zhVision/enVision) * @param {string} title 标题 * @returns {HTMLElement} 标签页内容元素 */ _createVisionTabPane(type, title) { const pane = document.createElement('div'); pane.className = 'tab-pane'; pane.dataset.tab = type; pane.style.display = 'none'; pane.style.padding = '16px'; // 创建规则列表容器 const listContainer = document.createElement('div'); listContainer.className = 'prompt-list-container vision-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); pane.appendChild(listContainer); // 存储列表引用 if (type === 'zhVision') { this.zhVisionScrollList = scrollList; } else { this.enVisionScrollList = scrollList; } return pane; } /** * 创建视频反推规则标签页内容 * @returns {HTMLElement} 标签页内容元素 */ _createVideoTabPane() { const pane = document.createElement('div'); pane.className = 'tab-pane'; pane.dataset.tab = 'video'; pane.style.display = 'none'; pane.style.padding = '16px'; // 创建规则列表容器 const listContainer = document.createElement('div'); listContainer.className = 'prompt-list-container video-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); pane.appendChild(listContainer); // 存储列表引用 this.videoScrollList = scrollList; return pane; } /** * 加载系统规则配置 */ async _loadSystemPrompts() { try { const response = await fetch(APIService.getApiUrl('/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, // 如果没有名称,使用键作为名称 tags: prompt.tags || [], category: prompt.category || '', showIn: prompt.showIn || ['frontend', 'node'], 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, tags: prompt.tags || [], category: prompt.category || '', showIn: prompt.showIn || ['frontend', 'node'], 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, tags: prompt.tags || [], category: prompt.category || '', showIn: prompt.showIn || ['frontend', 'node'], 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.translatePrompts = []; if (data.translate_prompts) { Object.keys(data.translate_prompts).forEach(key => { const prompt = data.translate_prompts[key]; this.translatePrompts.push({ id: key, name: key, // 使用键作为名称 content: prompt.content, role: prompt.role || 'system', isReadOnly: true // 标记为只读(不可增删) }); }); } // 转换视频反推规则数据 this.videoPrompts = []; const activeVideoId = activePrompts.video; if (data.video_prompts) { const videoKeys = Object.keys(data.video_prompts); videoKeys.forEach((key, index) => { const prompt = data.video_prompts[key]; this.videoPrompts.push({ id: key, name: prompt.name || key, tags: prompt.tags || [], category: prompt.category || '', showIn: prompt.showIn || ['frontend', 'node'], content: prompt.content, isActive: key === activeVideoId, order: index }); }); // 如果没有找到激活的视频反推规则,则激活第一个 if (!activeVideoId && this.videoPrompts.length > 0) { this.videoPrompts[0].isActive = true; this.activeVideoPromptId = this.videoPrompts[0].id; } else { this.activeVideoPromptId = activeVideoId; } } // 备份初始状态 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.translatePrompts?.length || 0}个 | 中文反推:${this.zhVisionPrompts.length}个 | 英文反推:${this.enVisionPrompts.length}个`); // 渲染扩写规则列表 if (this.expandScrollList) { this._renderPromptList(this.expandScrollList, this.expandPrompts, 'expand'); logger.debug(`渲染提示词优化规则列表完成`); } else { logger.error(`提示词优化规则列表容器不存在`); } // 渲染翻译规则列表(只读编辑模式) if (this.translateScrollList) { this._renderTranslateList(this.translateScrollList, this.translatePrompts || []); logger.debug(`渲染翻译规则列表完成`); } // 渲染中文反推规则列表 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(`英文反推规则列表容器不存在`); } // 渲染视频反推规则列表 if (this.videoScrollList) { this._renderPromptList(this.videoScrollList, this.videoPrompts || [], 'video'); logger.debug(`渲染视频反推规则列表完成`); } } /** * 渲染翻译规则列表(只读编辑模式) * @param {HTMLElement} container 列表容器 * @param {Array} prompts 翻译规则数组 */ _renderTranslateList(container, prompts) { container.innerHTML = ''; prompts.forEach(prompt => { const row = document.createElement('div'); row.className = 'prompt-list-row translate-row'; row.dataset.promptId = prompt.id; row.dataset.type = 'translate'; // 名称列 const nameCell = document.createElement('div'); nameCell.className = 'prompt-list-cell name-cell'; nameCell.textContent = prompt.name; nameCell.title = prompt.name; // 内容列 const contentCell = document.createElement('div'); contentCell.className = 'prompt-list-cell content-cell'; contentCell.textContent = prompt.content; contentCell.title = prompt.content; // 操作列(只有编辑按钮) const actionCell = document.createElement('div'); actionCell.className = 'prompt-list-cell action-cell'; const editBtn = document.createElement('button'); editBtn.className = 'prompt-action-btn edit-btn'; editBtn.innerHTML = ''; editBtn.title = tUI('编辑翻译规则'); editBtn.onclick = (e) => { e.stopPropagation(); this._showTranslateEditDialog(prompt.id); }; actionCell.appendChild(editBtn); row.appendChild(nameCell); row.appendChild(contentCell); row.appendChild(actionCell); container.appendChild(row); }); } /** * 显示翻译规则编辑对话框 * @param {string} promptId 翻译规则ID */ _showTranslateEditDialog(promptId) { const prompt = (this.translatePrompts || []).find(p => p.id === promptId); if (!prompt) { logger.error(`找不到翻译规则: ${promptId}`); return; } createSettingsDialog({ title: `${tUI('编辑翻译规则')}`, isConfirmDialog: true, dialogClassName: 'translate-edit-dialog', renderContent: (content) => { content.className += ' dialog-form-content'; content.style.padding = '1rem'; content.style.display = 'flex'; content.style.flexDirection = 'column'; content.style.flex = '1'; // 显示规则名称(只读) const nameGroup = document.createElement('div'); nameGroup.className = 'settings-form-group-item'; nameGroup.innerHTML = `
`; nameGroup.style.marginBottom = '10px'; // 创建内容编辑区 const contentTextarea = createTextareaGroup('规则内容', tUI('请输入翻译规则内容'), 10); contentTextarea.group.style.flex = '1'; contentTextarea.group.style.display = 'flex'; contentTextarea.group.style.flexDirection = 'column'; contentTextarea.textarea.value = prompt.content || ''; content.appendChild(nameGroup); content.appendChild(contentTextarea.group); content.contentTextarea = contentTextarea.textarea; }, onSave: (content) => { const newContent = content.contentTextarea.value.trim(); if (!newContent) { app.extensionManager.toast.add({ severity: "error", summary: tUI("编辑失败"), detail: "规则内容不能为空", life: 3000 }); return false; } // 更新翻译规则内容 prompt.content = newContent; // 标记为已修改 this._setDirty(); // 重新渲染列表 this._renderPromptLists(); return true; } }); } /** * 渲染单个规则列表 * @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 ? tUI('当前使用中,点击取消激活') : tUI('未激活,点击激活'); 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 = tUI('编辑'); 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 = tUI('删除'); 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); } /** * 获取已存在的分类列表 * 从所有规则类型中提取已使用的分类 * @returns {Array<{value: string, text: string}>} 分类选项列表 */ _getExistingCategories() { const categories = new Set(); // 从所有规则类型中提取分类 const allPrompts = [ ...this.expandPrompts, ...this.zhVisionPrompts, ...this.enVisionPrompts, ...this.videoPrompts ]; allPrompts.forEach(prompt => { if (prompt.category && typeof prompt.category === 'string') { categories.add(prompt.category); } }); // 转换为下拉选项格式 return Array.from(categories) .sort() // 按字母顺序排序 .map(cat => ({ value: cat, text: cat })); } /** * 显示规则编辑对话框 * @param {string} type 类型 * @param {string|null} promptId 规则ID,null表示新建 */ _showPromptEditDialog(type, promptId) { this._showPromptConfigDialog(type, promptId); } /** * 显示通用的规则配置弹窗 * @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); } else if (defaultType === 'video') { editData = this.videoPrompts.find(p => p.id === promptId); } } createSettingsDialog({ title: isEdit ? tUI('编辑规则') : tUI('添加规则'), isConfirmDialog: true, dialogClassName: 'prompt-edit-dialog', disableBackdropAndCloseOnClickOutside: true, saveButtonText: isEdit ? tUI('保存') : tUI('添加'), cancelButtonText: tUI('取消'), renderContent: (content) => { content.className += ' dialog-form-content'; content.style.padding = '10px 40px'; // 增加两侧留白 content.style.display = 'flex'; content.style.flexDirection = 'column'; content.style.flex = '1'; content.style.overflow = 'hidden'; // 整体不滚动,内部 textarea 滚动 // 创建规则名称输入框 const nameInput = createInputGroup('规则名称', tUI('请输入规则名称')); nameInput.group.style.marginBottom = '10px'; if (isEdit && editData) { nameInput.input.value = editData.name || ''; } // 创建规则类型下拉框 const typeOptions = [ { value: 'expand', text: '提示词优化' }, { value: 'zhVision', text: '反推(中文)' }, { value: 'enVision', text: '反推(英文)' }, { value: 'video', text: '视频反推' } ]; const typeSelect = createSelectGroup('规则类型', typeOptions, defaultType); typeSelect.group.style.marginBottom = '10px'; // 创建分类选择框(可输入) const categoryOptions = this._getExistingCategories(); const initialCategory = (isEdit && editData) ? (editData.category || '') : ''; const categoryComboBox = createComboBoxGroup('分类', categoryOptions, initialCategory, { placeholder: '输入或选择分类(可留空)', emptyText: '暂无分类' }); categoryComboBox.group.style.flex = '1'; categoryComboBox.group.style.marginBottom = '0'; // 创建显示位置选择组件 const showInOptions = [ { value: 'frontend', label: '小助手上显示' }, { value: 'node', label: '节点上显示' } ]; // 获取初始值:编辑时从规则数据读取,新建时默认都选中 const initialShowIn = (isEdit && editData && editData.showIn) ? editData.showIn : ['frontend', 'node']; const showInSelectButton = createSelectButtonGroup('', showInOptions, initialShowIn, { mode: 'multiple', size: 'small', allowEmpty: true }); showInSelectButton.group.style.marginBottom = '0'; // 创建行容器放置分类和显示位置组件 const categoryRow = document.createElement('div'); categoryRow.style.display = 'flex'; categoryRow.style.flexDirection = 'row'; categoryRow.style.alignItems = 'flex-end'; categoryRow.style.gap = '1rem'; categoryRow.style.marginBottom = '10px'; categoryRow.appendChild(categoryComboBox.group); categoryRow.appendChild(showInSelectButton.group); // 创建规则内容多行输入框 const contentTextarea = createTextareaGroup('规则内容', tUI('请输入规则内容')); contentTextarea.group.style.marginBottom = '0'; contentTextarea.group.style.flex = '1'; contentTextarea.group.style.display = 'flex'; contentTextarea.group.style.flexDirection = 'column'; // 确保 textarea 填充容器 const textareaContainer = contentTextarea.group.querySelector('.float-label-container'); if (textareaContainer) { textareaContainer.style.flex = '1'; textareaContainer.style.display = 'flex'; textareaContainer.style.flexDirection = 'column'; contentTextarea.textarea.style.flex = '1'; } if (isEdit && editData) { contentTextarea.textarea.value = editData.content || ''; } content.appendChild(nameInput.group); content.appendChild(typeSelect.group); content.appendChild(categoryRow); content.appendChild(contentTextarea.group); content.nameInput = nameInput.input; content.typeSelect = typeSelect.select; content.categoryInput = categoryComboBox.input; content.showInSelectButton = showInSelectButton; content.contentTextarea = contentTextarea.textarea; // 聚焦名称输入框 setTimeout(() => { nameInput.input.focus(); }, 100); }, onSave: (content) => { const name = content.nameInput.value.trim(); const type = content.typeSelect.value; const category = content.categoryInput.value.trim(); const showIn = content.showInSelectButton.getValue(); const promptContent = content.contentTextarea.value.trim(); if (!name || !promptContent) { app.extensionManager.toast.add({ severity: "error", summary: isEdit ? tUI("编辑失败") : tUI("添加失败"), 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 if (type === 'video') { targetArray = this.videoPrompts; } 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 ? tUI("编辑失败") : tUI("添加失败"), detail: `${tUI('规则名称')} "${name}" ${tUI('已存在')}`, 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, category: category, showIn: showIn, 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.category = category; editData.showIn = showIn; 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 if (type === 'video') { newId = 'video_' + 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 if (type === 'video') { newId = 'video_' + name.replace(/\s+/g, '_'); } else { newId = name; } const newPrompt = { id: newId, name: name, category: category, showIn: showIn, 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; case 'video': dataArray = this.videoPrompts; activeIdProperty = 'activeVideoPromptId'; configKey = 'video'; 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; case 'video': dataArray = this.videoPrompts; break; default: return; } // 找到要删除的规则索引 const index = dataArray.findIndex(p => p.id === promptId); if (index === -1) return; // 获取规则名称用于提示 const promptName = dataArray[index].name; // 创建确认对话框(使用危险按钮样式) createSettingsDialog({ title: `${tUI('确认删除')}`, isConfirmDialog: true, dialogClassName: 'confirm-dialog', disableBackdropAndCloseOnClickOutside: true, saveButtonText: tUI('删除'), saveButtonIcon: 'pi-trash', isDangerButton: true, cancelButtonText: tUI('取消'), renderContent: (content) => { content.style.textAlign = 'center'; content.style.padding = '1rem'; const confirmMessage = document.createElement('p'); confirmMessage.textContent = `${tUI('确定要删除规则')} "${promptName}" ${tUI('吗?')}`; 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 { // 获取原始版本号(从加载时保存的 systemPrompts 获取) const configVersion = this.systemPrompts?.__config_version || '2.0'; // 构建系统规则配置(保留版本号) const systemPrompts = { __config_version: configVersion, expand_prompts: {}, translate_prompts: {}, vision_prompts: {}, video_prompts: {}, active_prompts: { expand: null, vision_zh: null, vision_en: null, video: null } }; // 添加翻译规则(从 this.translatePrompts 动态构建) (this.translatePrompts || []).forEach(prompt => { systemPrompts.translate_prompts[prompt.id] = { role: prompt.role || 'system', content: prompt.content }; }); // 创建一个有序的规则列表 const orderedExpandPrompts = [...this.expandPrompts].sort((a, b) => a.order - b.order); // 添加扩写规则,并记录激活的规则ID orderedExpandPrompts.forEach(prompt => { systemPrompts.expand_prompts[prompt.id] = { name: prompt.name, tags: prompt.tags || [], category: prompt.category || '', showIn: prompt.showIn || ['frontend', 'node'], 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, tags: prompt.tags || [], category: prompt.category || '', showIn: prompt.showIn || ['frontend', 'node'], 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, tags: prompt.tags || [], category: prompt.category || '', showIn: prompt.showIn || ['frontend', 'node'], 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}`); } // 添加视频反推规则 const orderedVideoPrompts = [...(this.videoPrompts || [])].sort((a, b) => a.order - b.order); orderedVideoPrompts.forEach(prompt => { systemPrompts.video_prompts[prompt.id] = { name: prompt.name, tags: prompt.tags || [], category: prompt.category || '', showIn: prompt.showIn || ['frontend', 'node'], role: "system", content: prompt.content }; // 记录激活的规则ID if (prompt.isActive) { systemPrompts.active_prompts.video = prompt.id; logger.debug(`保存激活的视频反推规则ID: ${prompt.id}`); } }); // 如果没有找到激活的视频反推规则,使用实例属性中保存的ID if (!systemPrompts.active_prompts.video && this.activeVideoPromptId) { systemPrompts.active_prompts.video = this.activeVideoPromptId; logger.debug(`使用实例属性中的视频反推规则ID: ${this.activeVideoPromptId}`); } // 输出最终的激活状态 logger.debug(`最终激活的规则ID: expand=${systemPrompts.active_prompts.expand}, vision_zh=${systemPrompts.active_prompts.vision_zh}, vision_en=${systemPrompts.active_prompts.vision_en}, video=${systemPrompts.active_prompts.video}`); const response = await fetch(APIService.getApiUrl('/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', // 排除状态列和操作列,只允许从名称和内容列拖拽 filter: '.status-cell, .action-cell', preventOnFilter: true, 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; case 'video': dataArray = this.videoPrompts; 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();