import { api } from "../../scripts/api.js"; import { app } from "../../scripts/app.js"; import { $el, ComfyDialog } from "../../scripts/ui.js"; import { SUPPORTED_OUTPUT_NODE_TYPES, ShareDialog, ShareDialogChooser, getPotentialOutputsAndOutputNodes, showOpenArtShareDialog, showShareDialog, showYouMLShareDialog } from "./comfyui-share-common.js"; import { OpenArtShareDialog } from "./comfyui-share-openart.js"; import { free_models, install_pip, install_via_git_url, manager_instance, rebootAPI, setManagerInstance, show_message } from "./common.js"; import { ComponentBuilderDialog, getPureName, load_components, set_component_policy } from "./components-manager.js"; import { CustomNodesManager } from "./custom-nodes-manager.js"; import { ModelManager } from "./model-manager.js"; import { set_double_click_policy } from "./node_fixer.js"; import { SnapshotManager } from "./snapshot.js"; var docStyle = document.createElement('style'); docStyle.innerHTML = ` .comfy-toast { position: fixed; bottom: 20px; left: 50%; transform: translateX(-50%); background-color: rgba(0, 0, 0, 0.7); color: white; padding: 10px 20px; border-radius: 5px; z-index: 1000; transition: opacity 0.5s; } .comfy-toast-fadeout { opacity: 0; } #cm-manager-dialog { width: 1000px; height: 520px; box-sizing: content-box; z-index: 10000; overflow-y: auto; } .cb-widget { width: 400px; height: 25px; box-sizing: border-box; z-index: 10000; margin-top: 10px; margin-bottom: 5px; } .cb-widget-input { width: 305px; height: 25px; box-sizing: border-box; } .cb-widget-input:disabled { background-color: #444444; color: white; } .cb-widget-input-label { width: 90px; height: 25px; box-sizing: border-box; color: white; text-align: right; display: inline-block; margin-right: 5px; } .cm-menu-container { column-gap: 20px; display: flex; flex-wrap: wrap; justify-content: center; box-sizing: content-box; } .cm-menu-column { display: flex; flex-direction: column; flex: 1 1 auto; width: 300px; box-sizing: content-box; } .cm-title { background-color: black; text-align: center; height: 40px; width: calc(100% - 10px); font-weight: bold; justify-content: center; align-content: center; vertical-align: middle; } #cm-channel-badge { color: white; background-color: #AA0000; width: 220px; height: 23px; font-size: 13px; border-radius: 5px; left: 5px; top: 5px; align-content: center; justify-content: center; text-align: center; font-weight: bold; float: left; vertical-align: middle; position: relative; } #custom-nodes-grid a { color: #5555FF; font-weight: bold; text-decoration: none; } #custom-nodes-grid a:hover { color: #7777FF; text-decoration: underline; } #external-models-grid a { color: #5555FF; font-weight: bold; text-decoration: none; } #external-models-grid a:hover { color: #7777FF; text-decoration: underline; } #alternatives-grid a { color: #5555FF; font-weight: bold; text-decoration: none; } #alternatives-grid a:hover { color: #7777FF; text-decoration: underline; } .cm-notice-board { width: 290px; height: 270px; overflow: auto; color: var(--input-text); border: 1px solid var(--descrip-text); padding: 5px 10px; overflow-x: hidden; box-sizing: content-box; } .cm-notice-board > ul { display: block; list-style-type: disc; margin-block-start: 1em; margin-block-end: 1em; margin-inline-start: 0px; margin-inline-end: 0px; padding-inline-start: 40px; } .cm-conflicted-nodes-text { background-color: #CCCC55 !important; color: #AA3333 !important; font-size: 10px; border-radius: 5px; padding: 10px; } .cm-warn-note { background-color: #101010 !important; color: #FF3800 !important; font-size: 13px; border-radius: 5px; padding: 10px; overflow-x: hidden; overflow: auto; } .cm-info-note { background-color: #101010 !important; color: #FF3800 !important; font-size: 13px; border-radius: 5px; padding: 10px; overflow-x: hidden; overflow: auto; } `; function is_legacy_front() { let compareVersion = '1.2.49'; try { const frontendVersion = window['__COMFYUI_FRONTEND_VERSION__']; if (typeof frontendVersion !== 'string') { return false; } function parseVersion(versionString) { const parts = versionString.split('.').map(Number); return parts.length === 3 && parts.every(part => !isNaN(part)) ? parts : null; } const currentVersion = parseVersion(frontendVersion); const comparisonVersion = parseVersion(compareVersion); if (!currentVersion || !comparisonVersion) { return false; } for (let i = 0; i < 3; i++) { if (currentVersion[i] > comparisonVersion[i]) { return false; } else if (currentVersion[i] < comparisonVersion[i]) { return true; } } return false; } catch { return true; } } document.head.appendChild(docStyle); var update_comfyui_button = null; var fetch_updates_button = null; var update_all_button = null; var badge_mode = "none"; let share_option = 'all'; // copied style from https://github.com/pythongosssss/ComfyUI-Custom-Scripts const style = ` #workflowgallery-button { width: 310px; height: 27px; padding: 0px !important; position: relative; overflow: hidden; font-size: 17px !important; } #cm-nodeinfo-button { width: 310px; height: 27px; padding: 0px !important; position: relative; overflow: hidden; font-size: 17px !important; } #cm-manual-button { width: 310px; height: 27px; position: relative; overflow: hidden; } .cm-button { width: 310px; height: 30px; position: relative; overflow: hidden; font-size: 17px !important; } .cm-button-red { width: 310px; height: 30px; position: relative; overflow: hidden; font-size: 17px !important; background-color: #500000 !important; color: white !important; } .cm-experimental-button { width: 290px; height: 30px; position: relative; overflow: hidden; font-size: 17px !important; } .cm-experimental { width: 310px; border: 1px solid #555; border-radius: 5px; padding: 10px; align-items: center; text-align: center; justify-content: center; box-sizing: border-box; } .cm-experimental-legend { margin-top: -20px; margin-left: 50%; width:auto; height:20px; font-size: 13px; font-weight: bold; background-color: #990000; color: #CCFFFF; border-radius: 5px; text-align: center; transform: translateX(-50%); display: block; } .cm-menu-combo { cursor: pointer; width: 310px; box-sizing: border-box; } .cm-small-button { width: 120px; height: 30px; position: relative; overflow: hidden; box-sizing: border-box; font-size: 17px !important; } #cm-install-customnodes-button { width: 200px; height: 30px; position: relative; overflow: hidden; box-sizing: border-box; font-size: 17px !important; } .cm-search-filter { width: 200px; height: 30px !important; position: relative; overflow: hidden; box-sizing: border-box; } .cb-node-label { width: 400px; height:28px; color: black; background-color: #777777; font-size: 18px; text-align: center; font-weight: bold; } #cm-close-button { width: calc(100% - 65px); bottom: 10px; position: absolute; overflow: hidden; } #cm-save-button { width: calc(100% - 65px); bottom:40px; position: absolute; overflow: hidden; } #cm-save-button:disabled { background-color: #444444; } .pysssss-workflow-arrow-2 { position: absolute; top: 0; bottom: 0; right: 0; font-size: 12px; display: flex; align-items: center; width: 24px; justify-content: center; background: rgba(255,255,255,0.1); content: "▼"; } .pysssss-workflow-arrow-2:after { content: "▼"; } .pysssss-workflow-arrow-2:hover { filter: brightness(1.6); background-color: var(--comfy-menu-bg); } .pysssss-workflow-popup-2 ~ .litecontextmenu { transform: scale(1.3); } #workflowgallery-button-menu { z-index: 10000000000 !important; } #cm-manual-button-menu { z-index: 10000000000 !important; } `; async function init_badge_mode() { api.fetchApi('/manager/badge_mode') .then(response => response.text()) .then(data => { badge_mode = data; }) } async function init_share_option() { api.fetchApi('/manager/share_option') .then(response => response.text()) .then(data => { share_option = data || 'all'; }); } async function init_notice(notice) { api.fetchApi('/manager/notice') .then(response => response.text()) .then(data => { notice.innerHTML = data; }) } await init_badge_mode(); await init_share_option(); async function fetchNicknames() { const response1 = await api.fetchApi(`/customnode/getmappings?mode=nickname`); const mappings = await response1.json(); let result = {}; let nickname_patterns = []; for (let i in mappings) { let item = mappings[i]; var nickname; if (item[1].nickname) { nickname = item[1].nickname; } else if (item[1].title) { nickname = item[1].title; } else { nickname = item[1].title_aux; } for (let j in item[0]) { result[item[0][j]] = nickname; } if(item[1].nodename_pattern) { nickname_patterns.push([item[1].nodename_pattern, nickname]); } } return [result, nickname_patterns]; } const [nicknames, nickname_patterns] = await fetchNicknames(); function getNickname(node, nodename) { if(node.nickname) { return node.nickname; } else { if (nicknames[nodename]) { node.nickname = nicknames[nodename]; } else if(node.getInnerNodes) { let pure_name = getPureName(node); let groupNode = app.graph.extra?.groupNodes?.[pure_name]; if(groupNode) { let packname = groupNode.packname; node.nickname = packname; } return node.nickname; } else { for(let i in nickname_patterns) { let item = nickname_patterns[i]; if(nodename.match(item[0])) { node.nickname = item[1]; } } } return node.nickname; } } function drawBadge(node, orig, restArgs) { let ctx = restArgs[0]; const r = orig?.apply?.(node, restArgs); if (!node.flags.collapsed && badge_mode != 'none' && node.constructor.title_mode != LiteGraph.NO_TITLE) { let text = ""; if (badge_mode.startsWith('id_nick')) text = `#${node.id} `; let nick = node.getNickname(); if (nick) { if (nick == 'ComfyUI') { if(badge_mode.endsWith('hide')) { nick = ""; } else { nick = "🦊" } } if (nick.length > 25) { text += nick.substring(0, 23) + ".."; } else { text += nick; } } if (text != "") { let fgColor = "white"; let bgColor = "#0F1F0F"; let visible = true; ctx.save(); ctx.font = "12px sans-serif"; const sz = ctx.measureText(text); ctx.fillStyle = bgColor; ctx.beginPath(); ctx.roundRect(node.size[0] - sz.width - 12, -LiteGraph.NODE_TITLE_HEIGHT - 20, sz.width + 12, 20, 5); ctx.fill(); ctx.fillStyle = fgColor; ctx.fillText(text, node.size[0] - sz.width - 6, -LiteGraph.NODE_TITLE_HEIGHT - 6); ctx.restore(); if (node.has_errors) { ctx.save(); ctx.font = "bold 14px sans-serif"; const sz2 = ctx.measureText(node.type); ctx.fillStyle = 'white'; ctx.fillText(node.type, node.size[0] / 2 - sz2.width / 2, node.size[1] / 2); ctx.restore(); } } } return r; } async function updateComfyUI() { let prev_text = update_comfyui_button.innerText; update_comfyui_button.innerText = "Updating ComfyUI..."; update_comfyui_button.disabled = true; update_comfyui_button.style.backgroundColor = "gray"; try { const response = await api.fetchApi('/comfyui_manager/update_comfyui'); if (response.status == 400) { show_message('Failed to update ComfyUI.'); return false; } if (response.status == 201) { show_message('ComfyUI has been successfully updated.'); } else { show_message('ComfyUI is already up to date with the latest version.'); } return true; } catch (exception) { show_message(`Failed to update ComfyUI / ${exception}`); return false; } finally { update_comfyui_button.disabled = false; update_comfyui_button.innerText = prev_text; update_comfyui_button.style.backgroundColor = ""; } } async function fetchUpdates(update_check_checkbox) { let prev_text = fetch_updates_button.innerText; fetch_updates_button.innerText = "Fetching updates..."; fetch_updates_button.disabled = true; fetch_updates_button.style.backgroundColor = "gray"; try { var mode = manager_instance.datasrc_combo.value; const response = await api.fetchApi(`/customnode/fetch_updates?mode=${mode}`); if (response.status != 200 && response.status != 201) { show_message('Failed to fetch updates.'); return false; } if (response.status == 201) { show_message("There is an updated extension available.

