diff --git a/__init__.py b/__init__.py
index 393bffec..86a4927e 100644
--- a/__init__.py
+++ b/__init__.py
@@ -1,4 +1,5 @@
import configparser
+import mimetypes
import shutil
import folder_paths
import os
@@ -577,7 +578,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":
@@ -1408,6 +1408,228 @@ async def channel_url_list(request):
return web.Response(status=200)
+def get_matrix_auth():
+ if not os.path.exists(os.path.join(folder_paths.base_path, "matrix_auth")):
+ return None
+ 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
+ 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):
+ # 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("Getting stored Comfyworkflows.com auth...")
+ comfyworkflows_auth = get_comfyworkflows_auth()
+ if not comfyworkflows_auth:
+ return web.Response(status=404)
+ 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']
+ description = json_data['description']
+ is_nsfw = json_data['is_nsfw']
+ prompt = json_data['prompt']
+ potential_outputs = json_data['potential_outputs']
+ 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')
+ 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']
+
+ 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]
+
+ 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,
+ "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()
+ 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("workflowJsonFileKey", workflowJsonFileKey)
+ 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:
+ comfyui_share_room_id = '!LGYSoacpJPhIfBqVfb:matrix.org'
+ filename = os.path.basename(asset_filepath)
+ content_type = assetFileType
+
+ try:
+ from matrix_client.api import MatrixHttpApi
+ 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']
+
+ 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"
+ 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)
+ 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()
+ return web.json_response({"error" : "An error occurred when sharing your art to Matrix."}, content_type='application/json', status=500)
+
+ 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 d4ddf463..a26eec7d 100644
--- a/js/comfyui-manager.js
+++ b/js/comfyui-manager.js
@@ -1,14 +1,15 @@
import { app } from "../../scripts/app.js";
import { api } from "../../scripts/api.js"
import { ComfyDialog, $el } from "../../scripts/ui.js";
+import { ShareDialog, SUPPORTED_OUTPUT_NODE_TYPES, getPotentialOutputsAndOutputNodes } from "./comfyui-share.js";
import { CustomNodesInstaller } from "./custom-nodes-downloader.js";
import { AlternativesInstaller } from "./a1111-alter-downloader.js";
import { SnapshotManager } from "./snapshot.js";
import { ModelInstaller } from "./model-downloader.js";
import { manager_instance, setManagerInstance, install_via_git_url } from "./common.js";
-var style = document.createElement('style');
-style.innerHTML = `
+var docStyle = document.createElement('style');
+docStyle.innerHTML = `
.cm-menu-container {
column-gap: 20px;
display: flex;
@@ -29,43 +30,78 @@ style.innerHTML = `
}
`;
-document.head.appendChild(style);
+document.head.appendChild(docStyle);
var update_comfyui_button = null;
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);
+}
+#comfyworkflows-button-menu {
+ z-index: 10000000000 !important;
+}
+`;
+
+
+
async function init_badge_mode() {
- api.fetchApi('/manager/badge_mode')
- .then(response => response.text())
- .then(data => { badge_mode = data; })
+ api.fetchApi('/manager/badge_mode')
+ .then(response => response.text())
+ .then(data => { badge_mode = data; })
}
await init_badge_mode();
-
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;
}
@@ -74,7 +110,7 @@ let nicknames = await fetchNicknames();
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";
@@ -82,13 +118,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 = 10010;
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 = 10010;
}
@@ -99,7 +135,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 = 10010;
return false;
@@ -107,12 +143,12 @@ 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";
@@ -124,13 +160,13 @@ async function fetchUpdates(update_check_checkbox) {
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 = 10010;
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 = 10010;
update_check_checkbox.checked = false;
@@ -142,7 +178,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 = 10010;
return false;
@@ -155,7 +191,7 @@ 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";
@@ -169,7 +205,7 @@ async function updateAll(update_check_checkbox) {
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 = 10010;
return false;
@@ -185,7 +221,7 @@ async function updateAll(update_check_checkbox) {
return true;
}
- catch(exception) {
+ catch (exception) {
app.ui.dialog.show(`Failed to update ComfyUI or several extensions / ${exception}`);
app.ui.dialog.element.style.zIndex = 10010;
return false;
@@ -197,6 +233,19 @@ async function updateAll(update_check_checkbox) {
}
}
+function newDOMTokenList(initialTokens) {
+ const tmp = document.createElement(`div`);
+
+ const classList = tmp.classList;
+ if (initialTokens) {
+ initialTokens.forEach(token => {
+ classList.add(token);
+ });
+ }
+
+ return classList;
+ }
+
// -----------
class ManagerMenuDialog extends ComfyDialog {
@@ -204,28 +253,28 @@ class ManagerMenuDialog extends ComfyDialog {
createControlsMid() {
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)
+ });
const res =
[
@@ -298,59 +347,59 @@ class ManagerMenuDialog extends ComfyDialog {
// 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) {
- }
- });
+ }
+ });
return [
$el("div", {}, [this.local_mode_checkbox, checkbox_text, this.update_check_checkbox, uc_checkbox_text]),
@@ -395,10 +444,70 @@ class ManagerMenuDialog extends ComfyDialog {
}),
$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();
+ }
+
+ app.graphToPrompt().then(prompt => {
+ // console.log({ prompt })
+ return app.graph._nodes;
+ }).then(nodes => {
+ // 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.)
+ 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'.");
+ }
+ return;
+ }
+
+ ShareDialog.instance.show({ potential_outputs, potential_output_nodes });
+ });
+ },
+ },
+ {
+ title: "Close",
+ callback: () => {
+ this.close();
+ },
+ }
+ ],
+ {
+ event: e,
+ scale: 1.3,
+ },
+ window
+ );
+ // set the id so that we can override the context menu's z-index to be above the comfyui manager menu
+ menu.root.id = "comfyworkflows-button-menu";
+ menu.root.classList.add("pysssss-workflow-popup-2");
+ },
+ })
+ ]),
$el("button", {
type: "button",
@@ -447,9 +556,15 @@ 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");
@@ -466,102 +581,152 @@ app.registerExtension({
manager_instance.show();
}
menu.append(managerButton);
+
+
+ const shareButton = document.createElement("button");
+ shareButton.textContent = "Share";
+ shareButton.onclick = () => {
+ if (!ShareDialog.instance) {
+ ShareDialog.instance = new ShareDialog();
+ }
+
+ app.graphToPrompt().then(prompt => {
+ // console.log({ prompt })
+ return app.graph._nodes;
+ }).then(nodes => {
+ // 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.)
+ 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'.");
+ }
+ 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%)";
+ 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);
},
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();
- }
- }
-
- return r;
- };
+ ctx.fillStyle = fgColor;
+ ctx.fillText(text, this.size[0] - sz.width - 6, -LiteGraph.NODE_TITLE_HEIGHT - 6);
+ ctx.restore();
+ }
+ }
+ 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
diff --git a/js/comfyui-share.js b/js/comfyui-share.js
new file mode 100644
index 00000000..4ce453b3
--- /dev/null
+++ b/js/comfyui-share.js
@@ -0,0 +1,684 @@
+import { app } from "../../scripts/app.js";
+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",
+]
+
+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 = [];
+
+ // 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 === "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);
+
+ // 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.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 } });
+ }
+ }
+ }
+ }
+ }
+ 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;
+ static matrix_auth = { homeserver: "matrix.org", username: "", password: "" };
+ static cw_sharekey = "";
+
+ constructor() {
+ super();
+ 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?"])
+ 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 = false; //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: "submit",
+ textContent: "Share",
+ style: {
+ backgroundColor: "blue"
+ }
+ }, []);
+
+ 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: "" };
+ 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;
+
+ // console.log({ prompt, 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, potential_output_nodes } = getPotentialOutputsAndOutputNodes(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.)
+ 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'.");
+ }
+ 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...";
+
+ 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,
+ selected_output_index: this.selectedOutputIndex,
+ // 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 #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";
+ // this.close();
+ }
+
+ const res =
+ [
+ $el("tr.td", { width: "100%" }, [
+ $el("font", { size: 6, color: "white" }, [`Share your art`]),
+ ]),
+ $el("br", {}, []),
+
+ this.share_finalmessage_container,
+ this.share_container,
+ ];
+
+ 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({ 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: "selectOutput-Options",
+ style: {
+ 'overflow-y': 'scroll',
+ 'max-height': '400px',
+ }
+ }, 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" || 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" } }, []);
+ } 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'
+ // }
+ }, [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',
+ 'margin-bottom': '10px',
+ }
+ }, [radio_button, radio_button_text, radio_button_img]);
+ }));
+ const header = $el("h3", {
+ textContent: "Select an image to share",
+ size: 3,
+ color: "white",
+ style: {
+ 'text-align': 'center',
+ 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";
+ }
+}
\ No newline at end of file
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