fred-dev/comfy_ui_ali
0
1import { app } from "../../scripts/app.js";2import { api } from "../../scripts/api.js"3import { sleep, show_message, customConfirm, customAlert } from "./common.js";4import { GroupNodeConfig, GroupNodeHandler } from "../../extensions/core/groupNode.js";5import { ComfyDialog, $el } from "../../scripts/ui.js";6 7const SEPARATOR = ">"8 9let pack_map = {};10let rpack_map = {};11 12export function getPureName(node) {13 // group nodes/14 let category = null;15 if(node.category) {16 category = node.category.substring(12);17 }18 else {19 category = node.constructor.category?.substring(12);20 }21 if(category) {22 let purename = node.comfyClass.substring(category.length+1);23 return purename;24 }25 else if(node.comfyClass.startsWith('workflow/') || node.comfyClass.startsWith(`workflow${SEPARATOR}`)) {26 return node.comfyClass.substring(9);27 }28 else {29 return node.comfyClass;30 }31}32 33function isValidVersionString(version) {34 const versionPattern = /^(\d+)\.(\d+)(\.(\d+))?$/;35 36 const match = version.match(versionPattern);37 38 return match !== null &&39 parseInt(match[1], 10) >= 0 &&40 parseInt(match[2], 10) >= 0 &&41 (!match[3] || parseInt(match[4], 10) >= 0);42}43 44function register_pack_map(name, data) {45 if(data.packname) {46 pack_map[data.packname] = name;47 rpack_map[name] = data;48 }49 else {50 rpack_map[name] = data;51 }52}53 54function storeGroupNode(name, data, register=true) {55 let extra = app.graph.extra;56 if (!extra) app.graph.extra = extra = {};57 let groupNodes = extra.groupNodes;58 if (!groupNodes) extra.groupNodes = groupNodes = {};59 groupNodes[name] = data;60 61 if(register) {62 register_pack_map(name, data);63 }64}65 66export async function load_components() {67 let data = await api.fetchApi('/manager/component/loads', {method: "POST"});68 let components = await data.json();69 70 let start_time = Date.now();71 let failed = [];72 let failed2 = [];73 74 for(let name in components) {75 if(app.graph.extra?.groupNodes?.[name]) {76 if(data) {77 let data = components[name];78 79 let category = data.packname;80 if(data.category) {81 category += SEPARATOR + data.category;82 }83 if(category == '') {84 category = 'components';85 }86 87 const config = new GroupNodeConfig(name, data);88 await config.registerType(category);89 90 register_pack_map(name, data);91 continue;92 }93 }94 95 let nodeData = components[name];96 97 storeGroupNode(name, nodeData);98 99 const config = new GroupNodeConfig(name, nodeData);100 101 while(true) {102 try {103 let category = nodeData.packname;104 if(nodeData.category) {105 category += SEPARATOR + nodeData.category;106 }107 if(category == '') {108 category = 'components';109 }110 111 await config.registerType(category);112 register_pack_map(name, nodeData);113 break;114 }115 catch {116 let elapsed_time = Date.now() - start_time;117 if (elapsed_time > 5000) {118 failed.push(name);119 break;120 } else {121 await sleep(100);122 }123 }124 }125 }126 127 // fallback1128 for(let i in failed) {129 let name = failed[i];130 131 if(app.graph.extra?.groupNodes?.[name]) {132 continue;133 }134 135 let nodeData = components[name];136 137 storeGroupNode(name, nodeData);138 139 const config = new GroupNodeConfig(name, nodeData);140 while(true) {141 try {142 let category = nodeData.packname;143 if(nodeData.workflow.category) {144 category += SEPARATOR + nodeData.category;145 }146 if(category == '') {147 category = 'components';148 }149 150 await config.registerType(category);151 register_pack_map(name, nodeData);152 break;153 }154 catch {155 let elapsed_time = Date.now() - start_time;156 if (elapsed_time > 10000) {157 failed2.push(name);158 break;159 } else {160 await sleep(100);161 }162 }163 }164 }165 166 // fallback2167 for(let name in failed2) {168 let name = failed2[i];169 170 let nodeData = components[name];171 172 storeGroupNode(name, nodeData);173 174 const config = new GroupNodeConfig(name, nodeData);175 while(true) {176 try {177 let category = nodeData.workflow.packname;178 if(nodeData.workflow.category) {179 category += SEPARATOR + nodeData.category;180 }181 if(category == '') {182 category = 'components';183 }184 185 await config.registerType(category);186 register_pack_map(name, nodeData);187 break;188 }189 catch {190 let elapsed_time = Date.now() - start_time;191 if (elapsed_time > 30000) {192 failed.push(name);193 break;194 } else {195 await sleep(100);196 }197 }198 }199 }200}201 202async function save_as_component(node, version, author, prefix, nodename, packname, category) {203 let component_name = `${prefix}::${nodename}`;204 205 let subgraph = app.graph.extra?.groupNodes?.[component_name];206 if(!subgraph) {207 subgraph = app.graph.extra?.groupNodes?.[getPureName(node)];208 }209 210 subgraph.version = version;211 subgraph.author = author;212 subgraph.datetime = Date.now();213 subgraph.packname = packname;214 subgraph.category = category;215 216 let body =217 {218 name: component_name,219 workflow: subgraph220 };221 222 pack_map[packname] = component_name;223 rpack_map[component_name] = subgraph;224 225 const res = await api.fetchApi('/manager/component/save', {226 method: "POST",227 headers: {228 "Content-Type": "application/json",229 },230 body: JSON.stringify(body),231 });232 233 if(res.status == 200) {234 storeGroupNode(component_name, subgraph);235 const config = new GroupNodeConfig(component_name, subgraph);236 237 let category = body.workflow.packname;238 if(body.workflow.category) {239 category += SEPARATOR + body.workflow.category;240 }241 if(category == '') {242 category = 'components';243 }244 245 await config.registerType(category);246 247 let path = await res.text();248 show_message(`Component '${component_name}' is saved into:\n${path}`);249 }250 else251 show_message(`Failed to save component.`);252}253 254async function import_component(component_name, component, mode) {255 if(mode) {256 let body =257 {258 name: component_name,259 workflow: component260 };261 262 const res = await api.fetchApi('/manager/component/save', {263 method: "POST",264 headers: { "Content-Type": "application/json", },265 body: JSON.stringify(body)266 });267 }268 269 let category = component.packname;270 if(component.category) {271 category += SEPARATOR + component.category;272 }273 if(category == '') {274 category = 'components';275 }276 277 storeGroupNode(component_name, component);278 const config = new GroupNodeConfig(component_name, component);279 await config.registerType(category);280}281 282function restore_to_loaded_component(component_name) {283 if(rpack_map[component_name]) {284 let component = rpack_map[component_name];285 storeGroupNode(component_name, component, false);286 const config = new GroupNodeConfig(component_name, component);287 config.registerType(component.category);288 }289}290 291// Using a timestamp prevents duplicate pastes and ensures the prevention of re-deletion of litegrapheditor_clipboard.292let last_paste_timestamp = null;293 294function versionCompare(v1, v2) {295 let ver1;296 let ver2;297 if(v1 && v1 != '') {298 ver1 = v1.split('.');299 ver1[0] = parseInt(ver1[0]);300 ver1[1] = parseInt(ver1[1]);301 if(ver1.length == 2)302 ver1.push(0);303 else304 ver1[2] = parseInt(ver2[2]);305 }306 else {307 ver1 = [0,0,0];308 }309 310 if(v2 && v2 != '') {311 ver2 = v2.split('.');312 ver2[0] = parseInt(ver2[0]);313 ver2[1] = parseInt(ver2[1]);314 if(ver2.length == 2)315 ver2.push(0);316 else317 ver2[2] = parseInt(ver2[2]);318 }319 else {320 ver2 = [0,0,0];321 }322 323 if(ver1[0] > ver2[0])324 return -1;325 else if(ver1[0] < ver2[0])326 return 1;327 328 if(ver1[1] > ver2[1])329 return -1;330 else if(ver1[1] < ver2[1])331 return 1;332 333 if(ver1[2] > ver2[2])334 return -1;335 else if(ver1[2] < ver2[2])336 return 1;337 338 return 0;339}340 341function checkVersion(name, component) {342 let msg = '';343 if(rpack_map[name]) {344 let old_version = rpack_map[name].version;345 if(!old_version || old_version == '') {346 msg = ` '${name}' Upgrade (V0.0 -> V${component.version})`;347 }348 else {349 let c = versionCompare(old_version, component.version);350 if(c < 0) {351 msg = ` '${name}' Downgrade (V${old_version} -> V${component.version})`;352 }353 else if(c > 0) {354 msg = ` '${name}' Upgrade (V${old_version} -> V${component.version})`;355 }356 else {357 msg = ` '${name}' Same version (V${component.version})`;358 }359 }360 }361 else {362 msg = `'${name}' NEW (V${component.version})`;363 }364 365 return msg;366}367 368async function handle_import_components(components) {369 let msg = 'Components:\n';370 let cnt = 0;371 for(let name in components) {372 let component = components[name];373 let v = checkVersion(name, component);374 375 if(cnt < 10) {376 msg += v + '\n';377 }378 else if (cnt == 10) {379 msg += '...\n';380 }381 else {382 // do nothing383 }384 385 cnt++;386 }387 388 let last_name = null;389 msg += '\nWill you load components?\n';390 const confirmed = await customConfirm(msg);391 if(confirmed) {392 const mode = await customConfirm('\nWill you save components?\n(cancel=load without save)');393 394 for(let name in components) {395 let component = components[name];396 import_component(name, component, mode);397 last_name = name;398 }399 400 if(mode) {401 show_message('Components are saved.');402 }403 else {404 show_message('Components are loaded.');405 }406 }407 408 if(cnt == 1 && last_name) {409 const node = LiteGraph.createNode(`workflow${SEPARATOR}${last_name}`);410 node.pos = [app.canvas.graph_mouse[0], app.canvas.graph_mouse[1]];411 app.canvas.graph.add(node, false);412 }413}414 415async function handlePaste(e) {416 let data = (e.clipboardData || window.clipboardData);417 const items = data.items;418 for(const item of items) {419 if(item.kind == 'string' && item.type == 'text/plain') {420 data = data.getData("text/plain");421 try {422 let json_data = JSON.parse(data);423 if(json_data.kind == 'ComfyUI Components' && last_paste_timestamp != json_data.timestamp) {424 last_paste_timestamp = json_data.timestamp;425 await handle_import_components(json_data.components);426 427 // disable paste node428 localStorage.removeItem("litegrapheditor_clipboard", null);429 }430 else {431 console.log('This components are already pasted: ignored');432 }433 }434 catch {435 // nothing to do436 }437 }438 }439}440 441document.addEventListener("paste", handlePaste);442 443 444export class ComponentBuilderDialog extends ComfyDialog {445 constructor() {446 super();447 }448 449 clear() {450 while (this.element.children.length) {451 this.element.removeChild(this.element.children[0]);452 }453 }454 455 show() {456 this.invalidateControl();457 458 this.element.style.display = "block";459 this.element.style.zIndex = 1099;460 this.element.style.width = "500px";461 this.element.style.height = "480px";462 }463 464 invalidateControl() {465 this.clear();466 467 let self = this;468 469 const close_button = $el("button", { id: "cm-close-button", type: "button", textContent: "Close", onclick: () => self.close() });470 this.save_button = $el("button",471 { id: "cm-save-button", type: "button", textContent: "Save", onclick: () =>472 {473 save_as_component(self.target_node, self.version_string.value.trim(), self.author.value.trim(), self.node_prefix.value.trim(),474 self.getNodeName(), self.getPackName(), self.category.value.trim());475 }476 });477 478 let default_nodename = getPureName(this.target_node).trim();479 480 let groupNode = app.graph.extra.groupNodes[default_nodename];481 let default_packname = groupNode.packname;482 if(!default_packname) {483 default_packname = '';484 }485 486 let default_category = groupNode.category;487 if(!default_category) {488 default_category = '';489 }490 491 this.default_ver = groupNode.version;492 if(!this.default_ver) {493 this.default_ver = '0.0';494 }495 496 let default_author = groupNode.author;497 if(!default_author) {498 default_author = '';499 }500 501 let delimiterIndex = default_nodename.indexOf('::');502 let default_prefix = "";503 if(delimiterIndex != -1) {504 default_prefix = default_nodename.substring(0, delimiterIndex);505 default_nodename = default_nodename.substring(delimiterIndex + 2);506 }507 508 if(!default_prefix) {509 this.save_button.disabled = true;510 }511 512 this.pack_list = this.createPackListCombo();513 514 let version_string = this.createLabeledInput('input version (e.g. 1.0)', '*Version : ', this.default_ver);515 this.version_string = version_string[1];516 this.version_string.disabled = true;517 518 let author = this.createLabeledInput('input author (e.g. Dr.Lt.Data)', 'Author : ', default_author);519 this.author = author[1];520 521 let node_prefix = this.createLabeledInput('input node prefix (e.g. mypack)', '*Prefix : ', default_prefix);522 this.node_prefix = node_prefix[1];523 524 let manual_nodename = this.createLabeledInput('input node name (e.g. MAKE_BASIC_PIPE)', 'Nodename : ', default_nodename);525 this.manual_nodename = manual_nodename[1];526 527 let manual_packname = this.createLabeledInput('input pack name (e.g. mypack)', 'Packname : ', default_packname);528 this.manual_packname = manual_packname[1];529 530 let category = this.createLabeledInput('input category (e.g. util/pipe)', 'Category : ', default_category);531 this.category = category[1];532 533 this.node_label = this.createNodeLabel();534 535 let author_mode = this.createAuthorModeCheck();536 this.author_mode = author_mode[0];537 538 const content =539 $el("div.comfy-modal-content",540 [541 $el("tr.cm-title", {}, [542 $el("font", {size:6, color:"white"}, [`ComfyUI-Manager: Component Builder`])]543 ),544 $el("br", {}, []),545 $el("div.cm-menu-container",546 [547 author_mode[0],548 author_mode[1],549 category[0],550 author[0],551 node_prefix[0],552 manual_nodename[0],553 manual_packname[0],554 version_string[0],555 this.pack_list,556 $el("br", {}, []),557 this.node_label558 ]),559 560 $el("br", {}, []),561 this.save_button,562 close_button,563 ]564 );565 566 content.style.width = '100%';567 content.style.height = '100%';568 569 this.element = $el("div.comfy-modal", { id:'cm-manager-dialog', parent: document.body }, [ content ]);570 }571 572 validateInput() {573 let msg = "";574 575 if(!isValidVersionString(this.version_string.value)) {576 msg += 'Invalid version string: '+event.value+"\n";577 }578 579 if(this.node_prefix.value.trim() == '') {580 msg += 'Node prefix cannot be empty\n';581 }582 583 if(this.manual_nodename.value.trim() == '') {584 msg += 'Node name cannot be empty\n';585 }586 587 if(msg != '') {588// alert(msg);589 }590 591 this.save_button.disabled = msg != "";592 }593 594 getPackName() {595 if(this.pack_list.selectedIndex == 0) {596 return this.manual_packname.value.trim();597 }598 599 return this.pack_list.value.trim();600 }601 602 getNodeName() {603 if(this.manual_nodename.value.trim() != '') {604 return this.manual_nodename.value.trim();605 }606 607 return getPureName(this.target_node);608 }609 610 createAuthorModeCheck() {611 let check = $el("input",{type:'checkbox', id:"author-mode"},[])612 const check_label = $el("label",{for:"author-mode"},["Enable author mode"]);613 check_label.style.color = "var(--fg-color)";614 check_label.style.cursor = "pointer";615 check.checked = false;616 617 let self = this;618 check.onchange = () => {619 self.version_string.disabled = !check.checked;620 621 if(!check.checked) {622 self.version_string.value = self.default_ver;623 }624 else {625 customAlert('If you are not the author, it is not recommended to change the version, as it may cause component update issues.');626 }627 };628 629 return [check, check_label];630 }631 632 createNodeLabel() {633 let label = $el('p');634 label.className = 'cb-node-label';635 if(this.target_node.comfyClass.includes('::'))636 label.textContent = getPureName(this.target_node);637 else638 label.textContent = " _::" + getPureName(this.target_node);639 return label;640 }641 642 createLabeledInput(placeholder, label, value) {643 let textbox = $el('input.cb-widget-input', {type:'text', placeholder:placeholder, value:value}, []);644 645 let self = this;646 textbox.onchange = () => {647 this.validateInput.call(self);648 this.node_label.textContent = this.node_prefix.value + "::" + this.manual_nodename.value;649 }650 let row = $el('span.cb-widget', {}, [ $el('span.cb-widget-input-label', label), textbox]);651 652 return [row, textbox];653 }654 655 createPackListCombo() {656 let combo = document.createElement("select");657 combo.className = "cb-widget";658 let default_packname_option = { value: '##manual', text: 'Packname: Manual' };659 660 combo.appendChild($el('option', default_packname_option, []));661 for(let name in pack_map) {662 combo.appendChild($el('option', { value: name, text: 'Packname: '+ name }, []));663 }664 665 let self = this;666 combo.onchange = function () {667 if(combo.selectedIndex == 0) {668 self.manual_packname.disabled = false;669 }670 else {671 self.manual_packname.disabled = true;672 }673 };674 675 return combo;676 }677}678 679let orig_handleFile = app.handleFile;680 681async function handleFile(file) {682 if (file.name?.endsWith(".json") || file.name?.endsWith(".pack")) {683 const reader = new FileReader();684 reader.onload = async () => {685 let is_component = false;686 const jsonContent = JSON.parse(reader.result);687 for(let name in jsonContent) {688 let cand = jsonContent[name];689 is_component = cand.datetime && cand.version;690 break;691 }692 693 if(is_component) {694 await handle_import_components(jsonContent);695 }696 else {697 orig_handleFile.call(app, file);698 }699 };700 reader.readAsText(file);701 702 return;703 }704 705 orig_handleFile.call(app, file);706}707 708app.handleFile = handleFile;709 710let current_component_policy = 'workflow';711try {712 api.fetchApi('/manager/policy/component')713 .then(response => response.text())714 .then(data => { current_component_policy = data; });715}716catch {}717 718function getChangedVersion(groupNodes) {719 if(!Object.keys(pack_map).length || !groupNodes)720 return null;721 722 let res = {};723 for(let component_name in groupNodes) {724 let data = groupNodes[component_name];725 726 if(rpack_map[component_name]) {727 let v = versionCompare(data.version, rpack_map[component_name].version);728 res[component_name] = v;729 }730 }731 732 return res;733}734 735const loadGraphData = app.loadGraphData;736app.loadGraphData = async function () {737 if(arguments.length == 0)738 return await loadGraphData.apply(this, arguments);739 740 let graphData = arguments[0];741 let groupNodes = graphData.extra?.groupNodes;742 let res = getChangedVersion(groupNodes);743 744 if(res) {745 let target_components = null;746 switch(current_component_policy) {747 case 'higher':748 target_components = Object.keys(res).filter(key => res[key] == 1);749 break;750 751 case 'mine':752 target_components = Object.keys(res);753 break;754 755 default:756 // do nothing757 }758 759 if(target_components) {760 for(let i in target_components) {761 let component_name = target_components[i];762 let component = rpack_map[component_name];763 if(component && graphData.extra?.groupNodes) {764 graphData.extra.groupNodes[component_name] = component;765 }766 }767 }768 }769 else {770 console.log('Empty components: policy ignored');771 }772 773 arguments[0] = graphData;774 return await loadGraphData.apply(this, arguments);775};776 777export function set_component_policy(v) {778 current_component_policy = v;779}780 781let graphToPrompt = app.graphToPrompt;782app.graphToPrompt = async function () {783 let p = await graphToPrompt.call(app);784 try {785 let groupNodes = p.workflow.extra?.groupNodes;786 if(groupNodes) {787 p.workflow.extra = { ... p.workflow.extra};788 789 // get used group nodes790 let used_group_nodes = new Set();791 for(let node of p.workflow.nodes) {792 if(node.type.startsWith(`workflow/`) || node.type.startsWith(`workflow${SEPARATOR}`)) {793 used_group_nodes.add(node.type.substring(9));794 }795 }796 797 // remove unused group nodes798 let new_groupNodes = {};799 for (let key in p.workflow.extra.groupNodes) {800 if (used_group_nodes.has(key)) {801 new_groupNodes[key] = p.workflow.extra.groupNodes[key];802 }803 }804 p.workflow.extra.groupNodes = new_groupNodes;805 }806 }807 catch(e) {808 console.log(`Failed to filtering group nodes: ${e}`);809 }810 811 return p;812}813 