NOTE:
Fetch Updates is not an update.
Please update from

"); const button = document.getElementById('cm-install-customnodes-button'); button.addEventListener("click", async function() { app.ui.dialog.close(); if(!CustomNodesManager.instance) { CustomNodesManager.instance = new CustomNodesManager(app, self); } await CustomNodesManager.instance.show(CustomNodesManager.ShowMode.UPDATE); } ); update_check_checkbox.checked = false; } else { show_message('All extensions are already up-to-date with the latest versions.'); } return true; } catch (exception) { show_message(`Failed to update custom nodes / ${exception}`); return false; } finally { fetch_updates_button.disabled = false; fetch_updates_button.innerText = prev_text; fetch_updates_button.style.backgroundColor = ""; } } async function updateAll(update_check_checkbox, manager_dialog) { let prev_text = update_all_button.innerText; update_all_button.innerText = "Updating all...(ComfyUI)"; update_all_button.disabled = true; update_all_button.style.backgroundColor = "gray"; try { var mode = manager_instance.datasrc_combo.value; update_all_button.innerText = "Updating all..."; const response1 = await api.fetchApi('/comfyui_manager/update_comfyui'); const response2 = await api.fetchApi(`/customnode/update_all?mode=${mode}`); if (response2.status == 403) { show_message('This action is not allowed with this security level configuration.'); return false; } if (response1.status == 400 || response2.status == 400) { show_message('Failed to update ComfyUI or several extensions.

