/** * 小助手设置服务 * 负责管理小助手的设置选项,提供开关控制功能 */ import { app } from "../../../../scripts/app.js"; import { logger } from '../utils/logger.js'; import { PromptAssistant } from "./PromptAssistant.js"; import { ImageCaption } from "./imageCaption.js"; import { EventManager } from "../utils/eventManager.js"; import { ResourceManager } from "../utils/resourceManager.js"; import { HistoryCacheService, TagCacheService, TranslateCacheService, CACHE_CONFIG } from "../services/cache.js"; import { UIToolkit } from "../utils/UIToolkit.js"; import { FEATURES, handleFeatureChange } from "../services/features.js"; import { APIService } from "../services/api.js"; import { UI_LANGUAGE_SETTING_ID, getStoredUiLanguage, persistUiLanguage, ensureUiLocaleLoaded, tUI, patchToastLocalization, syncGlobalUiLocalization, LANGUAGE_OPTIONS } from "../utils/uiI18n.js"; import { apiConfigManager } from "./apiConfigManager.js"; import { rulesConfigManager } from "./rulesConfigManager.js"; import { createSettingsDialog, closeModalWithAnimation, createFormGroup, createInputGroup, createSelectGroup, createHorizontalFormGroup, createLoadingButton } from "./uiComponents.js"; // 标记是否是首次加载页面 let isFirstLoad = true; function localizeSettingsPayload(payload) { if (Array.isArray(payload)) { return payload.map(item => localizeSettingsPayload(item)); } if (!payload || typeof payload !== "object") { return payload; } const localized = {}; for (const [key, value] of Object.entries(payload)) { if (key === "category" && Array.isArray(value)) { localized[key] = value.map(item => tUI(item, item)); continue; } if (["name", "tooltip", "text", "summary", "detail"].includes(key) && typeof value === "string") { localized[key] = tUI(value, value); continue; } if (typeof value === "function") { localized[key] = value; continue; } localized[key] = localizeSettingsPayload(value); } return localized; } // ---服务选择器配置--- const SERVICE_TYPES = { translate: { name: tUI('翻译'), configEndpoint: '/config/translate', serviceType: 'translate', filterKey: 'llm_models', includeBaidu: true }, llm: { name: tUI('提示词优化'), configEndpoint: '/config/llm', serviceType: 'llm', filterKey: 'llm_models', includeBaidu: false }, vlm: { name: tUI('图像反推'), configEndpoint: '/config/vision', serviceType: 'vlm', filterKey: 'vlm_models', includeBaidu: false } }; // ---服务选择器--- const serviceSelector = { _servicesCache: null, _cacheTime: 0, _cacheDuration: 2000, // 缓存2秒 /** * 清除服务缓存 */ clearCache() { this._servicesCache = null; this._cacheTime = 0; logger.debug('服务列表缓存已清除'); }, // 获取服务列表(带缓存) async getServices(forceRefresh = false) { const now = Date.now(); if (!forceRefresh && this._servicesCache && (now - this._cacheTime) < this._cacheDuration) { return this._servicesCache; } try { const response = await fetch(APIService.getApiUrl('/services')); if (response.ok) { const data = await response.json(); if (data.success) { this._servicesCache = data.services || []; this._cacheTime = now; return this._servicesCache; } } } catch (error) { logger.error(`获取服务列表失败: ${error.message}`); } return []; }, // 获取指定类型的当前服务ID async getCurrentService(type) { const config = SERVICE_TYPES[type]; if (!config) return null; try { const response = await fetch(APIService.getApiUrl(config.configEndpoint)); if (response.ok) { const data = await response.json(); return data.provider || null; } } catch (error) { logger.error(`获取${config.name}当前服务失败: ${error.message}`); } return null; }, // 设置指定类型的服务 async setCurrentService(type, serviceId) { const config = SERVICE_TYPES[type]; if (!config) return false; try { const response = await fetch(APIService.getApiUrl('/services/current'), { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ service_type: config.serviceType, service_id: serviceId }) }); if (response.ok) { logger.log(`${config.name}服务切换 | 服务ID: ${serviceId}`); // 派发全局事件通知其他组件同步 window.dispatchEvent(new CustomEvent('pa-service-changed', { detail: { service_type: config.serviceType, service_id: serviceId } })); return true; } } catch (error) { logger.error(`切换${config.name}服务失败: ${error.message}`); } return false; }, // 获取指定类型可用的服务选项列表 async getServiceOptions(type) { const config = SERVICE_TYPES[type]; if (!config) return []; const services = await this.getServices(); const options = []; // 添加百度翻译选项(仅翻译类型) if (config.includeBaidu) { options.push({ value: 'baidu', text: tUI('百度翻译') }); } // 过滤并添加其他服务 services .filter(service => { const models = service[config.filterKey]; return models && models.length > 0; }) .forEach(service => { options.push({ value: service.id, text: service.name || service.id }); }); return options; } }; // 将服务选择器挂载到全局 app 对象,方便其他模块(如 PromptAssistant.js, imageCaption.js)调用, // 同时避免模块间的循环引用问题。 app.paServiceSelector = serviceSelector; // ---版本检查工具函数--- // 版本检查状态缓存 let versionCheckCache = { checked: false, // 是否已检查过 latestVersion: null, // 最新版本号 hasUpdate: false // 是否有更新 }; /** * 从 jsDelivr 获取最新版本号(通过读取 pyproject.toml) * @returns {Promise} 返回最新版本号,格式如 "1.2.3",失败返回 null */ async function fetchLatestVersion() { // 如果已经检查过,直接返回缓存结果 if (versionCheckCache.checked) { return versionCheckCache.latestVersion; } try { const response = await fetch('https://cdn.jsdelivr.net/gh/yawiii/ComfyUI-Prompt-Assistant@main/pyproject.toml', { cache: 'no-cache' }); if (!response.ok) { logger.warn(`[版本检查] 请求失败: ${response.status}`); versionCheckCache.checked = true; return null; } const tomlContent = await response.text(); const versionMatch = tomlContent.match(/^version\s*=\s*["']([^"']+)["']/m); const version = versionMatch ? versionMatch[1] : null; // 缓存检查结果 versionCheckCache.checked = true; versionCheckCache.latestVersion = version; return version; } catch (error) { logger.warn(`[版本检查] 获取失败: ${error.message}`); versionCheckCache.checked = true; return null; } } /** * 比较两个版本号 * @param {string} v1 - 第一个版本号 * @param {string} v2 - 第二个版本号 * @returns {number} v1 > v2 返回 1,v1 < v2 返回 -1,v1 === v2 返回 0 */ function compareVersion(v1, v2) { // 将版本号分割为数字数组 const parts1 = v1.split('.').map(n => parseInt(n, 10) || 0); const parts2 = v2.split('.').map(n => parseInt(n, 10) || 0); // 确保两个数组长度相同 const maxLength = Math.max(parts1.length, parts2.length); for (let i = 0; i < maxLength; i++) { const num1 = parts1[i] || 0; const num2 = parts2[i] || 0; if (num1 > num2) return 1; if (num1 < num2) return -1; } return 0; } // ====================== 设置管理 ====================== /** * 显示API配置弹窗 */ function showAPIConfigModal() { try { // 调用API配置管理器的显示弹窗方法 apiConfigManager.showAPIConfigModal(); } catch (error) { logger.error(`打开API配置弹窗失败: ${error.message}`); app.extensionManager.toast.add({ severity: "error", summary: tUI("打开配置失败"), detail: error.message || tUI("打开配置弹窗过程中发生错误"), life: 3000 }); } } /** * 显示规则配置弹窗 */ function showRulesConfigModal() { try { // 调用规则配置管理器的显示弹窗方法 rulesConfigManager.showRulesConfigModal(); } catch (error) { logger.error(`打开规则配置弹窗失败: ${error.message}`); app.extensionManager.toast.add({ severity: "error", summary: tUI("打开配置失败"), detail: error.message || tUI("打开配置弹窗过程中发生错误"), life: 3000 }); } } /** * 创建服务选择器下拉框 * @param {string} type - 服务类型: 'translate' | 'llm' | 'vlm' * @param {string} label - 显示名称 * @returns {HTMLElement} 设置行元素 */ function createServiceSelector(type, label) { const row = document.createElement("tr"); row.className = "promptwidget-settings-row"; const labelCell = document.createElement("td"); labelCell.className = "comfy-menu-label"; row.appendChild(labelCell); const selectCell = document.createElement("td"); // 创建加载占位容器 const container = document.createElement("div"); container.style.minWidth = "180px"; container.innerHTML = `${tUI("加载中...")}`; selectCell.appendChild(container); row.appendChild(selectCell); let currentOptions = []; // 存储当前选项引用 let updateDropdownOptions = null; // 存储更新函数 /** * 更新下拉框内容 * @param {boolean} force - 是否强制刷新数据 */ const updateContent = async (force = false) => { try { if (force) { // 如果是强制刷新(如配置变更或点击触发),先清除缓存 serviceSelector.clearCache(); } // 获取服务列表和当前选中的服务 const [options, currentService] = await Promise.all([ serviceSelector.getServiceOptions(type), serviceSelector.getCurrentService(type) ]); // 如果已经存在下拉框实例,则尝试增量更新 if (updateDropdownOptions) { updateDropdownOptions(options, currentService); currentOptions = options; return; } // ---首次加载逻辑--- container.innerHTML = ''; if (options.length === 0) { container.innerHTML = `${tUI("暂无可用服务")}`; return; } currentOptions = options; const res = createSelectGroup(label, options, currentService, { showLabel: false }); const { group, select } = res; updateDropdownOptions = res.updateOptions; // 将 group 的子元素添加到容器 while (group.firstChild) { container.appendChild(group.firstChild); } // 监听点击/按下事件:当用户准备点击下拉框时,尝试静默同步最新配置 const dropdownContainer = container.querySelector('.pa-dropdown'); if (dropdownContainer) { dropdownContainer.addEventListener('mousedown', () => { // 点击时触发刷新,但不显示“同步中”以避免干扰 UI updateContent(true); }); } // 监听变更事件 select.addEventListener('change', async () => { const newValue = select.value; if (!newValue) return; const dropdown = container.querySelector('.pa-dropdown'); if (dropdown) { dropdown.style.opacity = '0.6'; dropdown.style.pointerEvents = 'none'; } try { const success = await serviceSelector.setCurrentService(type, newValue); if (success) { logger.log(`设置${label}服务 | 服务: ${newValue}`); } else { logger.error(`设置${label}服务失败`); const oldValue = await serviceSelector.getCurrentService(type); if (oldValue && updateDropdownOptions) { updateDropdownOptions(currentOptions, oldValue); } } } catch (error) { logger.error(`设置${label}服务异常: ${error.message}`); } finally { if (dropdown) { dropdown.style.opacity = ''; dropdown.style.pointerEvents = ''; } } }); } catch (error) { logger.error(`同步${label}配置失败: ${error.message}`); if (!updateDropdownOptions) { container.innerHTML = `${tUI("加载失败")}`; } } }; // 初始加载 updateContent(); // 监听配置更新事件(当 API 配置管理器修改配置后触发) const onConfigUpdated = () => { logger.debug(`收到配置更新通知,同步${label}状态...`); updateContent(true); }; window.addEventListener('pa-config-updated', onConfigUpdated); // 销毁监听器的清理函数(简单处理,因为设置面板通常随页面销毁) // 如果之后有更复杂的组件挂载逻辑,可以在这里返回一个清理函数给外部调用 return row; } /** * 注册设置选项 * 将设置选项添加到ComfyUI设置面板 */ export async function registerSettings() { try { await ensureUiLocaleLoaded(); patchToastLocalization(); syncGlobalUiLocalization(); app.registerExtension({ name: "PromptAssistant.Settings", settings: localizeSettingsPayload([ { id: UI_LANGUAGE_SETTING_ID, name: "界面语言", category: ["✨提示词小助手", "系统", "界面语言"], type: "combo", options: LANGUAGE_OPTIONS, defaultValue: "zh", tooltip: "切换插件界面语言,修改后自动刷新页面生效", onChange: (value) => { const previous = getStoredUiLanguage(); const normalized = persistUiLanguage(value); if (normalized === previous) return; logger.log(`界面语言已切换: ${normalized}`); setTimeout(() => window.location.reload(), 80); } }, // 总开关 - 独立控制小助手系统级功能 { id: "PromptAssistant.Features.Enabled", name: "启用小助手", category: ["✨提示词小助手", "小助手功能开关", "总开关"], type: "boolean", defaultValue: true, tooltip: "关闭后,提示词小助手所有功能将禁用", onChange: async (value) => { try { // 获取当前状态,用于判断是否是初始化 const currentState = window.FEATURES.enabled; // 只有状态真正变化时才输出日志 if (currentState !== value) { logger.log(`总开关状态变更 | 状态:${value ? "启用" : "禁用"}`); } else { // 如果状态没有变化,使用调试级别日志 logger.debug(`总开关状态保持不变 | 状态:${value ? "启用" : "禁用"}`); } // 更新全局状态 window.FEATURES.enabled = value; // 从全局 app 对象获取 promptAssistant 实例 const promptAssistantInstance = app.promptAssistant; const imageCaptionInstance = app.imageCaption; if (!promptAssistantInstance) { logger.error("总开关切换失败 | 错误:未找到PromptAssistant实例"); return; } // 根据开关状态执行相应操作 if (value) { // 启用功能 await promptAssistantInstance.toggleGlobalFeature(true, currentState !== value); if (imageCaptionInstance) { await imageCaptionInstance.toggleGlobalFeature(true, currentState !== value); } // 只在状态真正变化且不是首次加载时记录日志和显示提示 if (currentState !== value) { logger.debug("功能启用完成"); // 只在状态发生变化且不是首次加载时显示提示 if (!isFirstLoad) { app.extensionManager.toast.add({ severity: "info", summary: tUI("提示词小助手已启用"), life: 3000 }); } } } else { // 禁用功能 await promptAssistantInstance.toggleGlobalFeature(false, currentState !== value); if (imageCaptionInstance) { await imageCaptionInstance.toggleGlobalFeature(false, currentState !== value); } // 只在状态真正变化且不是首次加载时记录日志和显示提示 if (currentState !== value) { logger.debug("功能禁用完成"); // 只在状态发生变化且不是首次加载时显示提示 if (!isFirstLoad) { app.extensionManager.toast.add({ severity: "warn", summary: tUI("提示词小助手已禁用"), life: 3000 }); } } } // 设置首次加载标志为 false,表示已经完成首次加载 isFirstLoad = false; } catch (error) { logger.error(`总开关切换异常 | 错误:${error.message}`); } } }, // 小助手创建方式设置 { id: "PromptAssistant.Settings.CreationMode", name: "小助手创建方式(提示词)", category: ["✨提示词小助手", "系统", "提示词小助手创建方式"], type: "combo", options: [ { text: "自动创建", value: "auto" }, { text: "选中节点时创建", value: "manual" } ], defaultValue: "auto", tooltip: "自动创建:节点创建或加载时自动显示小助手;选中节点时创建:仅选中节点时显示", onChange: (value) => { logger.log(`小助手创建方式变更 | 模式:${value === 'auto' ? '自动创建' : '选中节点时创建'}`); // 如果切换到自动创建,立即尝试初始化所有节点 if (value === 'auto' && window.FEATURES.enabled && app.graph) { const nodes = app.graph._nodes || []; nodes.forEach(node => { if (node && !node._promptAssistantInitialized) { app.promptAssistant.checkAndSetupNode(node); } }); } } }, // 反推小助手创建方式设置 { id: "PromptAssistant.Settings.ImageCaptionCreationMode", name: "小助手创建方式(图像反推)", category: ["✨提示词小助手", "系统", "图像小助手创建方式"], type: "combo", options: [ { text: "自动创建", value: "auto" }, { text: "选中节点时创建", value: "manual" } ], defaultValue: "auto", tooltip: "自动创建:节点创建或加载时自动显示反推小助手;选中节点时创建:仅选中节点时显示", onChange: (value) => { logger.log(`反推小助手创建方式变更 | 模式:${value === 'auto' ? '自动创建' : '选中节点时创建'}`); // 如果切换到自动创建,立即尝试初始化所有节点 if (value === 'auto' && window.FEATURES.enabled && window.FEATURES.imageCaption && app.graph) { const nodes = app.graph._nodes || []; nodes.forEach(node => { if (node && !node._imageCaptionInitialized) { app.imageCaption.checkAndSetupNode(node); } }); } } }, // 小助手布局(提示词) { id: "PromptAssistant.Location", name: "小助手布局(提示词)", category: ["✨提示词小助手", "界面", "提示词小助手布局"], type: "combo", options: [ // { text: "左上(横向)", value: "top-left-h" }, // { text: "左上(垂直)", value: "top-left-v" }, // { text: "中上(横向)", value: "top-center-h" }, // { text: "⇗ ━", value: "top-right-h" }, // { text: "⇗ ┃", value: "top-right-v" }, { text: "右中(垂直)", value: "right-center-v" }, { text: "右下(横向)", value: "bottom-right-h" }, { text: "右下(垂直)", value: "bottom-right-v" }, { text: "下中(横向)", value: "bottom-center-h" }, { text: "左下(横向)", value: "bottom-left-h" }, // { text: "左下(垂直)", value: "bottom-left-v" }, // { text: "左中(垂直)", value: "left-center-v" } ], defaultValue: "bottom-right-h", // 默认右下横向 tooltip: "设置提示词小助手在输入框周围的布局和展开方向", onChange: (value) => { logger.log(`提示词小助手布局变更 | 布局:${value}`); // 通知所有实例更新布局(通过 CSS 类处理) PromptAssistant.instances.forEach(widget => { if (widget.container && widget.container.setAnchorPosition) { widget.container.setAnchorPosition(value); } }); } }, // 小助手位置设置(图像反推) { id: "ImageCaption.Location", name: "小助手布局(图像反推)", category: ["✨提示词小助手", "界面", "图像小助手布局"], type: "combo", options: [ { text: "横", value: "bottom-left-h" }, { text: "竖", value: "bottom-left-v" } ], defaultValue: "bottom-left-h", // 默认横向 tooltip: "设置图像反推小助手的展开方向(位置固定在左下角)", onChange: (value) => { logger.log(`图像反推小助手布局变更 | 布局:${value}`); // 通知所有实例更新布局 ImageCaption.instances.forEach(assistant => { if (assistant.container && assistant.container.setAnchorPosition) { assistant.container.setAnchorPosition(value); } }); }, }, // API 配置按钮 { id: "PromptAssistant.Features.APIConfig", name: "百度和大语言模型API配置", category: ["✨提示词小助手", " 配置", "API配置"], tooltip: "配置或修改API信息", type: () => { const row = document.createElement("tr"); row.className = "promptwidget-settings-row"; const labelCell = document.createElement("td"); labelCell.className = "comfy-menu-label"; row.appendChild(labelCell); const buttonCell = document.createElement("td"); const button = createLoadingButton(tUI("API管理器"), async () => { showAPIConfigModal(); }, false); // 设置 showSuccessToast 为 false buttonCell.appendChild(button); row.appendChild(buttonCell); return row; } }, // ---服务类别设置--- // 翻译服务选择 { id: "PromptAssistant.Service.Translate", name: "选择翻译服务", category: ["✨提示词小助手", " 配置", "翻译"], tooltip: "选择一个服务商用于翻译,也可以通过右键翻译按钮来切换", type: () => { return createServiceSelector('translate', '翻译'); } }, // 提示词优化服务选择 { id: "PromptAssistant.Service.LLM", name: "选择提示词优化服务", category: ["✨提示词小助手", " 配置", "提示词优化"], tooltip: "选择一个服务商用于提示词优化,也可以通过右键提示词优化按钮来切换", type: () => { return createServiceSelector('llm', '提示词优化'); } }, // 图像反推服务选择 { id: "PromptAssistant.Service.VLM", name: "选择图像反推服务", category: ["✨提示词小助手", " 配置", "图像反推"], tooltip: "选择一个服务商用于图像反推,也可以通过右键反推按钮来切换", type: () => { return createServiceSelector('vlm', '图像反推'); } }, // 历史功能(包含历史、撤销、重做按钮) { id: "PromptAssistant.Features.History", name: "启用历史功能", category: ["✨提示词小助手", "小助手功能开关", "历史功能"], type: "boolean", defaultValue: true, tooltip: "开启或关闭历史、撤销、重做功能", onChange: (value) => { const oldValue = FEATURES.history; FEATURES.history = value; handleFeatureChange('历史功能', value, oldValue); logger.log(`历史功能 - 已${value ? "启用" : "禁用"}`); } }, // 标签工具 { id: "PromptAssistant.Features.Tag", name: "启用标签工具", category: ["✨提示词小助手", "小助手功能开关", "标签功能"], type: "boolean", defaultValue: true, tooltip: "开启或关闭标签工具功能", onChange: (value) => { const oldValue = FEATURES.tag; FEATURES.tag = value; handleFeatureChange('标签工具', value, oldValue); logger.log(`标签工具 - 已${value ? "启用" : "禁用"}`); } }, // 扩写功能 { id: "PromptAssistant.Features.Expand", name: "启用提示词优化功能", category: ["✨提示词小助手", "小助手功能开关", "提示词优化功能"], type: "boolean", defaultValue: true, tooltip: "开启或关闭提示词优化功能", onChange: (value) => { const oldValue = FEATURES.expand; FEATURES.expand = value; handleFeatureChange('提示词优化功能', value, oldValue); logger.log(`提示词优化功能 - 已${value ? "启用" : "禁用"}`); } }, // 翻译功能 { id: "PromptAssistant.Features.Translate", name: "启用翻译功能", category: ["✨提示词小助手", "小助手功能开关", "翻译功能"], type: "boolean", defaultValue: true, tooltip: "开启或关闭翻译功能", onChange: (value) => { const oldValue = FEATURES.translate; FEATURES.translate = value; handleFeatureChange('翻译功能', value, oldValue); logger.log(`翻译功能 - 已${value ? "启用" : "禁用"}`); } }, // 使用翻译缓存功能 { id: "PromptAssistant.Features.UseTranslateCache", name: "使用翻译缓存", category: ["✨提示词小助手", " 翻译功能设置", "翻译缓存"], type: "boolean", defaultValue: true, tooltip: "开启后,如果翻译内容翻译过,则使用历史翻译结果,避免相同内容重复翻译改变原意。如果需要重新翻译,请随便加一个空格即可跳过缓存。", onChange: (value) => { const oldValue = FEATURES.useTranslateCache; FEATURES.useTranslateCache = value; logger.log(`使用翻译缓存 - 已${value ? "启用" : "禁用"}`); } }, // 混合语言缓存选项 { id: "PromptAssistant.Features.CacheMixedLangTranslation", name: "混合语言翻译进行缓存", category: ["✨提示词小助手", " 翻译功能设置", "混合语言缓存"], type: "boolean", defaultValue: false, tooltip: "关闭时,中英文混合内容的翻译结果不会写入缓存,避免污染缓存。开启后会正常缓存。", onChange: (value) => { FEATURES.cacheMixedLangTranslation = value; logger.log(`混合语言缓存 - 已${value ? "启用" : "禁用"}`); } }, // 混合语言翻译规则 { id: "PromptAssistant.Features.MixedLangTranslateRule", name: "混合语言翻译规则", category: ["✨提示词小助手", " 翻译功能设置", "混合语言规则"], type: "combo", options: [ { text: "翻译成英文", value: "to_en" }, { text: "翻译成中文", value: "to_zh" }, { text: "自动翻译小比例语言", value: "auto_minor" }, { text: "自动翻译大比例语言", value: "auto_major" } ], defaultValue: "to_en", tooltip: "根据个人使用偏好设置混合中英文内容的翻译规则", onChange: (value) => { FEATURES.mixedLangTranslateRule = value; logger.log(`混合语言翻译规则 - 已设置为:${value}`); } }, // 翻译格式化选项 { id: "PromptAssistant.Features.TranslateFormatPunctuation", name: "始终使用半角标点符号", category: ["✨提示词小助手", " 翻译功能设置", "标点处理"], type: "boolean", defaultValue: false, tooltip: "打开后,翻译结果会自动将中文标点符号替换成英文标点符号", onChange: (value) => { FEATURES.translateFormatPunctuation = value; logger.log(`标点符号转换 - 已${value ? "启用" : "禁用"}`); } }, { id: "PromptAssistant.Features.TranslateFormatSpace", name: "自动移除多余空格", category: ["✨提示词小助手", " 翻译功能设置", "空格处理"], type: "boolean", defaultValue: false, tooltip: "打开后,翻译结果会自动移除多余空格", onChange: (value) => { FEATURES.translateFormatSpace = value; logger.log(`移除多余空格 - 已${value ? "启用" : "禁用"}`); } }, { id: "PromptAssistant.Features.TranslateFormatDots", name: "移除多余连续点号", category: ["✨提示词小助手", " 翻译功能设置", "点号处理"], type: "boolean", defaultValue: false, tooltip: "打开后,翻译结果会将多余的“......”统一为“...”", onChange: (value) => { FEATURES.translateFormatDots = value; logger.log(`处理连续点号 - 已${value ? "启用" : "禁用"}`); } }, { id: "PromptAssistant.Features.TranslateFormatNewline", name: "保留换行符", category: ["✨提示词小助手", " 翻译功能设置", "换行处理"], type: "boolean", defaultValue: true, tooltip: "打开后,翻译结果会尽量保持原文的换行,避免翻译后丢失段落", onChange: (value) => { FEATURES.translateFormatNewline = value; logger.log(`保留换行符 - 已${value ? "启用" : "禁用"}`); } }, // 图像反推功能 { id: "PromptAssistant.Features.ImageCaption", name: "启用图像反推功能", category: ["✨提示词小助手", "小助手功能开关", "图像反推"], type: "boolean", defaultValue: true, tooltip: "开启或关闭图像反推提示词功能", onChange: (value) => { const oldValue = FEATURES.imageCaption; FEATURES.imageCaption = value; handleFeatureChange('图像反推', value, oldValue); logger.log(`图像反推功能 - 已${value ? "启用" : "禁用"}`); } }, // 节点帮助翻译功能 { id: "PromptAssistant.Features.NodeHelpTranslator", name: "启用节点信息翻译", category: ["✨提示词小助手", "小助手功能开关", "节点信息翻译"], type: "boolean", defaultValue: true, tooltip: "开启或关闭ComfyUI侧边栏节点帮助文档的翻译功能", onChange: (value) => { const oldValue = FEATURES.nodeHelpTranslator; FEATURES.nodeHelpTranslator = value; handleFeatureChange('节点信息翻译', value, oldValue); logger.log(`节点信息翻译功能 - 已${value ? "启用" : "禁用"}`); } }, // 系统设置 { id: "PromptAssistant.Settings.LogLevel", name: "日志级别", category: ["✨提示词小助手", "系统", "日志级别"], type: "hidden", defaultValue: "0", options: [ { text: "错误日志", value: "0" }, { text: "基础日志", value: "1" }, { text: "详细日志", value: "2" } ], tooltip: "设置日志输出级别:错误日志(仅错误)、基础日志(错误+基础信息)、详细日志(错误+基础信息+调试信息)", onChange: (value) => { const oldValue = window.FEATURES.logLevel; window.FEATURES.logLevel = parseInt(value); logger.setLevel(window.FEATURES.logLevel); logger.log(`日志级别已更新 | 原级别:${oldValue} | 新级别:${value}`); } }, // 显示流式输出进度 { id: "PromptAssistant.Settings.ShowStreamingProgress", name: "控制台流式输出进度日志", category: ["✨提示词小助手", "系统", "终端日志"], type: "boolean", defaultValue: false, tooltip: "开启后,控制台会显示流式输出过程,在某些终端可能导致刷屏;关闭后只显示静态的'生成中...'。", onChange: async (value) => { FEATURES.showStreamingProgress = value; // 通知后端更新设置 try { await fetch(APIService.getApiUrl('/settings/streaming_progress'), { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ enabled: value }) }); } catch (error) { logger.error(`更新流式进度设置失败: ${error.message}`); } logger.log(`流式输出进度 - 已${value ? "启用" : "禁用"}`); } }, // 流式输出开关 { id: "PromptAssistant.Settings.EnableStreaming", name: "流式输出开关", category: ["✨提示词小助手", "系统", "流式体验"], type: "boolean", defaultValue: true, tooltip: "开启时,翻译、扩写、识别等功能将以逐字生成的流式效果显示;关闭时则恢复为全部生成后一次性显示的阻塞模式。", onChange: (value) => { FEATURES.enableStreaming = value; logger.log(`流式输出开关 - 已${value ? "启用" : "禁用"}`); } }, { id: "PromptAssistant.Settings.IconOpacity", name: " 小助手图标不透明度", category: ["✨提示词小助手", "界面", "小助手图标"], type: "slider", min: 0, max: 100, step: 1, defaultValue: 20, tooltip: "设置折叠后小助手图标的不透明度", onChange: (value) => { // 将0-100的值转换为0-1的透明度 const opacity = value * 0.01; document.documentElement.style.setProperty('--assistant-icon-opacity', opacity); logger.log(`小助手图标不透明度已更新 | 值:${value}% | 透明度:${opacity}`); }, onLoad: (value) => { // 初始化时应用默认值 const opacity = value * 0.01; document.documentElement.style.setProperty('--assistant-icon-opacity', opacity); logger.debug(`小助手图标不透明度初始化 | 值:${value}% | 透明度:${opacity}`); } }, { id: "PromptAssistant.Settings.ClearCache", name: "清理历史、标签、翻译缓存", category: ["✨提示词小助手", "系统", "清理缓存"], tooltip: "清理所有缓存,包括历史记录、标签、翻译缓存、节点文档翻译缓存", type: () => { const row = document.createElement("tr"); row.className = "promptwidget-settings-row"; const labelCell = document.createElement("td"); labelCell.className = "comfy-menu-label"; row.appendChild(labelCell); const buttonCell = document.createElement("td"); const button = createLoadingButton(tUI("清理所有缓存"), async () => { try { // 获取清理前的缓存统计 const beforeStats = { history: HistoryCacheService.getHistoryStats(), tags: 0, translate: TranslateCacheService.getTranslateCacheStats(), nodeHelpTranslate: 0 // 节点文档翻译缓存 }; // 统计所有标签数量 const tagCacheKeys = Object.keys(localStorage) .filter(key => key.startsWith(CACHE_CONFIG.TAG_KEY_PREFIX)); // 计算所有缓存中的标签总数 tagCacheKeys.forEach(key => { try { const cacheData = JSON.parse(localStorage.getItem(key)); if (cacheData && typeof cacheData === 'object') { // 获取缓存中的标签数量 const tagCount = Object.keys(cacheData).length; beforeStats.tags += tagCount; } } catch (e) { // 移除错误日志,静默处理解析错误 } }); // 统计节点文档翻译缓存数量 try { const nodeHelpCache = sessionStorage.getItem('pa_node_help_translations'); if (nodeHelpCache) { const parsed = JSON.parse(nodeHelpCache); beforeStats.nodeHelpTranslate = Object.keys(parsed).length; } } catch (e) { // 静默处理 } // 执行历史记录清理操作 HistoryCacheService.clearAllHistory(); // 清理所有标签缓存 TagCacheService.clearAllTagCache(); // 清理翻译缓存 TranslateCacheService.clearAllTranslateCache(); // 清理节点文档翻译缓存(sessionStorage) sessionStorage.removeItem('pa_node_help_translations'); // 清理旧版本的标签缓存(以PromptAssistant_tag_cache_开头的所有记录) Object.keys(localStorage) .filter(key => key.startsWith('PromptAssistant_tag_cache_')) .forEach(key => localStorage.removeItem(key)); // 清除1.0.3以前版本遗留的三项配置信息,避免泄露 localStorage.removeItem("PromptAssistant_Settings_llm_api_key"); localStorage.removeItem("PromptAssistant_Settings_baidu_translate_secret"); localStorage.removeItem("PromptAssistant_Settings_baidu_translate_appid"); // 获取清理后的缓存统计 const afterStats = { history: HistoryCacheService.getHistoryStats(), tags: 0, // 清理后标签数应该为0 translate: TranslateCacheService.getTranslateCacheStats() }; // 计算清理数量 const clearedHistory = beforeStats.history.total - afterStats.history.total; const clearedTags = beforeStats.tags; const clearedTranslate = beforeStats.translate.total - afterStats.translate.total; const clearedNodeHelp = beforeStats.nodeHelpTranslate; // 只输出最终统计结果 logger.log(`缓存清理完成 | 历史记录: ${clearedHistory}条 | 标签: ${clearedTags}个 | 翻译: ${clearedTranslate}条 | 节点文档: ${clearedNodeHelp}个`); // 更新所有实例的撤销/重做按钮状态 PromptAssistant.instances.forEach((instance) => { if (instance && instance.nodeId && instance.inputId) { UIToolkit.updateUndoRedoButtonState(instance, HistoryCacheService); } }); } catch (error) { // 简化错误日志 logger.error(`缓存清理失败`); throw error; } }); buttonCell.appendChild(button); row.appendChild(buttonCell); return row; } }, // 关于插件信息 { id: "PromptAssistant.Settings.About", name: "关于", category: ["✨提示词小助手", " ✨提示词小助手"], type: () => { const row = document.createElement("tr"); row.className = "promptwidget-settings-row"; const cell = document.createElement("td"); cell.colSpan = 2; cell.style.display = "flex"; cell.style.alignItems = "center"; cell.style.gap = "12px"; // 版本徽标容器(整体可点击跳转最新版本) const versionLink = document.createElement("a"); versionLink.href = "https://github.com/yawiii/ComfyUI-Prompt-Assistant/releases/latest"; versionLink.target = "_blank"; versionLink.style.textDecoration = "none"; versionLink.style.display = "flex"; versionLink.style.alignItems = "center"; versionLink.style.cursor = "pointer"; const versionContainer = document.createElement("div"); versionContainer.style.display = "flex"; versionContainer.style.alignItems = "center"; versionContainer.style.gap = "8px"; versionLink.appendChild(versionContainer); // 版本徽标 const versionBadge = document.createElement("img"); versionBadge.alt = "Version"; versionBadge.style.display = "block"; versionBadge.style.height = "20px"; // 从全局变量获取版本号 if (!window.PromptAssistant_Version) { logger.error(tUI("未找到版本号,徽标将无法正确显示")); versionBadge.src = `https://img.shields.io/badge/%E7%89%88%E6%9C%AC-%E6%9C%AA%E7%9F%A5-red?style=flat`; versionContainer.appendChild(versionBadge); } else { const currentVersion = window.PromptAssistant_Version; versionBadge.src = `https://img.shields.io/badge/%E7%89%88%E6%9C%AC-${currentVersion}-green?style=flat`; versionContainer.appendChild(versionBadge); // 使用缓存检查版本,避免重复请求 if (versionCheckCache.checked && versionCheckCache.hasUpdate) { // 已检查过且有更新,直接应用缓存的结果 const latestVersion = versionCheckCache.latestVersion; const labelEncoded = encodeURIComponent(tUI("有新版本")); const messageEncoded = encodeURIComponent(`${currentVersion}→${latestVersion}`); versionBadge.src = `https://img.shields.io/badge/${labelEncoded}-${messageEncoded}-orange?style=flat&labelColor=555555`; versionBadge.style.cursor = "pointer"; versionBadge.title = `${tUI("当前版本:")} ${currentVersion}\n${tUI("最新版本:")} ${latestVersion}\n${tUI("点击前往下载")}`; } else if (!versionCheckCache.checked) { // 首次检查,发起异步请求 fetchLatestVersion().then(latestVersion => { if (latestVersion && compareVersion(latestVersion, currentVersion) > 0) { versionCheckCache.hasUpdate = true; const labelEncoded = encodeURIComponent(tUI("有新版本")); const messageEncoded = encodeURIComponent(`${currentVersion}→${latestVersion}`); versionBadge.src = `https://img.shields.io/badge/${labelEncoded}-${messageEncoded}-orange?style=flat&labelColor=555555`; versionBadge.style.cursor = "pointer"; versionBadge.title = `${tUI("当前版本:")} ${currentVersion}\n${tUI("最新版本:")} ${latestVersion}\n${tUI("点击前往下载")}`; logger.log(`[版本检查] 发现新版本: ${currentVersion} → ${latestVersion}`); } else if (latestVersion) { versionBadge.title = `${tUI("当前已是最新版本:")} ${currentVersion}`; logger.debug(`[版本检查] 当前版本: ${currentVersion}`); } }).catch(error => { logger.warn(`[版本检查] 出错: ${error.message}`); }); } else { // 已检查过但没有更新 versionBadge.title = `${tUI("当前已是最新版本:")} ${currentVersion}`; } } cell.appendChild(versionLink); // GitHub 徽标 const authorTag = document.createElement("a"); authorTag.href = "https://github.com/yawiii/ComfyUI-Prompt-Assistant"; authorTag.target = "_blank"; authorTag.style.textDecoration = "none"; authorTag.style.display = "flex"; authorTag.style.alignItems = "center"; const authorBadge = document.createElement("img"); authorBadge.alt = "Static Badge"; authorBadge.src = "https://img.shields.io/github/stars/yawiii/ComfyUI-Prompt-Assistant?style=flat&logo=github&logoColor=%23292F34&label=Yawiii&labelColor=%23FFFFFF&color=blue"; authorBadge.style.display = "block"; authorBadge.style.height = "20px"; authorTag.appendChild(authorBadge); cell.appendChild(authorTag); // B站徽标 const biliTag = document.createElement("a"); biliTag.href = "https://space.bilibili.com/520680644"; biliTag.target = "_blank"; biliTag.style.textDecoration = "none"; biliTag.style.display = "flex"; biliTag.style.alignItems = "center"; const biliBadge = document.createElement("img"); biliBadge.alt = "Bilibili"; biliBadge.src = "https://img.shields.io/badge/%E4%BD%BF%E7%94%A8%E6%95%99%E7%A8%8B-blue?style=flat&logo=bilibili&logoColor=2300A5DC&labelColor=%23FFFFFF&color=%2307A3D7"; biliBadge.style.display = "block"; biliBadge.style.height = "20px"; biliTag.appendChild(biliBadge); cell.appendChild(biliTag); // 交流群徽标 const wechatTag = document.createElement("a"); // 取消跳转;点击不再打开链接,避免本地缓存链接 wechatTag.href = 'javascript:void(0)'; wechatTag.addEventListener('click', (e) => { e.preventDefault(); toggleWechatQr(); }); wechatTag.style.textDecoration = "none"; wechatTag.style.display = "flex"; wechatTag.style.alignItems = "center"; wechatTag.classList.add("has-tooltip", "pa-wechat-badge"); const wechatBadge = document.createElement("img"); wechatBadge.alt = tUI("交流反馈群"); wechatBadge.src = "https://img.shields.io/badge/%E4%BA%A4%E6%B5%81%E5%8F%8D%E9%A6%88-blue?logo=wechat&logoColor=green&labelColor=%23FFFFFF&color=%2307A3D7"; wechatBadge.style.display = "block"; wechatBadge.style.height = "20px"; wechatTag.appendChild(wechatBadge); // 悬浮显示二维码 const wechatQr = document.createElement("div"); wechatQr.className = "pa-wechat-qr"; const wechatQrImg = document.createElement("img"); // 优先加载远程二维码,失败则回退到本地备用图 const remoteQrUrl = 'http://data.xflow.cc/wechat.png'; let qrFallbackTimer = null; const localQrUrl = ResourceManager.getAssetUrl('wechat.png'); // 每次显示时强制重新加载远程二维码(带时间戳),避免缓存 const loadWechatQr = () => { if (qrFallbackTimer) { clearTimeout(qrFallbackTimer); qrFallbackTimer = null; } wechatQrImg.dataset.fallbackApplied = ''; wechatQrImg.dataset.source = 'remote'; wechatQrImg.src = `${remoteQrUrl}?t=${Date.now()}`; // 超时回退到本地,但需要判断图片是否已开始加载 qrFallbackTimer = setTimeout(() => { // 检查是否已标记为已回退 if (wechatQrImg.dataset.fallbackApplied === '1') return; // 检查图片是否已开始加载(naturalHeight > 0 说明图片正在加载) if (wechatQrImg.naturalHeight > 0) { Logger.log(2, '远程二维码加载中,延长等待时间'); // 图片已开始加载,继续等待 onload,取消超时回退 if (qrFallbackTimer) { clearTimeout(qrFallbackTimer); qrFallbackTimer = null; } } else { // 图片未开始加载,可能是网络问题,回退到本地 Logger.log(1, '远程二维码加载超时,切换到本地备用图'); loadLocalQr(); } }, 3000); // 延长到 3 秒,给远程图片更多加载时间 }; // 手动切换到本地二维码(带时间戳),清理超时 const loadLocalQr = () => { if (qrFallbackTimer) { clearTimeout(qrFallbackTimer); qrFallbackTimer = null; } wechatQrImg.dataset.fallbackApplied = '1'; wechatQrImg.dataset.source = 'local'; wechatQrImg.src = localQrUrl; // 本地图片固定,不加时间戳 }; // 点击徽标时在远程/本地之间来回切换 const toggleWechatQr = () => { if (wechatQrImg.dataset.source === 'local') { loadWechatQr(); } else { loadLocalQr(); } }; wechatQrImg.alt = tUI("微信交流群二维码"); wechatQrImg.className = "pa-wechat-qr-img"; // 加载成功清理超时定时器 wechatQrImg.onload = () => { if (qrFallbackTimer) { clearTimeout(qrFallbackTimer); qrFallbackTimer = null; } }; // 远程加载失败时回退到本地备用图(也带时间戳避免缓存) wechatQrImg.onerror = () => { if (qrFallbackTimer) { clearTimeout(qrFallbackTimer); qrFallbackTimer = null; } if (wechatQrImg.dataset.fallbackApplied !== '1') { loadLocalQr(); } }; // 初次渲染和每次鼠标进入都触发重新加载 loadWechatQr(); wechatTag.addEventListener('mouseenter', loadWechatQr); wechatQr.appendChild(wechatQrImg); wechatTag.appendChild(wechatQr); cell.appendChild(wechatTag); row.appendChild(cell); return row; } }, // 规则配置按钮 { id: "PromptAssistant.Features.RulesConfig", name: "提示词优化和反推规则修改", category: ["✨提示词小助手", " 配置", "规则"], tooltip: "可以自定义提示词优化规则,和反推提示词规则,使得提示词生成更加符合你的需求", type: () => { const row = document.createElement("tr"); row.className = "promptwidget-settings-row"; const labelCell = document.createElement("td"); labelCell.className = "comfy-menu-label"; row.appendChild(labelCell); const buttonCell = document.createElement("td"); const button = createLoadingButton(tUI("规则管理器"), async () => { showRulesConfigModal(); }, false); buttonCell.appendChild(button); row.appendChild(buttonCell); return row; } }, ]) }); logger.log("小助手设置注册成功"); return true; } catch (error) { logger.error(`小助手设置注册失败: ${error.message}`); return false; } }