diff --git a/README.md b/README.md index 88ac8c3..ea8c146 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,21 @@ 针对api接口开发补充的一些自定义节点和功能。 转成base64的节点都是输出节点,websocket消息中会包含base64Images和base64Type属性(具体格式请查看ImageNode.py中的ImageToBase64Advanced类源代码,或者自己搭建简单流程运行在浏览器开发者工具-->网络中查看) +## 安装 +- 通过ComfyUI-Manager安装 +- 在ComfyUI安装目录根目录下打开命令行终端,执行以下命令 + ```sh + cd custom_nodes + git clone https://github.com/lldacing/comfyui-easyapi-nodes.git + cd comfyui-easyapi-nodes + pip install -r requirements.txt + ``` +## 升级 +- 在ComfyUI安装目录根目录下打开命令行终端,执行以下命令 + ```sh + cd custom_nodes/comfyui-easyapi-nodes + git pull + ``` ## 节点 | 名称 | 说明 | @@ -32,19 +47,36 @@ Tips: base64格式字符串比较长,会导致界面卡顿,接口请求带 ## 功能 - 扩展Save(Api Format)菜单。 - - 支持保存api格式workflow时,把LoadImage替换成Base64ToImage节点,把PreviewImage和SaveImage替换成ImageToBase64节点 + - 复制工作流 + - 复制/保存api格式工作流(需打开配置Settings->Enable Dev mode Options) + - Save as / Copy Api + + 保存/复制api格式workflow + - Copy EasyAi as / Copy EasyAi + + 保存/复制api格式workflow。把LoadImage替换成Base64ToImage节点,把PreviewImage和SaveImage替换成ImageToBase64节点 ![save api extended](docs/menu.gif) -- Setting中支持历史记录最大条数修改 +- Settings配置扩展 - 图片使用base64时,数据存在内存中,默认最大历史记录条数是10000,为防止内存溢出,所以新增此配置项。 + ![save api extended](docs/settings.png) + - 保留历史记录最大条数 - 配置路径:Setting -> Maximum History Size + 配置路径:Settings -> [EasyApi] Maximum History Size + + Tips: 图片使用base64时,数据存在内存中,默认最大历史记录条数是10000,为防止内存溢出,所以新增此配置项。 + + - 是否自动展开当前菜单下的子菜单 + 配置路径:Settings -> [EasyApi] Auto Open Sub Menu + ![save api extended](docs/menu_autoopen.gif) + - 菜单扩展 - - 重设某个节点的id + - 重设某个节点的id(Node Context Menu) - ![save api extended](docs/node_context_menu_set_id.png) - - 从序号1开始重新设置所有节点的id + ![save api extended](docs/node_context_menu_set_id.png) + - 从序号1开始重新设置所有节点的id(Canvas Context Menu) - ![save api extended](docs/context_menu_reset_all_id.png) + ![save api extended](docs/context_menu_reset_all_id.png) + - 定位到与当前节点有连接线的节点(Node Context Menu) + + ![save api extended](docs/node_context_menu_link_node.png) \ No newline at end of file diff --git a/docs/menu.gif b/docs/menu.gif index 93bc32c..839dd1c 100644 Binary files a/docs/menu.gif and b/docs/menu.gif differ diff --git a/docs/menu_autoopen.gif b/docs/menu_autoopen.gif new file mode 100644 index 0000000..7b9d0c2 Binary files /dev/null and b/docs/menu_autoopen.gif differ diff --git a/docs/node_context_menu_link_node.png b/docs/node_context_menu_link_node.png new file mode 100644 index 0000000..9c12d8e Binary files /dev/null and b/docs/node_context_menu_link_node.png differ diff --git a/docs/settings.png b/docs/settings.png new file mode 100644 index 0000000..4609f4a Binary files /dev/null and b/docs/settings.png differ diff --git a/static/js/easyapi.js b/static/js/easyapi.js index 678edf9..63f1f85 100644 --- a/static/js/easyapi.js +++ b/static/js/easyapi.js @@ -17,9 +17,9 @@ app.registerExtension({ maxSize: n }) }), 1000, false) - const setting = app.ui.settings.addSetting({ - id: name, - name: "Maximum History Size", + const setting = app.ui.settings.addSetting({ + id: "Easyapi.SizeOfHistory", + name: "[EasyApi] Maximum History Size", defaultValue: max, type: "slider", attrs: { @@ -31,5 +31,27 @@ app.registerExtension({ changeFun.apply(null, [newVal, oldVal]) } }); + const ctxMenu = LiteGraph.ContextMenu; + const replace = () => { + LiteGraph.ContextMenu = function (values, options) { + options = options || {}; + options.autoopen = true; + return ctxMenu.call(this, values, options); + }; + LiteGraph.ContextMenu.prototype = ctxMenu.prototype; + }; + app.ui.settings.addSetting({ + id: "Easyapi.ContextMenu.autoopen", + name: "[EasyApi] Auto Open Sub Menu", + type: "boolean", + defaultValue: false, + onChange(value) { + if (value) { + replace(value); + } else { + LiteGraph.ContextMenu = ctxMenu; + } + }, + }); } }) \ No newline at end of file diff --git a/static/js/workflows.js b/static/js/workflows.js index 95ce44b..44d61ee 100644 --- a/static/js/workflows.js +++ b/static/js/workflows.js @@ -196,7 +196,7 @@ class EasyApiWorkflows { title: "Copy workflow", callback: async () => { app.graphToPrompt().then(p => { - const json = JSON.stringify(app.graph.serialize(), null, null); // convert the data to a JSON string + const json = JSON.stringify(p.workflow, null, null); // convert the data to a JSON string copyToClipboard(json); showTip("Copied") }); @@ -218,6 +218,16 @@ class EasyApiWorkflows { } registerContextMenu() { const that = this; + /*const ctxMenu = LiteGraph.ContextMenu; + LiteGraph.ContextMenu = function (values, options) { + options = options || {}; + options.autoopen = true; + const ctx = ctxMenu.call(this, values, options); + return ctx; + } + LiteGraph.ContextMenu.prototype = ctxMenu.prototype;*/ + + const orig = LGraphCanvas.prototype.getCanvasMenuOptions; function showNodeIdSettingDialog(menu, nodes) { @@ -514,6 +524,8 @@ class EasyApiWorkflows { // add separator options.splice(options.length - 1, 0, null); } + let hasInputLink = node.inputs?.filter(input => input?.link).length > 0; + let hasOutputLink = node.outputs?.filter(output => output?.links).length > 0; options.splice(options.length - 1, 0, { content: "Set Node Id (EasyApi)", @@ -669,6 +681,70 @@ class EasyApiWorkflows { }); } }, + { + content: "Go To Link Node (EasyApi)", + disabled: !hasInputLink && !hasOutputLink, + has_submenu: hasInputLink || hasOutputLink, + submenu: { + options: [ + { + content: "Input", + disabled: !hasInputLink, + has_submenu: hasInputLink, + submenu: { + options: node.inputs?.filter(input => input?.link) + .map((input) => { + let linkId = input.link; + let llink = node.graph.links[linkId]; + let inputNodeId = llink.origin_id; + let inputNode = node.graph._nodes_by_id[inputNodeId] + return { + content: `${input.name} - ${input.type}`, + has_submenu: true, + submenu: { + options: [{ + content: `${inputNode.getOutputInfo(llink.origin_slot)?.name} - ${inputNode.getTitle()} - #${inputNode.id} (${inputNode.pos[0]}, ${inputNode.pos[1]})`, + callback: (item, options, e, menu, extra) => { + app.canvas.centerOnNode(inputNode); + app.canvas.selectNode(inputNode, false); + } + }] + } + } + }) + } + }, + { + content: "Output", + disabled: !hasOutputLink, + has_submenu: hasOutputLink, + submenu: { + options: node.outputs?.filter(output => output?.links) + .map((output) => { + return { + content: `${output.name} - ${output.type}`, + has_submenu: true, + submenu: { + options: output.links.map((linkId) => { + let llink = node.graph.links[linkId]; + let outputNodeId = llink.target_id; + let outputNode = node.graph._nodes_by_id[outputNodeId]; + return { + content: `${outputNode.getInputInfo(llink.target_slot)?.name} - ${outputNode.getTitle()} - #${outputNode.id} (${outputNode.pos[0]}, ${outputNode.pos[1]})`, + callback: (item, options, e, menu, extra) => { + app.canvas.centerOnNode(outputNode); + app.canvas.selectNode(outputNode, false); + } + }; + }) + } + } + }) + } + } + ] + } + }, null ); }