Files
yawiii-ComfyUI-Prompt-Assis…/js/modules/PromptAssistant.js
T
2025-06-06 14:38:34 +08:00

1798 lines
70 KiB
JavaScript

/**
* 提示词小助手核心类
* 统一管理小助手的生命周期、实例创建、UI交互等功能
*/
import { app } from "../../../../scripts/app.js";
import { logger } from '../utils/logger.js';
import { FEATURES } from "../config/features.js";
import { HistoryManager } from "./history.js";
import { TagManager } from "./tag.js";
import { HistoryCacheService, TagCacheService, TranslateCacheService, CACHE_CONFIG, CacheService } from "../services/cache.js";
import { EventManager } from "../utils/eventManager.js";
import { ResourceManager } from "../utils/resourceManager.js";
import { UIToolkit } from "../utils/UIToolkit.js";
import { PromptFormatter } from "../utils/promptFormatter.js";
import { BaiduTranslateService } from "../services/baidu.js";
import { llmService } from "../services/llm.js";
// ====================== 工具函数 ======================
/**
* 生成唯一请求ID
*/
function generateRequestId(prefix = 'baidu_trans', type = null) {
// 如果提供了type,根据type确定前缀
if (type) {
switch (type) {
case 'llm':
prefix = 'llm_trans';
break;
case 'baidu':
prefix = 'baidu_trans';
break;
case 'expand':
prefix = 'glm4_expand';
break;
default:
// 保持传入的prefix不变
break;
}
}
const timestamp = Date.now();
const random = Math.random().toString(36).substr(2, 6);
return `${prefix}_${timestamp}_${random}`;
}
/**
* 防抖函数
* 限制函数调用频率,避免频繁触发导致性能问题
*/
function debounce(func, wait = 100) {
return EventManager.debounce(func, wait);
}
// ====================== 主类实现 ======================
/**
* 提示词小助手主类
* 统一管理小助手的生命周期、实例和资源
*/
class PromptAssistant {
/** 存储所有小助手实例的Map集合 */
static instances = new Map();
constructor() {
this.initialized = false;
}
// ---生命周期管理功能---
/**
* 判断功能是否被禁用
*/
areAllFeaturesDisabled() {
return !window.FEATURES.enabled;
}
/**
* 初始化提示词小助手
*/
initialize() {
if (this.initialized) return;
try {
// 检查版本号
if (!window.PromptAssistant_Version) {
logger.error("初始化时未找到版本号!这可能导致UI显示异常");
} else {
logger.debug(`初始化时检测到版本号: ${window.PromptAssistant_Version}`);
}
// 初始化事件管理器
EventManager.init();
// 检查总开关的初始状态
const initialEnabled = app.ui.settings.getSettingValue("PromptAssistant.Features.Enabled");
window.FEATURES.enabled = initialEnabled !== undefined ? initialEnabled : true;
// 记录总开关状态
logger.log(`总开关状态 | 状态:${window.FEATURES.enabled ? "启用" : "禁用"}`);
// 迁移旧的缓存数据到新的键名格式
CacheService.migrateCache();
// 初始化资源管理器
ResourceManager.init();
// 只有在总开关打开时才做完整初始化
if (window.FEATURES.enabled) {
// 注册全局鼠标监听
this.registerGlobalMouseListener();
}
this.initialized = true;
logger.log("初始化完成 | 小助手已完全启动");
} catch (error) {
logger.error(`初始化失败 | 错误: ${error.message}`);
// 重置状态
this.initialized = false;
window.FEATURES.enabled = false;
// 确保清理
this.cleanup();
}
}
/**
* 统一控制总开关功能
* 集中管理所有受总开关控制的服务功能
*/
async toggleGlobalFeature(enable, force = false) {
// 更新状态
const oldValue = window.FEATURES.enabled;
window.FEATURES.enabled = enable;
// 状态未变化时不执行操作,除非force为true
if (!force && oldValue === enable) {
return;
}
logger.log(`总开关 | 动作:${enable ? "启用" : "禁用"}`);
try {
if (enable) {
// === 启用所有服务 ===
// 确保管理器已初始化
if (!EventManager.initialized) {
EventManager.init();
}
if (!ResourceManager.isInitialized()) {
ResourceManager.init();
}
// 1. 重置节点初始化标记,准备重新检测
if (app.canvas && app.canvas.graph) {
const nodes = app.canvas.graph._nodes || [];
nodes.forEach(node => {
if (node) {
node._promptAssistantInitialized = false;
}
});
}
// 2. 设置或恢复节点选择事件监听
if (app.canvas) {
// 避免重复设置监听器
if (!app.canvas._promptAssistantSelectionHandler) {
app.canvas._promptAssistantSelectionHandler = function (selected_nodes) {
// 当总开关关闭时,跳过所有节点处理
if (!window.FEATURES.enabled) {
return;
}
if (selected_nodes && Object.keys(selected_nodes).length > 0) {
Object.keys(selected_nodes).forEach(nodeId => {
const node = app.canvas.graph.getNodeById(nodeId);
if (!node) return;
// 初始化未初始化的节点
if (!node._promptAssistantInitialized) {
node._promptAssistantInitialized = true;
this.checkAndSetupNode(node);
}
});
}
}.bind(this);
}
// 保存当前监听器并设置新的
if (app.canvas.onSelectionChange && app.canvas.onSelectionChange !== app.canvas._promptAssistantSelectionHandler) {
app.canvas._originalSelectionChange = app.canvas.onSelectionChange;
}
app.canvas.onSelectionChange = app.canvas._promptAssistantSelectionHandler;
}
// 3. 注册全局鼠标监听
await this.registerGlobalMouseListener();
} else {
// === 禁用所有服务 ===
// 1. 计数并清理所有实例
const instanceCount = PromptAssistant.instances.size;
this.cleanup(null, true);
// 2. 恢复原始节点选择事件监听
if (app.canvas) {
if (app.canvas._originalSelectionChange) {
app.canvas.onSelectionChange = app.canvas._originalSelectionChange;
} else {
app.canvas.onSelectionChange = null;
}
}
// 3. 移除全局鼠标监听
this.removeGlobalMouseListener();
}
// 按钮可见性更新在features中单独处理
window.FEATURES.updateButtonsVisibility();
} catch (error) {
logger.error(`总开关操作失败 | 错误: ${error.message}`);
// 恢复原始状态
window.FEATURES.enabled = oldValue;
}
}
// ---资源管理功能---
/**
* 清理所有资源
*/
cleanup(nodeId = null, silent = false) {
// 清理特定节点
if (nodeId) {
// 获取当前节点的所有实例键
const keysToDelete = Array.from(PromptAssistant.instances.keys())
.filter(key => key === String(nodeId) || key.startsWith(`${nodeId}_`));
// 如果有实例需要清理
if (keysToDelete.length > 0) {
let historyCount = 0;
let tagCount = 0;
let instanceNames = [];
try {
// 统计并清理历史记录
const allHistory = HistoryCacheService.getAllHistory();
historyCount = allHistory.filter(item => item.node_id === nodeId).length;
HistoryCacheService.clearNodeHistory(nodeId);
// 统计并清理标签缓存
keysToDelete.forEach(key => {
const instance = PromptAssistant.getInstance(key);
if (instance && instance.inputId) {
const tags = TagCacheService.getAllRawTags(nodeId, instance.inputId);
tagCount += tags ? tags.length : 0;
TagCacheService.clearCache(nodeId, instance.inputId);
instanceNames.push(instance.inputId);
}
});
// 清理实例
keysToDelete.forEach(key => {
const instance = PromptAssistant.getInstance(key);
if (instance) {
this._cleanupInstance(instance, key, true);
PromptAssistant.instances.delete(key);
}
});
if (!silent) {
// 获取当前剩余的统计信息
const remainingInstances = PromptAssistant.instances.size;
const remainingTags = Object.keys(localStorage)
.filter(key => key.startsWith(CACHE_CONFIG.TAG_KEY_PREFIX))
.reduce((total, key) => {
try {
const cacheData = JSON.parse(localStorage.getItem(key));
return total + (cacheData ? Object.keys(cacheData).length : 0);
} catch (e) {
return total;
}
}, 0);
const remainingHistory = HistoryCacheService.getAllHistory().length;
logger.log(`[节点清理] 节点ID: ${nodeId} | 实例: ${instanceNames.join(', ')} | 历史: ${historyCount}条 | 标签: ${tagCount}个`);
logger.log(`[剩余统计] 小助手实例: ${remainingInstances}个 | 标签缓存: ${remainingTags}个 | 节点历史缓存: ${remainingHistory}条`);
}
} catch (error) {
logger.error(`[节点清理] 失败 | 节点ID: ${nodeId} | 错误: ${error.message}`);
}
}
return;
}
// 清理所有实例和历史
const beforeCleanupSize = PromptAssistant.instances.size;
if (beforeCleanupSize > 0) {
let totalHistoryCount = 0;
let totalTagCount = 0;
let allInstanceNames = [];
try {
// 统计并清理所有历史记录
const allHistory = HistoryCacheService.getAllHistory();
totalHistoryCount = allHistory.length;
HistoryCacheService.clearAllHistory();
// 统计并清理所有标签缓存和实例
for (const [key, instance] of PromptAssistant.instances) {
if (instance && instance.nodeId && instance.inputId) {
const tags = TagCacheService.getAllRawTags(instance.nodeId, instance.inputId);
totalTagCount += tags ? tags.length : 0;
TagCacheService.clearCache(instance.nodeId, instance.inputId);
allInstanceNames.push(instance.inputId);
this._cleanupInstance(instance, key, true);
}
}
// 清空实例集合
PromptAssistant.instances.clear();
if (!silent) {
logger.log(`[全局清理] 实例: ${allInstanceNames.join(', ')} | 历史: ${totalHistoryCount}条 | 标签: ${totalTagCount}个`);
logger.log(`[剩余统计] 小助手实例: 0个 | 标签缓存: 0个 | 节点历史缓存: 0条`);
}
} catch (error) {
logger.error(`[全局清理] 失败 | 错误: ${error.message}`);
}
}
}
// ---实例管理功能---
/**
* 检查节点是否有效
*/
static isValidNode(node) {
return node &&
typeof node.id !== 'undefined' &&
node.id !== -1 && // 防止id为-1的无效节点
node.widgets && // 确保有widgets属性
typeof node.type === 'string'; // 确保有类型
}
/**
* 添加实例到管理器
*/
static addInstance(nodeId, widget) {
if (nodeId != null && widget != null) {
this.instances.set(String(nodeId), widget);
return true;
}
return false;
}
/**
* 获取实例
*/
static getInstance(nodeId) {
if (nodeId == null) return null;
return this.instances.get(String(nodeId));
}
/**
* 检查实例是否存在
*/
static hasInstance(nodeId) {
if (nodeId == null) return false;
return this.instances.has(String(nodeId));
}
/**
* 检查节点并设置小助手
* 查找节点中的有效输入控件并创建小助手
*/
checkAndSetupNode(node) {
// 始终首先检查总开关状态
if (!window.FEATURES.enabled || !PromptAssistant.isValidNode(node) || (node.flags && node.flags.collapsed)) {
return;
}
// 查找有效的文本输入控件
const validInputs = [];
if (node.widgets) {
node.widgets.forEach(widget => {
// 不再直接设置 node 引用,因为它现在是只读的
if (UIToolkit.isValidInput(widget)) {
validInputs.push(widget);
}
});
}
// 如果没有找到有效的文本输入控件,直接返回
if (validInputs.length === 0) {
return;
}
// 为每个有效控件创建小助手
validInputs.forEach(inputWidget => {
const inputId = inputWidget.name || inputWidget.id;
const assistantKey = `${node.id}_${inputId}`;
// 检查实例是否已存在
if (PromptAssistant.hasInstance(assistantKey)) {
return; // 跳过已存在的实例
}
// 再次检查总开关状态,确保在创建过程中没有被禁用
if (!window.FEATURES.enabled) {
return;
}
// 创建小助手实例
const assistant = this.setupNodeAssistant(node, inputWidget);
if (assistant) {
logger.log(`创建小助手 | 节点:${node.id} | 控件:${inputId} | 实例:${assistantKey}`);
}
});
}
/**
* 为节点设置小助手
* 创建小助手实例并初始化显示状态
*/
setupNodeAssistant(node, inputWidget) {
// 简化参数检查
if (!node || !inputWidget) {
return null;
}
try {
const nodeId = node.id;
const inputId = inputWidget.name || inputWidget.id || Math.random().toString(36).substring(2, 10);
const isNoteNode = node.type === "Note";
// 简化节点信息
const nodeInfo = {
nodeType: node.type,
inputType: inputId,
isNoteNode: isNoteNode
};
// 创建小助手实例
const assistant = this.createAssistant(
node,
inputId,
isNoteNode ? { ...inputWidget, inputEl: inputWidget.element } : inputWidget, // Note节点特殊处理
nodeInfo
);
if (assistant) {
// 初始化显示状态
assistant.isFirstCreate = true;
this.showAssistantUI(assistant);
// 设置初始悬停状态,2秒后恢复正常行为
assistant.isMouseOver = true;
setTimeout(() => {
if (assistant) {
assistant.isFirstCreate = false;
if (!assistant.isMouseOver) {
this.updateAssistantVisibility(assistant);
}
}
}, 2000);
return assistant;
}
return null;
} catch (error) {
logger.error(`创建小助手 | 结果:异常 | 节点ID: ${node.id}, 错误: ${error.message}`);
return null;
}
}
/**
* 创建小助手实例
* 根据节点和输入控件构建小助手对象并初始化UI
*/
createAssistant(node, inputId, inputWidget, nodeInfo = {}) {
// 简化前置检查
if (!window.FEATURES.enabled || !node || !inputId || !inputWidget || !UIToolkit.isValidInput(inputWidget)) {
return null;
}
// 确保输入元素存在
const inputEl = inputWidget.inputEl || inputWidget.element;
if (!inputEl) {
return null;
}
const nodeId = node.id;
const widgetKey = `${nodeId}_${inputId}`;
// 检查是否已存在实例
if (PromptAssistant.hasInstance(widgetKey)) {
return PromptAssistant.getInstance(widgetKey);
}
// 创建小助手对象
const widget = {
type: "prompt_assistant",
name: inputId,
nodeId,
inputId,
widgetKey,
buttons: {},
text_element: inputEl,
inputEl: inputEl, // 直接添加inputEl引用,方便历史记录回写
// 不再直接设置 node 引用,而是保存 nodeId
nodeInfo: {
...nodeInfo,
nodeId: node.id,
nodeType: node.type
},
isMouseOver: false, // 鼠标悬停状态
isFirstCreate: false, // 首次创建标记
isTransitioning: false // 添加状态标记,避免频繁切换
};
// 创建全局输入框映射(如果不存在)
if (!window.PromptAssistantInputWidgetMap) {
window.PromptAssistantInputWidgetMap = {};
}
// 将当前输入框添加到映射
window.PromptAssistantInputWidgetMap[widgetKey] = {
inputEl: inputEl,
widget: widget
};
logger.debug(`输入框映射 | 添加映射 | 键:${widgetKey}`);
// 创建UI并添加到实例集合
this.createAssistantUI(widget, inputWidget);
PromptAssistant.addInstance(widgetKey, widget);
// 初始化撤销状态
HistoryCacheService.initUndoState(nodeId, inputId, inputEl.value);
// 初始化时立即更新撤销/重做按钮状态
UIToolkit.updateUndoRedoButtonState(widget, HistoryCacheService);
// 绑定输入框失焦事件,写入历史
if (inputWidget.inputEl) {
// 使用事件管理器添加DOM事件监听
const removeBlurListener = EventManager.addDOMListener(inputWidget.inputEl, 'blur', async () => {
logger.debug(`历史写入准备| 原因:失焦事件触发 node_id=${node.id} input_id=${inputId}`);
HistoryCacheService.addHistory({
workflow_id: nodeInfo?.workflow_id || '',
node_id: node.id,
input_id: inputId,
content: inputWidget.inputEl.value,
operation_type: 'input',
timestamp: Date.now()
});
// 重置撤销状态
HistoryCacheService.initUndoState(node.id, inputId, inputWidget.inputEl.value);
// 更新按钮状态
UIToolkit.updateUndoRedoButtonState(widget, HistoryCacheService);
logger.debug(`历史写入完成|原因:输入框失焦 node_id=${node.id} input_id=${inputId}`);
});
// 保存清理函数引用,以便后续清理
widget._eventCleanupFunctions = widget._eventCleanupFunctions || [];
widget._eventCleanupFunctions.push(removeBlurListener);
// 添加输入事件监听,实时更新撤销/重做按钮状态
const removeInputListener = EventManager.addDOMListener(inputWidget.inputEl, 'input', () => {
UIToolkit.updateUndoRedoButtonState(widget, HistoryCacheService);
});
widget._eventCleanupFunctions.push(removeInputListener);
}
return widget;
}
// ---UI管理功能---
/**
* 创建小助手UI
* 构建DOM元素并设置事件监听和初始样式
*/
createAssistantUI(widget, inputWidget) {
const nodeId = widget.nodeId;
const inputId = widget.inputId;
try {
// 获取DOM相关信息
const inputEl = inputWidget.inputEl;
const graphCanvasContainer = document.querySelector('.graphcanvas');
const canvasContainerRect = graphCanvasContainer?.getBoundingClientRect();
// 创建内部内容容器(分离视觉效果)
const innerContentDiv = document.createElement('div');
innerContentDiv.className = 'prompt-assistant-inner';
// 创建主容器 - 简化样式,减少触发栅格化的属性
const containerDiv = document.createElement('div');
containerDiv.className = 'prompt-assistant-container';
containerDiv.dataset.nodeId = nodeId;
containerDiv.dataset.inputId = inputId;
// 添加内容容器到主容器
containerDiv.appendChild(innerContentDiv);
// 保存引用并初始化
widget.element = containerDiv;
widget.innerContent = innerContentDiv;
widget.buttons = {};
// 初始化UI组件和事件
this.addFunctionButtons(widget);
this._setupUIEventHandling(widget, inputEl, containerDiv);
this._setupUIPosition(widget, inputEl, containerDiv, canvasContainerRect);
// 默认隐藏状态
containerDiv.style.display = 'none';
logger.log(`UI创建 | 结果:完成 | 节点ID: ${nodeId}`);
return containerDiv;
} catch (error) {
logger.error(`UI创建 | 结果:异常 | 节点ID: ${nodeId}, 错误: ${error.message}`);
return null;
}
}
/**
* 显示小助手UI
* 控制UI显示动画和状态
*/
showAssistantUI(widget, forceAnimation = false) {
if (!widget?.element) return;
// 避免重复显示和不必要的动画
const isCurrentlyShown = widget.element.classList.contains('assistant-show');
if (isCurrentlyShown && !forceAnimation) {
// 如果已经显示且不需要强制动画,则仅确保显示状态
widget.element.style.display = 'flex';
widget.element.style.opacity = '1';
return;
}
// 取消任何隐藏计时器
if (widget.hideTimeout) {
clearTimeout(widget.hideTimeout);
widget.hideTimeout = null;
}
// 设置过渡状态
widget.isTransitioning = true;
// 优化渲染性能
widget.element.style.willChange = 'auto';
widget.element.style.transform = 'translateZ(0)';
widget.element.style.opacity = '1';
// 显示元素并应用动画类
widget.element.style.display = 'flex';
void widget.element.offsetWidth; // 触发回流,确保动画生效
widget.element.classList.remove('assistant-hide');
widget.element.classList.add('assistant-show');
// 动画结束后重置过渡状态
setTimeout(() => {
widget.isTransitioning = false;
// 动画结束后检查鼠标状态
setTimeout(() => this.forceUpdateMouseState(widget), 10);
}, 300); // 与CSS动画时长匹配
}
/**
* 隐藏小助手UI
* 控制UI隐藏动画和状态
*/
hideAssistantUI(widget) {
if (!widget?.element) return;
// 避免重复隐藏
if (!widget.element.classList.contains('assistant-show')) return;
// 设置过渡状态
widget.isTransitioning = true;
// 应用隐藏动画类
widget.element.classList.add('assistant-hide');
widget.element.classList.remove('assistant-show');
// 触发回流确保动画生效
void widget.element.offsetWidth;
// 动画结束后隐藏元素
widget.hideTimeout = setTimeout(() => {
if (widget.element) {
widget.element.style.display = 'none';
}
// 重置过渡状态
widget.isTransitioning = false;
// 动画结束后检查鼠标状态
setTimeout(() => this.forceUpdateMouseState(widget), 10);
}, 300); // 与CSS动画时长匹配
}
/**
* 更新小助手可见性
* 根据鼠标悬停和首次创建状态决定是否显示
*/
updateAssistantVisibility(widget) {
if (!widget) return;
// 总开关关闭时不处理可见性更新
if (!window.FEATURES || !window.FEATURES.enabled) {
return;
}
// 检查是否有按钮处于激活或处理中状态
const hasActiveButtons = this._checkAssistantActiveState(widget);
// 如果有激活的按钮,强制显示小助手(带动画)
if (hasActiveButtons) {
this.showAssistantUI(widget, true);
return;
}
const isMouseOver = widget.isMouseOver === true;
const isFirstCreate = widget.isFirstCreate === true;
// 显示条件:首次创建或鼠标悬停
const shouldShow = isFirstCreate || isMouseOver;
const isCurrentlyShown = widget.element?.classList.contains('assistant-show');
// 仅在状态需变化时更新
if (shouldShow !== isCurrentlyShown) {
const reason = isFirstCreate ? "首次创建" : (isMouseOver ? "鼠标悬停" : "鼠标离开");
if (shouldShow) {
// 首次创建时使用动画,鼠标悬停时不使用动画
this.showAssistantUI(widget, isFirstCreate);
logger.debug(`UI显示 | 节点:${widget.nodeId} | 原因:${reason}`);
} else {
this.hideAssistantUI(widget);
logger.debug(`UI隐藏 | 节点:${widget.nodeId} | 原因:${reason}`);
}
}
}
/**
* 检查小助手是否有按钮处于激活状态
*/
_checkAssistantActiveState(widget) {
if (!widget || !widget.buttons) return false;
// 首先检查中央按钮状态管理器是否有该widget的激活按钮
const activeButtonInfo = UIToolkit.getActiveButtonInfo();
if (activeButtonInfo && activeButtonInfo.widget === widget) {
return true;
}
// 再遍历所有按钮,检查是否有按钮处于active或processing状态
for (const buttonId in widget.buttons) {
const button = widget.buttons[buttonId];
if (button.classList.contains('button-active') ||
button.classList.contains('button-processing')) {
return true;
}
}
return false;
}
/**
* 更新所有实例的可见性
* 在按钮状态变化时调用
*/
updateAllInstancesVisibility() {
PromptAssistant.instances.forEach(widget => {
if (!widget.isTransitioning) {
this.updateAssistantVisibility(widget);
}
});
}
/**
* 显示状态提示
* 创建临时提示信息气泡
*/
showStatusTip(anchorElement, type, message, position = null) {
return UIToolkit.showStatusTip(anchorElement, type, message, position);
}
// ---事件处理功能---
/**
* 设置UI事件处理
* 配置鼠标悬停相关的事件监听 - 简化版本
*/
_setupUIEventHandling(widget, inputEl, containerDiv) {
// 设置容器的事件穿透
containerDiv.style.pointerEvents = 'none';
widget.innerContent.style.pointerEvents = 'auto'; // 确保内容容器可以接收事件
// 确保所有按钮可点击
const buttons = containerDiv.querySelectorAll('button');
buttons.forEach(button => {
button.style.pointerEvents = 'auto';
button.classList.add('prompt-assistant-button-active');
});
// 初始化过渡状态标记
widget.isTransitioning = false;
// 使用清理函数数组存储所有事件清理函数
widget._eventCleanupFunctions = widget._eventCleanupFunctions || [];
// 添加清理函数
widget.cleanupListeners = () => {
// 执行所有保存的清理函数
if (widget._eventCleanupFunctions && widget._eventCleanupFunctions.length) {
widget._eventCleanupFunctions.forEach(cleanupFn => {
if (typeof cleanupFn === 'function') {
cleanupFn();
}
});
widget._eventCleanupFunctions = [];
}
};
}
/**
* 判断鼠标是否在元素上方
*/
isMouseOverElement(element) {
if (!element) return false;
try {
// 直接使用计算而非委托给EventManager,减少一层调用
const mousePos = EventManager.getMousePosition();
const rect = element.getBoundingClientRect();
return (
mousePos.x >= rect.left &&
mousePos.x <= rect.right &&
mousePos.y >= rect.top &&
mousePos.y <= rect.bottom
);
} catch (error) {
logger.error(`鼠标位置检测失败 | 错误: ${error.message}`);
return false;
}
}
/**
* 强制更新鼠标悬停状态
*/
forceUpdateMouseState(widget) {
if (!widget || !widget.node || !widget.text_element || !widget.element) return;
// 总开关关闭时不处理鼠标状态更新
if (!window.FEATURES || !window.FEATURES.enabled) {
return;
}
// 使用直接检测方法检查鼠标是否在元素上方
const isOverInput = this.isMouseOverElement(widget.text_element);
const isOverContainer = widget.element.style.display !== 'none' &&
this.isMouseOverElement(widget.element);
// 更新状态
const oldState = widget.isMouseOver;
widget.isMouseOver = isOverInput || isOverContainer;
// 如果状态变化,更新可见性
if (oldState !== widget.isMouseOver) {
logger.debug(`鼠标状态 | 手动更新 | 节点:${widget.nodeId} | 原状态:${oldState} | 新状态:${widget.isMouseOver}`);
this.updateAssistantVisibility(widget);
}
return widget.isMouseOver;
}
// ---鼠标监听功能---
/**
* 判断全局鼠标监听器是否已注册
*/
isGlobalMouseListenerRegistered() {
try {
return EventManager.listeners.has('global_mouse_move') &&
EventManager.listeners.get('global_mouse_move').has('assistant_manager');
} catch (error) {
logger.error(`鼠标监听器状态检查失败 | 错误: ${error.message}`);
return false;
}
}
/**
* 注册全局鼠标移动事件监听
*/
async registerGlobalMouseListener() {
try {
// 确保EventManager已初始化
EventManager.init();
// 检查是否已注册,避免重复
if (this.isGlobalMouseListenerRegistered()) {
return true;
}
// 创建去抖动的全局监听函数
const debouncedListener = EventManager.debounce((e) => {
// 检查总开关状态,如果总开关已关闭,则不处理
if (!window.FEATURES || !window.FEATURES.enabled) {
return;
}
try {
// 获取当前实例数量
const instanceCount = PromptAssistant.instances.size;
if (instanceCount === 0) return;
// 遍历所有实例,更新鼠标状态
PromptAssistant.instances.forEach(widget => {
// 跳过正在过渡的实例
if (widget.isTransitioning) return;
// 检查鼠标是否在实例上
const isOver = this.isMouseOverElement(widget.text_element) ||
(widget.element?.style.display !== 'none' && this.isMouseOverElement(widget.element));
// 如果状态变化,更新可见性
if (widget.isMouseOver !== isOver) {
widget.isMouseOver = isOver;
this.updateAssistantVisibility(widget);
}
});
} catch (error) {
logger.error(`鼠标处理异常 | 错误: ${error.message}`);
}
}, 50);
// 注册全局鼠标移动事件监听器
EventManager.on('global_mouse_move', 'assistant_manager', debouncedListener);
return true;
} catch (error) {
logger.error(`全局鼠标监听注册失败 | 错误: ${error.message}`);
return false;
}
}
/**
* 移除全局鼠标移动事件监听
*/
removeGlobalMouseListener() {
try {
// 检查是否已注册,未注册则不需要移除
if (!this.isGlobalMouseListenerRegistered()) {
return true;
}
// 移除事件监听器
EventManager.off('global_mouse_move', 'assistant_manager');
return true;
} catch (error) {
logger.error(`全局鼠标监听移除失败 | 错误: ${error.message}`);
return false;
}
}
// ---辅助功能---
/**
* 更新输入框内容并添加高亮效果
*/
updateInputWithHighlight(widget, content) {
if (!widget?.inputEl) return;
try {
// 更新输入框内容
widget.inputEl.value = content;
// 触发输入事件
widget.inputEl.dispatchEvent(new Event('input', { bubbles: true }));
widget.inputEl.dispatchEvent(new Event('change', { bubbles: true }));
// 添加高亮效果
widget.inputEl.classList.add('input-highlight');
// 动画结束后移除类
setTimeout(() => {
widget.inputEl.classList.remove('input-highlight');
}, 200); // 与 CSS 动画时长匹配
logger.debug(`输入框更新 | 结果:成功 | 内容长度:${content.length}`);
} catch (error) {
logger.error(`输入框更新 | 结果:异常 | 错误:${error.message}`);
}
}
// ---按钮管理功能---
/**
* 添加功能按钮
*/
addFunctionButtons(widget) {
if (!widget?.element) {
logger.error('添加按钮 | 结果:失败 | 原因: 容器不存在');
return;
}
// 检查总开关状态
if (!FEATURES.enabled) {
logger.debug('添加按钮 | 结果:跳过 | 原因: 总功能已禁用');
return;
}
// 检查是否有至少一个功能启用
const hasEnabledFeatures = FEATURES.history || FEATURES.tag || FEATURES.expand || FEATURES.translate;
if (!hasEnabledFeatures) {
logger.debug('添加按钮 | 结果:跳过 | 原因: 没有启用任何功能');
return;
}
// 检查是否是Note节点
const isNoteNode = widget.nodeInfo && widget.nodeInfo.isNoteNode === true;
// 获取历史状态(用于初始化撤销/重做按钮状态)
const canUndo = HistoryCacheService.canUndo(widget.nodeId, widget.inputId);
const canRedo = HistoryCacheService.canRedo(widget.nodeId, widget.inputId);
// 按钮配置
const buttonConfigs = [
{
id: 'history',
title: '历史',
icon: 'icon-history.svg',
onClick: (e, widget) => {
UIToolkit.handlePopupButtonClick(
e,
widget,
'history',
HistoryManager.showHistoryPopup.bind(HistoryManager),
HistoryManager.hideHistoryPopup.bind(HistoryManager)
);
},
visible: !isNoteNode && FEATURES.history, // Note节点不显示此按钮
initialState: { disabled: false }
},
{
id: 'undo',
title: '撤销',
icon: 'icon-undo.svg',
onClick: (e, widget) => {
e.preventDefault();
e.stopPropagation();
logger.debug('按钮点击 | 动作: 撤销');
// 执行撤销操作
const undoContent = HistoryCacheService.undo(widget.nodeId, widget.inputId);
if (undoContent !== null) {
// 更新输入框内容并添加高亮效果
this.updateInputWithHighlight(widget, undoContent);
// 更新按钮状态
UIToolkit.updateUndoRedoButtonState(widget, HistoryCacheService);
logger.debug(`撤销操作 | 结果:成功 | 节点:${widget.nodeId}`);
} else {
logger.debug(`撤销操作 | 结果:失败 | 节点:${widget.nodeId} | 原因:无可用内容`);
}
},
visible: !isNoteNode && FEATURES.history,
initialState: { disabled: !canUndo }
},
{
id: 'redo',
title: '重做',
icon: 'icon-redo.svg',
onClick: (e, widget) => {
e.preventDefault();
e.stopPropagation();
logger.debug('按钮点击 | 动作: 重做');
// 执行重做操作
const redoContent = HistoryCacheService.redo(widget.nodeId, widget.inputId);
if (redoContent !== null) {
// 更新输入框内容并添加高亮效果
this.updateInputWithHighlight(widget, redoContent);
// 更新按钮状态
UIToolkit.updateUndoRedoButtonState(widget, HistoryCacheService);
logger.debug(`重做操作 | 结果:成功 | 节点:${widget.nodeId}`);
} else {
logger.debug(`重做操作 | 结果:失败 | 节点:${widget.nodeId} | 原因:无可用内容`);
}
},
visible: !isNoteNode && FEATURES.history,
initialState: { disabled: !canRedo }
},
{
id: 'divider1',
type: 'divider',
visible: !isNoteNode // Note节点不显示分割线
},
{
id: 'tag',
title: '标签工具',
icon: 'icon-tag.svg',
onClick: (e, widget) => {
// 创建一个带有标签选择功能的显示函数
const showTagPopup = (options) => {
// 处理标签选择功能
const enhancedOptions = {
...options,
onTagSelect: (tag) => {
// 获取当前输入框的值和光标位置
const currentValue = widget.inputEl.value;
const cursorPos = widget.inputEl.selectionStart;
const beforeText = currentValue.substring(0, cursorPos);
const afterText = currentValue.substring(widget.inputEl.selectionEnd);
// 添加标签(英文值)
const newValue = beforeText + tag.en + afterText;
// 更新输入框内容并添加高亮效果
this.updateInputWithHighlight(widget, newValue);
// 更新光标位置
const newPos = cursorPos + tag.en.length;
widget.inputEl.setSelectionRange(newPos, newPos);
// 保持焦点在输入框
widget.inputEl.focus();
}
};
// 调用标签管理器显示弹窗
TagManager.showTagPopup(enhancedOptions);
};
// 使用统一的弹窗按钮点击处理
UIToolkit.handlePopupButtonClick(
e,
widget,
'tag',
showTagPopup,
TagManager.hideTagPopup.bind(TagManager)
);
},
visible: !isNoteNode && FEATURES.tag // Note节点不显示此按钮
},
{
id: 'expand',
title: '扩写',
icon: 'icon-expand.svg',
onClick: async (e, widget) => {
logger.debug('按钮点击 | 动作: 扩写');
// 如果按钮处于 processing 状态且被点击,显示"扩写中"提示并直接返回
if (e.currentTarget.classList.contains('button-processing')) {
const btnRect = e.currentTarget.getBoundingClientRect();
UIToolkit.showStatusTip(
e.currentTarget,
'loading',
'扩写中',
{ x: btnRect.left + btnRect.width / 2, y: btnRect.top }
);
return;
}
await UIToolkit.handleAsyncButtonOperation(
widget,
'expand',
e.currentTarget,
async () => {
try {
// 获取输入值
const inputValue = widget.inputEl.value;
if (!inputValue || inputValue.trim() === '') {
throw new Error('请输入要扩写的内容');
}
// 生成唯一request_id
const request_id = generateRequestId('glm4_expand');
// 显示扩写中提示
const btnRect = e.currentTarget.getBoundingClientRect();
UIToolkit.showStatusTip(
e.currentTarget,
'loading',
'扩写中',
{ x: btnRect.left + btnRect.width / 2, y: btnRect.top }
);
// 调用LLM服务进行扩写
const result = await llmService.expandPrompt(inputValue, request_id);
if (result.success) {
// 更新输入框内容并添加高亮效果
this.updateInputWithHighlight(widget, result.data.expanded);
// 添加扩写结果到历史记录
HistoryCacheService.addHistory({
workflow_id: widget.nodeInfo?.workflow_id || '',
node_id: widget.nodeId,
input_id: widget.inputId,
content: result.data.expanded,
operation_type: 'expand',
request_id: request_id,
timestamp: Date.now()
});
// 重置撤销状态
HistoryCacheService.initUndoState(widget.nodeId, widget.inputId, result.data.expanded);
// 更新按钮状态
UIToolkit.updateUndoRedoButtonState(widget, HistoryCacheService);
return {
success: true,
useCache: false,
tipType: 'success',
tipMessage: '扩写完成'
};
} else {
// 不在这里显示错误提示,直接抛出错误让 handleAsyncButtonOperation 处理
throw new Error(result.error);
}
} catch (error) {
// 不在这里显示错误提示,直接抛出错误让 handleAsyncButtonOperation 处理
throw error;
}
}
);
},
visible: !isNoteNode && FEATURES.expand // Note节点不显示此按钮
},
{
id: 'translate',
title: '翻译',
icon: 'icon-translate.svg',
onClick: async (e, widget) => {
logger.debug('按钮点击 | 动作: 翻译');
// 如果按钮处于 processing 状态且被点击,显示"翻译中"提示并直接返回
if (e.currentTarget.classList.contains('button-processing')) {
const btnRect = e.currentTarget.getBoundingClientRect();
UIToolkit.showStatusTip(
e.currentTarget,
'loading',
'翻译中',
{ x: btnRect.left + btnRect.width / 2, y: btnRect.top }
);
return;
}
await UIToolkit.handleAsyncButtonOperation(
widget,
'translate',
e.currentTarget,
async () => {
try {
// 获取输入值
const inputValue = widget.inputEl.value;
if (!inputValue || inputValue.trim() === '') {
throw new Error('请输入要翻译的内容');
}
// 显示翻译中提示
const btnRect = e.currentTarget.getBoundingClientRect();
UIToolkit.showStatusTip(
e.currentTarget,
'loading',
'翻译中',
{ x: btnRect.left + btnRect.width / 2, y: btnRect.top }
);
// 首先查询翻译缓存
const cacheResult = TranslateCacheService.queryTranslateCache(inputValue);
// 缓存命中情况处理
if (cacheResult) {
let resultText = '';
let tipMessage = '';
let useCache = true;
// 根据缓存匹配类型处理
if (cacheResult.type === 'source') {
// 命中原文,返回译文
resultText = cacheResult.translatedText;
tipMessage = '译文';
} else if (cacheResult.type === 'translated') {
// 命中译文,返回原文
resultText = cacheResult.sourceText;
tipMessage = '原文';
}
// 更新输入框内容并添加高亮效果
this.updateInputWithHighlight(widget, resultText);
// 添加翻译结果到历史记录
HistoryCacheService.addHistory({
workflow_id: widget.nodeInfo?.workflow_id || '',
node_id: widget.nodeId,
input_id: widget.inputId,
content: resultText,
operation_type: 'translate',
timestamp: Date.now()
});
// 重置撤销状态
HistoryCacheService.initUndoState(widget.nodeId, widget.inputId, resultText);
// 更新按钮状态
UIToolkit.updateUndoRedoButtonState(widget, HistoryCacheService);
return {
success: true,
useCache: useCache,
tipType: 'info',
tipMessage: tipMessage,
buttonElement: e.currentTarget // 传递按钮元素
};
}
// 缓存未命中,使用API翻译
// 获取翻译类型
const translateType = localStorage.getItem("PromptAssistant_Settings_translate_type") || "baidu";
// 生成唯一request_id,根据翻译类型生成对应的前缀
const request_id = generateRequestId(null, translateType);
// 检测语言
const langResult = PromptFormatter.detectLanguage(inputValue);
// 根据翻译类型选择服务
let result;
try {
if (translateType === "baidu") {
// 使用百度翻译服务
result = await BaiduTranslateService.translate(
inputValue,
langResult.from,
langResult.to,
request_id
);
} else {
// 使用LLM翻译服务
result = await llmService.translate(
inputValue,
langResult.from,
langResult.to,
request_id
);
}
if (!result) {
throw new Error('翻译服务返回空结果');
}
} catch (error) {
logger.error(`翻译失败 | 服务:${translateType} | 错误:${error.message}`);
throw new Error(`翻译失败: ${error.message}`);
}
if (result.success) {
// 格式化翻译结果(转换标点符号)
const formattedText = PromptFormatter.formatTranslatedText(result.data.translated);
// 添加翻译结果到历史记录
HistoryCacheService.addHistory({
workflow_id: widget.nodeInfo?.workflow_id || '',
node_id: widget.nodeId,
input_id: widget.inputId,
content: formattedText,
operation_type: 'translate',
request_id: request_id,
timestamp: Date.now()
});
// 更新输入框内容并添加高亮效果
this.updateInputWithHighlight(widget, formattedText);
// 重置撤销状态
HistoryCacheService.initUndoState(widget.nodeId, widget.inputId, formattedText);
// 更新按钮状态
UIToolkit.updateUndoRedoButtonState(widget, HistoryCacheService);
// 添加到翻译缓存
TranslateCacheService.addTranslateCache(inputValue, formattedText);
return {
success: true,
useCache: false,
tipType: 'success',
tipMessage: '翻译完成'
};
} else {
// 不在这里显示错误提示,直接抛出错误让 handleAsyncButtonOperation 处理
throw new Error(result.error);
}
} catch (error) {
// 不在这里显示错误提示,直接抛出错误让 handleAsyncButtonOperation 处理
throw error;
}
}
);
},
visible: FEATURES.translate // Note节点只显示此按钮
},
];
// 记录添加的按钮
let historyButtons = [];
let otherButtons = [];
let divider = null;
buttonConfigs.forEach(config => {
if (config.type === 'divider') {
// 创建分割线但暂不添加
divider = document.createElement('div');
divider.className = 'prompt-assistant-divider';
divider.dataset.id = config.id || `divider_${Date.now()}`;
// 保存引用
if (config.id) {
widget.buttons[config.id] = divider;
}
return;
}
// 检查按钮是否可见
if (config.visible === false) {
// 区分节点类型和功能禁用的情况
const reason = (widget.nodeInfo?.isNoteNode && config.id !== 'translate') ? "Note节点限制" : "功能禁用";
// logger.debug(`按钮跳过 | 按钮: ${config.id || 'unknown'} | 原因: ${reason}`);
return;
}
// 创建按钮
const button = this.addButtonWithIcon(widget, config);
if (!button) return;
// 设置初始状态
if (config.initialState) {
Object.entries(config.initialState).forEach(([stateType, value]) => {
UIToolkit.setButtonState(widget, config.id, stateType, value);
});
}
// 根据按钮类型分组
if (['history', 'undo', 'redo'].includes(config.id)) {
historyButtons.push(button);
} else {
otherButtons.push(button);
}
});
// 添加按钮到DOM,并在需要时添加分割线
let addedButtonCount = 0;
// 添加历史相关按钮
historyButtons.forEach(button => {
widget.innerContent.appendChild(button);
addedButtonCount++;
});
// 如果两种类型的按钮都存在,添加分割线
if (historyButtons.length > 0 && otherButtons.length > 0 && divider) {
widget.innerContent.appendChild(divider);
}
// 添加其他按钮
otherButtons.forEach(button => {
widget.innerContent.appendChild(button);
addedButtonCount++;
});
// 判断节点类型并记录日志
if (widget.nodeInfo?.isNoteNode) {
logger.debug(`按钮添加 | 结果:完成 | 节点ID: ${widget.nodeId} | Note节点: 仅显示翻译按钮 | 添加数量: ${addedButtonCount}`);
} else {
logger.debug(`按钮添加 | 结果:完成 | 节点ID: ${widget.nodeId} | 标准节点 | 添加数量: ${addedButtonCount}`);
}
}
/**
* 添加带图标的按钮
*/
addButtonWithIcon(widget, config) {
if (!widget?.element || !widget?.innerContent) {
logger.error('添加按钮 | 结果:失败 | 原因: 容器不存在');
return null;
}
const { id, title, icon, onClick } = config;
// 创建按钮
const button = document.createElement('button');
button.className = 'prompt-assistant-button';
button.title = title || '';
button.dataset.id = id || `btn_${Date.now()}`;
button.dataset.icon = icon?.replace('.svg', ''); // 保存不带后缀的图标名,用于CSS选择器
// 按钮样式在CSS中定义
// 添加图标
if (icon) {
UIToolkit.addIconToButton(button, icon, title || id);
}
// 添加事件
if (typeof onClick === 'function') {
button.addEventListener('click', (e) => {
e.preventDefault();
e.stopPropagation();
onClick(e, widget);
});
}
// 保存引用并添加到内部内容容器
if (id) {
widget.buttons[id] = button;
}
widget.innerContent.appendChild(button);
return button;
}
/**
* 设置UI位置
* 使用绝对定位方式
*/
_setupUIPosition(widget, inputEl, containerDiv, canvasContainerRect) {
// 查找dom-widget父容器(ComfyUI 0.3.27及以上版本的标准容器)
let domWidgetContainer = null;
// 向上查找dom-widget容器
let parent = inputEl.parentElement;
while (parent) {
if (parent.classList && parent.classList.contains('dom-widget')) {
domWidgetContainer = parent;
break;
}
parent = parent.parentElement;
}
// 检查是否需要使用兼容模式
const needCompatibilityMode = !domWidgetContainer;
// 清理函数列表
widget._eventCleanupFunctions = widget._eventCleanupFunctions || [];
if (needCompatibilityMode) {
// === 兼容方案定位 (ComfyUI 0.3.26及以下) ===
logger.log("定位方式 - 兼容方案");
// 使用固定定位样式,不添加额外类
containerDiv.style.position = 'fixed';
containerDiv.style.zIndex = '9999';
document.body.appendChild(containerDiv);
// 设置初始显示
containerDiv.style.display = 'flex';
// 更新位置的函数,确保跟随输入框并位于右下角
const updatePosition = () => {
if (!widget.element || !inputEl || !containerDiv) return;
try {
// 获取输入框的位置信息
const inputRect = inputEl.getBoundingClientRect();
// 设置容器样式 - 首先确保容器可见以便获得正确的尺寸
containerDiv.style.display = 'flex';
containerDiv.style.visibility = 'hidden'; // 暂时隐藏以避免闪烁
// 先设置样式,使其能正确计算尺寸
Object.assign(containerDiv.style, {
width: 'auto',
height: '24px',
pointerEvents: 'none'
});
// 确保小部件样式正确
Object.assign(widget.element.style, {
transformOrigin: 'right center',
margin: '0',
pointerEvents: 'auto'
});
// 强制回流以获取正确尺寸
void containerDiv.offsetWidth;
// 现在设置位置 - 放置在输入框右下角
const offsetRight = 12; // 右侧偏移
const offsetBottom = 8;
containerDiv.style.left = `${inputRect.right - containerDiv.offsetWidth - offsetRight}px`;
containerDiv.style.top = `${inputRect.bottom - containerDiv.offsetHeight - offsetBottom}px`;
containerDiv.style.visibility = 'visible'; // 恢复可见性
// 最终触发回流,确保样式更新
void containerDiv.offsetWidth;
} catch (error) {
logger.error("更新小助手位置出错:", error);
// 错误恢复 - 确保组件仍然可见
if (containerDiv) containerDiv.style.visibility = 'visible';
}
};
// 初始更新位置
updatePosition();
// 使用防抖函数优化位置更新
const debouncedUpdatePosition = EventManager.debounce(updatePosition, 100);
// 添加窗口resize事件监听
const removeResizeListener = EventManager.addDOMListener(window, 'resize', debouncedUpdatePosition);
widget._eventCleanupFunctions.push(removeResizeListener);
// 监听画布变化
const app = window.app || null;
if (!app || !app.canvas) {
logger.error("错误:无法获取app对象,小助手无法正常工作");
return;
}
// 1. 使用MutationObserver监听画布变化
const observer = new MutationObserver(debouncedUpdatePosition);
const canvasParent = app.canvas.canvas.parentElement;
if (canvasParent) {
observer.observe(canvasParent, {
attributes: true,
attributeFilter: ['style', 'transform']
});
// 添加Observer清理函数
widget._eventCleanupFunctions.push(() => observer.disconnect());
}
// 2. 监听画布重绘
const originalDrawBackground = app.canvas.onDrawBackground;
app.canvas.onDrawBackground = function () {
const ret = originalDrawBackground?.apply(this, arguments);
debouncedUpdatePosition();
return ret;
};
// 添加画布重绘清理函数
widget._eventCleanupFunctions.push(() => {
if (originalDrawBackground) {
app.canvas.onDrawBackground = originalDrawBackground;
}
});
// 3. 监听节点移动 - 使用 nodeInfo 而不是直接的 node 引用
const nodeId = widget.nodeInfo?.nodeId;
if (nodeId && app.canvas && app.canvas.graph) {
const node = app.canvas.graph.getNodeById(nodeId);
if (node) {
const originalOnNodeMoved = node.onNodeMoved;
node.onNodeMoved = function () {
const ret = originalOnNodeMoved?.apply(this, arguments);
debouncedUpdatePosition();
return ret;
};
// 添加节点移动清理函数
widget._eventCleanupFunctions.push(() => {
if (node && originalOnNodeMoved) {
node.onNodeMoved = originalOnNodeMoved;
}
});
}
}
// 添加DOM元素清理函数
widget._eventCleanupFunctions.push(() => {
// 移除DOM元素
if (containerDiv && document.body.contains(containerDiv)) {
document.body.removeChild(containerDiv);
}
});
} else {
// === 标准方案定位 (ComfyUI 0.3.27及以上) ===
logger.log("定位方式 - 标准方案");
// 标准模式使用默认的绝对定位,添加位置参数
containerDiv.style.right = '6px';
containerDiv.style.bottom = '8px';
// 直接添加到dom-widget容器,但不修改dom-widget本身
domWidgetContainer.appendChild(containerDiv);
// 触发回流,确保样式更新
void containerDiv.offsetWidth;
}
// 强制应用容器高度
containerDiv.style.height = '20px';
containerDiv.style.minHeight = '20px';
void containerDiv.offsetWidth; // 触发回流,确保样式应用
}
/**
* 清理单个实例的资源
*/
_cleanupInstance(instance, instanceKey, skipRemove = false) {
try {
if (!instance) return;
// 1. 重置所有按钮状态
if (instance.buttons) {
Object.keys(instance.buttons).forEach(buttonId => {
try {
const button = instance.buttons[buttonId];
if (button) {
// 移除所有状态类
button.classList.remove('button-active', 'button-processing', 'button-disabled');
// 移除所有事件监听器
button.replaceWith(button.cloneNode(true));
}
} catch (err) {
logger.debug(`按钮清理 | 按钮:${buttonId} | 错误:${err.message}`);
}
});
// 清空按钮引用
instance.buttons = {};
}
// 2. 清理事件监听器
if (instance.cleanupListeners) {
instance.cleanupListeners();
}
// 3. 清理所有保存的事件清理函数
if (instance._eventCleanupFunctions) {
instance._eventCleanupFunctions.forEach(cleanup => {
if (typeof cleanup === 'function') {
try {
cleanup();
} catch (err) {
logger.debug(`事件清理 | 错误:${err.message}`);
}
}
});
instance._eventCleanupFunctions = [];
}
// 4. 从DOM中移除元素
if (instance.element && instance.element.parentNode) {
// 确保在移除元素前清理所有子元素的事件
const allButtons = instance.element.querySelectorAll('button');
allButtons.forEach(button => {
button.replaceWith(button.cloneNode(true));
});
instance.element.parentNode.removeChild(instance.element);
}
// 5. 清理输入框映射
if (window.PromptAssistantInputWidgetMap && instanceKey) {
delete window.PromptAssistantInputWidgetMap[instanceKey];
}
// 6. 清理弹窗状态
if (window.FEATURES && window.FEATURES.updateButtonsVisibility) {
try {
window.FEATURES.updateButtonsVisibility();
} catch (err) {
logger.debug(`按钮可见性更新 | 错误:${err.message}`);
}
}
// 7. 从实例集合中移除(除非明确指定跳过)
if (!skipRemove && instanceKey) {
PromptAssistant.instances.delete(instanceKey);
}
// 8. 清理实例属性
Object.keys(instance).forEach(key => {
try {
delete instance[key];
} catch (err) {
logger.debug(`属性清理 | 属性:${key} | 错误:${err.message}`);
}
});
logger.debug(`实例清理 | 结果:成功 | 实例:${instanceKey}`);
} catch (error) {
logger.error(`实例清理失败 | 实例:${instanceKey} | 错误:${error.message}`);
}
}
}
// 创建单例实例
const promptAssistant = new PromptAssistant();
// 导出
export { promptAssistant, PromptAssistant };