From 8fb76e8d9abd4344830b172335ca3c96763e4496 Mon Sep 17 00:00:00 2001 From: thecooltechguy Date: Thu, 19 Oct 2023 23:52:51 -0700 Subject: [PATCH 01/14] wip --- __init__.py | 9 +- js/comfyui-manager.js | 1627 +++++++++++++++++++++++------------------ 2 files changed, 920 insertions(+), 716 deletions(-) diff --git a/__init__.py b/__init__.py index 2174dafd..da72cf70 100644 --- a/__init__.py +++ b/__init__.py @@ -531,7 +531,6 @@ def check_custom_nodes_installed(json_obj, do_fetch=False, do_update_check=True, elif do_update_check: print(f"\x1b[2K\rUpdate check done.") - @server.PromptServer.instance.routes.get("/customnode/getmappings") async def fetch_customnode_mappings(request): if request.rel_url.query["mode"] == "local": @@ -1196,6 +1195,14 @@ async def channel_url_list(request): return web.Response(status=200) + +@server.PromptServer.instance.routes.post("/manager/share") +async def share_art(request): + # get json data + json_data = await request.json() + print(json_data) + return web.Response(status=200) + WEB_DIRECTORY = "js" NODE_CLASS_MAPPINGS = {} __all__ = ['NODE_CLASS_MAPPINGS'] diff --git a/js/comfyui-manager.js b/js/comfyui-manager.js index 28ac95a0..c91980bb 100644 --- a/js/comfyui-manager.js +++ b/js/comfyui-manager.js @@ -1,7 +1,7 @@ import { app } from "../../scripts/app.js"; import { api } from "../../scripts/api.js" import { ComfyDialog, $el } from "../../scripts/ui.js"; -import {ComfyWidgets} from "../../scripts/widgets.js"; +import { ComfyWidgets } from "../../scripts/widgets.js"; var update_comfyui_button = null; var fetch_updates_button = null; @@ -9,16 +9,16 @@ var update_all_button = null; var badge_mode = "none"; async function init_badge_mode() { - api.fetchApi('/manager/badge_mode') - .then(response => response.text()) - .then(data => { badge_mode = data; }) + api.fetchApi('/manager/badge_mode') + .then(response => response.text()) + .then(data => { badge_mode = data; }) } await init_badge_mode(); async function getCustomnodeMappings() { var mode = "url"; - if(ManagerMenuDialog.instance.local_mode_checkbox.checked) + if (ManagerMenuDialog.instance.local_mode_checkbox.checked) mode = "local"; const response = await api.fetchApi(`/customnode/getmappings?mode=${mode}`); @@ -30,7 +30,7 @@ async function getCustomnodeMappings() { async function getUnresolvedNodesInComponent() { try { var mode = "url"; - if(ManagerMenuDialog.instance.local_mode_checkbox.checked) + if (ManagerMenuDialog.instance.local_mode_checkbox.checked) mode = "local"; const response = await api.fetchApi(`/component/get_unresolved`); @@ -45,11 +45,11 @@ async function getUnresolvedNodesInComponent() { async function getCustomNodes() { var mode = "url"; - if(ManagerMenuDialog.instance.local_mode_checkbox.checked) + if (ManagerMenuDialog.instance.local_mode_checkbox.checked) mode = "local"; var skip_update = ""; - if(ManagerMenuDialog.instance.update_check_checkbox.checked) + if (ManagerMenuDialog.instance.update_check_checkbox.checked) skip_update = "&skip_update=true"; const response = await api.fetchApi(`/customnode/getlist?mode=${mode}${skip_update}`); @@ -59,25 +59,25 @@ async function getCustomNodes() { } async function fetchNicknames() { - const response1 = await api.fetchApi(`/customnode/getmappings?mode=local`); - const mappings = await response1.json(); + const response1 = await api.fetchApi(`/customnode/getmappings?mode=local`); + const mappings = await response1.json(); - let result = {}; + let result = {}; - for(let i in mappings) { - let item = mappings[i]; - var nickname; - if(item[1].title) { - nickname = item[1].title; - } - else { - nickname = item[1].title_aux; - } + for (let i in mappings) { + let item = mappings[i]; + var nickname; + 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; - } - } + for (let j in item[0]) { + result[item[0][j]] = nickname; + } + } return result; } @@ -86,11 +86,11 @@ let nicknames = await fetchNicknames(); async function getAlterList() { var mode = "url"; - if(ManagerMenuDialog.instance.local_mode_checkbox.checked) + if (ManagerMenuDialog.instance.local_mode_checkbox.checked) mode = "local"; var skip_update = ""; - if(ManagerMenuDialog.instance.update_check_checkbox.checked) + if (ManagerMenuDialog.instance.update_check_checkbox.checked) skip_update = "&skip_update=true"; const response = await api.fetchApi(`/alternatives/getlist?mode=${mode}${skip_update}`); @@ -101,7 +101,7 @@ async function getAlterList() { async function getModelList() { var mode = "url"; - if(ManagerMenuDialog.instance.local_mode_checkbox.checked) + if (ManagerMenuDialog.instance.local_mode_checkbox.checked) mode = "local"; const response = await api.fetchApi(`/externalmodel/getlist?mode=${mode}`); @@ -111,61 +111,61 @@ async function getModelList() { } async function install_checked_custom_node(grid_rows, target_i, caller, mode) { - if(caller) { - let failed = ''; + if (caller) { + let failed = ''; - caller.disableButtons(); + caller.disableButtons(); - for(let i in grid_rows) { - if(!grid_rows[i].checkbox.checked && i != target_i) - continue; + for (let i in grid_rows) { + if (!grid_rows[i].checkbox.checked && i != target_i) + continue; - var target; + var target; - if(grid_rows[i].data.custom_node) { - target = grid_rows[i].data.custom_node; - } - else { - target = grid_rows[i].data; - } + if (grid_rows[i].data.custom_node) { + target = grid_rows[i].data.custom_node; + } + else { + target = grid_rows[i].data; + } - caller.startInstall(target); + caller.startInstall(target); - try { - const response = await api.fetchApi(`/customnode/${mode}`, { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify(target) - }); + try { + const response = await api.fetchApi(`/customnode/${mode}`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(target) + }); - if(response.status == 400) { - app.ui.dialog.show(`${mode} failed: ${target.title}`); - app.ui.dialog.element.style.zIndex = 9999; - continue; - } + if (response.status == 400) { + app.ui.dialog.show(`${mode} failed: ${target.title}`); + app.ui.dialog.element.style.zIndex = 9999; + continue; + } - const status = await response.json(); - app.ui.dialog.close(); - target.installed = 'True'; - continue; - } - catch(exception) { - failed += `
${target.title}`; - } + const status = await response.json(); + app.ui.dialog.close(); + target.installed = 'True'; + continue; + } + catch (exception) { + failed += `
${target.title}`; + } } - if(failed != '') { - app.ui.dialog.show(`${mode} failed: ${failed}`); - app.ui.dialog.element.style.zIndex = 9999; + if (failed != '') { + app.ui.dialog.show(`${mode} failed: ${failed}`); + app.ui.dialog.element.style.zIndex = 9999; } - await caller.invalidateControl(); - caller.updateMessage('
To apply the installed/disabled/enabled custom node, please restart ComfyUI.'); + await caller.invalidateControl(); + caller.updateMessage('
To apply the installed/disabled/enabled custom node, please restart ComfyUI.'); } } async function updateComfyUI() { - let prev_text = update_comfyui_button.innerText; + let prev_text = update_comfyui_button.innerText; update_comfyui_button.innerText = "Updating ComfyUI..."; update_comfyui_button.disabled = true; update_comfyui_button.style.backgroundColor = "gray"; @@ -173,13 +173,13 @@ async function updateComfyUI() { try { const response = await api.fetchApi('/comfyui_manager/update_comfyui'); - if(response.status == 400) { + if (response.status == 400) { app.ui.dialog.show('Failed to update ComfyUI.'); app.ui.dialog.element.style.zIndex = 9999; return false; } - if(response.status == 201) { + if (response.status == 201) { app.ui.dialog.show('ComfyUI has been successfully updated.'); app.ui.dialog.element.style.zIndex = 9999; } @@ -190,7 +190,7 @@ async function updateComfyUI() { return true; } - catch(exception) { + catch (exception) { app.ui.dialog.show(`Failed to update ComfyUI / ${exception}`); app.ui.dialog.element.style.zIndex = 9999; return false; @@ -198,30 +198,30 @@ async function updateComfyUI() { finally { update_comfyui_button.disabled = false; update_comfyui_button.innerText = prev_text; - update_comfyui_button.style.backgroundColor = ""; + update_comfyui_button.style.backgroundColor = ""; } } async function fetchUpdates(update_check_checkbox) { - let prev_text = fetch_updates_button.innerText; + 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 = "url"; - if(ManagerMenuDialog.instance.local_mode_checkbox.checked) - mode = "local"; + if (ManagerMenuDialog.instance.local_mode_checkbox.checked) + mode = "local"; const response = await api.fetchApi(`/customnode/fetch_updates?mode=${mode}`); - if(response.status != 200 && response.status != 201) { + if (response.status != 200 && response.status != 201) { app.ui.dialog.show('Failed to fetch updates.'); app.ui.dialog.element.style.zIndex = 9999; return false; } - if(response.status == 201) { + if (response.status == 201) { app.ui.dialog.show('There is an updated extension available.'); app.ui.dialog.element.style.zIndex = 9999; update_check_checkbox.checked = false; @@ -233,7 +233,7 @@ async function fetchUpdates(update_check_checkbox) { return true; } - catch(exception) { + catch (exception) { app.ui.dialog.show(`Failed to update custom nodes / ${exception}`); app.ui.dialog.element.style.zIndex = 9999; return false; @@ -246,37 +246,37 @@ async function fetchUpdates(update_check_checkbox) { } async function updateAll(update_check_checkbox) { - let prev_text = update_all_button.innerText; + 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 = "url"; - if(ManagerMenuDialog.instance.local_mode_checkbox.checked) - mode = "local"; + if (ManagerMenuDialog.instance.local_mode_checkbox.checked) + mode = "local"; 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(response1.status != 200 && response2.status != 201) { + if (response1.status != 200 && response2.status != 201) { app.ui.dialog.show('Failed to update ComfyUI or several extensions.

See terminal log.
'); app.ui.dialog.element.style.zIndex = 9999; return false; } - if(response1.status == 201 || response2.status == 201) { - app.ui.dialog.show('ComfyUI and all extensions have been updated to the latest version.'); + if (response1.status == 201 || response2.status == 201) { + app.ui.dialog.show('ComfyUI and all extensions have been updated to the latest version.'); app.ui.dialog.element.style.zIndex = 9999; } else { app.ui.dialog.show('ComfyUI and all extensions are already up-to-date with the latest versions.'); - app.ui.dialog.element.style.zIndex = 9999; - } + app.ui.dialog.element.style.zIndex = 9999; + } return true; } - catch(exception) { + catch (exception) { app.ui.dialog.show(`Failed to update ComfyUI or several extensions / ${exception}`); app.ui.dialog.element.style.zIndex = 9999; return false; @@ -289,22 +289,22 @@ async function updateAll(update_check_checkbox) { } async function install_model(target) { - if(ModelInstaller.instance) { + if (ModelInstaller.instance) { ModelInstaller.instance.startInstall(target); try { const response = await api.fetchApi('/model/install', { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify(target) - }); + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(target) + }); const status = await response.json(); app.ui.dialog.close(); target.installed = 'True'; return true; } - catch(exception) { + catch (exception) { app.ui.dialog.show(`Install failed: ${target.title} / ${exception}`); app.ui.dialog.element.style.zIndex = 9999; return false; @@ -344,7 +344,7 @@ class CustomNodesInstaller extends ComfyDialog { } disableButtons() { - for(let i in this.install_buttons) { + for (let i in this.install_buttons) { this.install_buttons[i].disabled = true; this.install_buttons[i].style.backgroundColor = 'gray'; } @@ -352,20 +352,20 @@ class CustomNodesInstaller extends ComfyDialog { apply_searchbox(data) { let keyword = this.search_box.value.toLowerCase(); - for(let i in this.grid_rows) { + for (let i in this.grid_rows) { let data = this.grid_rows[i].data; let content = data.author.toLowerCase() + data.description.toLowerCase() + data.title.toLowerCase(); - if(this.filter && this.filter != '*') { - if(this.filter != data.installed) { + if (this.filter && this.filter != '*') { + if (this.filter != data.installed) { this.grid_rows[i].control.style.display = 'none'; continue; } } - if(keyword == "") + if (keyword == "") this.grid_rows[i].control.style.display = null; - else if(content.includes(keyword)) { + else if (content.includes(keyword)) { this.grid_rows[i].control.style.display = null; } else { @@ -381,8 +381,8 @@ class CustomNodesInstaller extends ComfyDialog { // build regex->url map const regex_to_url = []; for (let i in data) { - if(data[i]['nodename_pattern']) { - let item = {regex: new RegExp(data[i].nodename_pattern), url: data[i].files[0]}; + if (data[i]['nodename_pattern']) { + let item = { regex: new RegExp(data[i].nodename_pattern), url: data[i].files[0] }; regex_to_url.push(item); } } @@ -391,7 +391,7 @@ class CustomNodesInstaller extends ComfyDialog { const name_to_url = {}; for (const url in mappings) { const names = mappings[url]; - for(const name in names[0]) { + for (const name in names[0]) { name_to_url[names[0][name]] = url; } } @@ -407,11 +407,11 @@ class CustomNodesInstaller extends ComfyDialog { const node_type = nodes[i].type; if (!registered_nodes.has(node_type)) { const url = name_to_url[node_type.trim()]; - if(url) + if (url) missing_nodes.add(url); else { - for(let j in regex_to_url) { - if(regex_to_url[j].regex.test(node_type)) { + for (let j in regex_to_url) { + if (regex_to_url[j].regex.test(node_type)) { missing_nodes.add(regex_to_url[j].url); } } @@ -423,7 +423,7 @@ class CustomNodesInstaller extends ComfyDialog { for (let i in unresolved_nodes) { let node_type = unresolved_nodes[i]; const url = name_to_url[node_type]; - if(url) + if (url) missing_nodes.add(url); } @@ -438,11 +438,11 @@ class CustomNodesInstaller extends ComfyDialog { this.element.removeChild(this.element.children[0]); } - const msg = $el('div', {id:'custom-message'}, + const msg = $el('div', { id: 'custom-message' }, [$el('br'), - 'The custom node DB is currently being updated, and updates to custom nodes are being checked for.', + 'The custom node DB is currently being updated, and updates to custom nodes are being checked for.', $el('br'), - 'NOTE: Update only checks for extensions that have been fetched.', + 'NOTE: Update only checks for extensions that have been fetched.', $el('br')]); msg.style.height = '100px'; msg.style.verticalAlign = 'middle'; @@ -453,7 +453,7 @@ class CustomNodesInstaller extends ComfyDialog { // invalidate this.data = (await getCustomNodes()).custom_nodes; - if(this.is_missing_node_mode) + if (this.is_missing_node_mode) this.data = await this.filter_missing_node(this.data); this.element.removeChild(msg); @@ -472,89 +472,89 @@ class CustomNodesInstaller extends ComfyDialog { this.message_box.innerHTML = msg; } - invalidate_checks(is_checked, install_state) { - if(is_checked) { - for(let i in this.grid_rows) { - let data = this.grid_rows[i].data; - let checkbox = this.grid_rows[i].checkbox; - let buttons = this.grid_rows[i].buttons; + invalidate_checks(is_checked, install_state) { + if (is_checked) { + for (let i in this.grid_rows) { + let data = this.grid_rows[i].data; + let checkbox = this.grid_rows[i].checkbox; + let buttons = this.grid_rows[i].buttons; - checkbox.disabled = data.installed != install_state; + checkbox.disabled = data.installed != install_state; - if(checkbox.disabled) { - for(let j in buttons) { - buttons[j].style.display = 'none'; - } - } - else { - for(let j in buttons) { - buttons[j].style.display = null; - } - } - } + if (checkbox.disabled) { + for (let j in buttons) { + buttons[j].style.display = 'none'; + } + } + else { + for (let j in buttons) { + buttons[j].style.display = null; + } + } + } - this.checkbox_all.disabled = false; - } - else { - for(let i in this.grid_rows) { - let checkbox = this.grid_rows[i].checkbox; - if(checkbox.check) - return; // do nothing - } + this.checkbox_all.disabled = false; + } + else { + for (let i in this.grid_rows) { + let checkbox = this.grid_rows[i].checkbox; + if (checkbox.check) + return; // do nothing + } - // every checkbox is unchecked -> enable all checkbox - for(let i in this.grid_rows) { - let checkbox = this.grid_rows[i].checkbox; - let buttons = this.grid_rows[i].buttons; - checkbox.disabled = false; + // every checkbox is unchecked -> enable all checkbox + for (let i in this.grid_rows) { + let checkbox = this.grid_rows[i].checkbox; + let buttons = this.grid_rows[i].buttons; + checkbox.disabled = false; - for(let j in buttons) { - buttons[j].style.display = null; - } - } + for (let j in buttons) { + buttons[j].style.display = null; + } + } - this.checkbox_all.checked = false; - this.checkbox_all.disabled = true; - } - } + this.checkbox_all.checked = false; + this.checkbox_all.disabled = true; + } + } - check_all(is_checked) { - if(is_checked) { - // lookup first checked item's state - let check_state = null; - for(let i in this.grid_rows) { - let checkbox = this.grid_rows[i].checkbox; - if(checkbox.checked) { - check_state = this.grid_rows[i].data.installed; - } - } + check_all(is_checked) { + if (is_checked) { + // lookup first checked item's state + let check_state = null; + for (let i in this.grid_rows) { + let checkbox = this.grid_rows[i].checkbox; + if (checkbox.checked) { + check_state = this.grid_rows[i].data.installed; + } + } - if(check_state == null) - return; + if (check_state == null) + return; - // check only same state items - for(let i in this.grid_rows) { - let checkbox = this.grid_rows[i].checkbox; - if(this.grid_rows[i].data.installed == check_state) - checkbox.checked = true; - } - } - else { - // uncheck all - for(let i in this.grid_rows) { - let checkbox = this.grid_rows[i].checkbox; - let buttons = this.grid_rows[i].buttons; - checkbox.checked = false; - checkbox.disabled = false; + // check only same state items + for (let i in this.grid_rows) { + let checkbox = this.grid_rows[i].checkbox; + if (this.grid_rows[i].data.installed == check_state) + checkbox.checked = true; + } + } + else { + // uncheck all + for (let i in this.grid_rows) { + let checkbox = this.grid_rows[i].checkbox; + let buttons = this.grid_rows[i].buttons; + checkbox.checked = false; + checkbox.disabled = false; - for(let j in buttons) { - buttons[j].style.display = null; - } - } + for (let j in buttons) { + buttons[j].style.display = null; + } + } - this.checkbox_all.disabled = true; - } - } + this.checkbox_all.disabled = true; + } + } async createGrid() { var grid = document.createElement('table'); @@ -562,24 +562,24 @@ class CustomNodesInstaller extends ComfyDialog { this.grid_rows = {}; - let self = this; + let self = this; - var thead = document.createElement('thead'); - var tbody = document.createElement('tbody'); + var thead = document.createElement('thead'); + var tbody = document.createElement('tbody'); var headerRow = document.createElement('tr'); thead.style.position = "sticky"; thead.style.top = "0px"; - thead.style.borderCollapse = "collapse"; - thead.style.tableLayout = "fixed"; + thead.style.borderCollapse = "collapse"; + thead.style.tableLayout = "fixed"; var header0 = document.createElement('th'); header0.style.width = "20px"; - this.checkbox_all = $el("input",{type:'checkbox', id:'check_all'},[]); - header0.appendChild(this.checkbox_all); - this.checkbox_all.checked = false; - this.checkbox_all.disabled = true; - this.checkbox_all.addEventListener('change', function() { self.check_all.call(self, self.checkbox_all.checked); }); + this.checkbox_all = $el("input", { type: 'checkbox', id: 'check_all' }, []); + header0.appendChild(this.checkbox_all); + this.checkbox_all.checked = false; + this.checkbox_all.disabled = true; + this.checkbox_all.addEventListener('change', function () { self.check_all.call(self, self.checkbox_all.checked); }); var header1 = document.createElement('th'); header1.innerHTML = '  ID  '; @@ -593,7 +593,7 @@ class CustomNodesInstaller extends ComfyDialog { var header4 = document.createElement('th'); header4.innerHTML = 'Description'; header4.style.width = "60%"; -// header4.classList.add('expandable-column'); + // header4.classList.add('expandable-column'); var header5 = document.createElement('th'); header5.innerHTML = 'Install'; header5.style.width = "130px"; @@ -611,7 +611,7 @@ class CustomNodesInstaller extends ComfyDialog { header5.style.position = "sticky"; header5.style.top = "0px"; - thead.appendChild(headerRow); + thead.appendChild(headerRow); headerRow.appendChild(header0); headerRow.appendChild(header1); headerRow.appendChild(header2); @@ -628,32 +628,32 @@ class CustomNodesInstaller extends ComfyDialog { grid.appendChild(thead); grid.appendChild(tbody); - if(this.data) + if (this.data) for (var i = 0; i < this.data.length; i++) { const data = this.data[i]; let dataRow = document.createElement('tr'); let data0 = document.createElement('td'); - let checkbox = $el("input",{type:'checkbox', id:`check_${i}`},[]); - data0.appendChild(checkbox); - checkbox.checked = false; - checkbox.addEventListener('change', function() { self.invalidate_checks.call(self, checkbox.checked, data.installed); }); + let checkbox = $el("input", { type: 'checkbox', id: `check_${i}` }, []); + data0.appendChild(checkbox); + checkbox.checked = false; + checkbox.addEventListener('change', function () { self.invalidate_checks.call(self, checkbox.checked, data.installed); }); var data1 = document.createElement('td'); data1.style.textAlign = "center"; - data1.innerHTML = i+1; + data1.innerHTML = i + 1; var data2 = document.createElement('td'); - data2.style.maxWidth = "100px"; + data2.style.maxWidth = "100px"; data2.className = "cm-node-author" data2.textContent = ` ${data.author}`; data2.style.whiteSpace = "nowrap"; - data2.style.overflow = "hidden"; + data2.style.overflow = "hidden"; data2.style.textOverflow = "ellipsis"; - var data3 = document.createElement('td'); - data3.style.maxWidth = "200px"; - data3.style.wordWrap = "break-word"; + var data3 = document.createElement('td'); + data3.style.maxWidth = "200px"; + data3.style.wordWrap = "break-word"; data3.className = "cm-node-name" - data3.innerHTML = ` ${data.title}`; + data3.innerHTML = ` ${data.title}`; var data4 = document.createElement('td'); data4.innerHTML = data.description; data4.className = "cm-node-desc" @@ -667,71 +667,71 @@ class CustomNodesInstaller extends ComfyDialog { this.install_buttons.push(installBtn); - switch(data.installed) { - case 'Disabled': - installBtn3 = document.createElement('button'); - installBtn3.innerHTML = 'Enable'; - installBtn3.className = "cm-btn-enable"; - installBtn3.style.backgroundColor = 'blue'; - installBtn3.style.color = 'white'; - this.install_buttons.push(installBtn3); + switch (data.installed) { + case 'Disabled': + installBtn3 = document.createElement('button'); + installBtn3.innerHTML = 'Enable'; + installBtn3.className = "cm-btn-enable"; + installBtn3.style.backgroundColor = 'blue'; + installBtn3.style.color = 'white'; + this.install_buttons.push(installBtn3); - installBtn.innerHTML = 'Uninstall'; - installBtn.style.backgroundColor = 'red'; - break; - case 'Update': - installBtn2 = document.createElement('button'); - installBtn2.innerHTML = 'Update'; - installBtn2.className = "cm-btn-update"; - installBtn2.style.backgroundColor = 'blue'; - installBtn2.style.color = 'white'; - this.install_buttons.push(installBtn2); + installBtn.innerHTML = 'Uninstall'; + installBtn.style.backgroundColor = 'red'; + break; + case 'Update': + installBtn2 = document.createElement('button'); + installBtn2.innerHTML = 'Update'; + installBtn2.className = "cm-btn-update"; + installBtn2.style.backgroundColor = 'blue'; + installBtn2.style.color = 'white'; + this.install_buttons.push(installBtn2); - installBtn3 = document.createElement('button'); - installBtn3.innerHTML = 'Disable'; - installBtn3.className = "cm-btn-disable"; - installBtn3.style.backgroundColor = 'MediumSlateBlue'; - installBtn3.style.color = 'white'; - this.install_buttons.push(installBtn3); + installBtn3 = document.createElement('button'); + installBtn3.innerHTML = 'Disable'; + installBtn3.className = "cm-btn-disable"; + installBtn3.style.backgroundColor = 'MediumSlateBlue'; + installBtn3.style.color = 'white'; + this.install_buttons.push(installBtn3); - installBtn.innerHTML = 'Uninstall'; - installBtn.style.backgroundColor = 'red'; - break; - case 'True': - installBtn3 = document.createElement('button'); - installBtn3.innerHTML = 'Disable'; - installBtn3.className = "cm-btn-disable"; - installBtn3.style.backgroundColor = 'MediumSlateBlue'; - installBtn3.style.color = 'white'; - this.install_buttons.push(installBtn3); + installBtn.innerHTML = 'Uninstall'; + installBtn.style.backgroundColor = 'red'; + break; + case 'True': + installBtn3 = document.createElement('button'); + installBtn3.innerHTML = 'Disable'; + installBtn3.className = "cm-btn-disable"; + installBtn3.style.backgroundColor = 'MediumSlateBlue'; + installBtn3.style.color = 'white'; + this.install_buttons.push(installBtn3); - installBtn.innerHTML = 'Uninstall'; - installBtn.style.backgroundColor = 'red'; - break; - case 'False': - installBtn.innerHTML = 'Install'; - installBtn.style.backgroundColor = 'black'; - installBtn.style.color = 'white'; - break; - default: - installBtn.innerHTML = 'Try Install'; - installBtn.style.backgroundColor = 'Gray'; - installBtn.style.color = 'white'; + installBtn.innerHTML = 'Uninstall'; + installBtn.style.backgroundColor = 'red'; + break; + case 'False': + installBtn.innerHTML = 'Install'; + installBtn.style.backgroundColor = 'black'; + installBtn.style.color = 'white'; + break; + default: + installBtn.innerHTML = 'Try Install'; + installBtn.style.backgroundColor = 'Gray'; + installBtn.style.color = 'white'; } - let j = i; - if(installBtn2 != null) { + let j = i; + if (installBtn2 != null) { installBtn2.style.width = "120px"; - installBtn2.addEventListener('click', function() { + installBtn2.addEventListener('click', function () { install_checked_custom_node(self.grid_rows, j, CustomNodesInstaller.instance, 'update'); }); data5.appendChild(installBtn2); } - if(installBtn3 != null) { + if (installBtn3 != null) { installBtn3.style.width = "120px"; - installBtn3.addEventListener('click', function() { + installBtn3.addEventListener('click', function () { install_checked_custom_node(self.grid_rows, j, CustomNodesInstaller.instance, 'toggle_active'); }); @@ -739,8 +739,8 @@ class CustomNodesInstaller extends ComfyDialog { } installBtn.style.width = "120px"; - installBtn.addEventListener('click', function() { - if(this.innerHTML == 'Uninstall') { + installBtn.addEventListener('click', function () { + if (this.innerHTML == 'Uninstall') { if (confirm(`Are you sure uninstall ${data.title}?`)) { install_checked_custom_node(self.grid_rows, j, CustomNodesInstaller.instance, 'uninstall'); } @@ -765,29 +765,29 @@ class CustomNodesInstaller extends ComfyDialog { tbody.appendChild(dataRow); let buttons = []; - if(installBtn) { - buttons.push(installBtn); - } - if(installBtn2) { - buttons.push(installBtn2); - } - if(installBtn3) { - buttons.push(installBtn3); - } + if (installBtn) { + buttons.push(installBtn); + } + if (installBtn2) { + buttons.push(installBtn2); + } + if (installBtn3) { + buttons.push(installBtn3); + } - this.grid_rows[i] = {data:data, buttons:buttons, checkbox:checkbox, control:dataRow}; + this.grid_rows[i] = { data: data, buttons: buttons, checkbox: checkbox, control: dataRow }; } const panel = document.createElement('div'); - panel.style.width = "100%"; + panel.style.width = "100%"; panel.appendChild(grid); - function handleResize() { - const parentHeight = self.element.clientHeight; - const gridHeight = parentHeight - 200; + function handleResize() { + const parentHeight = self.element.clientHeight; + const gridHeight = parentHeight - 200; - grid.style.height = gridHeight + "px"; - } + grid.style.height = gridHeight + "px"; + } window.addEventListener("resize", handleResize); grid.style.position = "relative"; @@ -799,7 +799,7 @@ class CustomNodesInstaller extends ComfyDialog { this.element.style.width = "80%"; this.element.appendChild(panel); - handleResize(); + handleResize(); } createFilterCombo() { @@ -816,11 +816,11 @@ class CustomNodesInstaller extends ComfyDialog { let items = [ - { value:'*', text:'Filter: all' }, - { value:'Disabled', text:'Filter: disabled' }, - { value:'Update', text:'Filter: update' }, - { value:'True', text:'Filter: installed' }, - { value:'False', text:'Filter: not-installed' }, + { value: '*', text: 'Filter: all' }, + { value: 'Disabled', text: 'Filter: disabled' }, + { value: 'Update', text: 'Filter: update' }, + { value: 'True', text: 'Filter: installed' }, + { value: 'False', text: 'Filter: not-installed' }, ]; items.forEach(item => { @@ -831,13 +831,13 @@ class CustomNodesInstaller extends ComfyDialog { }); let self = this; - combo.addEventListener('change', function(event) { + combo.addEventListener('change', function (event) { self.filter = event.target.value; self.apply_searchbox(); }); - if(self.filter) { - combo.value = self.filter; + if (self.filter) { + combo.value = self.filter; } return combo; @@ -845,18 +845,18 @@ class CustomNodesInstaller extends ComfyDialog { createHeaderControls() { let self = this; - this.search_box = $el('input', {type:'text', id:'manager-customnode-search-box', placeholder:'input search keyword', value:this.search_keyword}, []); + this.search_box = $el('input', { type: 'text', id: 'manager-customnode-search-box', placeholder: 'input search keyword', value: this.search_keyword }, []); this.search_box.style.height = "25px"; this.search_box.onkeydown = (event) => { - if (event.key === 'Enter') { - self.search_keyword = self.search_box.value; - self.apply_searchbox(); - } - if (event.key === 'Escape') { - self.search_keyword = self.search_box.value; - self.apply_searchbox(); - } - }; + if (event.key === 'Enter') { + self.search_keyword = self.search_box.value; + self.apply_searchbox(); + } + if (event.key === 'Escape') { + self.search_keyword = self.search_box.value; + self.apply_searchbox(); + } + }; let search_button = document.createElement("button"); @@ -870,12 +870,12 @@ class CustomNodesInstaller extends ComfyDialog { let filter_control = this.createFilterCombo(); filter_control.style.display = "inline-block"; - let cell = $el('td', {width:'100%'}, [filter_control, this.search_box, ' ', search_button]); - let search_control = $el('table', {width:'100%'}, - [ - $el('tr', {}, [cell]) - ] - ); + let cell = $el('td', { width: '100%' }, [filter_control, this.search_box, ' ', search_button]); + let search_control = $el('table', { width: '100%' }, + [ + $el('tr', {}, [cell]) + ] + ); cell.style.textAlign = "right"; @@ -888,7 +888,7 @@ class CustomNodesInstaller extends ComfyDialog { close_button.onclick = () => { this.close(); } close_button.style.display = "inline-block"; - this.message_box = $el('div', {id:'custom-installer-message'}, [$el('br'), '']); + this.message_box = $el('div', { id: 'custom-installer-message' }, [$el('br'), '']); this.message_box.style.height = '60px'; this.message_box.style.verticalAlign = 'middle'; @@ -903,7 +903,7 @@ class CustomNodesInstaller extends ComfyDialog { this.element.style.display = "block"; } - catch(exception) { + catch (exception) { app.ui.dialog.show(`Failed to get custom node list. / ${exception}`); } } @@ -936,7 +936,7 @@ class AlternativesInstaller extends ComfyDialog { } disableButtons() { - for(let i in this.install_buttons) { + for (let i in this.install_buttons) { this.install_buttons[i].disabled = true; this.install_buttons[i].style.backgroundColor = 'gray'; } @@ -944,25 +944,25 @@ class AlternativesInstaller extends ComfyDialog { apply_searchbox(data) { let keyword = this.search_box.value.toLowerCase(); - for(let i in this.grid_rows) { + for (let i in this.grid_rows) { let data1 = this.grid_rows[i].data; let data2 = data1.custom_node; - if(!data2) - continue; + if (!data2) + continue; let content = data1.tags.toLowerCase() + data1.description.toLowerCase() + data2.author.toLowerCase() + data2.description.toLowerCase() + data2.title.toLowerCase(); - if(this.filter && this.filter != '*') { - if(this.filter != data2.installed) { + if (this.filter && this.filter != '*') { + if (this.filter != data2.installed) { this.grid_rows[i].control.style.display = 'none'; continue; } } - if(keyword == "") + if (keyword == "") this.grid_rows[i].control.style.display = null; - else if(content.includes(keyword)) { + else if (content.includes(keyword)) { this.grid_rows[i].control.style.display = null; } else { @@ -979,11 +979,11 @@ class AlternativesInstaller extends ComfyDialog { this.element.removeChild(this.element.children[0]); } - const msg = $el('div', {id:'custom-message'}, + const msg = $el('div', { id: 'custom-message' }, [$el('br'), - 'The custom node DB is currently being updated, and updates to custom nodes are being checked for.', + 'The custom node DB is currently being updated, and updates to custom nodes are being checked for.', $el('br'), - 'NOTE: Update only checks for extensions that have been fetched.', + 'NOTE: Update only checks for extensions that have been fetched.', $el('br')]); msg.style.height = '100px'; msg.style.verticalAlign = 'middle'; @@ -1008,89 +1008,89 @@ class AlternativesInstaller extends ComfyDialog { this.message_box.innerHTML = msg; } - invalidate_checks(is_checked, install_state) { - if(is_checked) { - for(let i in this.grid_rows) { - let data = this.grid_rows[i].data; - let checkbox = this.grid_rows[i].checkbox; - let buttons = this.grid_rows[i].buttons; + invalidate_checks(is_checked, install_state) { + if (is_checked) { + for (let i in this.grid_rows) { + let data = this.grid_rows[i].data; + let checkbox = this.grid_rows[i].checkbox; + let buttons = this.grid_rows[i].buttons; - checkbox.disabled = data.custom_node.installed != install_state; + checkbox.disabled = data.custom_node.installed != install_state; - if(checkbox.disabled) { - for(let j in buttons) { - buttons[j].style.display = 'none'; - } - } - else { - for(let j in buttons) { - buttons[j].style.display = null; - } - } - } + if (checkbox.disabled) { + for (let j in buttons) { + buttons[j].style.display = 'none'; + } + } + else { + for (let j in buttons) { + buttons[j].style.display = null; + } + } + } - this.checkbox_all.disabled = false; - } - else { - for(let i in this.grid_rows) { - let checkbox = this.grid_rows[i].checkbox; - if(checkbox.check) - return; // do nothing - } + this.checkbox_all.disabled = false; + } + else { + for (let i in this.grid_rows) { + let checkbox = this.grid_rows[i].checkbox; + if (checkbox.check) + return; // do nothing + } - // every checkbox is unchecked -> enable all checkbox - for(let i in this.grid_rows) { - let checkbox = this.grid_rows[i].checkbox; - let buttons = this.grid_rows[i].buttons; - checkbox.disabled = false; + // every checkbox is unchecked -> enable all checkbox + for (let i in this.grid_rows) { + let checkbox = this.grid_rows[i].checkbox; + let buttons = this.grid_rows[i].buttons; + checkbox.disabled = false; - for(let j in buttons) { - buttons[j].style.display = null; - } - } + for (let j in buttons) { + buttons[j].style.display = null; + } + } - this.checkbox_all.checked = false; - this.checkbox_all.disabled = true; - } - } + this.checkbox_all.checked = false; + this.checkbox_all.disabled = true; + } + } - check_all(is_checked) { - if(is_checked) { - // lookup first checked item's state - let check_state = null; - for(let i in this.grid_rows) { - let checkbox = this.grid_rows[i].checkbox; - if(checkbox.checked) { - check_state = this.grid_rows[i].data.custom_node.installed; - } - } + check_all(is_checked) { + if (is_checked) { + // lookup first checked item's state + let check_state = null; + for (let i in this.grid_rows) { + let checkbox = this.grid_rows[i].checkbox; + if (checkbox.checked) { + check_state = this.grid_rows[i].data.custom_node.installed; + } + } - if(check_state == null) - return; + if (check_state == null) + return; - // check only same state items - for(let i in this.grid_rows) { - let checkbox = this.grid_rows[i].checkbox; - if(this.grid_rows[i].data.custom_node.installed == check_state) - checkbox.checked = true; - } - } - else { - // uncheck all - for(let i in this.grid_rows) { - let checkbox = this.grid_rows[i].checkbox; - let buttons = this.grid_rows[i].buttons; - checkbox.checked = false; - checkbox.disabled = false; + // check only same state items + for (let i in this.grid_rows) { + let checkbox = this.grid_rows[i].checkbox; + if (this.grid_rows[i].data.custom_node.installed == check_state) + checkbox.checked = true; + } + } + else { + // uncheck all + for (let i in this.grid_rows) { + let checkbox = this.grid_rows[i].checkbox; + let buttons = this.grid_rows[i].buttons; + checkbox.checked = false; + checkbox.disabled = false; - for(let j in buttons) { - buttons[j].style.display = null; - } - } + for (let j in buttons) { + buttons[j].style.display = null; + } + } - this.checkbox_all.disabled = true; - } - } + this.checkbox_all.disabled = true; + } + } async createGrid() { var grid = document.createElement('table'); @@ -1098,24 +1098,24 @@ class AlternativesInstaller extends ComfyDialog { this.grid_rows = {}; - let self = this; + let self = this; - var thead = document.createElement('thead'); - var tbody = document.createElement('tbody'); + var thead = document.createElement('thead'); + var tbody = document.createElement('tbody'); var headerRow = document.createElement('tr'); thead.style.position = "sticky"; thead.style.top = "0px"; - thead.style.borderCollapse = "collapse"; - thead.style.tableLayout = "fixed"; + thead.style.borderCollapse = "collapse"; + thead.style.tableLayout = "fixed"; var header0 = document.createElement('th'); header0.style.width = "20px"; - this.checkbox_all = $el("input",{type:'checkbox', id:'check_all'},[]); - header0.appendChild(this.checkbox_all); - this.checkbox_all.checked = false; - this.checkbox_all.disabled = true; - this.checkbox_all.addEventListener('change', function() { self.check_all.call(self, self.checkbox_all.checked); }); + this.checkbox_all = $el("input", { type: 'checkbox', id: 'check_all' }, []); + header0.appendChild(this.checkbox_all); + this.checkbox_all.checked = false; + this.checkbox_all.disabled = true; + this.checkbox_all.addEventListener('change', function () { self.check_all.call(self, self.checkbox_all.checked); }); var header1 = document.createElement('th'); header1.innerHTML = '  ID  '; @@ -1147,7 +1147,7 @@ class AlternativesInstaller extends ComfyDialog { header5.style.position = "sticky"; header5.style.top = "0px"; - thead.appendChild(headerRow); + thead.appendChild(headerRow); headerRow.appendChild(header0); headerRow.appendChild(header1); headerRow.appendChild(header2); @@ -1165,25 +1165,25 @@ class AlternativesInstaller extends ComfyDialog { grid.appendChild(thead); grid.appendChild(tbody); - if(this.data) + if (this.data) for (var i = 0; i < this.data.length; i++) { const data = this.data[i]; var dataRow = document.createElement('tr'); - let data0 = document.createElement('td'); - let checkbox = $el("input",{type:'checkbox', id:`check_${i}`},[]); - data0.appendChild(checkbox); - checkbox.checked = false; - checkbox.addEventListener('change', function() { self.invalidate_checks.call(self, checkbox.checked, data.custom_node?.installed); }); + let data0 = document.createElement('td'); + let checkbox = $el("input", { type: 'checkbox', id: `check_${i}` }, []); + data0.appendChild(checkbox); + checkbox.checked = false; + checkbox.addEventListener('change', function () { self.invalidate_checks.call(self, checkbox.checked, data.custom_node?.installed); }); var data1 = document.createElement('td'); data1.style.textAlign = "center"; - data1.innerHTML = i+1; + data1.innerHTML = i + 1; var data2 = document.createElement('td'); data2.innerHTML = ` ${data.tags}`; var data3 = document.createElement('td'); var data4 = document.createElement('td'); - if(data.custom_node) { + if (data.custom_node) { data3.innerHTML = ` ${data.custom_node.author}`; data4.innerHTML = ` ${data.custom_node.title}`; } @@ -1196,77 +1196,77 @@ class AlternativesInstaller extends ComfyDialog { var data6 = document.createElement('td'); data6.style.textAlign = "center"; - var installBtn = document.createElement('button'); - var installBtn2 = null; - var installBtn3 = null; + var installBtn = document.createElement('button'); + var installBtn2 = null; + var installBtn3 = null; - if(data.custom_node) { + if (data.custom_node) { this.install_buttons.push(installBtn); - switch(data.custom_node.installed) { - case 'Disabled': - installBtn3 = document.createElement('button'); - installBtn3.innerHTML = 'Enable'; - installBtn3.style.backgroundColor = 'blue'; - installBtn3.style.color = 'white'; - this.install_buttons.push(installBtn3); + switch (data.custom_node.installed) { + case 'Disabled': + installBtn3 = document.createElement('button'); + installBtn3.innerHTML = 'Enable'; + installBtn3.style.backgroundColor = 'blue'; + installBtn3.style.color = 'white'; + this.install_buttons.push(installBtn3); - installBtn.innerHTML = 'Uninstall'; - installBtn.style.backgroundColor = 'red'; - installBtn.style.color = 'white'; - break; - case 'Update': - installBtn2 = document.createElement('button'); - installBtn2.innerHTML = 'Update'; - installBtn2.style.backgroundColor = 'blue'; - installBtn2.style.color = 'white'; - this.install_buttons.push(installBtn2); + installBtn.innerHTML = 'Uninstall'; + installBtn.style.backgroundColor = 'red'; + installBtn.style.color = 'white'; + break; + case 'Update': + installBtn2 = document.createElement('button'); + installBtn2.innerHTML = 'Update'; + installBtn2.style.backgroundColor = 'blue'; + installBtn2.style.color = 'white'; + this.install_buttons.push(installBtn2); - installBtn3 = document.createElement('button'); - installBtn3.innerHTML = 'Disable'; - installBtn3.style.backgroundColor = 'MediumSlateBlue'; - installBtn3.style.color = 'white'; - this.install_buttons.push(installBtn3); + installBtn3 = document.createElement('button'); + installBtn3.innerHTML = 'Disable'; + installBtn3.style.backgroundColor = 'MediumSlateBlue'; + installBtn3.style.color = 'white'; + this.install_buttons.push(installBtn3); - installBtn.innerHTML = 'Uninstall'; - installBtn.style.backgroundColor = 'red'; - installBtn.style.color = 'white'; - break; - case 'True': - installBtn3 = document.createElement('button'); - installBtn3.innerHTML = 'Disable'; - installBtn3.style.backgroundColor = 'MediumSlateBlue'; - installBtn3.style.color = 'white'; - this.install_buttons.push(installBtn3); + installBtn.innerHTML = 'Uninstall'; + installBtn.style.backgroundColor = 'red'; + installBtn.style.color = 'white'; + break; + case 'True': + installBtn3 = document.createElement('button'); + installBtn3.innerHTML = 'Disable'; + installBtn3.style.backgroundColor = 'MediumSlateBlue'; + installBtn3.style.color = 'white'; + this.install_buttons.push(installBtn3); - installBtn.innerHTML = 'Uninstall'; - installBtn.style.backgroundColor = 'red'; - installBtn.style.color = 'white'; - break; - case 'False': - installBtn.innerHTML = 'Install'; - installBtn.style.backgroundColor = 'black'; - installBtn.style.color = 'white'; - break; - default: - installBtn.innerHTML = 'Try Install'; - installBtn.style.backgroundColor = 'Gray'; - installBtn.style.color = 'white'; + installBtn.innerHTML = 'Uninstall'; + installBtn.style.backgroundColor = 'red'; + installBtn.style.color = 'white'; + break; + case 'False': + installBtn.innerHTML = 'Install'; + installBtn.style.backgroundColor = 'black'; + installBtn.style.color = 'white'; + break; + default: + installBtn.innerHTML = 'Try Install'; + installBtn.style.backgroundColor = 'Gray'; + installBtn.style.color = 'white'; } - let j = i; - if(installBtn2 != null) { + let j = i; + if (installBtn2 != null) { installBtn2.style.width = "120px"; - installBtn2.addEventListener('click', function() { + installBtn2.addEventListener('click', function () { install_checked_custom_node(self.grid_rows, j, AlternativesInstaller.instance, 'update'); }); data6.appendChild(installBtn2); } - if(installBtn3 != null) { + if (installBtn3 != null) { installBtn3.style.width = "120px"; - installBtn3.addEventListener('click', function() { + installBtn3.addEventListener('click', function () { install_checked_custom_node(self.grid_rows, j, AlternativesInstaller.instance, 'toggle_active'); }); @@ -1275,8 +1275,8 @@ class AlternativesInstaller extends ComfyDialog { installBtn.style.width = "120px"; - installBtn.addEventListener('click', function() { - if(this.innerHTML == 'Uninstall') { + installBtn.addEventListener('click', function () { + if (this.innerHTML == 'Uninstall') { if (confirm(`Are you sure uninstall ${data.title}?`)) { install_checked_custom_node(self.grid_rows, j, AlternativesInstaller.instance, 'uninstall'); } @@ -1303,29 +1303,29 @@ class AlternativesInstaller extends ComfyDialog { tbody.appendChild(dataRow); let buttons = []; - if(installBtn) { - buttons.push(installBtn); - } - if(installBtn2) { - buttons.push(installBtn2); - } - if(installBtn3) { - buttons.push(installBtn3); - } + if (installBtn) { + buttons.push(installBtn); + } + if (installBtn2) { + buttons.push(installBtn2); + } + if (installBtn3) { + buttons.push(installBtn3); + } - this.grid_rows[i] = {data:data, buttons:buttons, checkbox:checkbox, control:dataRow}; + this.grid_rows[i] = { data: data, buttons: buttons, checkbox: checkbox, control: dataRow }; } const panel = document.createElement('div'); - panel.style.width = "100%"; + panel.style.width = "100%"; panel.appendChild(grid); - function handleResize() { - const parentHeight = self.element.clientHeight; - const gridHeight = parentHeight - 200; + function handleResize() { + const parentHeight = self.element.clientHeight; + const gridHeight = parentHeight - 200; - grid.style.height = gridHeight + "px"; - } + grid.style.height = gridHeight + "px"; + } window.addEventListener("resize", handleResize); grid.style.position = "relative"; @@ -1337,7 +1337,7 @@ class AlternativesInstaller extends ComfyDialog { this.element.style.width = "80%"; this.element.appendChild(panel); - handleResize(); + handleResize(); } createFilterCombo() { @@ -1354,11 +1354,11 @@ class AlternativesInstaller extends ComfyDialog { let items = [ - { value:'*', text:'Filter: all' }, - { value:'Disabled', text:'Filter: disabled' }, - { value:'Update', text:'Filter: update' }, - { value:'True', text:'Filter: installed' }, - { value:'False', text:'Filter: not-installed' }, + { value: '*', text: 'Filter: all' }, + { value: 'Disabled', text: 'Filter: disabled' }, + { value: 'Update', text: 'Filter: update' }, + { value: 'True', text: 'Filter: installed' }, + { value: 'False', text: 'Filter: not-installed' }, ]; items.forEach(item => { @@ -1369,13 +1369,13 @@ class AlternativesInstaller extends ComfyDialog { }); let self = this; - combo.addEventListener('change', function(event) { + combo.addEventListener('change', function (event) { self.filter = event.target.value; self.apply_searchbox(); }); - if(self.filter) { - combo.value = self.filter; + if (self.filter) { + combo.value = self.filter; } return combo; @@ -1383,18 +1383,18 @@ class AlternativesInstaller extends ComfyDialog { createHeaderControls() { let self = this; - this.search_box = $el('input', {type:'text', id:'manager-alternode-search-box', placeholder:'input search keyword', value:this.search_keyword}, []); + this.search_box = $el('input', { type: 'text', id: 'manager-alternode-search-box', placeholder: 'input search keyword', value: this.search_keyword }, []); this.search_box.style.height = "25px"; this.search_box.onkeydown = (event) => { - if (event.key === 'Enter') { - self.search_keyword = self.search_box.value; - self.apply_searchbox(); - } - if (event.key === 'Escape') { - self.search_keyword = self.search_box.value; - self.apply_searchbox(); - } - }; + if (event.key === 'Enter') { + self.search_keyword = self.search_box.value; + self.apply_searchbox(); + } + if (event.key === 'Escape') { + self.search_keyword = self.search_box.value; + self.apply_searchbox(); + } + }; let search_button = document.createElement("button"); search_button.innerHTML = "Search"; @@ -1407,12 +1407,12 @@ class AlternativesInstaller extends ComfyDialog { let filter_control = this.createFilterCombo(); filter_control.style.display = "inline-block"; - let cell = $el('td', {width:'100%'}, [filter_control, this.search_box, ' ', search_button]); - let search_control = $el('table', {width:'100%'}, - [ - $el('tr', {}, [cell]) - ] - ); + let cell = $el('td', { width: '100%' }, [filter_control, this.search_box, ' ', search_button]); + let search_control = $el('table', { width: '100%' }, + [ + $el('tr', {}, [cell]) + ] + ); cell.style.textAlign = "right"; this.element.appendChild(search_control); @@ -1424,7 +1424,7 @@ class AlternativesInstaller extends ComfyDialog { close_button.onclick = () => { this.close(); } close_button.style.display = "inline-block"; - this.message_box = $el('div', {id:'alternatives-installer-message'}, [$el('br'), '']); + this.message_box = $el('div', { id: 'alternatives-installer-message' }, [$el('br'), '']); this.message_box.style.height = '60px'; this.message_box.style.verticalAlign = 'middle'; @@ -1437,7 +1437,7 @@ class AlternativesInstaller extends ComfyDialog { this.invalidateControl(); this.element.style.display = "block"; } - catch(exception) { + catch (exception) { app.ui.dialog.show(`Failed to get alternatives list. / ${exception}`); console.error(exception); } @@ -1471,7 +1471,7 @@ class ModelInstaller extends ComfyDialog { type: "button", textContent: "Close", onclick: () => { this.close(); } - }) + }) ]; } @@ -1480,7 +1480,7 @@ class ModelInstaller extends ComfyDialog { self.updateMessage(`
Installing '${target.name}'`); - for(let i in self.install_buttons) { + for (let i in self.install_buttons) { self.install_buttons[i].disabled = true; self.install_buttons[i].style.backgroundColor = 'gray'; } @@ -1488,20 +1488,20 @@ class ModelInstaller extends ComfyDialog { apply_searchbox(data) { let keyword = this.search_box.value.toLowerCase(); - for(let i in this.grid_rows) { + for (let i in this.grid_rows) { let data = this.grid_rows[i].data; let content = data.name.toLowerCase() + data.type.toLowerCase() + data.base.toLowerCase() + data.description.toLowerCase(); - if(this.filter && this.filter != '*') { - if(this.filter != data.installed) { + if (this.filter && this.filter != '*') { + if (this.filter != data.installed) { this.grid_rows[i].control.style.display = 'none'; continue; } } - if(keyword == "") + if (keyword == "") this.grid_rows[i].control.style.display = null; - else if(content.includes(keyword)) { + else if (content.includes(keyword)) { this.grid_rows[i].control.style.display = null; } else { @@ -1520,7 +1520,7 @@ class ModelInstaller extends ComfyDialog { await this.createHeaderControls(); - if(this.search_keyword) { + if (this.search_keyword) { this.search_box.value = this.search_keyword; } @@ -1538,14 +1538,14 @@ class ModelInstaller extends ComfyDialog { var grid = document.createElement('table'); grid.setAttribute('id', 'external-models-grid'); - var thead = document.createElement('thead'); - var tbody = document.createElement('tbody'); + var thead = document.createElement('thead'); + var tbody = document.createElement('tbody'); var headerRow = document.createElement('tr'); thead.style.position = "sticky"; thead.style.top = "0px"; - thead.style.borderCollapse = "collapse"; - thead.style.tableLayout = "fixed"; + thead.style.borderCollapse = "collapse"; + thead.style.tableLayout = "fixed"; var header1 = document.createElement('th'); header1.innerHTML = '  ID  '; @@ -1570,7 +1570,7 @@ class ModelInstaller extends ComfyDialog { header_down.innerHTML = 'Download'; header_down.style.width = "50px"; - thead.appendChild(headerRow); + thead.appendChild(headerRow); headerRow.appendChild(header1); headerRow.appendChild(header2); headerRow.appendChild(header3); @@ -1590,13 +1590,13 @@ class ModelInstaller extends ComfyDialog { this.grid_rows = {}; - if(this.data) + if (this.data) for (var i = 0; i < this.data.length; i++) { const data = this.data[i]; var dataRow = document.createElement('tr'); var data1 = document.createElement('td'); data1.style.textAlign = "center"; - data1.innerHTML = i+1; + data1.innerHTML = i + 1; var data2 = document.createElement('td'); data2.innerHTML = ` ${data.type}`; var data3 = document.createElement('td'); @@ -1619,23 +1619,23 @@ class ModelInstaller extends ComfyDialog { installBtn.innerHTML = 'Install'; this.install_buttons.push(installBtn); - switch(data.installed) { - case 'True': - installBtn.innerHTML = 'Installed'; - installBtn.style.backgroundColor = 'green'; - installBtn.style.color = 'white'; - installBtn.disabled = true; - break; - default: - installBtn.innerHTML = 'Install'; - installBtn.style.backgroundColor = 'black'; - installBtn.style.color = 'white'; - break; + switch (data.installed) { + case 'True': + installBtn.innerHTML = 'Installed'; + installBtn.style.backgroundColor = 'green'; + installBtn.style.color = 'white'; + installBtn.disabled = true; + break; + default: + installBtn.innerHTML = 'Install'; + installBtn.style.backgroundColor = 'black'; + installBtn.style.color = 'white'; + break; } installBtn.style.width = "100px"; - installBtn.addEventListener('click', function() { + installBtn.addEventListener('click', function () { install_model(data); }); @@ -1654,20 +1654,20 @@ class ModelInstaller extends ComfyDialog { dataRow.appendChild(data_install); tbody.appendChild(dataRow); - this.grid_rows[i] = {data:data, control:dataRow}; + this.grid_rows[i] = { data: data, control: dataRow }; } - let self = this; + let self = this; const panel = document.createElement('div'); - panel.style.width = "100%"; + panel.style.width = "100%"; panel.appendChild(grid); - function handleResize() { - const parentHeight = self.element.clientHeight; - const gridHeight = parentHeight - 200; + function handleResize() { + const parentHeight = self.element.clientHeight; + const gridHeight = parentHeight - 200; - grid.style.height = gridHeight + "px"; - } + grid.style.height = gridHeight + "px"; + } window.addEventListener("resize", handleResize); grid.style.position = "relative"; @@ -1679,7 +1679,7 @@ class ModelInstaller extends ComfyDialog { this.element.style.width = "80%"; this.element.appendChild(panel); - handleResize(); + handleResize(); } createFilterCombo() { @@ -1696,9 +1696,9 @@ class ModelInstaller extends ComfyDialog { let items = [ - { value:'*', text:'Filter: all' }, - { value:'True', text:'Filter: installed' }, - { value:'False', text:'Filter: not-installed' }, + { value: '*', text: 'Filter: all' }, + { value: 'True', text: 'Filter: installed' }, + { value: 'False', text: 'Filter: not-installed' }, ]; items.forEach(item => { @@ -1709,7 +1709,7 @@ class ModelInstaller extends ComfyDialog { }); let self = this; - combo.addEventListener('change', function(event) { + combo.addEventListener('change', function (event) { self.filter = event.target.value; self.apply_searchbox(); }); @@ -1719,18 +1719,18 @@ class ModelInstaller extends ComfyDialog { createHeaderControls() { let self = this; - this.search_box = $el('input', {type:'text', id:'manager-model-search-box', placeholder:'input search keyword', value:this.search_keyword}, []); + this.search_box = $el('input', { type: 'text', id: 'manager-model-search-box', placeholder: 'input search keyword', value: this.search_keyword }, []); this.search_box.style.height = "25px"; this.search_box.onkeydown = (event) => { - if (event.key === 'Enter') { - self.search_keyword = self.search_box.value; - self.apply_searchbox(); - } - if (event.key === 'Escape') { - self.search_keyword = self.search_box.value; - self.apply_searchbox(); - } - }; + if (event.key === 'Enter') { + self.search_keyword = self.search_box.value; + self.apply_searchbox(); + } + if (event.key === 'Escape') { + self.search_keyword = self.search_box.value; + self.apply_searchbox(); + } + }; let search_button = document.createElement("button"); search_button.innerHTML = "Search"; @@ -1743,12 +1743,12 @@ class ModelInstaller extends ComfyDialog { let filter_control = this.createFilterCombo(); filter_control.style.display = "inline-block"; - let cell = $el('td', {width:'100%'}, [filter_control, this.search_box, ' ', search_button]); - let search_control = $el('table', {width:'100%'}, - [ - $el('tr', {}, [cell]) - ] - ); + let cell = $el('td', { width: '100%' }, [filter_control, this.search_box, ' ', search_button]); + let search_control = $el('table', { width: '100%' }, + [ + $el('tr', {}, [cell]) + ] + ); cell.style.textAlign = "right"; this.element.appendChild(search_control); @@ -1760,7 +1760,7 @@ class ModelInstaller extends ComfyDialog { close_button.onclick = () => { this.close(); } close_button.style.display = "inline-block"; - this.message_box = $el('div', {id:'custom-download-message'}, [$el('br'), '']); + this.message_box = $el('div', { id: 'custom-download-message' }, [$el('br'), '']); this.message_box.style.height = '60px'; this.message_box.style.verticalAlign = 'middle'; @@ -1773,7 +1773,7 @@ class ModelInstaller extends ComfyDialog { this.invalidateControl(); this.element.style.display = "block"; } - catch(exception) { + catch (exception) { app.ui.dialog.show(`Failed to get external model list. / ${exception}`); } } @@ -1786,99 +1786,99 @@ class ManagerMenuDialog extends ComfyDialog { local_mode_checkbox = null; createButtons() { - this.local_mode_checkbox = $el("input",{type:'checkbox', id:"use_local_db"},[]) - const checkbox_text = $el("label",{},[" Use local DB"]) + this.local_mode_checkbox = $el("input", { type: 'checkbox', id: "use_local_db" }, []) + const checkbox_text = $el("label", {}, [" Use local DB"]) checkbox_text.style.color = "var(--fg-color)"; checkbox_text.style.marginRight = "10px"; - this.update_check_checkbox = $el("input",{type:'checkbox', id:"skip_update_check"},[]) - const uc_checkbox_text = $el("label",{},[" Skip update check"]) + this.update_check_checkbox = $el("input", { type: 'checkbox', id: "skip_update_check" }, []) + const uc_checkbox_text = $el("label", {}, [" Skip update check"]) uc_checkbox_text.style.color = "var(--fg-color)"; this.update_check_checkbox.checked = true; update_comfyui_button = - $el("button", { - type: "button", - textContent: "Update ComfyUI", - onclick: - () => updateComfyUI() - }); + $el("button", { + type: "button", + textContent: "Update ComfyUI", + onclick: + () => updateComfyUI() + }); fetch_updates_button = - $el("button", { - type: "button", - textContent: "Fetch Updates", - onclick: - () => fetchUpdates(this.update_check_checkbox) - }); + $el("button", { + type: "button", + textContent: "Fetch Updates", + onclick: + () => fetchUpdates(this.update_check_checkbox) + }); update_all_button = - $el("button", { - type: "button", - textContent: "Update All", - onclick: - () => updateAll(this.update_check_checkbox) - }); + $el("button", { + type: "button", + textContent: "Update All", + onclick: + () => updateAll(this.update_check_checkbox) + }); - // preview method + // preview method let preview_combo = document.createElement("select"); - 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)'}, [])); + 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; }) + 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}`); + preview_combo.addEventListener('change', function (event) { + api.fetchApi(`/manager/preview_method?value=${event.target.value}`); }); - // nickname + // nickname let badge_combo = document.createElement("select"); - 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:'id_nick', text:'Badge: #ID Nickname'}, [])); + 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: 'id_nick', text: 'Badge: #ID Nickname' }, [])); - api.fetchApi('/manager/badge_mode') - .then(response => response.text()) - .then(data => { badge_combo.value = data; badge_mode = data; }); + 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); + 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 + // channel let channel_combo = document.createElement("select"); - 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]}`}, [])); - } - } + 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.addEventListener('change', function (event) { + api.fetchApi(`/manager/channel_url_list?value=${event.target.value}`); + }); - channel_combo.value = data.selected; - } - catch(exception) { + channel_combo.value = data.selected; + } + catch (exception) { - } - }); + } + }); const res = [ - $el("tr.td", {width:"100%"}, [$el("font", {size:6, color:"white"}, [`ComfyUI Manager Menu`])]), + $el("tr.td", { width: "100%" }, [$el("font", { size: 6, color: "white" }, [`ComfyUI Manager Menu`])]), $el("br", {}, []), $el("div", {}, [this.local_mode_checkbox, checkbox_text, this.update_check_checkbox, uc_checkbox_text]), $el("br", {}, []), @@ -1887,7 +1887,7 @@ class ManagerMenuDialog extends ComfyDialog { textContent: "Install Custom Nodes", onclick: () => { - if(!CustomNodesInstaller.instance) + if (!CustomNodesInstaller.instance) CustomNodesInstaller.instance = new CustomNodesInstaller(app); CustomNodesInstaller.instance.show(false); } @@ -1898,7 +1898,7 @@ class ManagerMenuDialog extends ComfyDialog { textContent: "Install Missing Custom Nodes", onclick: () => { - if(!CustomNodesInstaller.instance) + if (!CustomNodesInstaller.instance) CustomNodesInstaller.instance = new CustomNodesInstaller(app); CustomNodesInstaller.instance.show(true); } @@ -1909,13 +1909,13 @@ class ManagerMenuDialog extends ComfyDialog { textContent: "Install Models", onclick: () => { - if(!ModelInstaller.instance) + if (!ModelInstaller.instance) ModelInstaller.instance = new ModelInstaller(app); ModelInstaller.instance.show(); } }), - $el("br", {}, []), + $el("br", {}, []), update_all_button, update_comfyui_button, fetch_updates_button, @@ -1926,7 +1926,7 @@ class ManagerMenuDialog extends ComfyDialog { textContent: "Alternatives of A1111", onclick: () => { - if(!AlternativesInstaller.instance) + if (!AlternativesInstaller.instance) AlternativesInstaller.instance = new AlternativesInstaller(app); AlternativesInstaller.instance.show(); } @@ -1950,13 +1950,13 @@ class ManagerMenuDialog extends ComfyDialog { onclick: () => { window.open("https://ltdrdata.github.io/", "comfyui-node-info"); } }), - $el("br", {}, []), - $el("hr", {width: "100%"}, []), + $el("br", {}, []), + $el("hr", { width: "100%" }, []), preview_combo, badge_combo, channel_combo, - $el("hr", {width: "100%"}, []), - $el("br", {}, []), + $el("hr", { width: "100%" }, []), + $el("br", {}, []), $el("button", { type: "button", @@ -1976,7 +1976,7 @@ class ManagerMenuDialog extends ComfyDialog { constructor() { super(); this.element = $el("div.comfy-modal", { parent: document.body }, - [ $el("div.comfy-modal-content", + [$el("div.comfy-modal-content", [...this.createButtons()]), ]); } @@ -1986,6 +1986,189 @@ class ManagerMenuDialog extends ComfyDialog { } } +class ShareDialog extends ComfyDialog { + static instance = null; + + createButtons() { + this.is_nsfw_checkbox = $el("input", { type: 'checkbox', id: "is_nsfw" }, []) + const is_nsfw_checkbox_text = $el("label", {}, [" Is this NSFW?"]) + this.is_nsfw_checkbox.style.color = "var(--fg-color)"; + this.is_nsfw_checkbox.checked = false; + + this.credits_input = $el("input", { + type: "text", + placeholder: "This will be used to give you credits", + required: false, + }, []); + + this.title_input = $el("input", { + type: "text", + // placeholder: "ex: Zombie animation - AnimateDiff", + required: false, + }, []); + + this.description_input = $el("textarea", { + // placeholder: "ex: ", + required: false, + }, []); + + this.share_button = $el("button", { + type: "button", + textContent: "Share", + }, []); + + this.final_message = $el("div", {}, []); + + this.share_button.onclick = async () => { + // alert("Title: " + this.title_input.value + "\nDescription: " + this.description_input.value + "\nCredits: " + this.credits_input.value + "\nNSFW: " + this.is_nsfw_checkbox.checked); + const prompt = await app.graphToPrompt(); + console.log(prompt); + + const nodes = app.graph._nodes; + + const potential_outputs = []; + const potential_output_nodes = []; + + // iterate over the array of nodes to find the ones that are marked as SaveImage + // TODO: Add support for AnimateDiffCombine, etc. nodes that save videos/gifs, etc. + for (let i = 0; i < nodes.length; i++) { + const node = nodes[i]; + if (node.type !== "SaveImage") { + continue; + } + + if (node.type === "SaveImage") { + potential_output_nodes.push(node); + + // check if node has an 'images' array property + if (node.hasOwnProperty("images") && Array.isArray(node.images)) { + // iterate over the images array and add each image to the potential_outputs array + for (let j = 0; j < node.images.length; j++) { + potential_outputs.push({ "type": "image", "image": node.images[j] }); + } + } + } + } + + if (potential_outputs.length === 0) { + if (potential_output_nodes.length === 0) { + // todo: add support for other output node types (animatediff combine, etc.) + alert("No SaveImage node found. To share this workflow, please run a SaveImage node to your graph and re-run your prompt."); + } else { + alert("To share this, please run a prompt first and then click 'Share'."); + } + this.close(); + return; + } + + // Change the text of the share button to "Sharing..." to indicate that the share process has started + this.share_button.textContent = "Sharing..."; + + const response = await api.fetchApi(`/manager/share`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + credits: this.credits_input.value, + title: this.title_input.value, + description: this.description_input.value, + is_nsfw: this.is_nsfw_checkbox.checked, + prompt, + potential_outputs, + potential_output_nodes + }) + }); + + if (response.status != 200) { + alert("Failed to share your art. Please try again."); + this.close(); + return; + } + + this.final_message.innerHTML = "Your art has been shared: " + response.url + ""; + this.final_message.style.color = "green"; + + // hide the share button + this.share_button.style.display = "none"; + + // this.close(); + } + + + const res = + [ + $el("tr.td", { width: "100%" }, [ + $el("font", { size: 6, color: "white" }, [`Share your art`]), + $el("div", { size: 3, color: "white" }, [ + $el("a", { + href: `https://comfyworkflows.com/?ref=cms`, + target: `_blank`, + color: "white", + // style: `color:white;` + }, `comfyworkflows.com`) + ]) + ]), + $el("p", { size: 4, color: "white" }, [`Get a public link for this art & workflow.`]), + // $el("br", {}, []), + + $el("h2", { + textContent: "Your name/username (optional)", + size: 3, + color: "white" + }, []), + this.credits_input, + $el("br", {}, []), + + $el("h2", { + textContent: "Title (optional)", + size: 3, + color: "white" + }, []), + this.title_input, + $el("br", {}, []), + + $el("h2", { + textContent: "Description (optional)", + size: 3, + color: "white" + }, []), + this.description_input, + $el("br", {}, []), + + $el("div", {}, [this.is_nsfw_checkbox, is_nsfw_checkbox_text]), + this.final_message, + + $el("br", {}, []), + this.share_button, + + $el("button", { + type: "button", + textContent: "Close", + onclick: () => this.close() + }), + $el("br", {}, []), + ]; + + res[0].style.padding = "10px 10px 10px 10px"; + res[0].style.backgroundColor = "black"; //"linear-gradient(90deg, #00C9FF 0%, #92FE9D 100%)"; + res[0].style.textAlign = "center"; + res[0].style.height = "45px"; + return res; + } + + constructor() { + super(); + this.element = $el("div.comfy-modal", { parent: document.body }, + [$el("div.comfy-modal-content", + [...this.createButtons()]), + ]); + } + + show() { + this.element.style.display = "block"; + } +} + + app.registerExtension({ name: "Comfy.ManagerMenu", @@ -2000,107 +2183,121 @@ app.registerExtension({ const managerButton = document.createElement("button"); managerButton.textContent = "Manager"; managerButton.onclick = () => { - if(!ManagerMenuDialog.instance) - ManagerMenuDialog.instance = new ManagerMenuDialog(); - ManagerMenuDialog.instance.show(); - } + if (!ManagerMenuDialog.instance) + ManagerMenuDialog.instance = new ManagerMenuDialog(); + ManagerMenuDialog.instance.show(); + } menu.append(managerButton); + + const shareButton = document.createElement("button"); + shareButton.textContent = "Share"; + shareButton.onclick = () => { + if (!ShareDialog.instance) + ShareDialog.instance = new ShareDialog(); + ShareDialog.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"; + // shareButton.style.border = "none"; + // shareButton.style.borderRadius = "15px"; + menu.append(shareButton); }, async beforeRegisterNodeDef(nodeType, nodeData, app) { - const onDrawForeground = nodeType.prototype.onDrawForeground; - nodeType.prototype.onDrawForeground = function (ctx) { - const r = onDrawForeground?.apply?.(this, arguments); + const onDrawForeground = nodeType.prototype.onDrawForeground; + nodeType.prototype.onDrawForeground = function (ctx) { + const r = onDrawForeground?.apply?.(this, arguments); - if(!this.flags.collapsed && badge_mode != 'none' && nodeType.title_mode != LiteGraph.NO_TITLE) { - let text = ""; - if(badge_mode == 'id_nick') - text = `#${this.id} `; + if (!this.flags.collapsed && badge_mode != 'none' && nodeType.title_mode != LiteGraph.NO_TITLE) { + let text = ""; + if (badge_mode == 'id_nick') + text = `#${this.id} `; - if(nicknames[nodeData.name.trim()]) { - let nick = nicknames[nodeData.name.trim()]; + if (nicknames[nodeData.name.trim()]) { + let nick = nicknames[nodeData.name.trim()]; - if(nick.length > 25) { - text += nick.substring(0,23)+".."; - } - else { - text += nick; - } - } + if (nick.length > 25) { + text += nick.substring(0, 23) + ".."; + } + else { + text += nick; + } + } - if(text != "") { - let fgColor = "white"; - let bgColor = "#0F1F0F"; - let visible = true; + 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(this.size[0]-sz.width-12, -LiteGraph.NODE_TITLE_HEIGHT - 20, sz.width + 12, 20, 5); - ctx.fill(); + ctx.save(); + ctx.font = "12px sans-serif"; + const sz = ctx.measureText(text); + ctx.fillStyle = bgColor; + ctx.beginPath(); + ctx.roundRect(this.size[0] - sz.width - 12, -LiteGraph.NODE_TITLE_HEIGHT - 20, sz.width + 12, 20, 5); + ctx.fill(); - ctx.fillStyle = fgColor; - ctx.fillText(text, this.size[0]-sz.width-6, -LiteGraph.NODE_TITLE_HEIGHT - 6); - ctx.restore(); - } - } + ctx.fillStyle = fgColor; + ctx.fillText(text, this.size[0] - sz.width - 6, -LiteGraph.NODE_TITLE_HEIGHT - 6); + ctx.restore(); + } + } - return r; - }; + return r; + }; }, async loadedGraphNode(node, app) { - if(node.has_errors) { - const onDrawForeground = node.onDrawForeground; - node.onDrawForeground = function (ctx) { - const r = onDrawForeground?.apply?.(this, arguments); + if (node.has_errors) { + const onDrawForeground = node.onDrawForeground; + node.onDrawForeground = function (ctx) { + const r = onDrawForeground?.apply?.(this, arguments); - if(!this.flags.collapsed && badge_mode != 'none') { - let text = ""; - if(badge_mode == 'id_nick') - text = `#${this.id} `; + if (!this.flags.collapsed && badge_mode != 'none') { + let text = ""; + if (badge_mode == 'id_nick') + text = `#${this.id} `; - if(nicknames[node.type.trim()]) { - let nick = nicknames[node.type.trim()]; + if (nicknames[node.type.trim()]) { + let nick = nicknames[node.type.trim()]; - if(nick.length > 25) { - text += nick.substring(0,23)+".."; - } - else { - text += nick; - } - } + if (nick.length > 25) { + text += nick.substring(0, 23) + ".."; + } + else { + text += nick; + } + } - if(text != "") { - let fgColor = "white"; - let bgColor = "#0F1F0F"; - let visible = true; + 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(this.size[0]-sz.width-12, -LiteGraph.NODE_TITLE_HEIGHT - 20, sz.width + 12, 20, 5); - ctx.fill(); + ctx.save(); + ctx.font = "12px sans-serif"; + const sz = ctx.measureText(text); + ctx.fillStyle = bgColor; + ctx.beginPath(); + ctx.roundRect(this.size[0] - sz.width - 12, -LiteGraph.NODE_TITLE_HEIGHT - 20, sz.width + 12, 20, 5); + ctx.fill(); - ctx.fillStyle = fgColor; - ctx.fillText(text, this.size[0]-sz.width-6, -LiteGraph.NODE_TITLE_HEIGHT - 6); - ctx.restore(); + ctx.fillStyle = fgColor; + ctx.fillText(text, this.size[0] - sz.width - 6, -LiteGraph.NODE_TITLE_HEIGHT - 6); + ctx.restore(); - ctx.save(); - ctx.font = "bold 14px sans-serif"; - const sz2 = ctx.measureText(node.type); - ctx.fillStyle = 'white'; - ctx.fillText(node.type, this.size[0]/2-sz2.width/2, this.size[1]/2); - ctx.restore(); - } - } + ctx.save(); + ctx.font = "bold 14px sans-serif"; + const sz2 = ctx.measureText(node.type); + ctx.fillStyle = 'white'; + ctx.fillText(node.type, this.size[0] / 2 - sz2.width / 2, this.size[1] / 2); + ctx.restore(); + } + } - return r; - }; - } + return r; + }; + } } -}); +}); \ No newline at end of file From 31fee856474d59d86b3d1cd760a9b58ad5f481b7 Mon Sep 17 00:00:00 2001 From: thecooltechguy Date: Fri, 20 Oct 2023 00:54:49 -0700 Subject: [PATCH 02/14] working v1 --- __init__.py | 73 +++++++++++++++++++++++++++++++++++++++++-- js/comfyui-manager.js | 24 +++++++++++--- 2 files changed, 90 insertions(+), 7 deletions(-) diff --git a/__init__.py b/__init__.py index da72cf70..68ce38fd 100644 --- a/__init__.py +++ b/__init__.py @@ -1,4 +1,5 @@ import configparser +import mimetypes import shutil import folder_paths import os @@ -1200,8 +1201,76 @@ async def channel_url_list(request): async def share_art(request): # get json data json_data = await request.json() - print(json_data) - return web.Response(status=200) + + credits = json_data['credits'] + title = json_data['title'] + description = json_data['description'] + is_nsfw = json_data['is_nsfw'] + prompt = json_data['prompt'] + potential_outputs = json_data['potential_outputs'] + + # for now, pick the first output + output_to_share = potential_outputs[0] + assert output_to_share['type'] in ('image',) + + output_dir = folder_paths.get_output_directory() + asset_filename = output_to_share['image']['filename'] + asset_subfolder = output_to_share['image']['subfolder'] + asset_output_type = output_to_share['image']['type'] + + assert asset_output_type == "output" + + if asset_subfolder: + asset_filepath = os.path.join(output_dir, asset_subfolder, asset_filename) + else: + asset_filepath = os.path.join(output_dir, asset_filename) + + # get the mime type of the asset + assetFileType = mimetypes.guess_type(asset_filepath)[0] + + share_website_host = "https://comfyworkflows.com" + share_endpoint = f"{share_website_host}/api" + + # get presigned urls + async with aiohttp.ClientSession(trust_env=True, connector=aiohttp.TCPConnector(verify_ssl=False)) as session: + async with session.post( + f"{share_endpoint}/get_presigned_urls", + json={ + "assetFileName": asset_filename, + "assetFileType": assetFileType, + }, + ) as resp: + assert resp.status == 200 + presigned_urls_json = await resp.json() + assetFilePresignedUrl = presigned_urls_json["assetFilePresignedUrl"] + assetFileKey = presigned_urls_json["assetFileKey"] + + # upload asset + async with aiohttp.ClientSession(trust_env=True, connector=aiohttp.TCPConnector(verify_ssl=False)) as session: + async with session.put(assetFilePresignedUrl, data=open(asset_filepath, "rb")) as resp: + assert resp.status == 200 + + # make a POST request to /api/upload_workflow with form data key values + async with aiohttp.ClientSession(trust_env=True, connector=aiohttp.TCPConnector(verify_ssl=False)) as session: + form = aiohttp.FormData() + form.add_field("assetFileKey", assetFileKey) + form.add_field("assetFileType", assetFileType) + form.add_field("sharedWorkflowWorkflowJsonString", json.dumps(prompt['workflow'])) + form.add_field("sharedWorkflowPromptJsonString", json.dumps(prompt['output'])) + form.add_field("shareWorkflowCredits", credits) + form.add_field("shareWorkflowTitle", title) + form.add_field("shareWorkflowDescription", description) + form.add_field("shareWorkflowIsNSFW", str(is_nsfw).lower()) + + async with session.post( + f"{share_endpoint}/upload_workflow", + data=form, + ) as resp: + assert resp.status == 200 + upload_workflow_json = await resp.json() + workflowId = upload_workflow_json["workflowId"] + + return web.json_response({"url" : f"{share_website_host}/workflows/{workflowId}"}, content_type='application/json', status=200) WEB_DIRECTORY = "js" NODE_CLASS_MAPPINGS = {} diff --git a/js/comfyui-manager.js b/js/comfyui-manager.js index c91980bb..13ec6184 100644 --- a/js/comfyui-manager.js +++ b/js/comfyui-manager.js @@ -2055,7 +2055,7 @@ class ShareDialog extends ComfyDialog { // todo: add support for other output node types (animatediff combine, etc.) alert("No SaveImage node found. To share this workflow, please run a SaveImage node to your graph and re-run your prompt."); } else { - alert("To share this, please run a prompt first and then click 'Share'."); + alert("To share this, first run a prompt. Once it's done, click 'Share'."); } this.close(); return; @@ -2074,7 +2074,7 @@ class ShareDialog extends ComfyDialog { is_nsfw: this.is_nsfw_checkbox.checked, prompt, potential_outputs, - potential_output_nodes + // potential_output_nodes }) }); @@ -2084,12 +2084,15 @@ class ShareDialog extends ComfyDialog { return; } - this.final_message.innerHTML = "Your art has been shared: " + response.url + ""; + const response_json = await response.json(); + + this.final_message.innerHTML = "Your art has been shared: " + response_json.url + ""; this.final_message.style.color = "green"; // hide the share button + this.share_button.textContent = "Shared!"; this.share_button.style.display = "none"; - + // this.close(); } @@ -2143,7 +2146,18 @@ class ShareDialog extends ComfyDialog { $el("button", { type: "button", textContent: "Close", - onclick: () => this.close() + onclick: () => { + // Reset state + this.share_button.textContent = "Share"; + this.share_button.style.display = "inline-block"; + this.final_message.innerHTML = ""; + this.final_message.style.color = "white"; + this.credits_input.value = ""; + this.title_input.value = ""; + this.description_input.value = ""; + this.is_nsfw_checkbox.checked = false; + this.close() + } }), $el("br", {}, []), ]; From b93ae5af40524741e4100cb124b951793410c9ae Mon Sep 17 00:00:00 2001 From: thecooltechguy Date: Fri, 20 Oct 2023 17:06:39 -0700 Subject: [PATCH 03/14] added matrix server as a destination for sharing --- __init__.py | 133 +++++++++++++++++++++++++++++------------- js/comfyui-manager.js | 79 ++++++++++++++++++++++--- requirements.txt | 3 +- 3 files changed, 164 insertions(+), 51 deletions(-) diff --git a/__init__.py b/__init__.py index 68ce38fd..50e724a0 100644 --- a/__init__.py +++ b/__init__.py @@ -1196,12 +1196,25 @@ async def channel_url_list(request): return web.Response(status=200) +@server.PromptServer.instance.routes.get("/manager/check_matrix") +async def check_matrix(request): + # Check if the user has provided Matrix credentials in a file called 'matrix_accesstoken' + # in the same directory as the ComfyUI base folder + print("Checking for Matrix credentials...") + if not os.path.exists(os.path.join(folder_paths.base_path, "matrix_accesstoken")): + return web.Response(status=404) + with open(os.path.join(folder_paths.base_path, "matrix_accesstoken"), "r") as f: + access_token = f.read() + if not access_token.strip(): + return web.Response(status=404) + return web.Response(status=200) @server.PromptServer.instance.routes.post("/manager/share") async def share_art(request): # get json data json_data = await request.json() + share_destinations = json_data['share_destinations'] credits = json_data['credits'] title = json_data['title'] description = json_data['description'] @@ -1228,49 +1241,87 @@ async def share_art(request): # get the mime type of the asset assetFileType = mimetypes.guess_type(asset_filepath)[0] - share_website_host = "https://comfyworkflows.com" - share_endpoint = f"{share_website_host}/api" + if "comfyworkflows" in share_destinations: + share_website_host = "https://comfyworkflows.com" + share_endpoint = f"{share_website_host}/api" + + # get presigned urls + async with aiohttp.ClientSession(trust_env=True, connector=aiohttp.TCPConnector(verify_ssl=False)) as session: + async with session.post( + f"{share_endpoint}/get_presigned_urls", + json={ + "assetFileName": asset_filename, + "assetFileType": assetFileType, + }, + ) as resp: + assert resp.status == 200 + presigned_urls_json = await resp.json() + assetFilePresignedUrl = presigned_urls_json["assetFilePresignedUrl"] + assetFileKey = presigned_urls_json["assetFileKey"] + + # upload asset + async with aiohttp.ClientSession(trust_env=True, connector=aiohttp.TCPConnector(verify_ssl=False)) as session: + async with session.put(assetFilePresignedUrl, data=open(asset_filepath, "rb")) as resp: + assert resp.status == 200 - # get presigned urls - async with aiohttp.ClientSession(trust_env=True, connector=aiohttp.TCPConnector(verify_ssl=False)) as session: - async with session.post( - f"{share_endpoint}/get_presigned_urls", - json={ - "assetFileName": asset_filename, - "assetFileType": assetFileType, - }, - ) as resp: - assert resp.status == 200 - presigned_urls_json = await resp.json() - assetFilePresignedUrl = presigned_urls_json["assetFilePresignedUrl"] - assetFileKey = presigned_urls_json["assetFileKey"] + # make a POST request to /api/upload_workflow with form data key values + async with aiohttp.ClientSession(trust_env=True, connector=aiohttp.TCPConnector(verify_ssl=False)) as session: + form = aiohttp.FormData() + form.add_field("assetFileKey", assetFileKey) + form.add_field("assetFileType", assetFileType) + form.add_field("sharedWorkflowWorkflowJsonString", json.dumps(prompt['workflow'])) + form.add_field("sharedWorkflowPromptJsonString", json.dumps(prompt['output'])) + form.add_field("shareWorkflowCredits", credits) + form.add_field("shareWorkflowTitle", title) + form.add_field("shareWorkflowDescription", description) + form.add_field("shareWorkflowIsNSFW", str(is_nsfw).lower()) + + async with session.post( + f"{share_endpoint}/upload_workflow", + data=form, + ) as resp: + assert resp.status == 200 + upload_workflow_json = await resp.json() + workflowId = upload_workflow_json["workflowId"] + + # check if the user has provided Matrix credentials + if "matrix" in share_destinations: + with open(os.path.join(folder_paths.base_path, "matrix_accesstoken"), "r") as f: + access_token = f.read().strip() + + comfyui_share_room_id = '!LGYSoacpJPhIfBqVfb:matrix.org' + filename = os.path.basename(asset_filepath) + content_type = assetFileType + + try: + from matrix_client.api import MatrixHttpApi + matrix = MatrixHttpApi("https://matrix.org", token=access_token) + with open(asset_filepath, 'rb') as f: + mxc_url = matrix.media_upload(f.read(), content_type, filename=filename)['content_uri'] + text_content = "" + if title: + text_content += f"{title}\n" + if description: + text_content += f"{description}\n" + if credits: + text_content += f"\ncredits: {credits}\n" + response = matrix.send_message(comfyui_share_room_id, text_content) + print(response) + response = matrix.send_content(comfyui_share_room_id, mxc_url, filename, 'm.image') + print(response) + except: + import traceback + traceback.print_exc() + return web.json_response({"error" : "An error occurred when sharing your art to Matrix."}, content_type='application/json', status=500) - # upload asset - async with aiohttp.ClientSession(trust_env=True, connector=aiohttp.TCPConnector(verify_ssl=False)) as session: - async with session.put(assetFilePresignedUrl, data=open(asset_filepath, "rb")) as resp: - assert resp.status == 200 - - # make a POST request to /api/upload_workflow with form data key values - async with aiohttp.ClientSession(trust_env=True, connector=aiohttp.TCPConnector(verify_ssl=False)) as session: - form = aiohttp.FormData() - form.add_field("assetFileKey", assetFileKey) - form.add_field("assetFileType", assetFileType) - form.add_field("sharedWorkflowWorkflowJsonString", json.dumps(prompt['workflow'])) - form.add_field("sharedWorkflowPromptJsonString", json.dumps(prompt['output'])) - form.add_field("shareWorkflowCredits", credits) - form.add_field("shareWorkflowTitle", title) - form.add_field("shareWorkflowDescription", description) - form.add_field("shareWorkflowIsNSFW", str(is_nsfw).lower()) - - async with session.post( - f"{share_endpoint}/upload_workflow", - data=form, - ) as resp: - assert resp.status == 200 - upload_workflow_json = await resp.json() - workflowId = upload_workflow_json["workflowId"] - - return web.json_response({"url" : f"{share_website_host}/workflows/{workflowId}"}, content_type='application/json', status=200) + return web.json_response({ + "comfyworkflows" : { + "url" : None if "comfyworkflows" not in share_destinations else f"{share_website_host}/workflows/{workflowId}", + }, + "matrix" : { + "success" : None if "matrix" not in share_destinations else True + } + }, content_type='application/json', status=200) WEB_DIRECTORY = "js" NODE_CLASS_MAPPINGS = {} diff --git a/js/comfyui-manager.js b/js/comfyui-manager.js index 13ec6184..b98bf809 100644 --- a/js/comfyui-manager.js +++ b/js/comfyui-manager.js @@ -1995,6 +1995,16 @@ class ShareDialog extends ComfyDialog { this.is_nsfw_checkbox.style.color = "var(--fg-color)"; this.is_nsfw_checkbox.checked = false; + this.matrix_destination_checkbox = $el("input", { type: 'checkbox', id: "matrix_destination" }, []) + const matrix_destination_checkbox_text = $el("label", {}, [" ComfyUI Matrix server"]) + this.matrix_destination_checkbox.style.color = "var(--fg-color)"; + this.matrix_destination_checkbox.checked = true; + + this.comfyworkflows_destination_checkbox = $el("input", { type: 'checkbox', id: "comfyworkflows_destination" }, []) + const comfyworkflows_destination_checkbox_text = $el("label", {}, [" ComfyWorkflows.com"]) + this.comfyworkflows_destination_checkbox.style.color = "var(--fg-color)"; + this.comfyworkflows_destination_checkbox.checked = true; + this.credits_input = $el("input", { type: "text", placeholder: "This will be used to give you credits", @@ -2026,6 +2036,35 @@ class ShareDialog extends ComfyDialog { const nodes = app.graph._nodes; + const destinations = []; + if (this.matrix_destination_checkbox.checked) { + destinations.push("matrix"); + } + if (this.comfyworkflows_destination_checkbox.checked) { + destinations.push("comfyworkflows"); + } + + // if destinations includes matrix, make an api call to /manager/check_matrix to ensure that the user has configured their matrix settings + if (destinations.includes("matrix")) { + const response = await api.fetchApi(`/manager/check_matrix`); + if (response.status != 200) { + alert("Please add your Matrix access token in a file called 'matrix_accesstoken' in the ComfyUI folder. To get your Matrix access token, go to https://app.element.io/, click on your profile, click on 'All settings', click on 'Help & About', and copy 'Access Token', and then click on 'Regenerate'."); + // Reset state + this.matrix_destination_checkbox.checked = true; + this.comfyworkflows_destination_checkbox.checked = true; + this.share_button.textContent = "Share"; + this.share_button.style.display = "inline-block"; + this.final_message.innerHTML = ""; + this.final_message.style.color = "white"; + this.credits_input.value = ""; + this.title_input.value = ""; + this.description_input.value = ""; + this.is_nsfw_checkbox.checked = false; + this.close(); + return; + } + } + const potential_outputs = []; const potential_output_nodes = []; @@ -2068,6 +2107,7 @@ class ShareDialog extends ComfyDialog { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ + share_destinations: destinations, credits: this.credits_input.value, title: this.title_input.value, description: this.description_input.value, @@ -2086,7 +2126,17 @@ class ShareDialog extends ComfyDialog { const response_json = await response.json(); - this.final_message.innerHTML = "Your art has been shared: " + response_json.url + ""; + if (response_json.comfyworkflows.url) { + this.final_message.innerHTML = "Your art has been shared: " + response_json.comfyworkflows.url + ""; + if (response_json.matrix.success) { + this.final_message.innerHTML += "
Your art has been shared in the ComfyUI Matrix server's #share channel!"; + } + } else { + if (response_json.matrix.success) { + this.final_message.innerHTML = "Your art has been shared in the ComfyUI Matrix server's #share channel!"; + } + } + this.final_message.style.color = "green"; // hide the share button @@ -2101,18 +2151,27 @@ class ShareDialog extends ComfyDialog { [ $el("tr.td", { width: "100%" }, [ $el("font", { size: 6, color: "white" }, [`Share your art`]), - $el("div", { size: 3, color: "white" }, [ - $el("a", { - href: `https://comfyworkflows.com/?ref=cms`, - target: `_blank`, - color: "white", - // style: `color:white;` - }, `comfyworkflows.com`) - ]) + // $el("div", { size: 3, color: "white" }, [ + // $el("a", { + // href: `https://comfyworkflows.com/?ref=cms`, + // target: `_blank`, + // color: "white", + // // style: `color:white;` + // }, `comfyworkflows.com`) + // ]) ]), $el("p", { size: 4, color: "white" }, [`Get a public link for this art & workflow.`]), // $el("br", {}, []), + $el("div", {}, [ + $el("p", { size: 3, color: "white" }, [`Select where to share your art:`]), + this.matrix_destination_checkbox, + matrix_destination_checkbox_text, + $el("br", {}, []), + this.comfyworkflows_destination_checkbox, + comfyworkflows_destination_checkbox_text, + ]), + $el("h2", { textContent: "Your name/username (optional)", size: 3, @@ -2148,6 +2207,8 @@ class ShareDialog extends ComfyDialog { textContent: "Close", onclick: () => { // Reset state + this.matrix_destination_checkbox.checked = true; + this.comfyworkflows_destination_checkbox.checked = true; this.share_button.textContent = "Share"; this.share_button.style.display = "inline-block"; this.final_message.innerHTML = ""; diff --git a/requirements.txt b/requirements.txt index 5779f396..3467b3e5 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1 +1,2 @@ -GitPython \ No newline at end of file +GitPython +matrix-client==0.4.0 \ No newline at end of file From 6623bbc97970fdb32c6be76b99b2300370df9590 Mon Sep 17 00:00:00 2001 From: thecooltechguy Date: Tue, 24 Oct 2023 21:54:10 -0700 Subject: [PATCH 04/14] made it much easier to configure credentials for sharing --- __init__.py | 98 ++++++++++++++--- js/comfyui-manager.js | 239 ++++++++++++++++++++++++++++++++---------- 2 files changed, 267 insertions(+), 70 deletions(-) diff --git a/__init__.py b/__init__.py index 50e724a0..ea66e128 100644 --- a/__init__.py +++ b/__init__.py @@ -1196,24 +1196,75 @@ async def channel_url_list(request): return web.Response(status=200) -@server.PromptServer.instance.routes.get("/manager/check_matrix") -async def check_matrix(request): +def get_matrix_auth(): + if not os.path.exists(os.path.join(folder_paths.base_path, "matrix_auth")): + return None + with open(os.path.join(folder_paths.base_path, "matrix_auth"), "r") as f: + matrix_auth = f.read() + homeserver, username, password = matrix_auth.strip().split("\n") + if not homeserver or not username or not password: + return None + return { + "homeserver": homeserver, + "username": username, + "password": password, + } + +def get_comfyworkflows_auth(): + if not os.path.exists(os.path.join(folder_paths.base_path, "comfyworkflows_sharekey")): + return None + with open(os.path.join(folder_paths.base_path, "comfyworkflows_sharekey"), "r") as f: + share_key = f.read() + if not share_key.strip(): + return None + return share_key + +@server.PromptServer.instance.routes.get("/manager/get_matrix_auth") +async def api_get_matrix_auth(request): + # print("Getting stored Matrix credentials...") + matrix_auth = get_matrix_auth() + if not matrix_auth: + return web.Response(status=404) + return web.json_response(matrix_auth) + +@server.PromptServer.instance.routes.get("/manager/get_comfyworkflows_auth") +async def api_get_comfyworkflows_auth(request): # Check if the user has provided Matrix credentials in a file called 'matrix_accesstoken' # in the same directory as the ComfyUI base folder - print("Checking for Matrix credentials...") - if not os.path.exists(os.path.join(folder_paths.base_path, "matrix_accesstoken")): + # print("Getting stored Comfyworkflows.com auth...") + comfyworkflows_auth = get_comfyworkflows_auth() + if not comfyworkflows_auth: return web.Response(status=404) - with open(os.path.join(folder_paths.base_path, "matrix_accesstoken"), "r") as f: - access_token = f.read() - if not access_token.strip(): - return web.Response(status=404) - return web.Response(status=200) + return web.json_response({"comfyworkflows_sharekey" : comfyworkflows_auth}) + +def set_matrix_auth(json_data): + homeserver = json_data['homeserver'] + username = json_data['username'] + password = json_data['password'] + with open(os.path.join(folder_paths.base_path, "matrix_auth"), "w") as f: + f.write("\n".join([homeserver, username, password])) + +def set_comfyworkflows_auth(comfyworkflows_sharekey): + with open(os.path.join(folder_paths.base_path, "comfyworkflows_sharekey"), "w") as f: + f.write(comfyworkflows_sharekey) + +def has_provided_matrix_auth(matrix_auth): + return matrix_auth['homeserver'].strip() and matrix_auth['username'].strip() and matrix_auth['password'].strip() + +def has_provided_comfyworkflows_auth(comfyworkflows_sharekey): + return comfyworkflows_sharekey.strip() @server.PromptServer.instance.routes.post("/manager/share") async def share_art(request): # get json data json_data = await request.json() + matrix_auth = json_data['matrix_auth'] + comfyworkflows_sharekey = json_data['cw_auth']['cw_sharekey'] + + set_matrix_auth(matrix_auth) + set_comfyworkflows_auth(comfyworkflows_sharekey) + share_destinations = json_data['share_destinations'] credits = json_data['credits'] title = json_data['title'] @@ -1267,6 +1318,9 @@ async def share_art(request): # make a POST request to /api/upload_workflow with form data key values async with aiohttp.ClientSession(trust_env=True, connector=aiohttp.TCPConnector(verify_ssl=False)) as session: form = aiohttp.FormData() + if comfyworkflows_sharekey: + form.add_field("shareKey", comfyworkflows_sharekey) + form.add_field("source", "comfyui_manager") form.add_field("assetFileKey", assetFileKey) form.add_field("assetFileType", assetFileType) form.add_field("sharedWorkflowWorkflowJsonString", json.dumps(prompt['workflow'])) @@ -1285,17 +1339,31 @@ async def share_art(request): workflowId = upload_workflow_json["workflowId"] # check if the user has provided Matrix credentials - if "matrix" in share_destinations: - with open(os.path.join(folder_paths.base_path, "matrix_accesstoken"), "r") as f: - access_token = f.read().strip() - + if "matrix" in share_destinations: comfyui_share_room_id = '!LGYSoacpJPhIfBqVfb:matrix.org' filename = os.path.basename(asset_filepath) content_type = assetFileType try: from matrix_client.api import MatrixHttpApi - matrix = MatrixHttpApi("https://matrix.org", token=access_token) + from matrix_client.client import MatrixClient + + homeserver = 'matrix.org' + if matrix_auth: + homeserver = matrix_auth.get('homeserver', 'matrix.org') + homeserver = homeserver.replace("http://", "https://") + if not homeserver.startswith("https://"): + homeserver = "https://" + homeserver + + client = MatrixClient(homeserver) + try: + token = client.login(username=matrix_auth['username'], password=matrix_auth['password']) + if not token: + return web.json_response({"error" : "Invalid Matrix credentials."}, content_type='application/json', status=400) + except: + return web.json_response({"error" : "Invalid Matrix credentials."}, content_type='application/json', status=400) + + matrix = MatrixHttpApi(homeserver, token=token) with open(asset_filepath, 'rb') as f: mxc_url = matrix.media_upload(f.read(), content_type, filename=filename)['content_uri'] text_content = "" @@ -1306,9 +1374,7 @@ async def share_art(request): if credits: text_content += f"\ncredits: {credits}\n" response = matrix.send_message(comfyui_share_room_id, text_content) - print(response) response = matrix.send_content(comfyui_share_room_id, mxc_url, filename, 'm.image') - print(response) except: import traceback traceback.print_exc() diff --git a/js/comfyui-manager.js b/js/comfyui-manager.js index b98bf809..fb10121f 100644 --- a/js/comfyui-manager.js +++ b/js/comfyui-manager.js @@ -1988,6 +1988,22 @@ class ManagerMenuDialog extends ComfyDialog { class ShareDialog extends ComfyDialog { static instance = null; + static matrix_auth = { homeserver: "matrix.org", username: "", password: "" }; + static cw_sharekey = ""; + + constructor() { + super(); + + this.element = $el("div.comfy-modal", { parent: document.body }, + [$el("div.comfy-modal-content", + { + style: { + overflowY: "auto", + } + }, + [...this.createButtons()]), + ]); + } createButtons() { this.is_nsfw_checkbox = $el("input", { type: 'checkbox', id: "is_nsfw" }, []) @@ -2005,20 +2021,27 @@ class ShareDialog extends ComfyDialog { this.comfyworkflows_destination_checkbox.style.color = "var(--fg-color)"; this.comfyworkflows_destination_checkbox.checked = true; + this.matrix_homeserver_input = $el("input", { type: 'text', id: "matrix_homeserver", placeholder: "matrix.org", value: ShareDialog.matrix_auth.homeserver || 'matrix.org' }, []); + this.matrix_username_input = $el("input", { type: 'text', placeholder: "Username", value: ShareDialog.matrix_auth.username || '' }, []); + this.matrix_password_input = $el("input", { type: 'password', placeholder: "Password", value: ShareDialog.matrix_auth.password || '' }, []); + + this.cw_sharekey_input = $el("input", { type: 'text', placeholder: "Share key (found on your profile page)", value: ShareDialog.cw_sharekey || '' }, []); + this.cw_sharekey_input.style.width = "100%"; + this.credits_input = $el("input", { type: "text", - placeholder: "This will be used to give you credits", + placeholder: "This will be used to give credits", required: false, }, []); this.title_input = $el("input", { type: "text", - // placeholder: "ex: Zombie animation - AnimateDiff", + placeholder: "ex: My awesome art", required: false, }, []); this.description_input = $el("textarea", { - // placeholder: "ex: ", + placeholder: "ex: Trying out a new workflow... ", required: false, }, []); @@ -2029,11 +2052,43 @@ class ShareDialog extends ComfyDialog { this.final_message = $el("div", {}, []); - this.share_button.onclick = async () => { - // alert("Title: " + this.title_input.value + "\nDescription: " + this.description_input.value + "\nCredits: " + this.credits_input.value + "\nNSFW: " + this.is_nsfw_checkbox.checked); - const prompt = await app.graphToPrompt(); - console.log(prompt); + // get the user's existing matrix auth and share key + ShareDialog.matrix_auth = { homeserver: "matrix.org", username: "", password: "" }; + try { + api.fetchApi(`/manager/get_matrix_auth`) + .then(response => response.json()) + .then(data => { + ShareDialog.matrix_auth = data; + this.matrix_homeserver_input.value = ShareDialog.matrix_auth.homeserver; + this.matrix_username_input.value = ShareDialog.matrix_auth.username; + this.matrix_password_input.value = ShareDialog.matrix_auth.password; + }) + .catch(error => { + // console.log(error); + }); + } catch (error) { + // console.log(error); + } + // get the user's existing comfyworkflows share key + ShareDialog.cw_sharekey = ""; + try { + console.log("Fetching comfyworkflows share key") + api.fetchApi(`/manager/get_comfyworkflows_auth`) + .then(response => response.json()) + .then(data => { + ShareDialog.cw_sharekey = data.comfyworkflows_sharekey; + this.cw_sharekey_input.value = ShareDialog.cw_sharekey; + }) + .catch(error => { + // console.log(error); + }); + } catch (error) { + // console.log(error); + } + + this.share_button.onclick = async () => { + const prompt = await app.graphToPrompt(); const nodes = app.graph._nodes; const destinations = []; @@ -2046,25 +2101,17 @@ class ShareDialog extends ComfyDialog { // if destinations includes matrix, make an api call to /manager/check_matrix to ensure that the user has configured their matrix settings if (destinations.includes("matrix")) { - const response = await api.fetchApi(`/manager/check_matrix`); - if (response.status != 200) { - alert("Please add your Matrix access token in a file called 'matrix_accesstoken' in the ComfyUI folder. To get your Matrix access token, go to https://app.element.io/, click on your profile, click on 'All settings', click on 'Help & About', and copy 'Access Token', and then click on 'Regenerate'."); - // Reset state - this.matrix_destination_checkbox.checked = true; - this.comfyworkflows_destination_checkbox.checked = true; - this.share_button.textContent = "Share"; - this.share_button.style.display = "inline-block"; - this.final_message.innerHTML = ""; - this.final_message.style.color = "white"; - this.credits_input.value = ""; - this.title_input.value = ""; - this.description_input.value = ""; - this.is_nsfw_checkbox.checked = false; - this.close(); + let definedMatrixAuth = !!this.matrix_homeserver_input.value && !!this.matrix_username_input.value && !!this.matrix_password_input.value; + if (!definedMatrixAuth) { + alert("Please set your Matrix account details."); return; } } + if (destinations.includes("comfyworkflows") && !this.cw_sharekey_input.value && !confirm("You have NOT set your ComfyWorkflows.com share key. Your art will NOT be connected to your account (it will be shared anonymously). Continue?")) { + return; + } + const potential_outputs = []; const potential_output_nodes = []; @@ -2107,6 +2154,14 @@ class ShareDialog extends ComfyDialog { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ + matrix_auth: { + homeserver: this.matrix_homeserver_input.value, + username: this.matrix_username_input.value, + password: this.matrix_password_input.value, + }, + cw_auth: { + cw_sharekey: this.cw_sharekey_input.value, + }, share_destinations: destinations, credits: this.credits_input.value, title: this.title_input.value, @@ -2119,9 +2174,22 @@ class ShareDialog extends ComfyDialog { }); if (response.status != 200) { - alert("Failed to share your art. Please try again."); - this.close(); - return; + try { + const response_json = await response.json(); + if (response_json.error) { + alert(response_json.error); + this.close(); + return; + } else { + alert("Failed to share your art. Please try again."); + this.close(); + return; + } + } catch (e) { + alert("Failed to share your art. Please try again."); + this.close(); + return; + } } const response_json = await response.json(); @@ -2142,26 +2210,97 @@ class ShareDialog extends ComfyDialog { // hide the share button this.share_button.textContent = "Shared!"; this.share_button.style.display = "none"; - // this.close(); } - const res = [ $el("tr.td", { width: "100%" }, [ $el("font", { size: 6, color: "white" }, [`Share your art`]), - // $el("div", { size: 3, color: "white" }, [ - // $el("a", { - // href: `https://comfyworkflows.com/?ref=cms`, - // target: `_blank`, - // color: "white", - // // style: `color:white;` - // }, `comfyworkflows.com`) - // ]) ]), - $el("p", { size: 4, color: "white" }, [`Get a public link for this art & workflow.`]), - // $el("br", {}, []), + $el("br", {}, []), + + $el("details", { + style: { + border: "1px solid #999", + padding: "5px", + borderRadius: "5px", + backgroundColor: "#222" + } + }, [ + $el("summary", { + style: { + color: "white", + cursor: "pointer", + } + }, [`Matrix account`]), + $el("div", { + style: { + display: "flex", + flexDirection: "row", + } + }, [ + $el("div", { + textContent: "Homeserver", + style: { + marginRight: "10px", + } + }, []), + this.matrix_homeserver_input, + ]), + + $el("div", { + style: { + display: "flex", + flexDirection: "row", + } + }, [ + $el("div", { + textContent: "Username", + style: { + marginRight: "10px", + } + }, []), + this.matrix_username_input, + ]), + + $el("div", { + style: { + display: "flex", + flexDirection: "row", + } + }, [ + $el("div", { + textContent: "Password", + style: { + marginRight: "10px", + } + }, []), + this.matrix_password_input, + ]), + + ]), + $el("details", { + style: { + border: "1px solid #999", + marginTop: "10px", + padding: "5px", + borderRadius: "5px", + backgroundColor: "#222" + } + }, [ + $el("summary", { + style: { + color: "white", + cursor: "pointer", + } + }, [`Comfyworkflows.com account`]), + $el("h4", { + textContent: "Share key (found on your profile page)", + }, []), + $el("p", { size: 3, color: "white" }, ["When provided, your art will be saved to your account."]), + this.cw_sharekey_input, + ]), $el("div", {}, [ $el("p", { size: 3, color: "white" }, [`Select where to share your art:`]), @@ -2172,23 +2311,23 @@ class ShareDialog extends ComfyDialog { comfyworkflows_destination_checkbox_text, ]), - $el("h2", { - textContent: "Your name/username (optional)", + $el("h4", { + textContent: "Credits (optional)", size: 3, color: "white" }, []), this.credits_input, - $el("br", {}, []), + // $el("br", {}, []), - $el("h2", { + $el("h4", { textContent: "Title (optional)", size: 3, color: "white" }, []), this.title_input, - $el("br", {}, []), + // $el("br", {}, []), - $el("h2", { + $el("h4", { textContent: "Description (optional)", size: 3, color: "white" @@ -2230,14 +2369,6 @@ class ShareDialog extends ComfyDialog { return res; } - constructor() { - super(); - this.element = $el("div.comfy-modal", { parent: document.body }, - [$el("div.comfy-modal-content", - [...this.createButtons()]), - ]); - } - show() { this.element.style.display = "block"; } @@ -2264,18 +2395,18 @@ app.registerExtension({ } menu.append(managerButton); + const shareButton = document.createElement("button"); shareButton.textContent = "Share"; shareButton.onclick = () => { - if (!ShareDialog.instance) + if (!ShareDialog.instance) { ShareDialog.instance = new ShareDialog(); + } ShareDialog.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"; - // shareButton.style.border = "none"; - // shareButton.style.borderRadius = "15px"; menu.append(shareButton); }, From ba3a6d5afd08d8b0a6d765e6919890e2c066e558 Mon Sep 17 00:00:00 2001 From: thecooltechguy Date: Wed, 25 Oct 2023 08:28:45 -0700 Subject: [PATCH 05/14] separating it out into different js files --- js/comfyui-manager.js | 390 +---------------------------------------- js/comfyui-share.js | 391 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 392 insertions(+), 389 deletions(-) create mode 100644 js/comfyui-share.js diff --git a/js/comfyui-manager.js b/js/comfyui-manager.js index fb10121f..d37bd771 100644 --- a/js/comfyui-manager.js +++ b/js/comfyui-manager.js @@ -2,6 +2,7 @@ import { app } from "../../scripts/app.js"; import { api } from "../../scripts/api.js" import { ComfyDialog, $el } from "../../scripts/ui.js"; import { ComfyWidgets } from "../../scripts/widgets.js"; +import { ShareDialog } from "./comfyui-share.js"; var update_comfyui_button = null; var fetch_updates_button = null; @@ -1986,395 +1987,6 @@ class ManagerMenuDialog extends ComfyDialog { } } -class ShareDialog extends ComfyDialog { - static instance = null; - static matrix_auth = { homeserver: "matrix.org", username: "", password: "" }; - static cw_sharekey = ""; - - constructor() { - super(); - - this.element = $el("div.comfy-modal", { parent: document.body }, - [$el("div.comfy-modal-content", - { - style: { - overflowY: "auto", - } - }, - [...this.createButtons()]), - ]); - } - - createButtons() { - this.is_nsfw_checkbox = $el("input", { type: 'checkbox', id: "is_nsfw" }, []) - const is_nsfw_checkbox_text = $el("label", {}, [" Is this NSFW?"]) - this.is_nsfw_checkbox.style.color = "var(--fg-color)"; - this.is_nsfw_checkbox.checked = false; - - this.matrix_destination_checkbox = $el("input", { type: 'checkbox', id: "matrix_destination" }, []) - const matrix_destination_checkbox_text = $el("label", {}, [" ComfyUI Matrix server"]) - this.matrix_destination_checkbox.style.color = "var(--fg-color)"; - this.matrix_destination_checkbox.checked = true; - - this.comfyworkflows_destination_checkbox = $el("input", { type: 'checkbox', id: "comfyworkflows_destination" }, []) - const comfyworkflows_destination_checkbox_text = $el("label", {}, [" ComfyWorkflows.com"]) - this.comfyworkflows_destination_checkbox.style.color = "var(--fg-color)"; - this.comfyworkflows_destination_checkbox.checked = true; - - this.matrix_homeserver_input = $el("input", { type: 'text', id: "matrix_homeserver", placeholder: "matrix.org", value: ShareDialog.matrix_auth.homeserver || 'matrix.org' }, []); - this.matrix_username_input = $el("input", { type: 'text', placeholder: "Username", value: ShareDialog.matrix_auth.username || '' }, []); - this.matrix_password_input = $el("input", { type: 'password', placeholder: "Password", value: ShareDialog.matrix_auth.password || '' }, []); - - this.cw_sharekey_input = $el("input", { type: 'text', placeholder: "Share key (found on your profile page)", value: ShareDialog.cw_sharekey || '' }, []); - this.cw_sharekey_input.style.width = "100%"; - - this.credits_input = $el("input", { - type: "text", - placeholder: "This will be used to give credits", - required: false, - }, []); - - this.title_input = $el("input", { - type: "text", - placeholder: "ex: My awesome art", - required: false, - }, []); - - this.description_input = $el("textarea", { - placeholder: "ex: Trying out a new workflow... ", - required: false, - }, []); - - this.share_button = $el("button", { - type: "button", - textContent: "Share", - }, []); - - this.final_message = $el("div", {}, []); - - // get the user's existing matrix auth and share key - ShareDialog.matrix_auth = { homeserver: "matrix.org", username: "", password: "" }; - try { - api.fetchApi(`/manager/get_matrix_auth`) - .then(response => response.json()) - .then(data => { - ShareDialog.matrix_auth = data; - this.matrix_homeserver_input.value = ShareDialog.matrix_auth.homeserver; - this.matrix_username_input.value = ShareDialog.matrix_auth.username; - this.matrix_password_input.value = ShareDialog.matrix_auth.password; - }) - .catch(error => { - // console.log(error); - }); - } catch (error) { - // console.log(error); - } - - // get the user's existing comfyworkflows share key - ShareDialog.cw_sharekey = ""; - try { - console.log("Fetching comfyworkflows share key") - api.fetchApi(`/manager/get_comfyworkflows_auth`) - .then(response => response.json()) - .then(data => { - ShareDialog.cw_sharekey = data.comfyworkflows_sharekey; - this.cw_sharekey_input.value = ShareDialog.cw_sharekey; - }) - .catch(error => { - // console.log(error); - }); - } catch (error) { - // console.log(error); - } - - this.share_button.onclick = async () => { - const prompt = await app.graphToPrompt(); - const nodes = app.graph._nodes; - - const destinations = []; - if (this.matrix_destination_checkbox.checked) { - destinations.push("matrix"); - } - if (this.comfyworkflows_destination_checkbox.checked) { - destinations.push("comfyworkflows"); - } - - // if destinations includes matrix, make an api call to /manager/check_matrix to ensure that the user has configured their matrix settings - if (destinations.includes("matrix")) { - let definedMatrixAuth = !!this.matrix_homeserver_input.value && !!this.matrix_username_input.value && !!this.matrix_password_input.value; - if (!definedMatrixAuth) { - alert("Please set your Matrix account details."); - return; - } - } - - if (destinations.includes("comfyworkflows") && !this.cw_sharekey_input.value && !confirm("You have NOT set your ComfyWorkflows.com share key. Your art will NOT be connected to your account (it will be shared anonymously). Continue?")) { - return; - } - - const potential_outputs = []; - const potential_output_nodes = []; - - // iterate over the array of nodes to find the ones that are marked as SaveImage - // TODO: Add support for AnimateDiffCombine, etc. nodes that save videos/gifs, etc. - for (let i = 0; i < nodes.length; i++) { - const node = nodes[i]; - if (node.type !== "SaveImage") { - continue; - } - - if (node.type === "SaveImage") { - potential_output_nodes.push(node); - - // check if node has an 'images' array property - if (node.hasOwnProperty("images") && Array.isArray(node.images)) { - // iterate over the images array and add each image to the potential_outputs array - for (let j = 0; j < node.images.length; j++) { - potential_outputs.push({ "type": "image", "image": node.images[j] }); - } - } - } - } - - if (potential_outputs.length === 0) { - if (potential_output_nodes.length === 0) { - // todo: add support for other output node types (animatediff combine, etc.) - alert("No SaveImage node found. To share this workflow, please run a SaveImage node to your graph and re-run your prompt."); - } else { - alert("To share this, first run a prompt. Once it's done, click 'Share'."); - } - this.close(); - return; - } - - // Change the text of the share button to "Sharing..." to indicate that the share process has started - this.share_button.textContent = "Sharing..."; - - const response = await api.fetchApi(`/manager/share`, { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ - matrix_auth: { - homeserver: this.matrix_homeserver_input.value, - username: this.matrix_username_input.value, - password: this.matrix_password_input.value, - }, - cw_auth: { - cw_sharekey: this.cw_sharekey_input.value, - }, - share_destinations: destinations, - credits: this.credits_input.value, - title: this.title_input.value, - description: this.description_input.value, - is_nsfw: this.is_nsfw_checkbox.checked, - prompt, - potential_outputs, - // potential_output_nodes - }) - }); - - if (response.status != 200) { - try { - const response_json = await response.json(); - if (response_json.error) { - alert(response_json.error); - this.close(); - return; - } else { - alert("Failed to share your art. Please try again."); - this.close(); - return; - } - } catch (e) { - alert("Failed to share your art. Please try again."); - this.close(); - return; - } - } - - const response_json = await response.json(); - - if (response_json.comfyworkflows.url) { - this.final_message.innerHTML = "Your art has been shared: " + response_json.comfyworkflows.url + ""; - if (response_json.matrix.success) { - this.final_message.innerHTML += "
Your art has been shared in the ComfyUI Matrix server's #share channel!"; - } - } else { - if (response_json.matrix.success) { - this.final_message.innerHTML = "Your art has been shared in the ComfyUI Matrix server's #share channel!"; - } - } - - this.final_message.style.color = "green"; - - // hide the share button - this.share_button.textContent = "Shared!"; - this.share_button.style.display = "none"; - // this.close(); - } - - const res = - [ - $el("tr.td", { width: "100%" }, [ - $el("font", { size: 6, color: "white" }, [`Share your art`]), - ]), - $el("br", {}, []), - - $el("details", { - style: { - border: "1px solid #999", - padding: "5px", - borderRadius: "5px", - backgroundColor: "#222" - } - }, [ - $el("summary", { - style: { - color: "white", - cursor: "pointer", - } - }, [`Matrix account`]), - $el("div", { - style: { - display: "flex", - flexDirection: "row", - } - }, [ - $el("div", { - textContent: "Homeserver", - style: { - marginRight: "10px", - } - }, []), - this.matrix_homeserver_input, - ]), - - $el("div", { - style: { - display: "flex", - flexDirection: "row", - } - }, [ - $el("div", { - textContent: "Username", - style: { - marginRight: "10px", - } - }, []), - this.matrix_username_input, - ]), - - $el("div", { - style: { - display: "flex", - flexDirection: "row", - } - }, [ - $el("div", { - textContent: "Password", - style: { - marginRight: "10px", - } - }, []), - this.matrix_password_input, - ]), - - ]), - $el("details", { - style: { - border: "1px solid #999", - marginTop: "10px", - padding: "5px", - borderRadius: "5px", - backgroundColor: "#222" - } - }, [ - $el("summary", { - style: { - color: "white", - cursor: "pointer", - } - }, [`Comfyworkflows.com account`]), - $el("h4", { - textContent: "Share key (found on your profile page)", - }, []), - $el("p", { size: 3, color: "white" }, ["When provided, your art will be saved to your account."]), - this.cw_sharekey_input, - ]), - - $el("div", {}, [ - $el("p", { size: 3, color: "white" }, [`Select where to share your art:`]), - this.matrix_destination_checkbox, - matrix_destination_checkbox_text, - $el("br", {}, []), - this.comfyworkflows_destination_checkbox, - comfyworkflows_destination_checkbox_text, - ]), - - $el("h4", { - textContent: "Credits (optional)", - size: 3, - color: "white" - }, []), - this.credits_input, - // $el("br", {}, []), - - $el("h4", { - textContent: "Title (optional)", - size: 3, - color: "white" - }, []), - this.title_input, - // $el("br", {}, []), - - $el("h4", { - textContent: "Description (optional)", - size: 3, - color: "white" - }, []), - this.description_input, - $el("br", {}, []), - - $el("div", {}, [this.is_nsfw_checkbox, is_nsfw_checkbox_text]), - this.final_message, - - $el("br", {}, []), - this.share_button, - - $el("button", { - type: "button", - textContent: "Close", - onclick: () => { - // Reset state - this.matrix_destination_checkbox.checked = true; - this.comfyworkflows_destination_checkbox.checked = true; - this.share_button.textContent = "Share"; - this.share_button.style.display = "inline-block"; - this.final_message.innerHTML = ""; - this.final_message.style.color = "white"; - this.credits_input.value = ""; - this.title_input.value = ""; - this.description_input.value = ""; - this.is_nsfw_checkbox.checked = false; - this.close() - } - }), - $el("br", {}, []), - ]; - - res[0].style.padding = "10px 10px 10px 10px"; - res[0].style.backgroundColor = "black"; //"linear-gradient(90deg, #00C9FF 0%, #92FE9D 100%)"; - res[0].style.textAlign = "center"; - res[0].style.height = "45px"; - return res; - } - - show() { - this.element.style.display = "block"; - } -} - - app.registerExtension({ name: "Comfy.ManagerMenu", diff --git a/js/comfyui-share.js b/js/comfyui-share.js new file mode 100644 index 00000000..81892891 --- /dev/null +++ b/js/comfyui-share.js @@ -0,0 +1,391 @@ +import { app } from "../../scripts/app.js"; +import { api } from "../../scripts/api.js" +import { ComfyDialog, $el } from "../../scripts/ui.js"; + +export class ShareDialog extends ComfyDialog { + static instance = null; + static matrix_auth = { homeserver: "matrix.org", username: "", password: "" }; + static cw_sharekey = ""; + + constructor() { + super(); + + this.element = $el("div.comfy-modal", { parent: document.body }, + [$el("div.comfy-modal-content", + { + style: { + overflowY: "auto", + } + }, + [...this.createButtons()]), + ]); + } + + createButtons() { + this.is_nsfw_checkbox = $el("input", { type: 'checkbox', id: "is_nsfw" }, []) + const is_nsfw_checkbox_text = $el("label", {}, [" Is this NSFW?"]) + this.is_nsfw_checkbox.style.color = "var(--fg-color)"; + this.is_nsfw_checkbox.checked = false; + + this.matrix_destination_checkbox = $el("input", { type: 'checkbox', id: "matrix_destination" }, []) + const matrix_destination_checkbox_text = $el("label", {}, [" ComfyUI Matrix server"]) + this.matrix_destination_checkbox.style.color = "var(--fg-color)"; + this.matrix_destination_checkbox.checked = true; + + this.comfyworkflows_destination_checkbox = $el("input", { type: 'checkbox', id: "comfyworkflows_destination" }, []) + const comfyworkflows_destination_checkbox_text = $el("label", {}, [" ComfyWorkflows.com"]) + this.comfyworkflows_destination_checkbox.style.color = "var(--fg-color)"; + this.comfyworkflows_destination_checkbox.checked = true; + + this.matrix_homeserver_input = $el("input", { type: 'text', id: "matrix_homeserver", placeholder: "matrix.org", value: ShareDialog.matrix_auth.homeserver || 'matrix.org' }, []); + this.matrix_username_input = $el("input", { type: 'text', placeholder: "Username", value: ShareDialog.matrix_auth.username || '' }, []); + this.matrix_password_input = $el("input", { type: 'password', placeholder: "Password", value: ShareDialog.matrix_auth.password || '' }, []); + + this.cw_sharekey_input = $el("input", { type: 'text', placeholder: "Share key (found on your profile page)", value: ShareDialog.cw_sharekey || '' }, []); + this.cw_sharekey_input.style.width = "100%"; + + this.credits_input = $el("input", { + type: "text", + placeholder: "This will be used to give credits", + required: false, + }, []); + + this.title_input = $el("input", { + type: "text", + placeholder: "ex: My awesome art", + required: false, + }, []); + + this.description_input = $el("textarea", { + placeholder: "ex: Trying out a new workflow... ", + required: false, + }, []); + + this.share_button = $el("button", { + type: "button", + textContent: "Share", + }, []); + + this.final_message = $el("div", {}, []); + + // get the user's existing matrix auth and share key + ShareDialog.matrix_auth = { homeserver: "matrix.org", username: "", password: "" }; + try { + api.fetchApi(`/manager/get_matrix_auth`) + .then(response => response.json()) + .then(data => { + ShareDialog.matrix_auth = data; + this.matrix_homeserver_input.value = ShareDialog.matrix_auth.homeserver; + this.matrix_username_input.value = ShareDialog.matrix_auth.username; + this.matrix_password_input.value = ShareDialog.matrix_auth.password; + }) + .catch(error => { + // console.log(error); + }); + } catch (error) { + // console.log(error); + } + + // get the user's existing comfyworkflows share key + ShareDialog.cw_sharekey = ""; + try { + console.log("Fetching comfyworkflows share key") + api.fetchApi(`/manager/get_comfyworkflows_auth`) + .then(response => response.json()) + .then(data => { + ShareDialog.cw_sharekey = data.comfyworkflows_sharekey; + this.cw_sharekey_input.value = ShareDialog.cw_sharekey; + }) + .catch(error => { + // console.log(error); + }); + } catch (error) { + // console.log(error); + } + + this.share_button.onclick = async () => { + const prompt = await app.graphToPrompt(); + const nodes = app.graph._nodes; + + const destinations = []; + if (this.matrix_destination_checkbox.checked) { + destinations.push("matrix"); + } + if (this.comfyworkflows_destination_checkbox.checked) { + destinations.push("comfyworkflows"); + } + + // if destinations includes matrix, make an api call to /manager/check_matrix to ensure that the user has configured their matrix settings + if (destinations.includes("matrix")) { + let definedMatrixAuth = !!this.matrix_homeserver_input.value && !!this.matrix_username_input.value && !!this.matrix_password_input.value; + if (!definedMatrixAuth) { + alert("Please set your Matrix account details."); + return; + } + } + + if (destinations.includes("comfyworkflows") && !this.cw_sharekey_input.value && !confirm("You have NOT set your ComfyWorkflows.com share key. Your art will NOT be connected to your account (it will be shared anonymously). Continue?")) { + return; + } + + const potential_outputs = []; + const potential_output_nodes = []; + + // iterate over the array of nodes to find the ones that are marked as SaveImage + // TODO: Add support for AnimateDiffCombine, etc. nodes that save videos/gifs, etc. + for (let i = 0; i < nodes.length; i++) { + const node = nodes[i]; + if (node.type !== "SaveImage") { + continue; + } + + if (node.type === "SaveImage") { + potential_output_nodes.push(node); + + // check if node has an 'images' array property + if (node.hasOwnProperty("images") && Array.isArray(node.images)) { + // iterate over the images array and add each image to the potential_outputs array + for (let j = 0; j < node.images.length; j++) { + potential_outputs.push({ "type": "image", "image": node.images[j] }); + } + } + } + } + + if (potential_outputs.length === 0) { + if (potential_output_nodes.length === 0) { + // todo: add support for other output node types (animatediff combine, etc.) + alert("No SaveImage node found. To share this workflow, please run a SaveImage node to your graph and re-run your prompt."); + } else { + alert("To share this, first run a prompt. Once it's done, click 'Share'."); + } + this.close(); + return; + } + + // Change the text of the share button to "Sharing..." to indicate that the share process has started + this.share_button.textContent = "Sharing..."; + + const response = await api.fetchApi(`/manager/share`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + matrix_auth: { + homeserver: this.matrix_homeserver_input.value, + username: this.matrix_username_input.value, + password: this.matrix_password_input.value, + }, + cw_auth: { + cw_sharekey: this.cw_sharekey_input.value, + }, + share_destinations: destinations, + credits: this.credits_input.value, + title: this.title_input.value, + description: this.description_input.value, + is_nsfw: this.is_nsfw_checkbox.checked, + prompt, + potential_outputs, + // potential_output_nodes + }) + }); + + if (response.status != 200) { + try { + const response_json = await response.json(); + if (response_json.error) { + alert(response_json.error); + this.close(); + return; + } else { + alert("Failed to share your art. Please try again."); + this.close(); + return; + } + } catch (e) { + alert("Failed to share your art. Please try again."); + this.close(); + return; + } + } + + const response_json = await response.json(); + + if (response_json.comfyworkflows.url) { + this.final_message.innerHTML = "Your art has been shared: " + response_json.comfyworkflows.url + ""; + if (response_json.matrix.success) { + this.final_message.innerHTML += "
Your art has been shared in the ComfyUI Matrix server's #share channel!"; + } + } else { + if (response_json.matrix.success) { + this.final_message.innerHTML = "Your art has been shared in the ComfyUI Matrix server's #share channel!"; + } + } + + this.final_message.style.color = "green"; + + // hide the share button + this.share_button.textContent = "Shared!"; + this.share_button.style.display = "none"; + // this.close(); + } + + const res = + [ + $el("tr.td", { width: "100%" }, [ + $el("font", { size: 6, color: "white" }, [`Share your art`]), + ]), + $el("br", {}, []), + + $el("details", { + style: { + border: "1px solid #999", + padding: "5px", + borderRadius: "5px", + backgroundColor: "#222" + } + }, [ + $el("summary", { + style: { + color: "white", + cursor: "pointer", + } + }, [`Matrix account`]), + $el("div", { + style: { + display: "flex", + flexDirection: "row", + } + }, [ + $el("div", { + textContent: "Homeserver", + style: { + marginRight: "10px", + } + }, []), + this.matrix_homeserver_input, + ]), + + $el("div", { + style: { + display: "flex", + flexDirection: "row", + } + }, [ + $el("div", { + textContent: "Username", + style: { + marginRight: "10px", + } + }, []), + this.matrix_username_input, + ]), + + $el("div", { + style: { + display: "flex", + flexDirection: "row", + } + }, [ + $el("div", { + textContent: "Password", + style: { + marginRight: "10px", + } + }, []), + this.matrix_password_input, + ]), + + ]), + $el("details", { + style: { + border: "1px solid #999", + marginTop: "10px", + padding: "5px", + borderRadius: "5px", + backgroundColor: "#222" + } + }, [ + $el("summary", { + style: { + color: "white", + cursor: "pointer", + } + }, [`Comfyworkflows.com account`]), + $el("h4", { + textContent: "Share key (found on your profile page)", + }, []), + $el("p", { size: 3, color: "white" }, ["When provided, your art will be saved to your account."]), + this.cw_sharekey_input, + ]), + + $el("div", {}, [ + $el("p", { size: 3, color: "white" }, [`Select where to share your art:`]), + this.matrix_destination_checkbox, + matrix_destination_checkbox_text, + $el("br", {}, []), + this.comfyworkflows_destination_checkbox, + comfyworkflows_destination_checkbox_text, + ]), + + $el("h4", { + textContent: "Credits (optional)", + size: 3, + color: "white" + }, []), + this.credits_input, + // $el("br", {}, []), + + $el("h4", { + textContent: "Title (optional)", + size: 3, + color: "white" + }, []), + this.title_input, + // $el("br", {}, []), + + $el("h4", { + textContent: "Description (optional)", + size: 3, + color: "white" + }, []), + this.description_input, + $el("br", {}, []), + + $el("div", {}, [this.is_nsfw_checkbox, is_nsfw_checkbox_text]), + this.final_message, + + $el("br", {}, []), + this.share_button, + + $el("button", { + type: "button", + textContent: "Close", + onclick: () => { + // Reset state + this.matrix_destination_checkbox.checked = true; + this.comfyworkflows_destination_checkbox.checked = true; + this.share_button.textContent = "Share"; + this.share_button.style.display = "inline-block"; + this.final_message.innerHTML = ""; + this.final_message.style.color = "white"; + this.credits_input.value = ""; + this.title_input.value = ""; + this.description_input.value = ""; + this.is_nsfw_checkbox.checked = false; + this.close() + } + }), + $el("br", {}, []), + ]; + + res[0].style.padding = "10px 10px 10px 10px"; + res[0].style.backgroundColor = "black"; //"linear-gradient(90deg, #00C9FF 0%, #92FE9D 100%)"; + res[0].style.textAlign = "center"; + res[0].style.height = "45px"; + return res; + } + + show() { + this.element.style.display = "block"; + } +} \ No newline at end of file From 8b2048e5c343a7a74be78d9837df9cfe5a1ed080 Mon Sep 17 00:00:00 2001 From: thecooltechguy Date: Thu, 26 Oct 2023 14:37:47 -0700 Subject: [PATCH 06/14] Added dropdown under workflow gallery button for sharing, added a comfyui setting for showing/hiding share button in the main menu --- js/comfyui-manager.js | 98 ++++++++++++++++++++++++++++++++++++++++++- js/comfyui-share.js | 20 ++++----- 2 files changed, 106 insertions(+), 12 deletions(-) diff --git a/js/comfyui-manager.js b/js/comfyui-manager.js index d37bd771..bac6b4c6 100644 --- a/js/comfyui-manager.js +++ b/js/comfyui-manager.js @@ -9,6 +9,39 @@ var fetch_updates_button = null; var update_all_button = null; var badge_mode = "none"; +// copied style from https://github.com/pythongosssss/ComfyUI-Custom-Scripts +const style = ` +#comfyworkflows-button { + position: relative; + overflow: hidden; + } +.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); +} +`; + + + async function init_badge_mode() { api.fetchApi('/manager/badge_mode') .then(response => response.text()) @@ -1940,10 +1973,49 @@ class ManagerMenuDialog extends ComfyDialog { onclick: () => { window.open("https://blenderneko.github.io/ComfyUI-docs/", "comfyui-community-manual"); } }), $el("button", { + id: 'comfyworkflows-button', type: "button", textContent: "ComfyUI Workflow Gallery", onclick: () => { window.open("https://comfyworkflows.com/", "comfyui-workflow-gallery"); } - }), + }, [ + $el("div.pysssss-workflow-arrow-2", { + id: `comfyworkflows-button-arrow`, + // parent: document.getElementById(`comfyworkflows-button`), + onclick: (e) => { + e.preventDefault(); + e.stopPropagation(); + + LiteGraph.closeAllContextMenus(); + const menu = new LiteGraph.ContextMenu( + [ + { + title: "Share your art", + callback: () => { + this.close(); + if (!ShareDialog.instance) { + ShareDialog.instance = new ShareDialog(); + } + ShareDialog.instance.show(); + }, + }, + { + title: "Close", + callback: () => { + this.close(); + }, + } + ], + { + event: e, + scale: 1.3, + }, + window + ); + menu.root.classList.add("pysssss-workflow-popup-2"); + menu.root.classList.add(`pysssss-workflow-comfyworkflows-button`); + }, + }) + ]), $el("button", { type: "button", @@ -1989,7 +2061,12 @@ class ManagerMenuDialog extends ComfyDialog { app.registerExtension({ name: "Comfy.ManagerMenu", - + init() { + $el("style", { + textContent: style, + parent: document.head, + }); + }, async setup() { const menu = document.querySelector(".comfy-menu"); const separator = document.createElement("hr"); @@ -2019,6 +2096,23 @@ app.registerExtension({ // make the background color a gradient of blue to green shareButton.style.background = "linear-gradient(90deg, #00C9FF 0%, #92FE9D 100%)"; shareButton.style.color = "black"; + + app.ui.settings.addSetting({ + id: "ComfyUIManager.ShowShareButtonInMainMenu", + name: "Show 'Share' button in the main menu", + type: "boolean", + defaultValue: true, + onChange: (value) => { + if (value) { + // show the button + shareButton.style.display = "inline-block"; + } else { + // hide the button + shareButton.style.display = "none"; + } + } + }); + menu.append(shareButton); }, diff --git a/js/comfyui-share.js b/js/comfyui-share.js index 81892891..f060ecac 100644 --- a/js/comfyui-share.js +++ b/js/comfyui-share.js @@ -9,16 +9,16 @@ export class ShareDialog extends ComfyDialog { constructor() { super(); - - this.element = $el("div.comfy-modal", { parent: document.body }, - [$el("div.comfy-modal-content", - { - style: { - overflowY: "auto", - } - }, - [...this.createButtons()]), - ]); + + this.element = $el("div.comfy-modal", { parent: document.body }, + [$el("div.comfy-modal-content", + { + style: { + overflowY: "auto", + } + }, + [...this.createButtons()]), + ]); } createButtons() { From c25b2bd1e9ed0febcd257cb774b0823f0d578c09 Mon Sep 17 00:00:00 2001 From: thecooltechguy Date: Sun, 29 Oct 2023 16:27:24 -0700 Subject: [PATCH 07/14] added ability to select the output image to share --- __init__.py | 11 +++- js/comfyui-manager.js | 41 +++++++++++++- js/comfyui-share.js | 126 ++++++++++++++++++++++++++++++++++-------- 3 files changed, 149 insertions(+), 29 deletions(-) diff --git a/__init__.py b/__init__.py index ea66e128..0774ca73 100644 --- a/__init__.py +++ b/__init__.py @@ -1272,9 +1272,14 @@ async def share_art(request): is_nsfw = json_data['is_nsfw'] prompt = json_data['prompt'] potential_outputs = json_data['potential_outputs'] - - # for now, pick the first output - output_to_share = potential_outputs[0] + selected_output_index = json_data['selected_output_index'] + + try: + output_to_share = potential_outputs[int(selected_output_index)] + except: + # for now, pick the first output + output_to_share = potential_outputs[0] + assert output_to_share['type'] in ('image',) output_dir = folder_paths.get_output_directory() diff --git a/js/comfyui-manager.js b/js/comfyui-manager.js index bac6b4c6..1da642af 100644 --- a/js/comfyui-manager.js +++ b/js/comfyui-manager.js @@ -2000,7 +2000,7 @@ class ManagerMenuDialog extends ComfyDialog { }, { title: "Close", - callback: () => { + callback: () => { this.close(); }, } @@ -2091,7 +2091,44 @@ app.registerExtension({ if (!ShareDialog.instance) { ShareDialog.instance = new ShareDialog(); } - ShareDialog.instance.show(); + + app.graphToPrompt().then(prompt => app.graph._nodes).then(nodes => { + const potential_outputs = []; + const potential_output_nodes = []; + + // iterate over the array of nodes to find the ones that are marked as SaveImage + // TODO: Add support for AnimateDiffCombine, etc. nodes that save videos/gifs, etc. + for (let i = 0; i < nodes.length; i++) { + const node = nodes[i]; + if (node.type !== "SaveImage") { + continue; + } + + if (node.type === "SaveImage") { + potential_output_nodes.push(node); + + // check if node has an 'images' array property + if (node.hasOwnProperty("images") && Array.isArray(node.images)) { + // iterate over the images array and add each image to the potential_outputs array + for (let j = 0; j < node.images.length; j++) { + potential_outputs.push({ "type": "image", "image": node.images[j] }); + } + } + } + } + + if (potential_outputs.length === 0) { + if (potential_output_nodes.length === 0) { + // todo: add support for other output node types (animatediff combine, etc.) + alert("No SaveImage node found. To share this workflow, please run a SaveImage node to your graph and re-run your prompt."); + } else { + alert("To share this, first run a prompt. Once it's done, click 'Share'."); + } + return; + } + + ShareDialog.instance.show({ potential_outputs, potential_output_nodes }); + }); } // make the background color a gradient of blue to green shareButton.style.background = "linear-gradient(90deg, #00C9FF 0%, #92FE9D 100%)"; diff --git a/js/comfyui-share.js b/js/comfyui-share.js index f060ecac..5b85546f 100644 --- a/js/comfyui-share.js +++ b/js/comfyui-share.js @@ -2,35 +2,41 @@ import { app } from "../../scripts/app.js"; import { api } from "../../scripts/api.js" import { ComfyDialog, $el } from "../../scripts/ui.js"; -export class ShareDialog extends ComfyDialog { +export class ShareDialog extends ComfyDialog { static instance = null; static matrix_auth = { homeserver: "matrix.org", username: "", password: "" }; static cw_sharekey = ""; constructor() { super(); - - this.element = $el("div.comfy-modal", { parent: document.body }, - [$el("div.comfy-modal-content", - { - style: { - overflowY: "auto", - } - }, - [...this.createButtons()]), - ]); + this.element = $el("div.comfy-modal", { + parent: document.body, style: { + 'overflow-y': "auto", + } + }, + [$el("div.comfy-modal-content", + {}, + [...this.createButtons()]), + ]); + this.selectedOutputIndex = 0; } createButtons() { + + this.radio_buttons = $el("div", { + id: "selectOutputImages", + }, []); + this.is_nsfw_checkbox = $el("input", { type: 'checkbox', id: "is_nsfw" }, []) - const is_nsfw_checkbox_text = $el("label", {}, [" Is this NSFW?"]) + const is_nsfw_checkbox_text = $el("label", { + }, [" Is this NSFW?"]) this.is_nsfw_checkbox.style.color = "var(--fg-color)"; this.is_nsfw_checkbox.checked = false; this.matrix_destination_checkbox = $el("input", { type: 'checkbox', id: "matrix_destination" }, []) const matrix_destination_checkbox_text = $el("label", {}, [" ComfyUI Matrix server"]) this.matrix_destination_checkbox.style.color = "var(--fg-color)"; - this.matrix_destination_checkbox.checked = true; + this.matrix_destination_checkbox.checked = false; //true; this.comfyworkflows_destination_checkbox = $el("input", { type: 'checkbox', id: "comfyworkflows_destination" }, []) const comfyworkflows_destination_checkbox_text = $el("label", {}, [" ComfyWorkflows.com"]) @@ -53,7 +59,7 @@ export class ShareDialog extends ComfyDialog { this.title_input = $el("input", { type: "text", placeholder: "ex: My awesome art", - required: false, + required: false }, []); this.description_input = $el("textarea", { @@ -62,7 +68,7 @@ export class ShareDialog extends ComfyDialog { }, []); this.share_button = $el("button", { - type: "button", + type: "submit", textContent: "Share", }, []); @@ -89,7 +95,7 @@ export class ShareDialog extends ComfyDialog { // get the user's existing comfyworkflows share key ShareDialog.cw_sharekey = ""; try { - console.log("Fetching comfyworkflows share key") + // console.log("Fetching comfyworkflows share key") api.fetchApi(`/manager/get_comfyworkflows_auth`) .then(response => response.json()) .then(data => { @@ -159,12 +165,18 @@ export class ShareDialog extends ComfyDialog { } else { alert("To share this, first run a prompt. Once it's done, click 'Share'."); } + this.selectedOutputIndex = 0; this.close(); return; } // Change the text of the share button to "Sharing..." to indicate that the share process has started this.share_button.textContent = "Sharing..."; + console.log({ + potential_outputs, + potential_output_nodes, + selectedOutputIndex: this.selectedOutputIndex, + }) const response = await api.fetchApi(`/manager/share`, { method: 'POST', @@ -185,6 +197,7 @@ export class ShareDialog extends ComfyDialog { is_nsfw: this.is_nsfw_checkbox.checked, prompt, potential_outputs, + selected_output_index: this.selectedOutputIndex, // potential_output_nodes }) }); @@ -319,7 +332,11 @@ export class ShareDialog extends ComfyDialog { ]), $el("div", {}, [ - $el("p", { size: 3, color: "white" }, [`Select where to share your art:`]), + $el("p", { + size: 3, color: "white", style: { + color: 'white' + } + }, [`Select where to share your art:`]), this.matrix_destination_checkbox, matrix_destination_checkbox_text, $el("br", {}, []), @@ -330,7 +347,10 @@ export class ShareDialog extends ComfyDialog { $el("h4", { textContent: "Credits (optional)", size: 3, - color: "white" + color: "white", + style: { + color: 'white' + } }, []), this.credits_input, // $el("br", {}, []), @@ -338,7 +358,10 @@ export class ShareDialog extends ComfyDialog { $el("h4", { textContent: "Title (optional)", size: 3, - color: "white" + color: "white", + style: { + color: 'white' + } }, []), this.title_input, // $el("br", {}, []), @@ -346,15 +369,23 @@ export class ShareDialog extends ComfyDialog { $el("h4", { textContent: "Description (optional)", size: 3, - color: "white" + color: "white", + style: { + color: 'white' + } }, []), this.description_input, $el("br", {}, []), $el("div", {}, [this.is_nsfw_checkbox, is_nsfw_checkbox_text]), - this.final_message, - $el("br", {}, []), + + this.radio_buttons, + $el("br", {}, []), + + this.final_message, + $el("br", {}, []), + this.share_button, $el("button", { @@ -362,7 +393,7 @@ export class ShareDialog extends ComfyDialog { textContent: "Close", onclick: () => { // Reset state - this.matrix_destination_checkbox.checked = true; + this.matrix_destination_checkbox.checked = false; this.comfyworkflows_destination_checkbox.checked = true; this.share_button.textContent = "Share"; this.share_button.style.display = "inline-block"; @@ -372,6 +403,7 @@ export class ShareDialog extends ComfyDialog { this.title_input.value = ""; this.description_input.value = ""; this.is_nsfw_checkbox.checked = false; + this.selectedOutputIndex = 0; this.close() } }), @@ -385,7 +417,53 @@ export class ShareDialog extends ComfyDialog { return res; } - show() { + show({ potential_outputs, potential_output_nodes }) { + this.radio_buttons.innerHTML = ""; // clear the radio buttons + const new_radio_buttons = $el("div", { + id: "selectOutputImages-Options", + style: { + 'overflow-y': 'auto', + 'max-height': '400px', + } + }, potential_output_nodes.map((output, index) => { + const radio_button = $el("input", { type: 'radio', name: "selectOutputImages", value: index, required: index === 0 }, []) + const radio_button_img = $el("img", { src: output.imgs[0].src, style: { width: "auto", height: "100px" } }, []); + const radio_button_text = $el("label", { + // style: { + // color: 'white' + // } + }, [output.title]) + radio_button.style.color = "var(--fg-color)"; + radio_button.checked = index === 0; + if (radio_button.checked) { + this.selectedOutputIndex = index; + } + + radio_button.onchange = () => { + this.selectedOutputIndex = parseInt(radio_button.value); + }; + + return $el("div", { + style: { + display: "flex", + 'align-items': 'center', + 'justify-content': 'space-between', + } + }, [radio_button, radio_button_text, radio_button_img]); + })); + const header = $el("h4", { + textContent: "Select an image to share", + size: 3, + color: "white", + style: { + 'text-align': 'center', + color: 'white', + backgroundColor: 'black', + padding: '10px', + } + }, []); + this.radio_buttons.appendChild(header); + this.radio_buttons.appendChild(new_radio_buttons); this.element.style.display = "block"; } } \ No newline at end of file From 5da730dd8b18959c8f6ae84f5cd4bc6ec038f2b2 Mon Sep 17 00:00:00 2001 From: thecooltechguy Date: Sun, 29 Oct 2023 16:31:35 -0700 Subject: [PATCH 08/14] wip --- js/comfyui-share.js | 5 ----- 1 file changed, 5 deletions(-) diff --git a/js/comfyui-share.js b/js/comfyui-share.js index 5b85546f..0ba6f701 100644 --- a/js/comfyui-share.js +++ b/js/comfyui-share.js @@ -172,11 +172,6 @@ export class ShareDialog extends ComfyDialog { // Change the text of the share button to "Sharing..." to indicate that the share process has started this.share_button.textContent = "Sharing..."; - console.log({ - potential_outputs, - potential_output_nodes, - selectedOutputIndex: this.selectedOutputIndex, - }) const response = await api.fetchApi(`/manager/share`, { method: 'POST', From 6aa7b2c9e7a4093cf900e6273adde231bc28338d Mon Sep 17 00:00:00 2001 From: thecooltechguy Date: Mon, 30 Oct 2023 11:41:21 -0700 Subject: [PATCH 09/14] fix small bug --- __init__.py | 36 +++++++++++++++++++++--------------- 1 file changed, 21 insertions(+), 15 deletions(-) diff --git a/__init__.py b/__init__.py index 0774ca73..f00d4ddb 100644 --- a/__init__.py +++ b/__init__.py @@ -1199,25 +1199,31 @@ async def channel_url_list(request): def get_matrix_auth(): if not os.path.exists(os.path.join(folder_paths.base_path, "matrix_auth")): return None - with open(os.path.join(folder_paths.base_path, "matrix_auth"), "r") as f: - matrix_auth = f.read() - homeserver, username, password = matrix_auth.strip().split("\n") - if not homeserver or not username or not password: - return None - return { - "homeserver": homeserver, - "username": username, - "password": password, - } + try: + with open(os.path.join(folder_paths.base_path, "matrix_auth"), "r") as f: + matrix_auth = f.read() + homeserver, username, password = matrix_auth.strip().split("\n") + if not homeserver or not username or not password: + return None + return { + "homeserver": homeserver, + "username": username, + "password": password, + } + except: + return None def get_comfyworkflows_auth(): if not os.path.exists(os.path.join(folder_paths.base_path, "comfyworkflows_sharekey")): return None - with open(os.path.join(folder_paths.base_path, "comfyworkflows_sharekey"), "r") as f: - share_key = f.read() - if not share_key.strip(): - return None - return share_key + try: + with open(os.path.join(folder_paths.base_path, "comfyworkflows_sharekey"), "r") as f: + share_key = f.read() + if not share_key.strip(): + return None + return share_key + except: + return None @server.PromptServer.instance.routes.get("/manager/get_matrix_auth") async def api_get_matrix_auth(request): From aca212551966ffc6b269e83dfe1e6275de07a63e Mon Sep 17 00:00:00 2001 From: thecooltechguy Date: Sun, 5 Nov 2023 15:12:35 -0800 Subject: [PATCH 10/14] wip --- js/comfyui-manager.js | 7 ++++++- js/comfyui-share.js | 14 +++++++++++++- 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/js/comfyui-manager.js b/js/comfyui-manager.js index 1da642af..4e258266 100644 --- a/js/comfyui-manager.js +++ b/js/comfyui-manager.js @@ -2092,7 +2092,12 @@ app.registerExtension({ ShareDialog.instance = new ShareDialog(); } - app.graphToPrompt().then(prompt => app.graph._nodes).then(nodes => { + app.graphToPrompt().then(prompt => { + console.log({prompt}) + return app.graph._nodes; + }).then(nodes => { + console.log({nodes}); + const potential_outputs = []; const potential_output_nodes = []; diff --git a/js/comfyui-share.js b/js/comfyui-share.js index 0ba6f701..54078622 100644 --- a/js/comfyui-share.js +++ b/js/comfyui-share.js @@ -2,6 +2,8 @@ import { app } from "../../scripts/app.js"; import { api } from "../../scripts/api.js" import { ComfyDialog, $el } from "../../scripts/ui.js"; +const VALID_OUTPUT_TYPES = ["SaveImage", "VHS_VideoCombine"]; + export class ShareDialog extends ComfyDialog { static instance = null; static matrix_auth = { homeserver: "matrix.org", username: "", password: "" }; @@ -110,9 +112,12 @@ export class ShareDialog extends ComfyDialog { } this.share_button.onclick = async () => { + alert("Clicked"); const prompt = await app.graphToPrompt(); const nodes = app.graph._nodes; + console.log({ prompt, nodes }); + const destinations = []; if (this.matrix_destination_checkbox.checked) { destinations.push("matrix"); @@ -141,7 +146,9 @@ export class ShareDialog extends ComfyDialog { // TODO: Add support for AnimateDiffCombine, etc. nodes that save videos/gifs, etc. for (let i = 0; i < nodes.length; i++) { const node = nodes[i]; - if (node.type !== "SaveImage") { + console.log({ node }); + + if (!VALID_OUTPUT_TYPES.includes(node.type)) { continue; } @@ -156,8 +163,13 @@ export class ShareDialog extends ComfyDialog { } } } + else if (node.type === "VHS_VideoCombine") { + potential_output_nodes.push(node); + } } + console.log({ potential_outputs, potential_output_nodes }) + if (potential_outputs.length === 0) { if (potential_output_nodes.length === 0) { // todo: add support for other output node types (animatediff combine, etc.) From c6337a19a83be5247879d0e453d9320da66c31be Mon Sep 17 00:00:00 2001 From: thecooltechguy Date: Tue, 7 Nov 2023 01:12:22 -0800 Subject: [PATCH 11/14] Added support for SaveImage, AnimateDiffCombine, and VideoCombine --- __init__.py | 18 +++-- js/comfyui-manager.js | 36 ++-------- js/comfyui-share.js | 153 ++++++++++++++++++++++++++++++++---------- 3 files changed, 135 insertions(+), 72 deletions(-) diff --git a/__init__.py b/__init__.py index f00d4ddb..e7a2a24b 100644 --- a/__init__.py +++ b/__init__.py @@ -1286,14 +1286,16 @@ async def share_art(request): # for now, pick the first output output_to_share = potential_outputs[0] - assert output_to_share['type'] in ('image',) + assert output_to_share['type'] in ('image', 'output') output_dir = folder_paths.get_output_directory() - asset_filename = output_to_share['image']['filename'] - asset_subfolder = output_to_share['image']['subfolder'] - asset_output_type = output_to_share['image']['type'] - assert asset_output_type == "output" + if output_to_share['type'] == 'image': + asset_filename = output_to_share['image']['filename'] + asset_subfolder = output_to_share['image']['subfolder'] + else: + asset_filename = output_to_share['output']['filename'] + asset_subfolder = output_to_share['output']['subfolder'] if asset_subfolder: asset_filepath = os.path.join(output_dir, asset_subfolder, asset_filename) @@ -1350,7 +1352,7 @@ async def share_art(request): workflowId = upload_workflow_json["workflowId"] # check if the user has provided Matrix credentials - if "matrix" in share_destinations: + if "matrix" in share_destinations: comfyui_share_room_id = '!LGYSoacpJPhIfBqVfb:matrix.org' filename = os.path.basename(asset_filepath) content_type = assetFileType @@ -1377,6 +1379,9 @@ async def share_art(request): matrix = MatrixHttpApi(homeserver, token=token) with open(asset_filepath, 'rb') as f: mxc_url = matrix.media_upload(f.read(), content_type, filename=filename)['content_uri'] + + workflow_json_mxc_url = matrix.media_upload(prompt['workflow'], 'application/json', filename='workflow.json')['content_uri'] + text_content = "" if title: text_content += f"{title}\n" @@ -1386,6 +1391,7 @@ async def share_art(request): text_content += f"\ncredits: {credits}\n" response = matrix.send_message(comfyui_share_room_id, text_content) response = matrix.send_content(comfyui_share_room_id, mxc_url, filename, 'm.image') + response = matrix.send_content(comfyui_share_room_id, workflow_json_mxc_url, 'workflow.json', 'm.file') except: import traceback traceback.print_exc() diff --git a/js/comfyui-manager.js b/js/comfyui-manager.js index 4e258266..857acb6f 100644 --- a/js/comfyui-manager.js +++ b/js/comfyui-manager.js @@ -2,7 +2,7 @@ import { app } from "../../scripts/app.js"; import { api } from "../../scripts/api.js" import { ComfyDialog, $el } from "../../scripts/ui.js"; import { ComfyWidgets } from "../../scripts/widgets.js"; -import { ShareDialog } from "./comfyui-share.js"; +import { ShareDialog, SUPPORTED_OUTPUT_NODE_TYPES, parseURLPath, getPotentialOutputsAndOutputNodes } from "./comfyui-share.js"; var update_comfyui_button = null; var fetch_updates_button = null; @@ -2059,6 +2059,7 @@ class ManagerMenuDialog extends ComfyDialog { } } + app.registerExtension({ name: "Comfy.ManagerMenu", init() { @@ -2093,39 +2094,17 @@ app.registerExtension({ } app.graphToPrompt().then(prompt => { - console.log({prompt}) + // console.log({ prompt }) return app.graph._nodes; }).then(nodes => { - console.log({nodes}); - - const potential_outputs = []; - const potential_output_nodes = []; - - // iterate over the array of nodes to find the ones that are marked as SaveImage - // TODO: Add support for AnimateDiffCombine, etc. nodes that save videos/gifs, etc. - for (let i = 0; i < nodes.length; i++) { - const node = nodes[i]; - if (node.type !== "SaveImage") { - continue; - } - - if (node.type === "SaveImage") { - potential_output_nodes.push(node); - - // check if node has an 'images' array property - if (node.hasOwnProperty("images") && Array.isArray(node.images)) { - // iterate over the images array and add each image to the potential_outputs array - for (let j = 0; j < node.images.length; j++) { - potential_outputs.push({ "type": "image", "image": node.images[j] }); - } - } - } - } + // console.log({ nodes }); + const { potential_outputs, potential_output_nodes } = getPotentialOutputsAndOutputNodes(nodes); if (potential_outputs.length === 0) { if (potential_output_nodes.length === 0) { // todo: add support for other output node types (animatediff combine, etc.) - alert("No SaveImage node found. To share this workflow, please run a SaveImage node to your graph and re-run your prompt."); + const supported_nodes_string = SUPPORTED_OUTPUT_NODE_TYPES.join(", "); + alert(`No supported output node found (${supported_nodes_string}). To share this workflow, please add an output node to your graph and re-run your prompt.`); } else { alert("To share this, first run a prompt. Once it's done, click 'Share'."); } @@ -2197,7 +2176,6 @@ app.registerExtension({ ctx.restore(); } } - return r; }; }, diff --git a/js/comfyui-share.js b/js/comfyui-share.js index 54078622..36670ec4 100644 --- a/js/comfyui-share.js +++ b/js/comfyui-share.js @@ -2,7 +2,102 @@ import { app } from "../../scripts/app.js"; import { api } from "../../scripts/api.js" import { ComfyDialog, $el } from "../../scripts/ui.js"; -const VALID_OUTPUT_TYPES = ["SaveImage", "VHS_VideoCombine"]; +export const SUPPORTED_OUTPUT_NODE_TYPES = [ + "SaveImage", + "VHS_VideoCombine", + "ADE_AnimateDiffCombine", +] + +export function getPotentialOutputsAndOutputNodes(nodes) { + const potential_outputs = []; + const potential_output_nodes = []; + + // iterate over the array of nodes to find the ones that are marked as SaveImage + // TODO: Add support for AnimateDiffCombine, etc. nodes that save videos/gifs, etc. + for (let i = 0; i < nodes.length; i++) { + const node = nodes[i]; + if (!SUPPORTED_OUTPUT_NODE_TYPES.includes(node.type)) { + continue; + } + + if (node.type === "SaveImage") { + potential_output_nodes.push(node); + + // check if node has an 'images' array property + if (node.hasOwnProperty("images") && Array.isArray(node.images)) { + // iterate over the images array and add each image to the potential_outputs array + for (let j = 0; j < node.images.length; j++) { + potential_outputs.push({ "type": "image", "image": node.images[j], "title": node.title }); + } + } + } + else if (node.type === "VHS_VideoCombine") { + potential_output_nodes.push(node); + + // check if node has a 'widgets' array property, with type 'image' + if (node.hasOwnProperty("widgets") && Array.isArray(node.widgets)) { + // iterate over the widgets array and add each image to the potential_outputs array + for (let j = 0; j < node.widgets.length; j++) { + if (node.widgets[j].type === "image") { + const widgetValue = node.widgets[j].value; + const parsedURLVals = parseURLPath(widgetValue); + + // ensure that the parsedURLVals have 'filename', 'subfolder', 'type', and 'format' properties + if (parsedURLVals.hasOwnProperty("filename") && parsedURLVals.hasOwnProperty("subfolder") && parsedURLVals.hasOwnProperty("type") && parsedURLVals.hasOwnProperty("format")) { + if (parsedURLVals.type !== "output") { + // TODO + } + potential_outputs.push({ "type": "output", 'title' : node.title, "output": { "filename": parsedURLVals.filename, "subfolder": parsedURLVals.subfolder, "value": widgetValue, "format": parsedURLVals.format } }); + } + } + } + } + } + else if (node.type === "ADE_AnimateDiffCombine") { + potential_output_nodes.push(node); + + // check if node has a 'widgets' array property, with type 'image' + if (node.hasOwnProperty("widgets") && Array.isArray(node.widgets)) { + // iterate over the widgets array and add each image to the potential_outputs array + for (let j = 0; j < node.widgets.length; j++) { + if (node.widgets[j].type === "image") { + const widgetValue = node.widgets[j].value; + const parsedURLVals = parseURLPath(widgetValue); + // ensure that the parsedURLVals have 'filename', 'subfolder', 'type', and 'format' properties + if (parsedURLVals.hasOwnProperty("filename") && parsedURLVals.hasOwnProperty("subfolder") && parsedURLVals.hasOwnProperty("type") && parsedURLVals.hasOwnProperty("format")) { + if (parsedURLVals.type !== "output") { + // TODO + continue; + } + potential_outputs.push({ "type": "output", 'title' : node.title, "output": { "filename": parsedURLVals.filename, "subfolder": parsedURLVals.subfolder, "type": parsedURLVals.type, "value": widgetValue, "format": parsedURLVals.format } }); + } + } + } + } + } + } + return { potential_outputs, potential_output_nodes }; +} + + +export function parseURLPath(urlPath) { + // Extract the query string from the URL path + var queryString = urlPath.split('?')[1]; + + // Use the URLSearchParams API to parse the query string + var params = new URLSearchParams(queryString); + + // Create an object to store the parsed parameters + var parsedParams = {}; + + // Iterate over each parameter and add it to the object + for (var pair of params.entries()) { + parsedParams[pair[0]] = pair[1]; + } + + // Return the object with the parsed parameters + return parsedParams; +} export class ShareDialog extends ComfyDialog { static instance = null; @@ -24,7 +119,6 @@ export class ShareDialog extends ComfyDialog { } createButtons() { - this.radio_buttons = $el("div", { id: "selectOutputImages", }, []); @@ -112,11 +206,10 @@ export class ShareDialog extends ComfyDialog { } this.share_button.onclick = async () => { - alert("Clicked"); const prompt = await app.graphToPrompt(); const nodes = app.graph._nodes; - console.log({ prompt, nodes }); + // console.log({ prompt, nodes }); const destinations = []; if (this.matrix_destination_checkbox.checked) { @@ -139,41 +232,15 @@ export class ShareDialog extends ComfyDialog { return; } - const potential_outputs = []; - const potential_output_nodes = []; + const { potential_outputs, potential_output_nodes } = getPotentialOutputsAndOutputNodes(nodes); - // iterate over the array of nodes to find the ones that are marked as SaveImage - // TODO: Add support for AnimateDiffCombine, etc. nodes that save videos/gifs, etc. - for (let i = 0; i < nodes.length; i++) { - const node = nodes[i]; - console.log({ node }); - - if (!VALID_OUTPUT_TYPES.includes(node.type)) { - continue; - } - - if (node.type === "SaveImage") { - potential_output_nodes.push(node); - - // check if node has an 'images' array property - if (node.hasOwnProperty("images") && Array.isArray(node.images)) { - // iterate over the images array and add each image to the potential_outputs array - for (let j = 0; j < node.images.length; j++) { - potential_outputs.push({ "type": "image", "image": node.images[j] }); - } - } - } - else if (node.type === "VHS_VideoCombine") { - potential_output_nodes.push(node); - } - } - - console.log({ potential_outputs, potential_output_nodes }) + // console.log({ potential_outputs, potential_output_nodes }) if (potential_outputs.length === 0) { if (potential_output_nodes.length === 0) { // todo: add support for other output node types (animatediff combine, etc.) - alert("No SaveImage node found. To share this workflow, please run a SaveImage node to your graph and re-run your prompt."); + const supported_nodes_string = SUPPORTED_OUTPUT_NODE_TYPES.join(", "); + alert(`No supported output node found (${supported_nodes_string}). To share this workflow, please add an output node to your graph and re-run your prompt.`); } else { alert("To share this, first run a prompt. Once it's done, click 'Share'."); } @@ -425,16 +492,27 @@ export class ShareDialog extends ComfyDialog { } show({ potential_outputs, potential_output_nodes }) { + // console.log({ potential_outputs, potential_output_nodes }) this.radio_buttons.innerHTML = ""; // clear the radio buttons const new_radio_buttons = $el("div", { - id: "selectOutputImages-Options", + id: "selectOutput-Options", style: { 'overflow-y': 'auto', 'max-height': '400px', } - }, potential_output_nodes.map((output, index) => { + }, potential_outputs.map((output, index) => { const radio_button = $el("input", { type: 'radio', name: "selectOutputImages", value: index, required: index === 0 }, []) - const radio_button_img = $el("img", { src: output.imgs[0].src, style: { width: "auto", height: "100px" } }, []); + let radio_button_img; + if (output.type === "image") { + radio_button_img = $el("img", { src: `/view?filename=${output.image.filename}&subfolder=${output.image.subfolder}&type=${output.image.type}`, style: { width: "auto", height: "100px" } }, []); + } else if (output.type === "output") { + radio_button_img = $el("img", { src: output.output.value, style: { width: "auto", height: "100px" } }, []); + } else { + // unsupported output type + // this should never happen + // TODO + radio_button_img = $el("img", { src: "", style: { width: "auto", height: "100px" } }, []); + } const radio_button_text = $el("label", { // style: { // color: 'white' @@ -455,6 +533,7 @@ export class ShareDialog extends ComfyDialog { display: "flex", 'align-items': 'center', 'justify-content': 'space-between', + 'margin-bottom': '10px', } }, [radio_button, radio_button_text, radio_button_img]); })); From 7a79cf7d6083991e23308afc361dab3e8b61ed23 Mon Sep 17 00:00:00 2001 From: thecooltechguy Date: Tue, 7 Nov 2023 01:48:08 -0800 Subject: [PATCH 12/14] added support for previewimage --- __init__.py | 17 +++++++++++++++-- js/comfyui-share.js | 14 +++++++++++++- 2 files changed, 28 insertions(+), 3 deletions(-) diff --git a/__init__.py b/__init__.py index e7a2a24b..52c9bd2c 100644 --- a/__init__.py +++ b/__init__.py @@ -1285,14 +1285,16 @@ async def share_art(request): except: # for now, pick the first output output_to_share = potential_outputs[0] - + assert output_to_share['type'] in ('image', 'output') - output_dir = folder_paths.get_output_directory() if output_to_share['type'] == 'image': asset_filename = output_to_share['image']['filename'] asset_subfolder = output_to_share['image']['subfolder'] + + if output_to_share['image']['type'] == 'temp': + output_dir = folder_paths.get_temp_directory() else: asset_filename = output_to_share['output']['filename'] asset_subfolder = output_to_share['output']['subfolder'] @@ -1316,18 +1318,28 @@ async def share_art(request): json={ "assetFileName": asset_filename, "assetFileType": assetFileType, + "workflowJsonFileName" : 'workflow.json', + "workflowJsonFileType" : 'application/json', + }, ) as resp: assert resp.status == 200 presigned_urls_json = await resp.json() assetFilePresignedUrl = presigned_urls_json["assetFilePresignedUrl"] assetFileKey = presigned_urls_json["assetFileKey"] + workflowJsonFilePresignedUrl = presigned_urls_json["workflowJsonFilePresignedUrl"] + workflowJsonFileKey = presigned_urls_json["workflowJsonFileKey"] # upload asset async with aiohttp.ClientSession(trust_env=True, connector=aiohttp.TCPConnector(verify_ssl=False)) as session: async with session.put(assetFilePresignedUrl, data=open(asset_filepath, "rb")) as resp: assert resp.status == 200 + # upload workflow json + async with aiohttp.ClientSession(trust_env=True, connector=aiohttp.TCPConnector(verify_ssl=False)) as session: + async with session.put(workflowJsonFilePresignedUrl, data=json.dumps(prompt['workflow']).encode('utf-8')) as resp: + assert resp.status == 200 + # make a POST request to /api/upload_workflow with form data key values async with aiohttp.ClientSession(trust_env=True, connector=aiohttp.TCPConnector(verify_ssl=False)) as session: form = aiohttp.FormData() @@ -1336,6 +1348,7 @@ async def share_art(request): form.add_field("source", "comfyui_manager") form.add_field("assetFileKey", assetFileKey) form.add_field("assetFileType", assetFileType) + form.add_field("workflowJsonFileKey", workflowJsonFileKey) form.add_field("sharedWorkflowWorkflowJsonString", json.dumps(prompt['workflow'])) form.add_field("sharedWorkflowPromptJsonString", json.dumps(prompt['output'])) form.add_field("shareWorkflowCredits", credits) diff --git a/js/comfyui-share.js b/js/comfyui-share.js index 36670ec4..fb6a5514 100644 --- a/js/comfyui-share.js +++ b/js/comfyui-share.js @@ -3,6 +3,7 @@ import { api } from "../../scripts/api.js" import { ComfyDialog, $el } from "../../scripts/ui.js"; export const SUPPORTED_OUTPUT_NODE_TYPES = [ + "PreviewImage", "SaveImage", "VHS_VideoCombine", "ADE_AnimateDiffCombine", @@ -31,6 +32,17 @@ export function getPotentialOutputsAndOutputNodes(nodes) { } } } + else if (node.type === "PreviewImage") { + potential_output_nodes.push(node); + + // check if node has an 'images' array property + if (node.hasOwnProperty("images") && Array.isArray(node.images)) { + // iterate over the images array and add each image to the potential_outputs array + for (let j = 0; j < node.images.length; j++) { + potential_outputs.push({ "type": "image", "image": node.images[j], "title": node.title }); + } + } + } else if (node.type === "VHS_VideoCombine") { potential_output_nodes.push(node); @@ -503,7 +515,7 @@ export class ShareDialog extends ComfyDialog { }, potential_outputs.map((output, index) => { const radio_button = $el("input", { type: 'radio', name: "selectOutputImages", value: index, required: index === 0 }, []) let radio_button_img; - if (output.type === "image") { + if (output.type === "image" || output.type === "temp") { radio_button_img = $el("img", { src: `/view?filename=${output.image.filename}&subfolder=${output.image.subfolder}&type=${output.image.type}`, style: { width: "auto", height: "100px" } }, []); } else if (output.type === "output") { radio_button_img = $el("img", { src: output.output.value, style: { width: "auto", height: "100px" } }, []); From 4ef53f9f0a02f804ee1b724c561c9d15402ffbbe Mon Sep 17 00:00:00 2001 From: thecooltechguy Date: Wed, 8 Nov 2023 08:45:54 -0800 Subject: [PATCH 13/14] improved share ui --- js/comfyui-share.js | 436 +++++++++++++++++++++++++++----------------- 1 file changed, 271 insertions(+), 165 deletions(-) diff --git a/js/comfyui-share.js b/js/comfyui-share.js index fb6a5514..4169a29a 100644 --- a/js/comfyui-share.js +++ b/js/comfyui-share.js @@ -9,6 +9,29 @@ export const SUPPORTED_OUTPUT_NODE_TYPES = [ "ADE_AnimateDiffCombine", ] +var docStyle = document.createElement('style'); +docStyle.innerHTML = ` +.cm-menu-container { + column-gap: 20px; + display: flex; + flex-wrap: wrap; + justify-content: center; +} + +.cm-menu-column { + display: flex; + flex-direction: column; +} + +.cm-title { + padding: 10px 10px 0 10p; + background-color: black; + text-align: center; + height: 45px; +} +`; +document.head.appendChild(docStyle); + export function getPotentialOutputsAndOutputNodes(nodes) { const potential_outputs = []; const potential_output_nodes = []; @@ -59,7 +82,7 @@ export function getPotentialOutputsAndOutputNodes(nodes) { if (parsedURLVals.type !== "output") { // TODO } - potential_outputs.push({ "type": "output", 'title' : node.title, "output": { "filename": parsedURLVals.filename, "subfolder": parsedURLVals.subfolder, "value": widgetValue, "format": parsedURLVals.format } }); + potential_outputs.push({ "type": "output", 'title': node.title, "output": { "filename": parsedURLVals.filename, "subfolder": parsedURLVals.subfolder, "value": widgetValue, "format": parsedURLVals.format } }); } } } @@ -81,7 +104,7 @@ export function getPotentialOutputsAndOutputNodes(nodes) { // TODO continue; } - potential_outputs.push({ "type": "output", 'title' : node.title, "output": { "filename": parsedURLVals.filename, "subfolder": parsedURLVals.subfolder, "type": parsedURLVals.type, "value": widgetValue, "format": parsedURLVals.format } }); + potential_outputs.push({ "type": "output", 'title': node.title, "output": { "filename": parsedURLVals.filename, "subfolder": parsedURLVals.subfolder, "type": parsedURLVals.type, "value": widgetValue, "format": parsedURLVals.format } }); } } } @@ -178,9 +201,230 @@ export class ShareDialog extends ComfyDialog { this.share_button = $el("button", { type: "submit", textContent: "Share", + style: { + backgroundColor: "blue" + } }, []); - this.final_message = $el("div", {}, []); + this.final_message = $el("div", { + style: { + color: "white", + textAlign: "center", + // marginTop: "10px", + // backgroundColor: "black", + padding: "10px", + } + }, []); + + this.share_finalmessage_container = $el("div.cm-menu-container", { + id: "comfyui-share-finalmessage-container", + style: { + display: "none", + } + }, [ + $el("div.cm-menu-column", [ + this.final_message, + $el("button", { + type: "button", + textContent: "Close", + onclick: () => { + // Reset state + this.matrix_destination_checkbox.checked = false; + this.comfyworkflows_destination_checkbox.checked = true; + this.share_button.textContent = "Share"; + this.share_button.style.display = "inline-block"; + this.final_message.innerHTML = ""; + this.final_message.style.color = "white"; + this.credits_input.value = ""; + this.title_input.value = ""; + this.description_input.value = ""; + this.is_nsfw_checkbox.checked = false; + this.selectedOutputIndex = 0; + + // hide the final message + this.share_finalmessage_container.style.display = "none"; + + // show the share container + this.share_container.style.display = "flex"; + + this.close() + } + }), + ]) + ]); + this.share_container = $el("div.cm-menu-container", { + id: "comfyui-share-container" + }, [ + $el("div.cm-menu-column", [ + $el("details", { + style: { + border: "1px solid #999", + padding: "5px", + borderRadius: "5px", + backgroundColor: "#222" + } + }, [ + $el("summary", { + style: { + color: "white", + cursor: "pointer", + } + }, [`Matrix account`]), + $el("div", { + style: { + display: "flex", + flexDirection: "row", + } + }, [ + $el("div", { + textContent: "Homeserver", + style: { + marginRight: "10px", + } + }, []), + this.matrix_homeserver_input, + ]), + + $el("div", { + style: { + display: "flex", + flexDirection: "row", + } + }, [ + $el("div", { + textContent: "Username", + style: { + marginRight: "10px", + } + }, []), + this.matrix_username_input, + ]), + + $el("div", { + style: { + display: "flex", + flexDirection: "row", + } + }, [ + $el("div", { + textContent: "Password", + style: { + marginRight: "10px", + } + }, []), + this.matrix_password_input, + ]), + + ]), + $el("details", { + style: { + border: "1px solid #999", + marginTop: "10px", + padding: "5px", + borderRadius: "5px", + backgroundColor: "#222" + } + }, [ + $el("summary", { + style: { + color: "white", + cursor: "pointer", + } + }, [`Comfyworkflows.com account`]), + $el("h4", { + textContent: "Share key (found on your profile page)", + }, []), + $el("p", { size: 3, color: "white" }, ["When provided, your art will be saved to your account."]), + this.cw_sharekey_input, + ]), + + $el("div", {}, [ + $el("p", { + size: 3, color: "white", style: { + color: 'white' + } + }, [`Select where to share your art:`]), + this.matrix_destination_checkbox, + matrix_destination_checkbox_text, + $el("br", {}, []), + this.comfyworkflows_destination_checkbox, + comfyworkflows_destination_checkbox_text, + ]), + + $el("h4", { + textContent: "Credits (optional)", + size: 3, + color: "white", + style: { + color: 'white' + } + }, []), + this.credits_input, + // $el("br", {}, []), + + $el("h4", { + textContent: "Title (optional)", + size: 3, + color: "white", + style: { + color: 'white' + } + }, []), + this.title_input, + // $el("br", {}, []), + + $el("h4", { + textContent: "Description (optional)", + size: 3, + color: "white", + style: { + color: 'white' + } + }, []), + this.description_input, + $el("br", {}, []), + + $el("div", {}, [this.is_nsfw_checkbox, is_nsfw_checkbox_text]), + // $el("br", {}, []), + + // this.final_message, + // $el("br", {}, []), + ]), + $el("div.cm-menu-column", [ + this.radio_buttons, + $el("br", {}, []), + + this.share_button, + + $el("button", { + type: "button", + textContent: "Close", + onclick: () => { + // Reset state + this.matrix_destination_checkbox.checked = false; + this.comfyworkflows_destination_checkbox.checked = true; + this.share_button.textContent = "Share"; + this.share_button.style.display = "inline-block"; + this.final_message.innerHTML = ""; + this.final_message.style.color = "white"; + this.credits_input.value = ""; + this.title_input.value = ""; + this.description_input.value = ""; + this.is_nsfw_checkbox.checked = false; + this.selectedOutputIndex = 0; + + // hide the final message + this.share_finalmessage_container.style.display = "none"; + + // show the share container + this.share_container.style.display = "flex"; + + this.close() + } + }), + $el("br", {}, []), + ]), + ]); // get the user's existing matrix auth and share key ShareDialog.matrix_auth = { homeserver: "matrix.org", username: "", password: "" }; @@ -322,6 +566,10 @@ export class ShareDialog extends ComfyDialog { this.final_message.style.color = "green"; + // hide #comfyui-share-container and show #comfyui-share-finalmessage-container + this.share_container.style.display = "none"; + this.share_finalmessage_container.style.display = "block"; + // hide the share button this.share_button.textContent = "Shared!"; this.share_button.style.display = "none"; @@ -335,165 +583,8 @@ export class ShareDialog extends ComfyDialog { ]), $el("br", {}, []), - $el("details", { - style: { - border: "1px solid #999", - padding: "5px", - borderRadius: "5px", - backgroundColor: "#222" - } - }, [ - $el("summary", { - style: { - color: "white", - cursor: "pointer", - } - }, [`Matrix account`]), - $el("div", { - style: { - display: "flex", - flexDirection: "row", - } - }, [ - $el("div", { - textContent: "Homeserver", - style: { - marginRight: "10px", - } - }, []), - this.matrix_homeserver_input, - ]), - - $el("div", { - style: { - display: "flex", - flexDirection: "row", - } - }, [ - $el("div", { - textContent: "Username", - style: { - marginRight: "10px", - } - }, []), - this.matrix_username_input, - ]), - - $el("div", { - style: { - display: "flex", - flexDirection: "row", - } - }, [ - $el("div", { - textContent: "Password", - style: { - marginRight: "10px", - } - }, []), - this.matrix_password_input, - ]), - - ]), - $el("details", { - style: { - border: "1px solid #999", - marginTop: "10px", - padding: "5px", - borderRadius: "5px", - backgroundColor: "#222" - } - }, [ - $el("summary", { - style: { - color: "white", - cursor: "pointer", - } - }, [`Comfyworkflows.com account`]), - $el("h4", { - textContent: "Share key (found on your profile page)", - }, []), - $el("p", { size: 3, color: "white" }, ["When provided, your art will be saved to your account."]), - this.cw_sharekey_input, - ]), - - $el("div", {}, [ - $el("p", { - size: 3, color: "white", style: { - color: 'white' - } - }, [`Select where to share your art:`]), - this.matrix_destination_checkbox, - matrix_destination_checkbox_text, - $el("br", {}, []), - this.comfyworkflows_destination_checkbox, - comfyworkflows_destination_checkbox_text, - ]), - - $el("h4", { - textContent: "Credits (optional)", - size: 3, - color: "white", - style: { - color: 'white' - } - }, []), - this.credits_input, - // $el("br", {}, []), - - $el("h4", { - textContent: "Title (optional)", - size: 3, - color: "white", - style: { - color: 'white' - } - }, []), - this.title_input, - // $el("br", {}, []), - - $el("h4", { - textContent: "Description (optional)", - size: 3, - color: "white", - style: { - color: 'white' - } - }, []), - this.description_input, - $el("br", {}, []), - - $el("div", {}, [this.is_nsfw_checkbox, is_nsfw_checkbox_text]), - $el("br", {}, []), - - this.radio_buttons, - $el("br", {}, []), - - this.final_message, - $el("br", {}, []), - - this.share_button, - - $el("button", { - type: "button", - textContent: "Close", - onclick: () => { - // Reset state - this.matrix_destination_checkbox.checked = false; - this.comfyworkflows_destination_checkbox.checked = true; - this.share_button.textContent = "Share"; - this.share_button.style.display = "inline-block"; - this.final_message.innerHTML = ""; - this.final_message.style.color = "white"; - this.credits_input.value = ""; - this.title_input.value = ""; - this.description_input.value = ""; - this.is_nsfw_checkbox.checked = false; - this.selectedOutputIndex = 0; - this.close() - } - }), - $el("br", {}, []), + this.share_finalmessage_container, + this.share_container, ]; res[0].style.padding = "10px 10px 10px 10px"; @@ -509,7 +600,7 @@ export class ShareDialog extends ComfyDialog { const new_radio_buttons = $el("div", { id: "selectOutput-Options", style: { - 'overflow-y': 'auto', + 'overflow-y': 'scroll', 'max-height': '400px', } }, potential_outputs.map((output, index) => { @@ -549,7 +640,7 @@ export class ShareDialog extends ComfyDialog { } }, [radio_button, radio_button_text, radio_button_img]); })); - const header = $el("h4", { + const header = $el("h3", { textContent: "Select an image to share", size: 3, color: "white", @@ -558,9 +649,24 @@ export class ShareDialog extends ComfyDialog { color: 'white', backgroundColor: 'black', padding: '10px', + 'margin-top': '0px', } - }, []); + }, [ + $el("p", { + textContent: "Scroll to see all outputs", + size: 2, + color: "white", + style: { + 'text-align': 'center', + color: 'white', + 'margin-bottom': '5px', + 'font-style': 'italic', + 'font-size': '12px', + }, + }, []) + ]); this.radio_buttons.appendChild(header); + // this.radio_buttons.appendChild(subheader); this.radio_buttons.appendChild(new_radio_buttons); this.element.style.display = "block"; } From 8a3dac8985d2cad294036a4243c725a68443dfd1 Mon Sep 17 00:00:00 2001 From: thecooltechguy Date: Fri, 10 Nov 2023 12:59:47 -0800 Subject: [PATCH 14/14] fixed video combine node not working --- js/comfyui-share.js | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/js/comfyui-share.js b/js/comfyui-share.js index 4169a29a..4ce453b3 100644 --- a/js/comfyui-share.js +++ b/js/comfyui-share.js @@ -84,6 +84,17 @@ export function getPotentialOutputsAndOutputNodes(nodes) { } potential_outputs.push({ "type": "output", 'title': node.title, "output": { "filename": parsedURLVals.filename, "subfolder": parsedURLVals.subfolder, "value": widgetValue, "format": parsedURLVals.format } }); } + } else if (node.widgets[j].type === "preview") { + const widgetValue = node.widgets[j].value; + const parsedURLVals = widgetValue.params; + + // ensure that the parsedURLVals have 'filename', 'subfolder', 'type', and 'format' properties + if (parsedURLVals.hasOwnProperty("filename") && parsedURLVals.hasOwnProperty("subfolder") && parsedURLVals.hasOwnProperty("type") && parsedURLVals.hasOwnProperty("format")) { + if (parsedURLVals.type !== "output") { + // TODO + } + potential_outputs.push({ "type": "output", 'title': node.title, "output": { "filename": parsedURLVals.filename, "subfolder": parsedURLVals.subfolder, "value": `/view?filename=${parsedURLVals.filename}&subfolder=${parsedURLVals.subfolder}&type=${parsedURLVals.type}&format=${parsedURLVals.format}`, "format": parsedURLVals.format } }); + } } } }