mirror of
https://github.com/Comfy-Org/ComfyUI-Manager.git
synced 2025-12-27 23:30:52 +08:00
Merge pull request #146 from thecooltechguy/main
Adding a "Share" button for people to easily share their ComfyUI art & workflows
This commit is contained in:
commit
24204eb494
224
__init__.py
224
__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 = {}
|
||||
|
||||
@ -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.<BR><BR>See terminal log.<BR>');
|
||||
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;
|
||||
};
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
684
js/comfyui-share.js
Normal file
684
js/comfyui-share.js
Normal file
@ -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: <a href='" + response_json.comfyworkflows.url + "' target='_blank'>" + response_json.comfyworkflows.url + "</a>";
|
||||
if (response_json.matrix.success) {
|
||||
this.final_message.innerHTML += "<br>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";
|
||||
}
|
||||
}
|
||||
@ -1 +1,2 @@
|
||||
GitPython
|
||||
GitPython
|
||||
matrix-client==0.4.0
|
||||
Loading…
Reference in New Issue
Block a user