343 lines
13 KiB
JavaScript
343 lines
13 KiB
JavaScript
/**
|
|
* 提示词小助手 (PromptAssistant) 主入口文件
|
|
* 负责扩展初始化、节点检测和功能注入
|
|
*/
|
|
|
|
import { app } from "../../../scripts/app.js";
|
|
import { promptAssistant, PromptAssistant } from './modules/PromptAssistant.js';
|
|
import { registerSettings } from './modules/settings.js';
|
|
import { FEATURES as ASSISTANT_FEATURES, handleFeatureChange, setFeatureModuleDeps } from './services/features.js';
|
|
import { EventManager } from './utils/eventManager.js';
|
|
import { ResourceManager } from './utils/resourceManager.js';
|
|
import { UIToolkit } from "./utils/UIToolkit.js";
|
|
import { logger } from './utils/logger.js';
|
|
import { HistoryCacheService, TagCacheService } from './services/cache.js';
|
|
import { imageCaption, ImageCaption } from './modules/imageCaption.js';
|
|
|
|
// import { ensureAutoTranslateInterceptorInstalled } from './services/interceptor.js'; // 导入自动翻译拦截器
|
|
|
|
// ====================== 全局配置与状态 ======================
|
|
|
|
// 设置全局对象供其他模块访问
|
|
window.FEATURES = ASSISTANT_FEATURES;
|
|
|
|
// 将实例添加到全局对象
|
|
window.promptAssistant = promptAssistant;
|
|
window.imageCaption = imageCaption;
|
|
|
|
// 将实例添加到全局app对象
|
|
app.promptAssistant = promptAssistant;
|
|
app.imageCaption = imageCaption;
|
|
|
|
// ====================== 扩展注册 ======================
|
|
|
|
/**
|
|
* 注册ComfyUI扩展
|
|
*/
|
|
app.registerExtension({
|
|
name: "Comfy.PromptAssistant",
|
|
|
|
// ---扩展生命周期钩子---
|
|
/**
|
|
* 初始化扩展
|
|
*/
|
|
async setup() {
|
|
try {
|
|
// 注册设置选项
|
|
registerSettings();
|
|
|
|
// 初始化自动翻译拦截器(独立于提示词小助手)
|
|
// ensureAutoTranslateInterceptorInstalled();
|
|
|
|
// 初始化提示词小助手(内部会处理版本号检查和总开关状态)
|
|
await promptAssistant.initialize();
|
|
|
|
// 初始化图像小助手(只初始化一次)
|
|
if (!imageCaption.initialized) {
|
|
await imageCaption.initialize();
|
|
}
|
|
|
|
// 清理旧引用
|
|
if (app.canvas) {
|
|
app.canvas.updateNodeAssistantsVisibility = null;
|
|
app.canvas._onNodeSelectionChange = null;
|
|
}
|
|
|
|
// 将管理器添加到app对象,使其可以通过window.app访问
|
|
app.EventManager = EventManager;
|
|
app.ResourceManager = ResourceManager;
|
|
app.UIToolkit = UIToolkit;
|
|
|
|
// 先初始化 features.js 依赖
|
|
setFeatureModuleDeps({
|
|
promptAssistant,
|
|
PromptAssistant,
|
|
UIToolkit,
|
|
HistoryCacheService,
|
|
TagCacheService,
|
|
imageCaption,
|
|
ImageCaption
|
|
});
|
|
|
|
// 然后再自动注册服务功能
|
|
if (window.FEATURES.enabled) {
|
|
await promptAssistant.toggleGlobalFeature(true, true);
|
|
// 避免重复初始化,只在必要时启用图像小助手功能
|
|
if (window.FEATURES.imageCaption) {
|
|
await imageCaption.toggleGlobalFeature(true, false);
|
|
}
|
|
}
|
|
|
|
logger.log("扩展初始化完成");
|
|
} catch (error) {
|
|
logger.error(`扩展初始化失败: ${error.message}`);
|
|
}
|
|
|
|
// 仅保留工作流ID识别功能,不处理工作流切换事件
|
|
try {
|
|
const LGraph = app.graph.constructor;
|
|
const origConfigure = LGraph.prototype.configure;
|
|
LGraph.prototype.configure = function (data) {
|
|
// 在图表对象上存储工作流ID
|
|
this._workflow_id = data.id || LiteGraph.uuidv4();
|
|
|
|
// 执行原始方法
|
|
return origConfigure.apply(this, arguments);
|
|
};
|
|
|
|
// 添加工作流加载监听,只标记切换状态,不做特殊处理
|
|
const origLoadGraphData = app.loadGraphData;
|
|
app.loadGraphData = async function (data) {
|
|
// 设置工作流切换标记,避免删除缓存
|
|
window.PROMPT_ASSISTANT_WORKFLOW_SWITCHING = true;
|
|
|
|
// 只在debug模式下打印工作流切换信息
|
|
const workflowId = data?.id || (data?.extra?.workflow_id) || "未知工作流";
|
|
logger.debug(`[工作流] 正在切换工作流: ${workflowId}`);
|
|
|
|
try {
|
|
// 调用原始加载方法
|
|
const result = await origLoadGraphData.apply(this, arguments);
|
|
return result;
|
|
} finally {
|
|
// 延迟重置工作流切换标记
|
|
setTimeout(() => {
|
|
window.PROMPT_ASSISTANT_WORKFLOW_SWITCHING = false;
|
|
}, 500);
|
|
}
|
|
};
|
|
} catch (e) {
|
|
logger.error("[PromptAssistant] 注入 LGraph 设置工作流ID失败", e);
|
|
}
|
|
},
|
|
|
|
// ---节点生命周期钩子---
|
|
/**
|
|
* 节点创建钩子
|
|
* 在节点创建时初始化特定类型节点的小助手
|
|
*/
|
|
async nodeCreated(node) {
|
|
try {
|
|
// 始终检查总开关状态
|
|
if (!window.FEATURES.enabled) {
|
|
return;
|
|
}
|
|
|
|
// 只在节点被选中时进行检测和初始化
|
|
if (!node || !node.id || node.id === -1) {
|
|
return;
|
|
}
|
|
|
|
// 检查是否为提示词节点
|
|
if (PromptAssistant.isValidNode(node)) {
|
|
if (!node._promptAssistantInitialized) {
|
|
node._promptAssistantInitialized = true;
|
|
promptAssistant.checkAndSetupNode(node);
|
|
}
|
|
return;
|
|
}
|
|
|
|
// 检查是否为图像节点,同时检查图像反推功能开关
|
|
if (window.FEATURES.imageCaption && imageCaption.hasValidImage(node)) {
|
|
if (!node._imageCaptionInitialized) {
|
|
node._imageCaptionInitialized = true;
|
|
imageCaption.checkAndSetupNode(node);
|
|
}
|
|
return;
|
|
}
|
|
} catch (error) {
|
|
logger.error(`节点创建处理失败: ${error.message}`);
|
|
}
|
|
},
|
|
|
|
/**
|
|
* 节点移除钩子
|
|
* 在节点被删除时清理对应的小助手实例
|
|
*/
|
|
async nodeRemoved(node) {
|
|
// 如果正在切换工作流,则不执行任何清理操作
|
|
if (window.PROMPT_ASSISTANT_WORKFLOW_SWITCHING) {
|
|
return;
|
|
}
|
|
|
|
try {
|
|
if (!node || node.id === undefined || node.id === -1) return;
|
|
|
|
// 安全获取节点ID,用于日志记录
|
|
const nodeId = node.id;
|
|
|
|
// 添加清理标记,避免重复清理
|
|
node._promptAssistantCleaned = false;
|
|
node._imageCaptionCleaned = false;
|
|
|
|
// 清理提示词小助手
|
|
if (node._promptAssistantInitialized) {
|
|
promptAssistant.cleanup(nodeId);
|
|
node._promptAssistantCleaned = true;
|
|
logger.debug(`[节点移除钩子] 提示词小助手清理完成 | 节点ID: ${nodeId}`);
|
|
}
|
|
|
|
// 清理图像小助手
|
|
if (node._imageCaptionInitialized) {
|
|
imageCaption.cleanup(nodeId);
|
|
node._imageCaptionCleaned = true;
|
|
logger.debug(`[节点移除钩子] 图像小助手清理完成 | 节点ID: ${nodeId}`);
|
|
}
|
|
} catch (error) {
|
|
const safeNodeId = node && node.id !== undefined ? node.id : "unknown";
|
|
logger.error(`[节点移除钩子] 处理失败 | 节点ID: ${safeNodeId} | 错误: ${error.message}`);
|
|
}
|
|
},
|
|
|
|
/**
|
|
* 节点定义注册前钩子
|
|
* 向所有节点类型注入小助手相关功能
|
|
*/
|
|
async beforeRegisterNodeDef(nodeType, nodeData) {
|
|
// 保存原始方法
|
|
const origOnNodeCreated = nodeType.prototype.onNodeCreated;
|
|
const origOnRemoved = nodeType.prototype.onRemoved;
|
|
const origOnSelected = nodeType.prototype.onSelected;
|
|
|
|
// 注入节点创建方法
|
|
nodeType.prototype.onNodeCreated = function () {
|
|
if (origOnNodeCreated) {
|
|
origOnNodeCreated.apply(this, arguments);
|
|
}
|
|
|
|
// 始终检查最新的总开关状态
|
|
const currentEnabled = app.ui.settings.getSettingValue("PromptAssistant.Features.Enabled");
|
|
window.FEATURES.enabled = currentEnabled !== undefined ? currentEnabled : true;
|
|
|
|
// 总开关关闭时,直接返回
|
|
if (!window.FEATURES.enabled) {
|
|
return;
|
|
}
|
|
|
|
// 设置未初始化标记,等待nodeCreated钩子处理
|
|
this._promptAssistantInitialized = false;
|
|
this._imageCaptionInitialized = false;
|
|
};
|
|
|
|
// 注入节点选择方法
|
|
nodeType.prototype.onSelected = function () {
|
|
if (origOnSelected) {
|
|
origOnSelected.apply(this, arguments);
|
|
}
|
|
|
|
// 确保总开关开启
|
|
if (!window.FEATURES.enabled) {
|
|
return;
|
|
}
|
|
|
|
// 重置提示词小助手初始化标记,确保每次选择都重新检测节点状态
|
|
this._promptAssistantInitialized = false;
|
|
promptAssistant.checkAndSetupNode(this);
|
|
|
|
// 确保选择事件能触发到图像小助手,同时检查图像反推功能开关
|
|
if (window.FEATURES.imageCaption &&
|
|
app.canvas && app.canvas._imageCaptionSelectionHandler) {
|
|
// 重置初始化标记,确保每次选择都重新检测节点状态
|
|
this._imageCaptionInitialized = false;
|
|
|
|
const selected_nodes = {};
|
|
selected_nodes[this.id] = this;
|
|
app.canvas._imageCaptionSelectionHandler(selected_nodes);
|
|
}
|
|
};
|
|
|
|
// 注入节点移除方法
|
|
nodeType.prototype.onRemoved = function () {
|
|
try {
|
|
// 首先检查this和this.id是否存在和有效
|
|
if (!this) {
|
|
logger.debug("[onRemoved方法] 节点实例不存在,跳过清理");
|
|
if (origOnRemoved) {
|
|
origOnRemoved.apply(this, arguments);
|
|
}
|
|
return;
|
|
}
|
|
|
|
// 安全获取节点ID,如果不存在则使用占位符
|
|
const nodeId = this.id !== undefined ? this.id : "unknown";
|
|
|
|
// 清理提示词小助手(如果尚未清理)
|
|
if (this._promptAssistantInitialized && !this._promptAssistantCleaned) {
|
|
if (this.id !== undefined) {
|
|
promptAssistant.cleanup(this.id);
|
|
this._promptAssistantCleaned = true;
|
|
}
|
|
}
|
|
|
|
// 清理图像小助手(如果尚未清理)
|
|
if (this._imageCaptionInitialized && !this._imageCaptionCleaned) {
|
|
if (this.id !== undefined) {
|
|
imageCaption.cleanup(this.id);
|
|
this._imageCaptionCleaned = true;
|
|
}
|
|
}
|
|
|
|
// 即使没有初始化标记,也尝试清理(关键修复)
|
|
// 这是为了处理可能的边缘情况,确保完全清理
|
|
if (!this._promptAssistantCleaned && this.id !== undefined) {
|
|
promptAssistant.cleanup(this.id, true);
|
|
}
|
|
if (!this._imageCaptionCleaned && this.id !== undefined) {
|
|
imageCaption.cleanup(this.id, true);
|
|
}
|
|
|
|
// 只打印一次清理完成日志
|
|
if (this._promptAssistantCleaned && this._imageCaptionCleaned) {
|
|
logger.debug(`[onRemoved方法] 节点助手清理完成 | 节点ID: ${nodeId}`);
|
|
} else if (this._promptAssistantCleaned) {
|
|
logger.debug(`[onRemoved方法] 提示词小助手清理完成 | 节点ID: ${nodeId}`);
|
|
} else if (this._imageCaptionCleaned) {
|
|
logger.debug(`[onRemoved方法] 图像小助手清理完成 | 节点ID: ${nodeId}`);
|
|
}
|
|
|
|
if (origOnRemoved) {
|
|
origOnRemoved.apply(this, arguments);
|
|
}
|
|
} catch (error) {
|
|
// 安全获取节点ID,用于错误日志
|
|
const safeNodeId = this && this.id !== undefined ? this.id : "unknown";
|
|
logger.error(`[onRemoved方法] 清理失败 | 节点ID: ${safeNodeId} | 错误: ${error.message}`);
|
|
// 确保原始方法仍然被调用
|
|
if (origOnRemoved) {
|
|
origOnRemoved.apply(this, arguments);
|
|
}
|
|
}
|
|
};
|
|
},
|
|
|
|
/**
|
|
* 扩展卸载钩子
|
|
* 在扩展被卸载时清理所有资源
|
|
*/
|
|
async beforeExtensionUnload() {
|
|
promptAssistant.cleanup();
|
|
imageCaption.cleanup();
|
|
}
|
|
});
|
|
|
|
export { EventManager, UIToolkit }; |