CoolFace
Apppublic

fred-dev/comfy_ui_ali

sourceHugging Facemitupdated 1y agoView on Hugging Face
0likes
components-manager.js813 linesDownload Raw Back to js
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