See terminal log.
'); return false; } if(response1.status == 201 || response2.status == 201) { const update_info = await response2.json(); let failed_list = ""; if(update_info.failed.length > 0) { failed_list = "
FAILED: "+update_info.failed.join(", "); } let updated_list = ""; if(update_info.updated.length > 0) { updated_list = "
UPDATED: "+update_info.updated.join(", "); } show_message( "ComfyUI and all extensions have been updated to the latest version.
To apply the updated custom node, please ComfyUI. And refresh browser.
" +failed_list +updated_list ); const rebootButton = document.getElementById('cm-reboot-button5'); rebootButton.addEventListener("click", function() { if(rebootAPI()) { manager_dialog.close(); } }); } else { show_message('ComfyUI and all extensions are already up-to-date with the latest versions.'); } return true; } catch (exception) { show_message(`Failed to update ComfyUI or several extensions / ${exception}`); return false; } finally { update_all_button.disabled = false; update_all_button.innerText = prev_text; update_all_button.style.backgroundColor = ""; } } function newDOMTokenList(initialTokens) { const tmp = document.createElement(`div`); const classList = tmp.classList; if (initialTokens) { initialTokens.forEach(token => { classList.add(token); }); } return classList; } /** * Check whether the node is a potential output node (img, gif or video output) */ const isOutputNode = (node) => { return SUPPORTED_OUTPUT_NODE_TYPES.includes(node.type); } // ----------- class ManagerMenuDialog extends ComfyDialog { createControlsMid() { let self = this; update_comfyui_button = $el("button.cm-button", { type: "button", textContent: "Update ComfyUI", onclick: () => updateComfyUI() }); fetch_updates_button = $el("button.cm-button", { type: "button", textContent: "Fetch Updates", onclick: () => fetchUpdates(this.update_check_checkbox) }); update_all_button = $el("button.cm-button", { type: "button", textContent: "Update All", onclick: () => updateAll(this.update_check_checkbox, self) }); const res = [ $el("button.cm-button", { type: "button", textContent: "Custom Nodes Manager", onclick: () => { if(!CustomNodesManager.instance) { CustomNodesManager.instance = new CustomNodesManager(app, self); } CustomNodesManager.instance.show(CustomNodesManager.ShowMode.NORMAL); } }), $el("button.cm-button", { type: "button", textContent: "Install Missing Custom Nodes", onclick: () => { if(!CustomNodesManager.instance) { CustomNodesManager.instance = new CustomNodesManager(app, self); } CustomNodesManager.instance.show(CustomNodesManager.ShowMode.MISSING); } }), $el("button.cm-button", { type: "button", textContent: "Model Manager", onclick: () => { if(!ModelManager.instance) { ModelManager.instance = new ModelManager(app, self); } ModelManager.instance.show(); } }), $el("button.cm-button", { type: "button", textContent: "Install via Git URL", onclick: () => { var url = prompt("Please enter the URL of the Git repository to install", ""); if (url !== null) { install_via_git_url(url, self); } } }), $el("br", {}, []), update_all_button, update_comfyui_button, fetch_updates_button, $el("br", {}, []), $el("button.cm-button", { type: "button", textContent: "Alternatives of A1111", onclick: () => { if(!CustomNodesManager.instance) { CustomNodesManager.instance = new CustomNodesManager(app, self); } CustomNodesManager.instance.show(CustomNodesManager.ShowMode.ALTERNATIVES); } }), $el("br", {}, []), $el("button.cm-button-red", { type: "button", textContent: "Restart", onclick: () => rebootAPI() }), ]; return res; } createControlsLeft() { let self = this; this.update_check_checkbox = $el("input",{type:'checkbox', id:"skip_update_check"},[]) const uc_checkbox_text = $el("label",{for:"skip_update_check"},[" Skip update check"]) uc_checkbox_text.style.color = "var(--fg-color)"; uc_checkbox_text.style.cursor = "pointer"; this.update_check_checkbox.checked = true; // db mode this.datasrc_combo = document.createElement("select"); this.datasrc_combo.setAttribute("title", "Configure where to retrieve node/model information. If set to 'local,' the channel is ignored, and if set to 'channel (remote),' it fetches the latest information each time the list is opened."); this.datasrc_combo.className = "cm-menu-combo"; this.datasrc_combo.appendChild($el('option', { value: 'cache', text: 'DB: Channel (1day cache)' }, [])); this.datasrc_combo.appendChild($el('option', { value: 'local', text: 'DB: Local' }, [])); this.datasrc_combo.appendChild($el('option', { value: 'url', text: 'DB: Channel (remote)' }, [])); // preview method let preview_combo = document.createElement("select"); preview_combo.setAttribute("title", "Configure how latent variables will be decoded during preview in the sampling process."); preview_combo.className = "cm-menu-combo"; preview_combo.appendChild($el('option', { value: 'auto', text: 'Preview method: Auto' }, [])); preview_combo.appendChild($el('option', { value: 'taesd', text: 'Preview method: TAESD (slow)' }, [])); preview_combo.appendChild($el('option', { value: 'latent2rgb', text: 'Preview method: Latent2RGB (fast)' }, [])); preview_combo.appendChild($el('option', { value: 'none', text: 'Preview method: None (very fast)' }, [])); api.fetchApi('/manager/preview_method') .then(response => response.text()) .then(data => { preview_combo.value = data; }); preview_combo.addEventListener('change', function (event) { api.fetchApi(`/manager/preview_method?value=${event.target.value}`); }); // nickname let badge_combo = ""; if(is_legacy_front()) { badge_combo = document.createElement("select"); badge_combo.setAttribute("title", "Configure the content to be displayed on the badge at the top right corner of the node. The ID is the identifier of the node. If 'hide built-in' is selected, both unknown nodes and built-in nodes will be omitted, making them indistinguishable"); badge_combo.className = "cm-menu-combo"; badge_combo.appendChild($el('option', { value: 'none', text: 'Badge: None' }, [])); badge_combo.appendChild($el('option', { value: 'nick', text: 'Badge: Nickname' }, [])); badge_combo.appendChild($el('option', { value: 'nick_hide', text: 'Badge: Nickname (hide built-in)' }, [])); badge_combo.appendChild($el('option', { value: 'id_nick', text: 'Badge: #ID Nickname' }, [])); badge_combo.appendChild($el('option', { value: 'id_nick_hide', text: 'Badge: #ID Nickname (hide built-in)' }, [])); api.fetchApi('/manager/badge_mode') .then(response => response.text()) .then(data => { badge_combo.value = data; badge_mode = data; }); badge_combo.addEventListener('change', function (event) { api.fetchApi(`/manager/badge_mode?value=${event.target.value}`); badge_mode = event.target.value; app.graph.setDirtyCanvas(true); }); } // channel let channel_combo = document.createElement("select"); channel_combo.setAttribute("title", "Configure the channel for retrieving data from the Custom Node list (including missing nodes) or the Model list. Note that the badge utilizes local information."); channel_combo.className = "cm-menu-combo"; api.fetchApi('/manager/channel_url_list') .then(response => response.json()) .then(async data => { try { let urls = data.list; for (let i in urls) { if (urls[i] != '') { let name_url = urls[i].split('::'); channel_combo.appendChild($el('option', { value: name_url[0], text: `Channel: ${name_url[0]}` }, [])); } } channel_combo.addEventListener('change', function (event) { api.fetchApi(`/manager/channel_url_list?value=${event.target.value}`); }); channel_combo.value = data.selected; } catch (exception) { } }); // default ui state let default_ui_combo = document.createElement("select"); default_ui_combo.setAttribute("title", "Set the default state to be displayed in the main menu when the browser starts."); default_ui_combo.className = "cm-menu-combo"; default_ui_combo.appendChild($el('option', { value: 'none', text: 'Default UI: None' }, [])); default_ui_combo.appendChild($el('option', { value: 'history', text: 'Default UI: History' }, [])); default_ui_combo.appendChild($el('option', { value: 'queue', text: 'Default UI: Queue' }, [])); api.fetchApi('/manager/default_ui') .then(response => response.text()) .then(data => { default_ui_combo.value = data; }); default_ui_combo.addEventListener('change', function (event) { api.fetchApi(`/manager/default_ui?value=${event.target.value}`); }); // share let share_combo = document.createElement("select"); share_combo.setAttribute("title", "Hide the share button in the main menu or set the default action upon clicking it. Additionally, configure the default share site when sharing via the context menu's share button."); share_combo.className = "cm-menu-combo"; const share_options = [ ['none', 'None'], ['openart', 'OpenArt AI'], ['youml', 'YouML'], ['matrix', 'Matrix Server'], ['comfyworkflows', 'ComfyWorkflows'], ['copus', 'Copus'], ['all', 'All'], ]; for (const option of share_options) { share_combo.appendChild($el('option', { value: option[0], text: `Share: ${option[1]}` }, [])); } // default ui state let component_policy_combo = document.createElement("select"); component_policy_combo.setAttribute("title", "When loading the workflow, configure which version of the component to use."); component_policy_combo.className = "cm-menu-combo"; component_policy_combo.appendChild($el('option', { value: 'workflow', text: 'Component: Use workflow version' }, [])); component_policy_combo.appendChild($el('option', { value: 'higher', text: 'Component: Use higher version' }, [])); component_policy_combo.appendChild($el('option', { value: 'mine', text: 'Component: Use my version' }, [])); api.fetchApi('/manager/component/policy') .then(response => response.text()) .then(data => { component_policy_combo.value = data; set_component_policy(data); }); component_policy_combo.addEventListener('change', function (event) { api.fetchApi(`/manager/component/policy?value=${event.target.value}`); set_component_policy(event.target.value); }); let dbl_click_policy_combo = document.createElement("select"); dbl_click_policy_combo.setAttribute("title", "Sets the behavior when you double-click the title area of a node."); dbl_click_policy_combo.className = "cm-menu-combo"; dbl_click_policy_combo.appendChild($el('option', { value: 'none', text: 'Double-Click: None' }, [])); dbl_click_policy_combo.appendChild($el('option', { value: 'copy-all', text: 'Double-Click: Copy All Connections' }, [])); dbl_click_policy_combo.appendChild($el('option', { value: 'copy-full', text: 'Double-Click: Copy All Connections and shape' }, [])); dbl_click_policy_combo.appendChild($el('option', { value: 'copy-input', text: 'Double-Click: Copy Input Connections' }, [])); dbl_click_policy_combo.appendChild($el('option', { value: 'possible-input', text: 'Double-Click: Possible Input Connections' }, [])); dbl_click_policy_combo.appendChild($el('option', { value: 'dual', text: 'Double-Click: Possible(left) + Copy(right)' }, [])); api.fetchApi('/manager/dbl_click/policy') .then(response => response.text()) .then(data => { dbl_click_policy_combo.value = data; set_double_click_policy(data); }); dbl_click_policy_combo.addEventListener('change', function (event) { api.fetchApi(`/manager/dbl_click/policy?value=${event.target.value}`); set_double_click_policy(event.target.value); }); api.fetchApi('/manager/share_option') .then(response => response.text()) .then(data => { share_combo.value = data || 'all'; share_option = data || 'all'; }); share_combo.addEventListener('change', function (event) { const value = event.target.value; share_option = value; api.fetchApi(`/manager/share_option?value=${value}`); const shareButton = document.getElementById("shareButton"); if (value === 'none') { shareButton.style.display = "none"; } else { shareButton.style.display = "inline-block"; } }); return [ $el("div", {}, [this.update_check_checkbox, uc_checkbox_text]), $el("br", {}, []), this.datasrc_combo, channel_combo, preview_combo, badge_combo, default_ui_combo, share_combo, component_policy_combo, dbl_click_policy_combo, $el("br", {}, []), $el("br", {}, []), $el("filedset.cm-experimental", {}, [ $el("legend.cm-experimental-legend", {}, ["EXPERIMENTAL"]), $el("button.cm-experimental-button", { type: "button", textContent: "Snapshot Manager", onclick: () => { if(!SnapshotManager.instance) SnapshotManager.instance = new SnapshotManager(app, self); SnapshotManager.instance.show(); } }), $el("button.cm-experimental-button", { type: "button", textContent: "Install PIP packages", onclick: () => { var url = prompt("Please enumerate the pip packages to be installed.\n\nExample: insightface opencv-python-headless>=4.1.1\n", ""); if (url !== null) { install_pip(url, self); } } }), $el("button.cm-experimental-button", { type: "button", textContent: "Unload models", onclick: () => { free_models(); } }) ]), ]; } createControlsRight() { const elts = [ $el("button.cm-button", { id: 'cm-manual-button', type: "button", textContent: "Community Manual", onclick: () => { window.open("https://blenderneko.github.io/ComfyUI-docs/", "comfyui-community-manual"); } }, [ $el("div.pysssss-workflow-arrow-2", { id: `cm-manual-button-arrow`, onclick: (e) => { e.preventDefault(); e.stopPropagation(); LiteGraph.closeAllContextMenus(); const menu = new LiteGraph.ContextMenu( [ { title: "ComfyUI Docs", callback: () => { window.open("https://docs.comfy.org/", "comfyui-official-manual"); }, }, { title: "Comfy Custom Node How To", callback: () => { window.open("https://github.com/chrisgoringe/Comfy-Custom-Node-How-To/wiki/aaa_index", "comfyui-community-manual1"); }, }, { title: "ComfyUI Guide To Making Custom Nodes", callback: () => { window.open("https://github.com/Suzie1/ComfyUI_Guide_To_Making_Custom_Nodes/wiki", "comfyui-community-manual2"); }, }, { title: "ComfyUI Examples", callback: () => { window.open("https://comfyanonymous.github.io/ComfyUI_examples", "comfyui-community-manual3"); }, }, { title: "Close", callback: () => { LiteGraph.closeAllContextMenus(); }, } ], { event: e, scale: 1.3, }, window ); // set the id so that we can override the context menu's z-index to be above the comfyui manager menu menu.root.id = "cm-manual-button-menu"; menu.root.classList.add("pysssss-workflow-popup-2"); }, }) ]), $el("button", { id: 'workflowgallery-button', type: "button", style: { ...(localStorage.getItem("wg_last_visited") ? {height: '50px'} : {}) }, onclick: (e) => { const last_visited_site = localStorage.getItem("wg_last_visited") if (!!last_visited_site) { window.open(last_visited_site, last_visited_site); } else { this.handleWorkflowGalleryButtonClick(e) } }, }, [ $el("p", { textContent: 'Workflow Gallery', style: { 'text-align': 'center', 'color': 'var(--input-text)', 'font-size': '18px', 'margin': 0, 'padding': 0, } }, [ $el("p", { id: 'workflowgallery-button-last-visited-label', textContent: `(${localStorage.getItem("wg_last_visited") ? localStorage.getItem("wg_last_visited").split('/')[2] : ''})`, style: { 'text-align': 'center', 'color': 'var(--input-text)', 'font-size': '12px', 'margin': 0, 'padding': 0, } }) ]), $el("div.pysssss-workflow-arrow-2", { id: `comfyworkflows-button-arrow`, onclick: this.handleWorkflowGalleryButtonClick }) ]), $el("button.cm-button", { id: 'cm-nodeinfo-button', type: "button", textContent: "Nodes Info", onclick: () => { window.open("https://ltdrdata.github.io/", "comfyui-node-info"); } }), $el("br", {}, []), ]; var textarea = document.createElement("div"); textarea.className = "cm-notice-board"; elts.push(textarea); init_notice(textarea); return elts; } constructor() { super(); const close_button = $el("button", { id: "cm-close-button", type: "button", textContent: "Close", onclick: () => this.close() }); const content = $el("div.comfy-modal-content", [ $el("tr.cm-title", {}, [ $el("font", {size:6, color:"white"}, [`ComfyUI Manager Menu`])] ), $el("br", {}, []), $el("div.cm-menu-container", [ $el("div.cm-menu-column", [...this.createControlsLeft()]), $el("div.cm-menu-column", [...this.createControlsMid()]), $el("div.cm-menu-column", [...this.createControlsRight()]) ]), $el("br", {}, []), close_button, ] ); content.style.width = '100%'; content.style.height = '100%'; this.element = $el("div.comfy-modal", { id:'cm-manager-dialog', parent: document.body }, [ content ]); } show() { this.element.style.display = "block"; } handleWorkflowGalleryButtonClick(e) { e.preventDefault(); e.stopPropagation(); LiteGraph.closeAllContextMenus(); // Modify the style of the button so that the UI can indicate the last // visited site right away. const modifyButtonStyle = (url) => { const workflowGalleryButton = document.getElementById('workflowgallery-button'); workflowGalleryButton.style.height = '50px'; const lastVisitedLabel = document.getElementById('workflowgallery-button-last-visited-label'); lastVisitedLabel.textContent = `(${url.split('/')[2]})`; } const menu = new LiteGraph.ContextMenu( [ { title: "Share your art", callback: () => { if (share_option === 'openart') { showOpenArtShareDialog(); return; } else if (share_option === 'matrix' || share_option === 'comfyworkflows') { showShareDialog(share_option); return; } else if (share_option === 'youml') { showYouMLShareDialog(); return; } if (!ShareDialogChooser.instance) { ShareDialogChooser.instance = new ShareDialogChooser(); } ShareDialogChooser.instance.show(); }, }, { title: "Open 'openart.ai'", callback: () => { const url = "https://openart.ai/workflows/dev"; localStorage.setItem("wg_last_visited", url); window.open(url, url); modifyButtonStyle(url); }, }, { title: "Open 'youml.com'", callback: () => { const url = "https://youml.com/?from=comfyui-share"; localStorage.setItem("wg_last_visited", url); window.open(url, url); modifyButtonStyle(url); }, }, { title: "Open 'comfyworkflows.com'", callback: () => { const url = "https://comfyworkflows.com/"; localStorage.setItem("wg_last_visited", url); window.open(url, url); modifyButtonStyle(url); }, }, { title: "Open 'esheep'", callback: () => { const url = "https://www.esheep.com"; localStorage.setItem("wg_last_visited", url); window.open(url, url); modifyButtonStyle(url); }, }, { title: "Open 'Copus.io'", callback: () => { const url = "https://www.copus.io"; localStorage.setItem("wg_last_visited", url); window.open(url, url); modifyButtonStyle(url); }, }, { title: "Close", callback: () => { LiteGraph.closeAllContextMenus(); }, } ], { event: e, scale: 1.3, }, window ); // set the id so that we can override the context menu's z-index to be above the comfyui manager menu menu.root.id = "workflowgallery-button-menu"; menu.root.classList.add("pysssss-workflow-popup-2"); } } app.registerExtension({ name: "Comfy.ManagerMenu", init() { $el("style", { textContent: style, parent: document.head, }); }, async setup() { let orig_clear = app.graph.clear; app.graph.clear = function () { orig_clear.call(app.graph); load_components(); }; load_components(); const menu = document.querySelector(".comfy-menu"); const separator = document.createElement("hr"); separator.style.margin = "20px 0"; separator.style.width = "100%"; menu.append(separator); try { // new style Manager buttons // unload models button into new style Manager button let cmGroup = new (await import("../../scripts/ui/components/buttonGroup.js")).ComfyButtonGroup( new(await import("../../scripts/ui/components/button.js")).ComfyButton({ icon: "puzzle", action: () => { if(!manager_instance) setManagerInstance(new ManagerMenuDialog()); manager_instance.show(); }, tooltip: "ComfyUI Manager", content: "Manager", classList: "comfyui-button comfyui-menu-mobile-collapse primary" }).element, new(await import("../../scripts/ui/components/button.js")).ComfyButton({ icon: "vacuum-outline", action: () => { free_models(); }, tooltip: "Unload Models" }).element, new(await import("../../scripts/ui/components/button.js")).ComfyButton({ icon: "vacuum", action: () => { free_models(true); }, tooltip: "Free model and node cache" }).element, new(await import("../../scripts/ui/components/button.js")).ComfyButton({ icon: "share", action: () => { if (share_option === 'openart') { showOpenArtShareDialog(); return; } else if (share_option === 'matrix' || share_option === 'comfyworkflows') { showShareDialog(share_option); return; } else if (share_option === 'youml') { showYouMLShareDialog(); return; } if(!ShareDialogChooser.instance) { ShareDialogChooser.instance = new ShareDialogChooser(); } ShareDialogChooser.instance.show(); }, tooltip: "Share" }).element ); app.menu?.settingsGroup.element.before(cmGroup.element); } catch(exception) { console.log('ComfyUI is outdated. New style menu based features are disabled.'); } // old style Manager button const managerButton = document.createElement("button"); managerButton.textContent = "Manager"; managerButton.onclick = () => { if(!manager_instance) setManagerInstance(new ManagerMenuDialog()); manager_instance.show(); } menu.append(managerButton); const shareButton = document.createElement("button"); shareButton.id = "shareButton"; shareButton.textContent = "Share"; shareButton.onclick = () => { if (share_option === 'openart') { showOpenArtShareDialog(); return; } else if (share_option === 'matrix' || share_option === 'comfyworkflows') { showShareDialog(share_option); return; } else if (share_option === 'youml') { showYouMLShareDialog(); return; } if(!ShareDialogChooser.instance) { ShareDialogChooser.instance = new ShareDialogChooser(); } ShareDialogChooser.instance.show(); } // make the background color a gradient of blue to green shareButton.style.background = "linear-gradient(90deg, #00C9FF 0%, #92FE9D 100%)"; shareButton.style.color = "black"; // Load share option from local storage to determine whether to show // the share button. const shouldShowShareButton = share_option !== 'none'; shareButton.style.display = shouldShowShareButton ? "inline-block" : "none"; menu.append(shareButton); }, async beforeRegisterNodeDef(nodeType, nodeData, app) { this._addExtraNodeContextMenu(nodeType, app); }, async nodeCreated(node, app) { if(is_legacy_front()) { if(!node.badge_enabled) { node.getNickname = function () { return getNickname(node, node.comfyClass.trim()) }; let orig = node.onDrawForeground; if(!orig) orig = node.__proto__.onDrawForeground; node.onDrawForeground = function (ctx) { drawBadge(node, orig, arguments) }; node.badge_enabled = true; } } }, async loadedGraphNode(node, app) { if(is_legacy_front()) { if(!node.badge_enabled) { const orig = node.onDrawForeground; node.getNickname = function () { return getNickname(node, node.type.trim()) }; node.onDrawForeground = function (ctx) { drawBadge(node, orig, arguments) }; } } }, _addExtraNodeContextMenu(node, app) { const origGetExtraMenuOptions = node.prototype.getExtraMenuOptions; node.prototype.cm_menu_added = true; node.prototype.getExtraMenuOptions = function (_, options) { origGetExtraMenuOptions?.apply?.(this, arguments); if (node.category.startsWith('group nodes>')) { options.push({ content: "Save As Component", callback: (obj) => { if (!ComponentBuilderDialog.instance) { ComponentBuilderDialog.instance = new ComponentBuilderDialog(); } ComponentBuilderDialog.instance.target_node = node; ComponentBuilderDialog.instance.show(); } }, null); } if (isOutputNode(node)) { const { potential_outputs } = getPotentialOutputsAndOutputNodes([this]); const hasOutput = potential_outputs.length > 0; // Check if the previous menu option is `null`. If it's not, // then we need to add a `null` as a separator. if (options[options.length - 1] !== null) { options.push(null); } options.push({ content: "🏞️ Share Output", disabled: !hasOutput, callback: (obj) => { if (!ShareDialog.instance) { ShareDialog.instance = new ShareDialog(); } const shareButton = document.getElementById("shareButton"); if (shareButton) { const currentNode = this; if (!OpenArtShareDialog.instance) { OpenArtShareDialog.instance = new OpenArtShareDialog(); } OpenArtShareDialog.instance.selectedNodeId = currentNode.id; if (!ShareDialog.instance) { ShareDialog.instance = new ShareDialog(share_option); } ShareDialog.instance.selectedNodeId = currentNode.id; shareButton.click(); } } }, null); } } }, }); async function set_default_ui() { let res = await api.fetchApi('/manager/default_ui'); if(res.status == 200) { let mode = await res.text(); switch(mode) { case 'history': app.ui.queue.hide(); app.ui.history.show(); break; case 'queue': app.ui.queue.show(); app.ui.history.hide(); break; default: // do nothing break; } } } set_default_ui();