Some checks failed
Python Linting / Run Ruff (push) Has been cancelled
Python Linting / Run Pylint (push) Has been cancelled
Full Comfy CI Workflow Runs / test-stable (12.1, , linux, 3.10, [self-hosted Linux], stable) (push) Has been cancelled
Full Comfy CI Workflow Runs / test-stable (12.1, , linux, 3.11, [self-hosted Linux], stable) (push) Has been cancelled
Full Comfy CI Workflow Runs / test-stable (12.1, , linux, 3.12, [self-hosted Linux], stable) (push) Has been cancelled
Full Comfy CI Workflow Runs / test-unix-nightly (12.1, , linux, 3.11, [self-hosted Linux], nightly) (push) Has been cancelled
Execution Tests / test (macos-latest) (push) Has been cancelled
Execution Tests / test (ubuntu-latest) (push) Has been cancelled
Execution Tests / test (windows-latest) (push) Has been cancelled
Test server launches without errors / test (push) Has been cancelled
Unit Tests / test (macos-latest) (push) Has been cancelled
Unit Tests / test (ubuntu-latest) (push) Has been cancelled
Unit Tests / test (windows-2022) (push) Has been cancelled
Includes 30 custom nodes committed directly, 7 Civitai-exclusive loras stored via Git LFS, and a setup script that installs all dependencies and downloads HuggingFace-hosted models on vast.ai. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
250 lines
8.7 KiB
JavaScript
250 lines
8.7 KiB
JavaScript
// 1.0.2
|
||
import { app } from "../../../../scripts/app.js";
|
||
import { GroupNodeConfig } from "../../../../extensions/core/groupNode.js";
|
||
import { api } from "../../../../scripts/api.js";
|
||
import { $t } from "../common/i18n.js"
|
||
|
||
const nodeTemplateShortcutId = "Comfy.EasyUse.NodeTemplateShortcut"
|
||
const processBarId = "Comfy.EasyUse.queueProcessBar"
|
||
|
||
let enableNodeTemplateShortcut = true
|
||
let enableQueueProcess = false
|
||
|
||
export function addNodeTemplateShortcutSetting(app) {
|
||
app.ui.settings.addSetting({
|
||
id: nodeTemplateShortcutId,
|
||
name: $t("Enable ALT+1~9 to paste nodes from nodes template (ComfyUI-Easy-Use)"),
|
||
type: "boolean",
|
||
defaultValue: enableNodeTemplateShortcut,
|
||
onChange(value) {
|
||
enableNodeTemplateShortcut = !!value;
|
||
},
|
||
});
|
||
}
|
||
export function addQueueProcessSetting(app) {
|
||
app.ui.settings.addSetting({
|
||
id: processBarId,
|
||
name: $t("Enable process bar in queue button (ComfyUI-Easy-Use)"),
|
||
type: "boolean",
|
||
defaultValue: enableQueueProcess,
|
||
onChange(value) {
|
||
enableQueueProcess = !!value;
|
||
},
|
||
});
|
||
}
|
||
const getEnableNodeTemplateShortcut = _ => app.ui.settings.getSettingValue(nodeTemplateShortcutId, true)
|
||
const getQueueProcessSetting = _ => app.ui.settings.getSettingValue(processBarId, false)
|
||
|
||
function loadTemplate(){
|
||
return localStorage['Comfy.NodeTemplates'] ? JSON.parse(localStorage['Comfy.NodeTemplates']) : null
|
||
}
|
||
const clipboardAction = async (cb) => {
|
||
const old = localStorage.getItem("litegrapheditor_clipboard");
|
||
await cb();
|
||
localStorage.setItem("litegrapheditor_clipboard", old);
|
||
};
|
||
async function addTemplateToCanvas(t){
|
||
const data = JSON.parse(t.data);
|
||
await GroupNodeConfig.registerFromWorkflow(data.groupNodes, {});
|
||
localStorage.setItem("litegrapheditor_clipboard", t.data);
|
||
app.canvas.pasteFromClipboard();
|
||
}
|
||
|
||
app.registerExtension({
|
||
name: 'comfy.easyUse.quick',
|
||
init() {
|
||
const keybindListener = async function (event) {
|
||
let modifierPressed = event.altKey;
|
||
const isEnabled = getEnableNodeTemplateShortcut()
|
||
if(isEnabled){
|
||
const mac_alt_nums = ['¡','™','£','¢','∞','§','¶','•','ª']
|
||
const nums = ['1','2','3','4','5','6','7','8','9']
|
||
let key = event.key
|
||
if(mac_alt_nums.includes(key)){
|
||
const idx = mac_alt_nums.findIndex(cate=> cate == key)
|
||
key = nums[idx]
|
||
modifierPressed = true
|
||
}
|
||
if(['1','2','3','4','5','6','7','8','9'].includes(key) && modifierPressed) {
|
||
const template = loadTemplate()
|
||
const idx = parseInt(key) - 1
|
||
if (template && template[idx]) {
|
||
let t = template[idx]
|
||
try{
|
||
let data = JSON.parse(t.data)
|
||
data.title = t.name
|
||
t.data = JSON.stringify(data)
|
||
clipboardAction(_ => {
|
||
addTemplateToCanvas(t)
|
||
})
|
||
}catch (e){
|
||
console.error(e)
|
||
}
|
||
|
||
}
|
||
if (event.ctrlKey || event.altKey || event.metaKey) {
|
||
return;
|
||
}
|
||
}
|
||
}
|
||
|
||
}
|
||
window.addEventListener("keydown", keybindListener, true);
|
||
},
|
||
|
||
setup(app) {
|
||
addNodeTemplateShortcutSetting(app)
|
||
addQueueProcessSetting(app)
|
||
registerListeners()
|
||
}
|
||
});
|
||
|
||
const registerListeners = () => {
|
||
const queue_button = document.getElementById("queue-button")
|
||
const old_queue_button_text = queue_button.innerText
|
||
api.addEventListener('progress', ({
|
||
detail,
|
||
}) => {
|
||
const isEnabled = getQueueProcessSetting()
|
||
if(isEnabled){
|
||
const {
|
||
value, max, node,
|
||
} = detail;
|
||
const progress = Math.floor((value / max) * 100);
|
||
// console.log(progress)
|
||
if (!isNaN(progress) && progress >= 0 && progress <= 100) {
|
||
queue_button.innerText = progress ==0 || progress == 100 ? old_queue_button_text : "ㅤ "
|
||
const width = progress ==0 || progress == 100 ? '0%' : progress.toString() + '%'
|
||
let bar = document.createElement("div")
|
||
queue_button.setAttribute('data-attr', progress ==0 || progress == 100 ? "" : progress.toString() + '%')
|
||
document.documentElement.style.setProperty('--process-bar-width', width)
|
||
}
|
||
}
|
||
|
||
}, false);
|
||
|
||
api.addEventListener('status', ({
|
||
detail,
|
||
}) => {
|
||
const queueRemaining = detail?.exec_info.queue_remaining;
|
||
if(queueRemaining === 0){
|
||
let queue_button = document.getElementById("queue-button")
|
||
queue_button.innerText = old_queue_button_text
|
||
queue_button.setAttribute('data-attr', "")
|
||
document.documentElement.style.setProperty('--process-bar-width', '0%')
|
||
}
|
||
}, false);
|
||
};
|
||
|
||
|
||
// 修改粘贴指令
|
||
LGraphCanvas.prototype.pasteFromClipboard = function(isConnectUnselected = false) {
|
||
// if ctrl + shift + v is off, return when isConnectUnselected is true (shift is pressed) to maintain old behavior
|
||
if (!LiteGraph.ctrl_shift_v_paste_connect_unselected_outputs && isConnectUnselected) {
|
||
return;
|
||
}
|
||
var data = localStorage.getItem("litegrapheditor_clipboard");
|
||
if (!data) {
|
||
return;
|
||
}
|
||
|
||
this.graph.beforeChange();
|
||
|
||
//create nodes
|
||
var clipboard_info = JSON.parse(data);
|
||
// calculate top-left node, could work without this processing but using diff with last node pos :: clipboard_info.nodes[clipboard_info.nodes.length-1].pos
|
||
var posMin = false;
|
||
var posMinIndexes = false;
|
||
for (var i = 0; i < clipboard_info.nodes.length; ++i) {
|
||
if (posMin){
|
||
if(posMin[0]>clipboard_info.nodes[i].pos[0]){
|
||
posMin[0] = clipboard_info.nodes[i].pos[0];
|
||
posMinIndexes[0] = i;
|
||
}
|
||
if(posMin[1]>clipboard_info.nodes[i].pos[1]){
|
||
posMin[1] = clipboard_info.nodes[i].pos[1];
|
||
posMinIndexes[1] = i;
|
||
}
|
||
}
|
||
else{
|
||
posMin = [clipboard_info.nodes[i].pos[0], clipboard_info.nodes[i].pos[1]];
|
||
posMinIndexes = [i, i];
|
||
}
|
||
}
|
||
var nodes = [];
|
||
var left_arr = [], right_arr = [], top_arr =[], bottom_arr =[];
|
||
|
||
for (var i = 0; i < clipboard_info.nodes.length; ++i) {
|
||
var node_data = clipboard_info.nodes[i];
|
||
var node = LiteGraph.createNode(node_data.type);
|
||
if (node) {
|
||
|
||
node.configure(node_data);
|
||
//paste in last known mouse position
|
||
node.pos[0] += this.graph_mouse[0] - posMin[0]; //+= 5;
|
||
node.pos[1] += this.graph_mouse[1] - posMin[1]; //+= 5;
|
||
|
||
left_arr.push(node.pos[0])
|
||
right_arr.push(node.pos[0] + node.size[0])
|
||
top_arr.push(node.pos[1])
|
||
bottom_arr.push(node.pos[1] + node.size[1])
|
||
|
||
this.graph.add(node,{doProcessChange:false});
|
||
|
||
nodes.push(node);
|
||
|
||
}
|
||
}
|
||
|
||
if(clipboard_info.title){
|
||
var l = Math.min(...left_arr) - 15;
|
||
var r = Math.max(...right_arr) - this.graph_mouse[0] + 30;
|
||
var t = Math.min(...top_arr) - 100;
|
||
var b = Math.max(...bottom_arr) - this.graph_mouse[1] + 130;
|
||
|
||
// create group
|
||
const groups = [
|
||
{
|
||
"title": clipboard_info.title,
|
||
"bounding": [
|
||
l,
|
||
t,
|
||
r,
|
||
b
|
||
],
|
||
"color": "#3f789e",
|
||
"font_size": 24,
|
||
"locked": false
|
||
}
|
||
]
|
||
|
||
for (var i = 0; i < groups.length; ++i) {
|
||
var group = new LiteGraph.LGraphGroup();
|
||
group.configure(groups[i]);
|
||
this.graph.add(group);
|
||
}
|
||
}
|
||
|
||
//create links
|
||
for (var i = 0; i < clipboard_info.links.length; ++i) {
|
||
var link_info = clipboard_info.links[i];
|
||
var origin_node;
|
||
var origin_node_relative_id = link_info[0];
|
||
if (origin_node_relative_id != null) {
|
||
origin_node = nodes[origin_node_relative_id];
|
||
} else if (LiteGraph.ctrl_shift_v_paste_connect_unselected_outputs && isConnectUnselected) {
|
||
var origin_node_id = link_info[4];
|
||
if (origin_node_id) {
|
||
origin_node = this.graph.getNodeById(origin_node_id);
|
||
}
|
||
}
|
||
var target_node = nodes[link_info[2]];
|
||
if( origin_node && target_node )
|
||
origin_node.connect(link_info[1], target_node, link_info[3]);
|
||
else
|
||
console.warn("Warning, nodes missing on pasting");
|
||
}
|
||
|
||
this.selectNodes(nodes);
|
||
this.graph.afterChange();
|
||
}; |