Major version update with enhanced functionality
Added environment-based configuration to streamline deployments Improved data validation logic for more robust CRUD operations Refined folder structure to optimize code organization
This commit is contained in:
+84
-32
@@ -1,47 +1,99 @@
|
||||
from functools import wraps
|
||||
import json
|
||||
from .trim_workflow import PromptTrim
|
||||
from .crypto_node import SaveCryptoNode, ExcuteCryptoNode, RandomSeedNode,CryptoCatImage
|
||||
from functools import partial, wraps
|
||||
import os
|
||||
from .trim_workflow import WorkflowTrimHandler
|
||||
from .crypto_node import (
|
||||
SaveCryptoNode,
|
||||
RandomSeedNode,
|
||||
SaveCryptoBridgeNode,
|
||||
DecodeCryptoNode,
|
||||
)
|
||||
from .crypto_node_old import ExcuteCryptoNode, CryptoCatImage
|
||||
|
||||
NODE_CLASS_MAPPINGS = {
|
||||
"SaveCryptoNode": SaveCryptoNode,
|
||||
"ExcuteCryptoNode": ExcuteCryptoNode,
|
||||
"RandomSeedNode": RandomSeedNode,
|
||||
"CryptoCatImage":CryptoCatImage,
|
||||
"CryptoCatImage": CryptoCatImage,
|
||||
"SaveCryptoBridgeNode": SaveCryptoBridgeNode,
|
||||
"DecodeCryptoNode": DecodeCryptoNode,
|
||||
}
|
||||
|
||||
# A dictionary that contains the friendly/humanly readable titles for the nodes
|
||||
NODE_DISPLAY_NAME_MAPPINGS = {
|
||||
"SaveCryptoNode": "InputCrypto",
|
||||
"SaveCryptoNode": "加密组件",
|
||||
"RandomSeedNode": "随机种子",
|
||||
"SaveCryptoBridgeNode": "加密结束桥接",
|
||||
"DecodeCryptoNode": "解密组件",
|
||||
"ExcuteCryptoNode": "OutputCrypto",
|
||||
"RandomSeedNode": "RandomSeedNode",
|
||||
"CryptoCatImage":"CryptoCatImage",
|
||||
"CryptoCatImage": "CryptoCatImage",
|
||||
}
|
||||
|
||||
WEB_DIRECTORY = "./js"
|
||||
|
||||
__all__ = ['NODE_CLASS_MAPPINGS', 'NODE_DISPLAY_NAMES_MAPPINGS', 'WEB_DIRECTORY']
|
||||
|
||||
|
||||
__all__ = ["NODE_CLASS_MAPPINGS", "NODE_DISPLAY_NAMES_MAPPINGS", "WEB_DIRECTORY"]
|
||||
from server import PromptServer
|
||||
import aiohttp
|
||||
|
||||
workspace_path = os.path.join(os.path.dirname(__file__))
|
||||
dist_path = os.path.join(workspace_path, "static")
|
||||
if os.path.exists(dist_path):
|
||||
PromptServer.instance.app.add_routes(
|
||||
[aiohttp.web.static("/cryptocat/static", dist_path)]
|
||||
)
|
||||
from urllib.parse import unquote
|
||||
from .auth_unit import AuthUnit
|
||||
from .updown_workflow import UploadWorkflow, UserWorkflowSetting
|
||||
|
||||
handler_instance = WorkflowTrimHandler()
|
||||
onprompt_callback = partial(handler_instance.onprompt_handler)
|
||||
PromptServer.instance.add_on_prompt_handler(onprompt_callback)
|
||||
routes = PromptServer.instance.routes
|
||||
|
||||
|
||||
method_name = "trigger_on_prompt"
|
||||
original_method = getattr(PromptServer, method_name)
|
||||
@routes.get("/cryptocat/auth_callback")
|
||||
async def auth_callback(request):
|
||||
auth_query = request.query
|
||||
token = auth_query.get("token", "")
|
||||
client_key = auth_query.get("client_key", "")
|
||||
if token and client_key:
|
||||
token = unquote(token)
|
||||
client_key = unquote(client_key)
|
||||
AuthUnit().save_user_token(token, client_key)
|
||||
return aiohttp.web.json_response({"status": "success"}, status=200)
|
||||
|
||||
# 创建 hook
|
||||
@wraps(original_method)
|
||||
def new_trigger_on_prompt(self, json_data):
|
||||
prompt = json_data["prompt"]
|
||||
pr = PromptTrim(prompt)
|
||||
if pr.has_crypto_node():
|
||||
print("has crypto node")
|
||||
json_data["prompt"] = pr.replace_prompt()
|
||||
# 删除 extra_data 字段
|
||||
if "extra_data" in json_data:
|
||||
json_data["extra_data"] = {}
|
||||
print("hook new_trigger_on_prompt delete extra_data")
|
||||
result = original_method(self, json_data)
|
||||
return result
|
||||
|
||||
setattr(PromptServer, method_name, new_trigger_on_prompt)
|
||||
@routes.post("/cryptocat/keygen")
|
||||
async def keygen(request):
|
||||
data = await request.json()
|
||||
template_id = data.get("template_id", "").strip()
|
||||
if not template_id or len(template_id) != 32:
|
||||
return aiohttp.web.json_response(
|
||||
{"error_msg": "template_id is required"}, status=500
|
||||
)
|
||||
user_token, error_msg = AuthUnit().get_user_token()
|
||||
if not user_token:
|
||||
return aiohttp.web.json_response({"error_msg": error_msg}, status=200)
|
||||
user_workflow = UploadWorkflow(user_token)
|
||||
serial_numbers = user_workflow.generate_serial_number(template_id, 1)
|
||||
if not serial_numbers:
|
||||
return aiohttp.web.json_response({"error_msg": "获取失败"}, status=200)
|
||||
serial_number = serial_numbers[0]
|
||||
return aiohttp.web.json_response({"serial_number": serial_number}, status=200)
|
||||
|
||||
|
||||
@routes.get("/cryptocat/logout")
|
||||
async def logout(request):
|
||||
AuthUnit().clear_user_token()
|
||||
return aiohttp.web.json_response({"status": "success"}, status=200)
|
||||
|
||||
|
||||
@routes.post("/cryptocat/set_long_token")
|
||||
async def set_long_token(request):
|
||||
data = await request.json()
|
||||
long_token = data.get("long_token", "")
|
||||
AuthUnit().save_long_token(long_token)
|
||||
return aiohttp.web.json_response({"status": "success"}, status=200)
|
||||
|
||||
|
||||
@routes.post("/cryptocat/set_auto_overwrite")
|
||||
async def set_auto_overwrite(request):
|
||||
data = await request.json()
|
||||
auto_overwrite = data.get("auto_overwrite")
|
||||
UserWorkflowSetting().set_auto_overwrite(auto_overwrite)
|
||||
return aiohttp.web.json_response({"status": "success"}, status=200)
|
||||
|
||||
+107
@@ -0,0 +1,107 @@
|
||||
_G='clear_key'
|
||||
_F='cryptocat_clear_user_info'
|
||||
_E='long_token'
|
||||
_D='user_token'
|
||||
_C=None
|
||||
_B='Auth'
|
||||
_A='utf-8'
|
||||
import base64,json,logging,os
|
||||
from pathlib import Path
|
||||
import time
|
||||
from.url_config import CatUrlConfig
|
||||
from server import PromptServer
|
||||
import requests,configparser
|
||||
from.utils import generate_random_string,get_local_app_setting_path,get_machine_id
|
||||
CRYPT_FLAG='CAT_CRYPT'
|
||||
CRYPT_FLAG_LENGTH=len(CRYPT_FLAG)
|
||||
class AuthUnit:
|
||||
_instance=_C
|
||||
def __new__(A,*B,**C):
|
||||
if A._instance is _C:A._instance=super(AuthUnit,A).__new__(A)
|
||||
return A._instance
|
||||
def __init__(A):
|
||||
B=True
|
||||
if not hasattr(A,'initialized'):A.machine_id=get_machine_id();C=get_local_app_setting_path();C.mkdir(parents=B,exist_ok=B);A.config_path=C/'config.ini';A.token=A.read_user_token();A.long_token=A.read_long_token();A.client_key='';A.shift_key=sum(ord(A)for A in A.machine_id[:8])%20+1;A.last_check_time=0;A.initialized=B
|
||||
def empty_token(A,token,clear=False):
|
||||
C=clear;B=token
|
||||
if B==A.token:
|
||||
A.token=''
|
||||
if C:PromptServer.instance.send_sync(_F,{_G:_D})
|
||||
elif B==A.long_token:
|
||||
A.long_token=''
|
||||
if C:PromptServer.instance.send_sync(_F,{_G:_E})
|
||||
else:print('empty_token error',B)
|
||||
def get_user_token(B):
|
||||
A=B.token
|
||||
if not A:
|
||||
A=B.read_user_token()
|
||||
if not A:
|
||||
if B.long_token and len(B.long_token)>50:A=B.long_token
|
||||
else:return _C,'no token found'
|
||||
else:B.token=A
|
||||
if time.time()-B.last_check_time>120 and A:
|
||||
try:
|
||||
D={'Content-Type':'application/json','Authorization':f"Bearer {A}"};print('login_api_url',CatUrlConfig().login_api_url);C=requests.get(CatUrlConfig().login_api_url,headers=D)
|
||||
if C.status_code==200:B.last_check_time=time.time()
|
||||
else:print('crypto cat login result error',C);B.empty_token(A,C.status_code==401);A=_C;return _C,'login result error'
|
||||
except requests.RequestException as E:B.empty_token(A);A=_C;print('crypto cat login failed',E);return _C,'login failed'
|
||||
return A,''
|
||||
def login_dialog(A,title=''):A.client_key=generate_random_string(8);PromptServer.instance.send_sync('cryptocat_login_dialog',{'machine_id':A.machine_id,'client_key':A.client_key,'title':title})
|
||||
@staticmethod
|
||||
def _encrypt(text,shift_key):
|
||||
'Encrypt text using a simple character shift and base64 encoding.'
|
||||
if not text:return''
|
||||
try:B=CRYPT_FLAG+''.join(chr((ord(A)+shift_key)%65536)for A in text);return base64.b64encode(B.encode(_A)).decode(_A)
|
||||
except Exception as A:logging.error(f"Encryption error: {A}");raise RuntimeError(f"Encryption failed: {A}")
|
||||
@staticmethod
|
||||
def _decrypt(encoded_text,shift_key):
|
||||
'Decrypt text that was encrypted with the _encrypt method.';B=encoded_text
|
||||
try:
|
||||
A=base64.b64decode(B.encode(_A)).decode(_A)
|
||||
if not A.startswith(CRYPT_FLAG):return B
|
||||
A=A[CRYPT_FLAG_LENGTH:];return''.join(chr((ord(A)-shift_key)%65536)for A in A)
|
||||
except Exception as C:print(f"Decryption error: {C}");return B
|
||||
def read_user_token(A):
|
||||
'Retrieve and decrypt the user token.'
|
||||
if not os.path.exists(A.config_path):return''
|
||||
try:
|
||||
B=configparser.ConfigParser();B.read(A.config_path,encoding=_A);C=B.get(_B,_D,fallback='')
|
||||
if not C:return''
|
||||
return AuthUnit._decrypt(C,A.shift_key)
|
||||
except Exception as D:print(f"Error reading token: {D}");return''
|
||||
def save_user_token(A,user_token,client_key):
|
||||
'Encrypt and save the user token.';D=client_key;C=user_token
|
||||
if not C:return
|
||||
if not D or A.client_key!=D:return
|
||||
try:
|
||||
A.token=C;B=configparser.ConfigParser()
|
||||
if os.path.exists(A.config_path):B.read(A.config_path,encoding=_A)
|
||||
if _B not in B:B.add_section(_B)
|
||||
F=AuthUnit._encrypt(C,A.shift_key);B[_B][_D]=F
|
||||
with open(A.config_path,'w',encoding=_A)as G:B.write(G)
|
||||
except Exception as E:print(f"Error saving token: {E}");raise RuntimeError(f"Failed to save token: {E}")
|
||||
def save_long_token(A,long_token):
|
||||
C=long_token
|
||||
try:
|
||||
if not C or C=='null':A.long_token='';C=''
|
||||
else:A.long_token=C
|
||||
B=configparser.ConfigParser()
|
||||
if os.path.exists(A.config_path):B.read(A.config_path,encoding=_A)
|
||||
if _B not in B:B.add_section(_B)
|
||||
B[_B][_E]=A.long_token
|
||||
with open(A.config_path,'w',encoding=_A)as E:B.write(E)
|
||||
except Exception as D:print(f"Error saving long token: {D}");raise RuntimeError(f"Failed to save long token: {D}")
|
||||
def read_long_token(A):
|
||||
if not os.path.exists(A.config_path):return''
|
||||
try:B=configparser.ConfigParser();B.read(A.config_path,encoding=_A);return B.get(_B,_E,fallback='')
|
||||
except Exception as C:print(f"Error reading long token: {C}");return''
|
||||
def clear_user_token(B):
|
||||
B.token='';B.long_token='';PromptServer.instance.send_sync(_F,{_G:'all'})
|
||||
if not os.path.exists(B.config_path):return
|
||||
try:
|
||||
A=configparser.ConfigParser();A.read(B.config_path,encoding=_A)
|
||||
if _B not in A:return
|
||||
if _D not in A[_B]:return
|
||||
A[_B][_D]='';A[_B][_E]=''
|
||||
with open(B.config_path,'w',encoding=_A)as D:A.write(D)
|
||||
except Exception as C:print(f"Error clearing token: {C}");raise RuntimeError(f"Failed to clear token: {C}")
|
||||
+217
-139
@@ -1,187 +1,265 @@
|
||||
import json
|
||||
import os
|
||||
import random
|
||||
import uuid
|
||||
import torch
|
||||
|
||||
from nodes import SaveImage
|
||||
from .trim_workflow import PromptTrim, WorkflowTrim
|
||||
from.file_compressor import FileCompressor
|
||||
import folder_paths
|
||||
from comfy_execution.graph import ExecutionBlocker
|
||||
from comfy_execution.graph_utils import GraphBuilder
|
||||
from .updown_workflow import UploadWorkflow
|
||||
from server import PromptServer
|
||||
from .trim_workflow import CryptoWorkflow, DecodeCryptoWorkflow
|
||||
from .auth_unit import AuthUnit
|
||||
|
||||
|
||||
class SaveCryptoNode():
|
||||
class SaveCryptoNode:
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
return {
|
||||
"required": {
|
||||
"crypto_folder": ("STRING", {"default": folder_paths.output_directory}),
|
||||
"crypto_name": ("STRING", {"default": "my_cat.json"}),
|
||||
"output_images" : ("IMAGE",),
|
||||
},
|
||||
"optional": {
|
||||
"input_anything" : ("*",),
|
||||
},
|
||||
"hidden": {
|
||||
"unique_id": "UNIQUE_ID",
|
||||
"prompt": "PROMPT",
|
||||
"extra_pnginfo": "EXTRA_PNGINFO",
|
||||
}
|
||||
}
|
||||
"required": {"template_id": ("STRING", {"default": uuid.uuid4().hex})},
|
||||
"optional": {"input_anything": ("*", {})},
|
||||
"hidden": {
|
||||
"unique_id": "UNIQUE_ID",
|
||||
"prompt": "PROMPT",
|
||||
"extra_pnginfo": "EXTRA_PNGINFO",
|
||||
},
|
||||
}
|
||||
|
||||
# @classmethod
|
||||
# def VALIDATE_INPUTS(s, input_types):
|
||||
# return True
|
||||
|
||||
|
||||
RETURN_TYPES = () #"STRING",
|
||||
RETURN_TYPES = ()
|
||||
OUTPUT_NODE = True
|
||||
FUNCTION = "crypto" #这个节点中的主函数
|
||||
CATEGORY = "advanced/CryptoCat"
|
||||
|
||||
def crypto(self, crypto_folder, crypto_name, output_images, **kwargs):
|
||||
if not crypto_name or len(crypto_folder) < 2 or len(crypto_name) == 0:
|
||||
raise Exception("CryptoCat folder and filename must be at least two characters long")
|
||||
|
||||
# 从 kwargs 中提取特定参数
|
||||
unique_id = kwargs.pop('unique_id', None)
|
||||
prompt = kwargs.pop('prompt', None)
|
||||
extra_pnginfo = kwargs.pop('extra_pnginfo', None)
|
||||
FUNCTION = "crypto"
|
||||
CATEGORY = "advanced/CryptoCat"
|
||||
|
||||
# 检查必需参数是否存在
|
||||
@classmethod
|
||||
def IS_CHANGED(s, **kwargs):
|
||||
return float("NaN")
|
||||
|
||||
@classmethod
|
||||
def VALIDATE_INPUTS(s, input_types):
|
||||
return True
|
||||
|
||||
def crypto(self, template_id, **kwargs):
|
||||
unique_id = kwargs.pop("unique_id", None)
|
||||
prompt = kwargs.pop("prompt", None)
|
||||
extra_pnginfo = kwargs.pop("extra_pnginfo", None)
|
||||
if unique_id is None:
|
||||
raise Exception("Warning: 'unique_id' is missing.")
|
||||
if prompt is None:
|
||||
raise Exception("Warning: 'prompt' is missing.")
|
||||
|
||||
# 将剩余的 kwargs 存储在 inputs 列表中
|
||||
inputs = list(kwargs.values())
|
||||
|
||||
temp_dir = os.environ.get('TEMP') or os.environ.get('TMP') or '/tmp'
|
||||
project_temp_folder = os.path.join(temp_dir, crypto_name)
|
||||
if not os.path.exists(project_temp_folder):
|
||||
os.makedirs(project_temp_folder)
|
||||
if len(template_id) != 32:
|
||||
raise Exception("Warning: 'template_id' length is not 32.")
|
||||
crypto_workflow = CryptoWorkflow(extra_pnginfo["workflow"], prompt, template_id)
|
||||
crypto_workflow.invalid_workflow()
|
||||
crypto_workflow.load_workflow()
|
||||
crypto_workflow.load_prompt()
|
||||
crypto_workflow.analysis_node()
|
||||
crypto_dir = crypto_workflow.calculate_crypto_result(
|
||||
f"crypto_{template_id}.json"
|
||||
)
|
||||
output_dir = crypto_workflow.output_workflow_simple_shell(
|
||||
f"cryptocat_{template_id}.json"
|
||||
)
|
||||
crypto_workflow.save_original_workflow(
|
||||
f"original_workflow_{template_id}.json", crypto_dir
|
||||
)
|
||||
crypto_workflow.save_original_prompt(
|
||||
f"original_prompt_{template_id}.json", crypto_dir
|
||||
)
|
||||
user_token, error_msg = AuthUnit().get_user_token()
|
||||
if not user_token:
|
||||
if error_msg == "no token found" or error_msg == "login failed":
|
||||
AuthUnit().login_dialog("输出CryptoCat加密工作流,请先完成登录")
|
||||
else:
|
||||
PromptServer.instance.send_sync(
|
||||
"cryptocat_toast",
|
||||
{"content": "无法完成鉴权登录,请检查网络或完成登录步骤", "type": "error"},
|
||||
)
|
||||
return (ExecutionBlocker(None),)
|
||||
upload = UploadWorkflow(user_token)
|
||||
ret = upload.upload_workflow(template_id, crypto_dir)
|
||||
if not ret:
|
||||
print("crypto cat upload failed")
|
||||
return (ExecutionBlocker(None),)
|
||||
serial_numbers = upload.generate_serial_number(template_id, 10)
|
||||
if serial_numbers:
|
||||
sn_file_path = os.path.join(output_dir, f"serial_numbers_{template_id}.txt")
|
||||
with open(sn_file_path, "w", encoding="utf-8") as f:
|
||||
f.write("\n".join(serial_numbers))
|
||||
PromptServer.instance.send_sync(
|
||||
"cryptocat_toast",
|
||||
{
|
||||
"content": f"加密完成,在{sn_file_path}中查看序列号",
|
||||
"type": "info",
|
||||
"duration": 5000,
|
||||
},
|
||||
)
|
||||
return (template_id,)
|
||||
|
||||
|
||||
# 保存源文件,用于调试
|
||||
with open(os.path.join(project_temp_folder, "prompt.json"), "w", encoding="utf-8") as f:
|
||||
f.write(json.dumps(prompt, indent=4, ensure_ascii=False))
|
||||
with open(os.path.join(project_temp_folder, "workflow.json"), "w", encoding="utf-8") as f:
|
||||
f.write(json.dumps(extra_pnginfo, indent=4, ensure_ascii=False))
|
||||
|
||||
# 保存需要的输出文件
|
||||
project_folder = os.path.join(crypto_folder, crypto_name)
|
||||
if not os.path.exists(project_folder):
|
||||
os.makedirs(project_folder)
|
||||
|
||||
hide_prompt_path = os.path.join(project_folder, "prompt.dat")
|
||||
# 解析workflow
|
||||
wt = WorkflowTrim(extra_pnginfo)
|
||||
wt.trim_workflow()
|
||||
show_workflow = wt.replace_workflow(hide_prompt_path)
|
||||
with open(os.path.join(project_folder, "workflow.json"), "w", encoding="utf-8") as f:
|
||||
f.write(json.dumps(show_workflow, indent=4, ensure_ascii=False))
|
||||
|
||||
# 拆分prompt
|
||||
pr = PromptTrim(prompt)
|
||||
show_part_prompt, hide_part_prompt = pr.split_prompt(wt.get_remaining_node_ids())
|
||||
with open(os.path.join(project_temp_folder, "prompt_show.json"), "w", encoding="utf-8") as f:
|
||||
f.write(json.dumps(show_part_prompt, indent=4, ensure_ascii=False))
|
||||
|
||||
FileCompressor.compress_to_json(hide_part_prompt, hide_prompt_path, "19040822")
|
||||
return (hide_part_prompt,)
|
||||
class AnyType(str):
|
||||
def __ne__(self, __value: object) -> bool:
|
||||
return False
|
||||
|
||||
|
||||
any = AnyType("*")
|
||||
|
||||
class ExcuteCryptoNode():
|
||||
|
||||
class SaveCryptoBridgeNode:
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
return {
|
||||
"required": {
|
||||
"crypto_file_path": ("STRING", {"default": folder_paths.output_directory}),
|
||||
},
|
||||
"optional": {
|
||||
"input_anything" : ("*",),
|
||||
},
|
||||
"hidden": {
|
||||
"unique_id": "UNIQUE_ID",
|
||||
"prompt": "PROMPT",
|
||||
"extra_pnginfo": "EXTRA_PNGINFO",
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("IMAGE",)
|
||||
FUNCTION = "excute" #这个节点中的主函数
|
||||
CATEGORY = "advanced/CryptoCat"
|
||||
|
||||
def excute(self, **kwargs):
|
||||
batch_size = 1
|
||||
height = 1024
|
||||
width = 1024
|
||||
color = 0xFF0000
|
||||
r = torch.full([batch_size, height, width, 1], ((color >> 16) & 0xFF) / 0xFF)
|
||||
g = torch.full([batch_size, height, width, 1], ((color >> 8) & 0xFF) / 0xFF)
|
||||
b = torch.full([batch_size, height, width, 1], ((color) & 0xFF) / 0xFF)
|
||||
return (torch.cat((r, g, b), dim=-1), )
|
||||
|
||||
return {"required": {"value": (any,)}}
|
||||
|
||||
# 产生随机数
|
||||
class RandomSeedNode():
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
return {
|
||||
"required": {
|
||||
},
|
||||
"optional": {
|
||||
},
|
||||
"hidden": {
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("INT",)
|
||||
FUNCTION = "random" #这个节点中的主函数
|
||||
CATEGORY = "advanced/CryptoCat"
|
||||
def VALIDATE_INPUTS(s, input_types):
|
||||
return True
|
||||
|
||||
def IS_CHANGED():
|
||||
@classmethod
|
||||
def IS_CHANGED(cls, **kwargs):
|
||||
return float("NaN")
|
||||
|
||||
def random(self):
|
||||
# 产生随机数
|
||||
return (random.randint(0, 999999), )
|
||||
|
||||
|
||||
class CryptoCatImage(SaveImage):
|
||||
RETURN_TYPES = (any,)
|
||||
FUNCTION = "doit"
|
||||
CATEGORY = "advanced/CryptoCat"
|
||||
|
||||
def doit(self, value):
|
||||
return (value,)
|
||||
|
||||
|
||||
def is_link(obj):
|
||||
if not isinstance(obj, list):
|
||||
return False
|
||||
if len(obj) != 2:
|
||||
return False
|
||||
if not isinstance(obj[0], str):
|
||||
return False
|
||||
if (
|
||||
not isinstance(obj[1], int)
|
||||
and not isinstance(obj[1], float)
|
||||
and not isinstance(obj[1], str)
|
||||
):
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
class AlwaysEqualProxy(str):
|
||||
def __eq__(self, _):
|
||||
return True
|
||||
|
||||
def __ne__(self, _):
|
||||
return False
|
||||
|
||||
|
||||
class AlwaysTupleZero(tuple):
|
||||
def __getitem__(self, _):
|
||||
return AlwaysEqualProxy(super().__getitem__(0))
|
||||
|
||||
|
||||
class DecodeCryptoNode:
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
pass
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
return {
|
||||
"required": {
|
||||
"images": ("IMAGE", {"tooltip": "The images to save."}),
|
||||
"filename_prefix": ("STRING", {"default": "ComfyUI", "tooltip": "The prefix for the file to save. This may include formatting information such as %date:yyyy-MM-dd% or %Empty Latent Image.width% to include values from nodes."})
|
||||
"template_id": ("STRING",),
|
||||
"serial_number": (
|
||||
"STRING",
|
||||
{"multiline": True, "placeholder": "请输入序列号"},
|
||||
),
|
||||
},
|
||||
"optional": {"input_anything": (any,)},
|
||||
"hidden": {
|
||||
"prompt": "PROMPT", "extra_pnginfo": "EXTRA_PNGINFO"
|
||||
"unique_id": "UNIQUE_ID",
|
||||
"prompt": "PROMPT",
|
||||
"extra_pnginfo": "EXTRA_PNGINFO",
|
||||
},
|
||||
}
|
||||
|
||||
RETURN_TYPES = ()
|
||||
FUNCTION = "save_images"
|
||||
@classmethod
|
||||
def VALIDATE_INPUTS(s, input_types):
|
||||
return True
|
||||
|
||||
OUTPUT_NODE = True
|
||||
@classmethod
|
||||
def IS_CHANGED(cls, **kwargs):
|
||||
return float("NaN")
|
||||
|
||||
RETURN_TYPES = AlwaysTupleZero(AlwaysEqualProxy("*"))
|
||||
FUNCTION = "decode"
|
||||
CATEGORY = "advanced/CryptoCat"
|
||||
DESCRIPTION = "Saves the input images to your ComfyUI output directory."
|
||||
OUTPUT_NODE = False
|
||||
|
||||
def save_images(self, images, filename_prefix="ComfyUI", prompt=None, extra_pnginfo=None):
|
||||
return super().save_images(images, filename_prefix, None, None)
|
||||
def decode(self, template_id, serial_number, **kwargs):
|
||||
unique_id = kwargs.pop("unique_id", None)
|
||||
prompt = kwargs.pop("prompt", None)
|
||||
extra_pnginfo = kwargs.pop("extra_pnginfo", None)
|
||||
decode_crypto_workflow = DecodeCryptoWorkflow(
|
||||
prompt, extra_pnginfo["workflow"], template_id
|
||||
)
|
||||
crypto_prompt = decode_crypto_workflow.load_crypto_prompt(serial_number)
|
||||
decode_crypto_workflow.calculate_input_anything_map()
|
||||
processed_nodes = {}
|
||||
graph = GraphBuilder()
|
||||
|
||||
def get_node_result(nodeData, id):
|
||||
inputKeys = []
|
||||
for ikey in nodeData["inputs"].keys():
|
||||
input_value = nodeData["inputs"][ikey]
|
||||
if (
|
||||
is_link(input_value)
|
||||
and decode_crypto_workflow.get_hidden_input(input_value) is None
|
||||
and input_value[0] not in processed_nodes
|
||||
):
|
||||
inputKeys.append(input_value[0])
|
||||
for ikey in inputKeys:
|
||||
if ikey not in crypto_prompt:
|
||||
continue
|
||||
node = get_node_result(crypto_prompt[ikey], ikey)
|
||||
processed_nodes[ikey] = node
|
||||
inputs = nodeData["inputs"]
|
||||
newInputs = {}
|
||||
for ikey in inputs.keys():
|
||||
if is_link(inputs[ikey]):
|
||||
hidden_input_name = decode_crypto_workflow.get_hidden_input(
|
||||
inputs[ikey]
|
||||
)
|
||||
if hidden_input_name:
|
||||
if hidden_input_name in kwargs:
|
||||
newInputs[ikey] = kwargs[hidden_input_name]
|
||||
elif inputs[ikey][0] in processed_nodes:
|
||||
newInputs[ikey] = processed_nodes[inputs[ikey][0]].out(
|
||||
inputs[ikey][1]
|
||||
)
|
||||
else:
|
||||
newInputs[ikey] = inputs[ikey]
|
||||
return graph.node(nodeData["class_type"], id, **newInputs)
|
||||
|
||||
node_id, link_idx = decode_crypto_workflow.get_outputs()
|
||||
nodeData = crypto_prompt[node_id]
|
||||
node = get_node_result(nodeData, node_id)
|
||||
value = node.out(link_idx)
|
||||
return {"result": tuple([value]), "expand": graph.finalize()}
|
||||
|
||||
|
||||
class RandomSeedNode:
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
return {"required": {}, "optional": {}, "hidden": {}}
|
||||
|
||||
RETURN_TYPES = ("INT",)
|
||||
FUNCTION = "random"
|
||||
CATEGORY = "advanced/CryptoCat"
|
||||
|
||||
def IS_CHANGED():
|
||||
return float("NaN")
|
||||
|
||||
def random(self):
|
||||
return (random.randint(0, 999999),)
|
||||
|
||||
@@ -0,0 +1,71 @@
|
||||
import torch
|
||||
from nodes import SaveImage
|
||||
import folder_paths
|
||||
|
||||
|
||||
class ExcuteCryptoNode:
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
return {
|
||||
"required": {
|
||||
"crypto_file_path": (
|
||||
"STRING",
|
||||
{"default": folder_paths.output_directory},
|
||||
)
|
||||
},
|
||||
"optional": {"input_anything": ("*",)},
|
||||
"hidden": {
|
||||
"unique_id": "UNIQUE_ID",
|
||||
"prompt": "PROMPT",
|
||||
"extra_pnginfo": "EXTRA_PNGINFO",
|
||||
},
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("IMAGE",)
|
||||
FUNCTION = "excute"
|
||||
CATEGORY = "__hidden__"
|
||||
|
||||
def excute(self, **kwargs):
|
||||
batch_size = 1
|
||||
height = 1024
|
||||
width = 1024
|
||||
color = 16711680
|
||||
r = torch.full([batch_size, height, width, 1], (color >> 16 & 255) / 255)
|
||||
g = torch.full([batch_size, height, width, 1], (color >> 8 & 255) / 255)
|
||||
b = torch.full([batch_size, height, width, 1], (color & 255) / 255)
|
||||
return (torch.cat((r, g, b), dim=-1),)
|
||||
|
||||
|
||||
class CryptoCatImage(SaveImage):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
return {
|
||||
"required": {
|
||||
"images": ("IMAGE", {"tooltip": "The images to save."}),
|
||||
"filename_prefix": (
|
||||
"STRING",
|
||||
{
|
||||
"default": "ComfyUI",
|
||||
"tooltip": "The prefix for the file to save. This may include formatting information such as %date:yyyy-MM-dd% or %Empty Latent Image.width% to include values from nodes.",
|
||||
},
|
||||
),
|
||||
},
|
||||
"hidden": {"prompt": "PROMPT", "extra_pnginfo": "EXTRA_PNGINFO"},
|
||||
}
|
||||
|
||||
RETURN_TYPES = ()
|
||||
FUNCTION = "save_images"
|
||||
OUTPUT_NODE = True
|
||||
CATEGORY = "__hidden__"
|
||||
DESCRIPTION = "Saves the input images to your ComfyUI output directory."
|
||||
|
||||
def save_images(
|
||||
self, images, filename_prefix="ComfyUI", prompt=None, extra_pnginfo=None
|
||||
):
|
||||
return super().save_images(images, filename_prefix, None, None)
|
||||
@@ -0,0 +1,258 @@
|
||||
{
|
||||
"last_node_id": 17,
|
||||
"last_link_id": 21,
|
||||
"nodes": [
|
||||
{
|
||||
"id": 4,
|
||||
"type": "CheckpointLoaderSimple",
|
||||
"pos": [
|
||||
-476,
|
||||
301
|
||||
],
|
||||
"size": [
|
||||
315,
|
||||
98
|
||||
],
|
||||
"flags": {},
|
||||
"order": 0,
|
||||
"mode": 0,
|
||||
"inputs": [],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "MODEL",
|
||||
"type": "MODEL",
|
||||
"links": [
|
||||
1,
|
||||
13,
|
||||
21
|
||||
],
|
||||
"slot_index": 0,
|
||||
"label": "模型"
|
||||
},
|
||||
{
|
||||
"name": "CLIP",
|
||||
"type": "CLIP",
|
||||
"links": [
|
||||
3,
|
||||
5,
|
||||
16
|
||||
],
|
||||
"slot_index": 1,
|
||||
"label": "CLIP"
|
||||
},
|
||||
{
|
||||
"name": "VAE",
|
||||
"type": "VAE",
|
||||
"links": [
|
||||
8,
|
||||
17
|
||||
],
|
||||
"slot_index": 2,
|
||||
"label": "VAE"
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "CheckpointLoaderSimple"
|
||||
},
|
||||
"widgets_values": [
|
||||
"1.5\\majicmixRealistic_v7.safetensors"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 12,
|
||||
"type": "DecodeCryptoNode",
|
||||
"pos": [
|
||||
267,
|
||||
-157
|
||||
],
|
||||
"size": [
|
||||
400,
|
||||
162
|
||||
],
|
||||
"flags": {},
|
||||
"order": 5,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "input_anything",
|
||||
"type": "*",
|
||||
"link": 19,
|
||||
"label": "input_anything",
|
||||
"shape": 7
|
||||
},
|
||||
{
|
||||
"name": "input_anything1",
|
||||
"type": "*",
|
||||
"link": 16,
|
||||
"label": "input_anything1"
|
||||
},
|
||||
{
|
||||
"name": "input_anything2",
|
||||
"type": "*",
|
||||
"link": 17,
|
||||
"label": "input_anything2"
|
||||
},
|
||||
{
|
||||
"name": "input_anything3",
|
||||
"type": "*",
|
||||
"link": 21,
|
||||
"label": "input_anything3"
|
||||
},
|
||||
{
|
||||
"name": "input_anything4",
|
||||
"type": "*",
|
||||
"link": null,
|
||||
"label": "input_anything4"
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "*",
|
||||
"type": "*",
|
||||
"links": [
|
||||
18
|
||||
],
|
||||
"slot_index": 0,
|
||||
"label": "*"
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "DecodeCryptoNode"
|
||||
},
|
||||
"widgets_values": [
|
||||
"ffbb8ad2c5574cdfa702e9b08b0ccaa2",
|
||||
""
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 14,
|
||||
"type": "PreviewImage",
|
||||
"pos": [
|
||||
1698,
|
||||
158
|
||||
],
|
||||
"size": [
|
||||
210,
|
||||
246
|
||||
],
|
||||
"flags": {},
|
||||
"order": 10,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "images",
|
||||
"type": "IMAGE",
|
||||
"link": 18,
|
||||
"label": "图像"
|
||||
}
|
||||
],
|
||||
"outputs": [],
|
||||
"properties": {
|
||||
"Node name for S&R": "PreviewImage"
|
||||
},
|
||||
"widgets_values": []
|
||||
},
|
||||
{
|
||||
"id": 17,
|
||||
"type": "DF_Text",
|
||||
"pos": [
|
||||
-505,
|
||||
20
|
||||
],
|
||||
"size": [
|
||||
315,
|
||||
58
|
||||
],
|
||||
"flags": {},
|
||||
"order": 3,
|
||||
"mode": 0,
|
||||
"inputs": [],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "STRING",
|
||||
"type": "STRING",
|
||||
"links": [
|
||||
19,
|
||||
20
|
||||
],
|
||||
"slot_index": 0,
|
||||
"label": "STRING"
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "DF_Text"
|
||||
},
|
||||
"widgets_values": [
|
||||
""
|
||||
]
|
||||
}
|
||||
],
|
||||
"links": [
|
||||
[
|
||||
13,
|
||||
4,
|
||||
0,
|
||||
12,
|
||||
0,
|
||||
"*"
|
||||
],
|
||||
[
|
||||
16,
|
||||
4,
|
||||
1,
|
||||
12,
|
||||
1,
|
||||
"*"
|
||||
],
|
||||
[
|
||||
17,
|
||||
4,
|
||||
2,
|
||||
12,
|
||||
2,
|
||||
"*"
|
||||
],
|
||||
[
|
||||
18,
|
||||
12,
|
||||
0,
|
||||
14,
|
||||
0,
|
||||
"IMAGE"
|
||||
],
|
||||
[
|
||||
19,
|
||||
17,
|
||||
0,
|
||||
12,
|
||||
0,
|
||||
"*"
|
||||
],
|
||||
[
|
||||
21,
|
||||
4,
|
||||
0,
|
||||
12,
|
||||
3,
|
||||
"*"
|
||||
]
|
||||
],
|
||||
"config": {},
|
||||
"extra": {
|
||||
"ds": {
|
||||
"scale": 1.167184107045001,
|
||||
"offset": [
|
||||
793.6073786214808,
|
||||
404.0759897478937
|
||||
]
|
||||
}
|
||||
},
|
||||
"version": 0.4,
|
||||
"widget_idx_map": {
|
||||
"3": {
|
||||
"sampler_name": 4,
|
||||
"scheduler": 5
|
||||
}
|
||||
},
|
||||
"seed_widgets": {}
|
||||
}
|
||||
+433
-342
@@ -1,20 +1,234 @@
|
||||
{
|
||||
"last_node_id": 11,
|
||||
"last_link_id": 16,
|
||||
"last_node_id": 17,
|
||||
"last_link_id": 21,
|
||||
"nodes": [
|
||||
{
|
||||
"id": 3,
|
||||
"type": "KSampler",
|
||||
"pos": [
|
||||
863,
|
||||
186
|
||||
],
|
||||
"size": [
|
||||
315,
|
||||
446
|
||||
],
|
||||
"flags": {},
|
||||
"order": 7,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "model",
|
||||
"type": "MODEL",
|
||||
"link": 1,
|
||||
"label": "模型"
|
||||
},
|
||||
{
|
||||
"name": "positive",
|
||||
"type": "CONDITIONING",
|
||||
"link": 4,
|
||||
"label": "正面条件"
|
||||
},
|
||||
{
|
||||
"name": "negative",
|
||||
"type": "CONDITIONING",
|
||||
"link": 6,
|
||||
"label": "负面条件"
|
||||
},
|
||||
{
|
||||
"name": "latent_image",
|
||||
"type": "LATENT",
|
||||
"link": 2,
|
||||
"label": "Latent"
|
||||
},
|
||||
{
|
||||
"name": "seed",
|
||||
"type": "INT",
|
||||
"link": 11,
|
||||
"widget": {
|
||||
"name": "seed"
|
||||
},
|
||||
"label": "随机种"
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "LATENT",
|
||||
"type": "LATENT",
|
||||
"links": [
|
||||
7
|
||||
],
|
||||
"slot_index": 0,
|
||||
"label": "Latent"
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "KSampler"
|
||||
},
|
||||
"widgets_values": [
|
||||
665777626286280,
|
||||
"randomize",
|
||||
20,
|
||||
8,
|
||||
"euler",
|
||||
"normal",
|
||||
1
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 4,
|
||||
"type": "CheckpointLoaderSimple",
|
||||
"pos": [
|
||||
-476,
|
||||
301
|
||||
],
|
||||
"size": [
|
||||
315,
|
||||
98
|
||||
],
|
||||
"flags": {},
|
||||
"order": 0,
|
||||
"mode": 0,
|
||||
"inputs": [],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "MODEL",
|
||||
"type": "MODEL",
|
||||
"links": [
|
||||
1,
|
||||
13,
|
||||
21
|
||||
],
|
||||
"slot_index": 0,
|
||||
"label": "模型"
|
||||
},
|
||||
{
|
||||
"name": "CLIP",
|
||||
"type": "CLIP",
|
||||
"links": [
|
||||
3,
|
||||
5,
|
||||
16
|
||||
],
|
||||
"slot_index": 1,
|
||||
"label": "CLIP"
|
||||
},
|
||||
{
|
||||
"name": "VAE",
|
||||
"type": "VAE",
|
||||
"links": [
|
||||
8,
|
||||
17
|
||||
],
|
||||
"slot_index": 2,
|
||||
"label": "VAE"
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "CheckpointLoaderSimple"
|
||||
},
|
||||
"widgets_values": [
|
||||
"1.5\\majicmixRealistic_v7.safetensors"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 5,
|
||||
"type": "EmptyLatentImage",
|
||||
"pos": [
|
||||
473,
|
||||
609
|
||||
],
|
||||
"size": [
|
||||
315,
|
||||
106
|
||||
],
|
||||
"flags": {},
|
||||
"order": 1,
|
||||
"mode": 0,
|
||||
"inputs": [],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "LATENT",
|
||||
"type": "LATENT",
|
||||
"links": [
|
||||
2
|
||||
],
|
||||
"slot_index": 0,
|
||||
"label": "Latent"
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "EmptyLatentImage"
|
||||
},
|
||||
"widgets_values": [
|
||||
512,
|
||||
512,
|
||||
1
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 6,
|
||||
"type": "CLIPTextEncode",
|
||||
"pos": [
|
||||
168,
|
||||
200
|
||||
],
|
||||
"size": [
|
||||
422.84503173828125,
|
||||
164.31304931640625
|
||||
],
|
||||
"flags": {},
|
||||
"order": 6,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "clip",
|
||||
"type": "CLIP",
|
||||
"link": 3,
|
||||
"label": "CLIP"
|
||||
},
|
||||
{
|
||||
"name": "text",
|
||||
"type": "STRING",
|
||||
"link": 20,
|
||||
"widget": {
|
||||
"name": "text"
|
||||
},
|
||||
"label": "文本"
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "CONDITIONING",
|
||||
"type": "CONDITIONING",
|
||||
"links": [
|
||||
4
|
||||
],
|
||||
"slot_index": 0,
|
||||
"label": "条件"
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "CLIPTextEncode"
|
||||
},
|
||||
"widgets_values": [
|
||||
"beautiful scenery nature glass bottle landscape, , purple galaxy bottle,",
|
||||
true
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 7,
|
||||
"type": "CLIPTextEncode",
|
||||
"pos": {
|
||||
"0": 413,
|
||||
"1": 389
|
||||
},
|
||||
"size": {
|
||||
"0": 425.27801513671875,
|
||||
"1": 180.6060791015625
|
||||
},
|
||||
"pos": [
|
||||
413,
|
||||
389
|
||||
],
|
||||
"size": [
|
||||
425.27801513671875,
|
||||
180.6060791015625
|
||||
],
|
||||
"flags": {},
|
||||
"order": 3,
|
||||
"order": 4,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
@@ -43,203 +257,19 @@
|
||||
true
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 5,
|
||||
"type": "EmptyLatentImage",
|
||||
"pos": {
|
||||
"0": 473,
|
||||
"1": 609
|
||||
},
|
||||
"size": {
|
||||
"0": 315,
|
||||
"1": 106
|
||||
},
|
||||
"flags": {},
|
||||
"order": 0,
|
||||
"mode": 0,
|
||||
"inputs": [],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "LATENT",
|
||||
"type": "LATENT",
|
||||
"links": [
|
||||
2
|
||||
],
|
||||
"slot_index": 0,
|
||||
"label": "Latent"
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "EmptyLatentImage"
|
||||
},
|
||||
"widgets_values": [
|
||||
512,
|
||||
512,
|
||||
1
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 3,
|
||||
"type": "KSampler",
|
||||
"pos": {
|
||||
"0": 863,
|
||||
"1": 186
|
||||
},
|
||||
"size": {
|
||||
"0": 315,
|
||||
"1": 262
|
||||
},
|
||||
"flags": {},
|
||||
"order": 5,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "model",
|
||||
"type": "MODEL",
|
||||
"link": 1,
|
||||
"label": "模型"
|
||||
},
|
||||
{
|
||||
"name": "positive",
|
||||
"type": "CONDITIONING",
|
||||
"link": 4,
|
||||
"label": "正面条件"
|
||||
},
|
||||
{
|
||||
"name": "negative",
|
||||
"type": "CONDITIONING",
|
||||
"link": 6,
|
||||
"label": "负面条件"
|
||||
},
|
||||
{
|
||||
"name": "latent_image",
|
||||
"type": "LATENT",
|
||||
"link": 2,
|
||||
"label": "Latent"
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "LATENT",
|
||||
"type": "LATENT",
|
||||
"links": [
|
||||
7
|
||||
],
|
||||
"slot_index": 0,
|
||||
"label": "Latent"
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "KSampler"
|
||||
},
|
||||
"widgets_values": [
|
||||
156680208700286,
|
||||
"randomize",
|
||||
20,
|
||||
8,
|
||||
"euler",
|
||||
"normal",
|
||||
1
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 9,
|
||||
"type": "SaveImage",
|
||||
"pos": {
|
||||
"0": 1451,
|
||||
"1": 189
|
||||
},
|
||||
"size": {
|
||||
"0": 210,
|
||||
"1": 58
|
||||
},
|
||||
"flags": {},
|
||||
"order": 7,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "images",
|
||||
"type": "IMAGE",
|
||||
"link": 9,
|
||||
"label": "图像"
|
||||
}
|
||||
],
|
||||
"outputs": [],
|
||||
"properties": {
|
||||
"Node name for S&R": "SaveImage"
|
||||
},
|
||||
"widgets_values": [
|
||||
"ComfyUI"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 4,
|
||||
"type": "CheckpointLoaderSimple",
|
||||
"pos": {
|
||||
"0": 26,
|
||||
"1": 474
|
||||
},
|
||||
"size": {
|
||||
"0": 315,
|
||||
"1": 98
|
||||
},
|
||||
"flags": {},
|
||||
"order": 1,
|
||||
"mode": 0,
|
||||
"inputs": [],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "MODEL",
|
||||
"type": "MODEL",
|
||||
"links": [
|
||||
1,
|
||||
14
|
||||
],
|
||||
"slot_index": 0,
|
||||
"label": "模型"
|
||||
},
|
||||
{
|
||||
"name": "CLIP",
|
||||
"type": "CLIP",
|
||||
"links": [
|
||||
3,
|
||||
5,
|
||||
15
|
||||
],
|
||||
"slot_index": 1,
|
||||
"label": "CLIP"
|
||||
},
|
||||
{
|
||||
"name": "VAE",
|
||||
"type": "VAE",
|
||||
"links": [
|
||||
8,
|
||||
16
|
||||
],
|
||||
"slot_index": 2,
|
||||
"label": "VAE"
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "CheckpointLoaderSimple"
|
||||
},
|
||||
"widgets_values": [
|
||||
"1.5\\majicmixRealistic_v7.safetensors"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 8,
|
||||
"type": "VAEDecode",
|
||||
"pos": {
|
||||
"0": 1209,
|
||||
"1": 188
|
||||
},
|
||||
"size": {
|
||||
"0": 210,
|
||||
"1": 46
|
||||
},
|
||||
"pos": [
|
||||
1209,
|
||||
188
|
||||
],
|
||||
"size": [
|
||||
210,
|
||||
46
|
||||
],
|
||||
"flags": {},
|
||||
"order": 6,
|
||||
"order": 8,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
@@ -260,8 +290,7 @@
|
||||
"name": "IMAGE",
|
||||
"type": "IMAGE",
|
||||
"links": [
|
||||
9,
|
||||
11
|
||||
14
|
||||
],
|
||||
"slot_index": 0,
|
||||
"label": "图像"
|
||||
@@ -269,139 +298,85 @@
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "VAEDecode"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 6,
|
||||
"type": "CLIPTextEncode",
|
||||
"pos": {
|
||||
"0": 314,
|
||||
"1": 93
|
||||
},
|
||||
"size": [
|
||||
422.84503173828125,
|
||||
164.31304931640625
|
||||
],
|
||||
"flags": {},
|
||||
"order": 4,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "clip",
|
||||
"type": "CLIP",
|
||||
"link": 3,
|
||||
"label": "CLIP"
|
||||
},
|
||||
{
|
||||
"name": "text",
|
||||
"type": "STRING",
|
||||
"link": 12,
|
||||
"widget": {
|
||||
"name": "text"
|
||||
},
|
||||
"label": "文本"
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "CONDITIONING",
|
||||
"type": "CONDITIONING",
|
||||
"links": [
|
||||
4
|
||||
],
|
||||
"slot_index": 0,
|
||||
"label": "条件"
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "CLIPTextEncode"
|
||||
},
|
||||
"widgets_values": [
|
||||
"beautiful scenery nature glass bottle landscape, , purple galaxy bottle,",
|
||||
true
|
||||
]
|
||||
"widgets_values": []
|
||||
},
|
||||
{
|
||||
"id": 11,
|
||||
"type": "Text Multiline",
|
||||
"pos": {
|
||||
"0": -88,
|
||||
"1": -194
|
||||
},
|
||||
"size": {
|
||||
"0": 400,
|
||||
"1": 200
|
||||
},
|
||||
"type": "RandomSeedNode",
|
||||
"pos": [
|
||||
509,
|
||||
64
|
||||
],
|
||||
"size": [
|
||||
210,
|
||||
26
|
||||
],
|
||||
"flags": {},
|
||||
"order": 2,
|
||||
"mode": 0,
|
||||
"inputs": [],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "STRING",
|
||||
"type": "STRING",
|
||||
"name": "INT",
|
||||
"type": "INT",
|
||||
"links": [
|
||||
12,
|
||||
13
|
||||
11
|
||||
],
|
||||
"shape": 3,
|
||||
"label": "字符串",
|
||||
"slot_index": 0
|
||||
"slot_index": 0,
|
||||
"label": "INT"
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "Text Multiline"
|
||||
"Node name for S&R": "RandomSeedNode"
|
||||
},
|
||||
"widgets_values": [
|
||||
"",
|
||||
true
|
||||
]
|
||||
"widgets_values": []
|
||||
},
|
||||
{
|
||||
"id": 10,
|
||||
"id": 12,
|
||||
"type": "SaveCryptoNode",
|
||||
"pos": {
|
||||
"0": 562,
|
||||
"1": -298
|
||||
},
|
||||
"pos": [
|
||||
267,
|
||||
-157
|
||||
],
|
||||
"size": [
|
||||
515.0016206873283,
|
||||
223.84233134141914
|
||||
400,
|
||||
162
|
||||
],
|
||||
"flags": {},
|
||||
"order": 8,
|
||||
"order": 5,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "output_images",
|
||||
"type": "IMAGE",
|
||||
"link": 11,
|
||||
"label": "output_images"
|
||||
"name": "input_anything",
|
||||
"type": "*",
|
||||
"link": 19,
|
||||
"label": "input_anything",
|
||||
"shape": 7
|
||||
},
|
||||
{
|
||||
"name": "input_anything1",
|
||||
"type": "*",
|
||||
"link": 13
|
||||
"link": 16,
|
||||
"label": "input_anything1"
|
||||
},
|
||||
{
|
||||
"name": "input_anything2",
|
||||
"type": "*",
|
||||
"link": 14
|
||||
"link": 17,
|
||||
"label": "input_anything2"
|
||||
},
|
||||
{
|
||||
"name": "input_anything3",
|
||||
"type": "*",
|
||||
"link": 15
|
||||
"link": 21,
|
||||
"label": "input_anything3"
|
||||
},
|
||||
{
|
||||
"name": "input_anything4",
|
||||
"type": "*",
|
||||
"link": 16
|
||||
},
|
||||
{
|
||||
"name": "input_anything5",
|
||||
"type": "*",
|
||||
"link": null
|
||||
"link": null,
|
||||
"label": "input_anything4"
|
||||
}
|
||||
],
|
||||
"outputs": [],
|
||||
@@ -409,8 +384,108 @@
|
||||
"Node name for S&R": "SaveCryptoNode"
|
||||
},
|
||||
"widgets_values": [
|
||||
"D:\\work\\ComfyUI\\ComfyUI\\output",
|
||||
"demo"
|
||||
"ffbb8ad2c5574cdfa702e9b08b0ccaa2",
|
||||
null
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 13,
|
||||
"type": "SaveCryptoBridgeNode",
|
||||
"pos": [
|
||||
1467,
|
||||
199
|
||||
],
|
||||
"size": [
|
||||
189.17031860351562,
|
||||
26
|
||||
],
|
||||
"flags": {},
|
||||
"order": 9,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "value",
|
||||
"type": "*",
|
||||
"link": 14,
|
||||
"label": "value"
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "*",
|
||||
"type": "*",
|
||||
"links": [
|
||||
18
|
||||
],
|
||||
"slot_index": 0,
|
||||
"label": "*"
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "SaveCryptoBridgeNode"
|
||||
},
|
||||
"widgets_values": []
|
||||
},
|
||||
{
|
||||
"id": 14,
|
||||
"type": "PreviewImage",
|
||||
"pos": [
|
||||
1698,
|
||||
158
|
||||
],
|
||||
"size": [
|
||||
210,
|
||||
246
|
||||
],
|
||||
"flags": {},
|
||||
"order": 10,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "images",
|
||||
"type": "IMAGE",
|
||||
"link": 18,
|
||||
"label": "图像"
|
||||
}
|
||||
],
|
||||
"outputs": [],
|
||||
"properties": {
|
||||
"Node name for S&R": "PreviewImage"
|
||||
},
|
||||
"widgets_values": []
|
||||
},
|
||||
{
|
||||
"id": 17,
|
||||
"type": "DF_Text",
|
||||
"pos": [
|
||||
-505,
|
||||
20
|
||||
],
|
||||
"size": [
|
||||
315,
|
||||
58
|
||||
],
|
||||
"flags": {},
|
||||
"order": 3,
|
||||
"mode": 0,
|
||||
"inputs": [],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "STRING",
|
||||
"type": "STRING",
|
||||
"links": [
|
||||
19,
|
||||
20
|
||||
],
|
||||
"slot_index": 0,
|
||||
"label": "STRING"
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "DF_Text"
|
||||
},
|
||||
"widgets_values": [
|
||||
""
|
||||
]
|
||||
}
|
||||
],
|
||||
@@ -479,71 +554,87 @@
|
||||
1,
|
||||
"VAE"
|
||||
],
|
||||
[
|
||||
9,
|
||||
8,
|
||||
0,
|
||||
9,
|
||||
0,
|
||||
"IMAGE"
|
||||
],
|
||||
[
|
||||
11,
|
||||
8,
|
||||
11,
|
||||
0,
|
||||
10,
|
||||
0,
|
||||
"IMAGE"
|
||||
3,
|
||||
4,
|
||||
"INT"
|
||||
],
|
||||
[
|
||||
13,
|
||||
4,
|
||||
0,
|
||||
12,
|
||||
11,
|
||||
0,
|
||||
"*"
|
||||
],
|
||||
[
|
||||
14,
|
||||
8,
|
||||
0,
|
||||
13,
|
||||
0,
|
||||
"*"
|
||||
],
|
||||
[
|
||||
16,
|
||||
4,
|
||||
1,
|
||||
12,
|
||||
1,
|
||||
"*"
|
||||
],
|
||||
[
|
||||
17,
|
||||
4,
|
||||
2,
|
||||
12,
|
||||
2,
|
||||
"*"
|
||||
],
|
||||
[
|
||||
18,
|
||||
13,
|
||||
0,
|
||||
14,
|
||||
0,
|
||||
"IMAGE"
|
||||
],
|
||||
[
|
||||
19,
|
||||
17,
|
||||
0,
|
||||
12,
|
||||
0,
|
||||
"*"
|
||||
],
|
||||
[
|
||||
20,
|
||||
17,
|
||||
0,
|
||||
6,
|
||||
1,
|
||||
"STRING"
|
||||
],
|
||||
[
|
||||
13,
|
||||
11,
|
||||
0,
|
||||
10,
|
||||
1,
|
||||
"*"
|
||||
],
|
||||
[
|
||||
14,
|
||||
21,
|
||||
4,
|
||||
0,
|
||||
10,
|
||||
2,
|
||||
"*"
|
||||
],
|
||||
[
|
||||
15,
|
||||
4,
|
||||
1,
|
||||
10,
|
||||
12,
|
||||
3,
|
||||
"*"
|
||||
],
|
||||
[
|
||||
16,
|
||||
4,
|
||||
2,
|
||||
10,
|
||||
4,
|
||||
"*"
|
||||
]
|
||||
],
|
||||
"groups": [],
|
||||
"config": {},
|
||||
"extra": {
|
||||
"ds": {
|
||||
"scale": 0.8769226950000005,
|
||||
"scale": 1.167184107045001,
|
||||
"offset": [
|
||||
772.3852019447384,
|
||||
464.1369668451785
|
||||
793.6073786214808,
|
||||
404.0759897478937
|
||||
]
|
||||
}
|
||||
},
|
||||
@@ -0,0 +1,10 @@
|
||||
FM5bb7MgAIDhf9SIfF8zLpt6ghScxgNwsyh2BeepnVbl1y9ev3mT574TXYfKxIbg3GLADP7FQ/pfXfEZ/0y8uBJ0uu/EkSVY6zBwZIEmecRe9qLH/0SZ7MLmgNm0laHUzF52mXWGetrQkLWi9HdsVqNc/VZD+q36YJEhmjlkneo7XQdoaUpgJMcH4slcBiqYrzQqYOXOQ+0SePyS6xW348Yshcy7bDR7bLcrmQRPTNz6kFoB40zY2AvQKfocnGYZX/4DkSZ/0oWTr+oyjx+3l4kqUGSpzs+JfruZ8xcAAP//
|
||||
FM7djqowFEDhNzJykJNwafjp7M60jYpC980kVrQtVIkzaNunn3C9spKvD1SfiTLCUDhGSLiBH7jvM1XAfxim7lTQfNUHusY2eZ9JvcZTPuESHTrpYCPbXZDxmPC4t0hQ87gN2IyGldowwq1sqwDmbdQ//VL3/VW5ekaS/3YpH5Ub9bnO50ubGOxgQQyKZCm3tWcfQ9Y39MWbnV9+7PQb7MPzyFJebj1rbv6roJPsdkbYKmVRpqKRUZR1vlLPw0OMB7xUuLk9+41183d1CENdZnAN4qanT9sXhZ9P8BcAAP//
|
||||
FMvRkqogGADgN2oyT6f1skQZ3AU2x1K4W8mFnzQtLYWn3+n6m69xmamxAg4ZOXkSMCAjueVbFZP/5DpU5ziLVo3L1rIM5hqna3mOBvnGTnaiI/9EeXTCnwLmcyuxNMzvnSxaoMgAxcyKMnEEZlAb81K3/Fd16VPiaKpC1qquNXUaPS9lALIiwIFslL+ARHrLkVhEcehZmWzfX1ZmJrZfmKchQ/uFFnr5irNBVEfgNgmpFyEvhOcojVat4w+9e9n1HVOfTB9jcb0fGv3txuBH75oe2UecT59n3Yu/AAAA//8=
|
||||
FMvRbqsgAADQP2pqrTfl0auFwgauhirwslTWClaqy3QoX7/0+eTcVmIapG1hCb4EHDGLf/CzTHSG/+HHKKqMgM1tJVtVR75BcKsqMKoXOuWkw3tZn1cZLhELZaeQMiykq+K9pbmxFLFO1scVW2/1zvzqZ3nXDs4KgUnErNeuNw0E81cdWSWwLSz2FLVechJRMeyurgrXegyvr4TxuBsWFmjM8nShvF3eMzJKcbZFd4xpkHHBZShyCDb0zveLZlk/L3w66OT/4wTT6oPLZJBve3Bqvj+nCfq2rQ5/AQAA//8=
|
||||
FMvRboIwFADQL5oRGCY8OpDm4mgDorT3ZQlV14sW2dRB+/WLzyfn5ArTMU2CCth7CDjBHYY61ims4DLKQ1oki5MrltgGU8fyJR6SEV9o0SoL76qtnPL7gPu6R4aG+7XD5kplZqhkvFftxgFNpEPzp4f6rG3+RJY8ZMSv2l5NlyfPYxsQSiBBEKswDnG4eB599MreZh3m4eujNBP0t5n7MuLZei6b7/kzLUYlKxL9Jiq9ikSjvMjyZDHcz/IX60audlv1aMURxurgNlmeOrZtup/lGcnu3uKvqfoPAAD//w==
|
||||
FM7dboIwHEDxNzIC6tJLB7ZrsxYlfPV/Y6Q6WkYVNxm0T79wfXKS380x3RBlUsNo4WkgDP2l92yrYrqj30Ndxgytbo6toQqmhuA1lGiAJVqw0tKNrE5O+iIQPuuAgBZ+7yDvDU+04UR0sjo4aiajQv2n7tmXsngEgl51JHple91gNF6rwEBNF8TjEmKTkuApff9srH6pBLrlh1pPtHvMwvNIJPuZ5+38GbNB1ieTdoeIexmlufRpgtHqSEoSXjk0m+NIwg+zO6Nz67eavF2muHhXeR4zJd2Pxe1/AAAA//8=
|
||||
FM7fbqsgAIDxN2pq6TkNlw1UBpmYOv8Al6IrMHE21So8/eL1ly/59YGZlmibW0arSBNu6YuOxT+N6H/6M4kaMXjoAzuqJllbkh5VDSe1R6+89PQsm3uQsUp4LJwiyvB4DaocbIaNzQh3srkFalerT+atx+Jb+3RRBM4C8EH7wbQpXLomsUrQHXHmzgQNHsdeGNOdhncr2Lr/SpiVut+NxwxwfN2y8rF9IjZJcbe5u4EsSpCXMuY4hQeMr0/oEPoYib7MNWfVq5sHD/DS3WbgwWYQ+ro80dlUfwEAAP//
|
||||
FM7bboIwAIDhNzIi06yXzgJrtxYlaA83SyjYwyiSOIT26Reu//zJ1wVsmkLZ0mJ0jSihFj3RUO3VCR3Q78hvJww2XcBbyZK5KfKtvIFRrtFLLzx6E+wSRLwmNFZOFtLQeAyy7i2BxpKCOsGygOxs1c681FDdlc8nWYA/ntJe+d40OZhalljJ0Yp4kpjtOmhCN2RBMRRb38b1l9zMyD0WGklK4XEhtV6+T3gU/GJLl6UkirSsRSxhDjaUYTXqsz5/7nvHJwbemdK11q5m95/z1wvCBT/A8fJxyP4DAAD//w==
|
||||
HM7dboMgHEDxJ1pTStuFy6ZWBhmYmlbkf7NMZgUV6z6cwtMv3fXJSX514LaixmWOs2tkSDr2zYZ8Z45sz7qxLI6crOrA16DQXNF0DQUZ4RE9eO3ZVqtz0PGKZMxboGBlPAS49E4k1gkqW61OgbnZmY39NUN+Mz6dgJKfEsve+N5WKZk+FHJQsgeik0OxEy/dRvodlqofKiz+fyjtzNr7IqPAMjks4tIsr0c+6vLssvaERdQ4u+iYJSlZvdXvk90D588dqVvUfG7lF4LC32uVb5OTZWJZ+NMtDLT5CwAA//8=
|
||||
FMvbcuogFADQP3KMUc/hMY0J3bRAdaIB3gpe2DFYO14S+PqOz2vWITJvqUOJDLYJMoFwg8tm4UpYwvmqdiUjk0NkU9Nmg6X11OzI1bwwmKADzHW7jjptM5E2naHGi1RE0/TIVx45FZ1uqwg4oJv5p7tsji7UD0PJXeWid6H3tiaPfZuhUYASYe4CH3TbR0MXF/leDd90nL2+UX6A7mcUiediVYy8OY2fJbtqtUbZVTlPOpeNTnJVk8lvY9n8a//2Lxf+zo9Rnj+eXVWcCU6XqjwVaNn/I9R2V93+AgAA//8=
|
||||
+13
-11
@@ -2,31 +2,33 @@ import json
|
||||
import zlib
|
||||
import os
|
||||
|
||||
class FileCompressor:
|
||||
|
||||
class FileCompressor:
|
||||
@staticmethod
|
||||
def obfuscate(data, password):
|
||||
return bytes([b ^ ord(password[i % len(password)]) for i, b in enumerate(data)])
|
||||
return bytes(
|
||||
[b ^ ord(password[i % len(password)]) for (i, b) in enumerate(data)]
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def compress_string(string_data, zip_path, password):
|
||||
compressed_data = zlib.compress(string_data.encode('utf-8'))
|
||||
compressed_data = zlib.compress(string_data.encode("utf-8"))
|
||||
obfuscated_data = FileCompressor.obfuscate(compressed_data, password)
|
||||
with open(zip_path, 'wb') as zipf:
|
||||
with open(zip_path, "wb") as zipf:
|
||||
zipf.write(obfuscated_data)
|
||||
|
||||
@staticmethod
|
||||
def decompress_to_string(zip_path, password):
|
||||
with open(zip_path, 'rb') as zipf:
|
||||
with open(zip_path, "rb") as zipf:
|
||||
obfuscated_data = zipf.read()
|
||||
compressed_data = FileCompressor.obfuscate(obfuscated_data, password) # 反混淆
|
||||
return zlib.decompress(compressed_data).decode('utf-8')
|
||||
compressed_data = FileCompressor.obfuscate(obfuscated_data, password)
|
||||
return zlib.decompress(compressed_data).decode("utf-8")
|
||||
|
||||
@staticmethod
|
||||
def compress_to_json(data, zip_path, password):
|
||||
try:
|
||||
json_string = json.dumps(data, indent=4, ensure_ascii=False) # Convert data to JSON string
|
||||
FileCompressor.compress_string(json_string, zip_path, password) # Call compress_string
|
||||
json_string = json.dumps(data, indent=4, ensure_ascii=False)
|
||||
FileCompressor.compress_string(json_string, zip_path, password)
|
||||
except Exception as e:
|
||||
print(f"Compression failed: {e}")
|
||||
|
||||
@@ -35,7 +37,7 @@ class FileCompressor:
|
||||
try:
|
||||
if not os.path.exists(zip_path):
|
||||
raise FileNotFoundError(f"{zip_path} does not exist")
|
||||
json_string = FileCompressor.decompress_to_string(zip_path, password) # Call decompress_to_string
|
||||
return json.loads(json_string) # Convert back to dictionary
|
||||
json_string = FileCompressor.decompress_to_string(zip_path, password)
|
||||
return json.loads(json_string)
|
||||
except Exception as e:
|
||||
print(f"Decompression failed: {e}")
|
||||
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
@echo off
|
||||
|
||||
set "requirements_txt=%~dp0\requirements.txt"
|
||||
set "requirements_repair_txt=%~dp0\repair_dependency_list.txt"
|
||||
set "python_exec=..\..\..\python_embeded\python.exe"
|
||||
set "aki_python_exec=..\..\python\python.exe"
|
||||
|
||||
echo Installing EasyUse Requirements...
|
||||
|
||||
if exist "%python_exec%" (
|
||||
echo Installing with ComfyUI Portable
|
||||
"%python_exec%" -s -m pip install -r "%requirements_txt%"
|
||||
)^
|
||||
else if exist "%aki_python_exec%" (
|
||||
echo Installing with ComfyUI Aki
|
||||
"%aki_python_exec%" -s -m pip install -r "%requirements_txt%"
|
||||
for /f "delims=" %%i in (%requirements_repair_txt%) do (
|
||||
%aki_python_exec% -s -m pip install -i https://pypi.tuna.tsinghua.edu.cn/simple "%%i"
|
||||
)
|
||||
)^
|
||||
else (
|
||||
echo Installing with system Python
|
||||
pip install -r "%requirements_txt%"
|
||||
)
|
||||
|
||||
pause
|
||||
@@ -0,0 +1 @@
|
||||
import{api}from"../../../scripts/api.js";import{ComfyApp,app}from"../../../scripts/app.js";export const UserTokenKey="cryptocat_user_token";async function showKeygenMessageBox(e){const t=document.createElement("div");t.style.position="fixed",t.style.top="0",t.style.left="0",t.style.width="100%",t.style.height="100%",t.style.backgroundColor="rgba(0, 0, 0, 0.5)",t.style.zIndex="9999",t.style.display="flex",t.style.justifyContent="center",t.style.alignItems="center";const o=document.createElement("div");o.style.backgroundColor="#fff",o.style.padding="20px",o.style.borderRadius="10px",o.style.width="400px",o.style.boxShadow="0 4px 8px rgba(0, 0, 0, 0.2)",o.style.display="flex",o.style.flexDirection="column",o.style.alignItems="center";const n=document.createElement("h2");n.innerText=e,n.style.color="#333",n.style.marginBottom="20px",o.appendChild(n);const r=document.createElement("input");r.type="text",r.placeholder="请输入template_id",r.style.width="100%",r.style.padding="10px",r.style.marginBottom="20px",r.style.border="1px solid #ccc",r.style.borderRadius="4px",r.style.fontSize="16px",o.appendChild(r);const s=document.createElement("div");s.style.marginBottom="20px",s.style.width="100%",s.style.backgroundColor="#f9f9f9",s.style.border="1px solid #ddd",s.style.borderRadius="4px",s.style.padding="10px",s.style.fontSize="14px",s.style.color="#333",s.style.wordWrap="break-word",s.style.wordBreak="break-all",s.style.minHeight="50px",s.innerText="生成的序列号: ",o.appendChild(s);const i=document.createElement("button");function a(e){i.disabled=e,i.innerText=e?"生成中...":"生成序列号",e&&(s.innerText="正在获取序列号...")}i.innerText="生成序列号",i.style.padding="10px 20px",i.style.border="none",i.style.backgroundColor="#4CAF50",i.style.color="white",i.style.borderRadius="4px",i.style.cursor="pointer",i.style.fontSize="16px",i.style.alignSelf="center",i.addEventListener("click",(async()=>{a(!0);try{const e=r.value.trim(),t=await async function(e){try{const t=await api.fetchApi("/cryptocat/keygen",{method:"POST",body:JSON.stringify({template_id:e})}),o=await t.json();if(!t.ok)throw new Error(o.error_msg||`HTTP error! status: ${t.status}`);return o.serial_number||o.error_msg||"获取失败:服务器返回数据格式错误"}catch(e){throw new Error(`获取序列号失败: ${e.message}`)}}(e);s.innerText=`${t}`}catch(e){s.innerText=`发生错误: ${e.message||"未知错误"}`}finally{a(!1)}})),o.appendChild(i),t.appendChild(o),document.body.appendChild(t),t.addEventListener("click",(function(e){e.target===t&&t.remove()}))}function isValidJWTFormat(e){if("string"!=typeof e)return!1;if(e.length<50)return!1;const t=e.split(".");if(3!==t.length)return!1;const o=/^[A-Za-z0-9_-]+$/;return t.every((e=>e.length>0&&o.test(e)))}app.registerExtension({name:"CryptoCat.config",async setup(){app.ui.settings.addSetting({id:"CryptoCat.Keygen.name",name:"输入template_id,计算序列号,序列号有效期7天",type:()=>{const e=document.createElement("tr"),t=document.createElement("td"),o=document.createElement("input");return o.type="button",o.value="算号器",o.style.borderRadius="8px",o.style.padding="8px 16px",o.style.fontSize="14px",o.style.cursor="pointer",o.style.border="1px solid #666",o.style.backgroundColor="#444",o.style.color="#fff",o.onmouseover=()=>{o.style.backgroundColor="#555"},o.onmouseout=()=>{o.style.backgroundColor="#444"},o.onclick=()=>{showKeygenMessageBox("算号器","算号器","question")},t.appendChild(o),e.appendChild(t),e}}),app.ui.settings.addSetting({id:"CryptoCat.User.logout",name:"登出当前用户",type:()=>{const e=document.createElement("tr"),t=document.createElement("td"),o=document.createElement("input");return o.type="button",o.value="登出",o.style.borderRadius="8px",o.style.padding="8px 16px",o.style.fontSize="14px",o.style.cursor="pointer",o.style.border="1px solid #666",o.style.backgroundColor="#444",o.style.color="#fff",o.onclick=async()=>{localStorage.setItem(UserTokenKey,""),window.clearUserInfo(),await api.fetchApi("/cryptocat/logout")},t.appendChild(o),e.appendChild(t),e}}),app.ui.settings.addSetting({id:"CryptoCat.User.long_token",name:"设置长效token",type:"text",textType:"password",defaultValue:"",tooltip:"用于非本机授权登录情况,请勿泄露!提倡使用本机登录授权更安全!",onChange:async function(e){!isValidJWTFormat(e)&&e||await api.fetchApi("/cryptocat/set_long_token",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({long_token:e})})}}),app.ui.settings.addSetting({id:"CryptoCat.Setting.auto_overwrite",name:"自动覆盖更新同名工作流",type:"boolean",defaultValue:!1,tooltip:"设置为true时,会自动覆盖已有的template_id的数据",onChange:function(e){api.fetchApi("/cryptocat/set_auto_overwrite",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({auto_overwrite:e})})}})}});
|
||||
+1
-73
File diff suppressed because one or more lines are too long
+3
-3
@@ -3,13 +3,13 @@ name = "comfyui_cryptocat"
|
||||
description = "a lightweight open-source node for ComfyUI, designed to simplify workflows while providing encryption protection for them."
|
||||
version = "1.0.0"
|
||||
license = {file = "LICENSE"}
|
||||
dependencies = ["torch", "aiohttp"]
|
||||
dependencies = ["pyzipper", "aiohttp", "websockets"]
|
||||
|
||||
[project.urls]
|
||||
Repository = "https://github.com/RiceRound/ComfyUI_CryptoCat"
|
||||
# Used by Comfy Registry https://comfyregistry.org
|
||||
|
||||
[tool.comfy]
|
||||
PublisherId = ""
|
||||
PublisherId = "RiceRound"
|
||||
DisplayName = "ComfyUI_CryptoCat"
|
||||
Icon = ""
|
||||
Icon = "https://www.riceround.online/CryptoCat.svg"
|
||||
|
||||
+2
-1
@@ -1,2 +1,3 @@
|
||||
torch
|
||||
aiohttp
|
||||
pyzipper
|
||||
websockets
|
||||
|
||||
File diff suppressed because one or more lines are too long
+318
-330
@@ -1,348 +1,336 @@
|
||||
from collections import defaultdict
|
||||
import copy
|
||||
import os
|
||||
import random
|
||||
import sys
|
||||
from .updown_workflow import DownloadWorkflow
|
||||
import folder_paths
|
||||
import json
|
||||
from.file_compressor import FileCompressor
|
||||
from .file_compressor import FileCompressor
|
||||
from typing import Dict, Any
|
||||
from .auth_unit import AuthUnit
|
||||
|
||||
class WorkflowTrim():
|
||||
def __init__(self, workflow):
|
||||
if 'workflow' in workflow :
|
||||
self.workflow = copy.deepcopy(workflow['workflow'])
|
||||
|
||||
class CryptoWorkflow:
|
||||
def __init__(self, workflow, prompt, template_id):
|
||||
self.workflow = copy.deepcopy(workflow)
|
||||
self.prompt = copy.deepcopy(prompt)
|
||||
self.template_id = template_id
|
||||
self.workflow_nodes_dict = {}
|
||||
self.link_owner_map = defaultdict(dict)
|
||||
self.node_prompt_map = {}
|
||||
self.last_node_id = 0
|
||||
self.last_link_id = 0
|
||||
self.save_crypto_node_id = 0
|
||||
self.crypto_bridge_node_id = 0
|
||||
self.input_nodes_ids = set()
|
||||
self.output_nodes_ids = set()
|
||||
self.crypto_nodes_ids = set()
|
||||
self.crypto_result = {}
|
||||
|
||||
def invalid_workflow(self):
|
||||
for node in self.workflow["nodes"]:
|
||||
node_type = node.get("type", "")
|
||||
if node_type == "SaveCryptoBridgeNode":
|
||||
if self.crypto_bridge_node_id == 0:
|
||||
self.crypto_bridge_node_id = int(node["id"])
|
||||
else:
|
||||
raise ValueError(
|
||||
"Error: Multiple 'SaveCryptoBridgeNode' instances found."
|
||||
)
|
||||
continue
|
||||
elif node_type == "SaveCryptoNode":
|
||||
if self.save_crypto_node_id == 0:
|
||||
self.save_crypto_node_id = int(node["id"])
|
||||
else:
|
||||
raise ValueError(
|
||||
"Error: Multiple 'SaveCryptoNode' instances found."
|
||||
)
|
||||
continue
|
||||
if self.save_crypto_node_id == 0:
|
||||
raise ValueError("Error: No 'SaveCryptoNode' instances found.")
|
||||
if self.crypto_bridge_node_id == 0:
|
||||
raise ValueError("Error: No 'SaveCryptoBridgeNode' instances found.")
|
||||
|
||||
def load_workflow(self):
|
||||
simplify_workflow = self.workflow
|
||||
self.workflow_nodes_dict = {
|
||||
int(node["id"]): node for node in simplify_workflow["nodes"]
|
||||
}
|
||||
for node in simplify_workflow["nodes"]:
|
||||
output_nodes = node.get("outputs", [])
|
||||
if not output_nodes:
|
||||
continue
|
||||
for output in output_nodes:
|
||||
links = output.get("links", [])
|
||||
if not links:
|
||||
continue
|
||||
for link in links:
|
||||
link = int(link)
|
||||
self.link_owner_map[link]["links"] = copy.deepcopy(links)
|
||||
self.link_owner_map[link]["slot_index"] = output.get(
|
||||
"slot_index", 0
|
||||
)
|
||||
self.link_owner_map[link]["owner_id"] = int(node["id"])
|
||||
self.link_owner_map[link]["type"] = output.get("type", "")
|
||||
self.last_node_id = int(simplify_workflow.get("last_node_id", 0))
|
||||
self.last_link_id = int(simplify_workflow.get("last_link_id", 0))
|
||||
|
||||
def load_prompt(self):
|
||||
simplify_prompt = self.prompt
|
||||
self.node_prompt_map = {
|
||||
int(node_id): node for (node_id, node) in simplify_prompt.items()
|
||||
}
|
||||
|
||||
def analysis_node(self):
|
||||
self.input_nodes_ids.clear()
|
||||
self.output_nodes_ids.clear()
|
||||
self.crypto_nodes_ids.clear()
|
||||
|
||||
def find_input_nodes(node_id, visited=None):
|
||||
if visited is None:
|
||||
visited = set()
|
||||
if node_id in visited:
|
||||
return
|
||||
visited.add(node_id)
|
||||
self.input_nodes_ids.add(node_id)
|
||||
node = self.workflow_nodes_dict.get(node_id)
|
||||
if not node:
|
||||
return
|
||||
for input_node in node.get("inputs", []):
|
||||
input_link = input_node.get("link")
|
||||
if input_link is not None and input_link in self.link_owner_map:
|
||||
owner_id = self.link_owner_map[input_link]["owner_id"]
|
||||
find_input_nodes(owner_id, visited)
|
||||
|
||||
for input_node in self.workflow_nodes_dict[self.save_crypto_node_id].get(
|
||||
"inputs", []
|
||||
):
|
||||
if input_node["name"] and input_node["name"].startswith("input_anything"):
|
||||
input_link = input_node["link"]
|
||||
if input_link is not None and input_link in self.link_owner_map:
|
||||
owner_id = self.link_owner_map[input_link]["owner_id"]
|
||||
find_input_nodes(owner_id)
|
||||
|
||||
def find_output_nodes(node_id, visited=None):
|
||||
if visited is None:
|
||||
visited = set()
|
||||
if node_id in visited:
|
||||
return
|
||||
visited.add(node_id)
|
||||
self.output_nodes_ids.add(node_id)
|
||||
node = self.workflow_nodes_dict.get(node_id)
|
||||
if not node:
|
||||
return
|
||||
for output in node.get("outputs", []):
|
||||
for link in output.get("links", []):
|
||||
if link is not None:
|
||||
for workflow_link in self.workflow.get("links", []):
|
||||
if workflow_link[0] == link:
|
||||
target_node_id = workflow_link[3]
|
||||
find_output_nodes(target_node_id, visited)
|
||||
|
||||
output_links = set()
|
||||
for output_node in self.workflow_nodes_dict[self.crypto_bridge_node_id].get(
|
||||
"outputs", []
|
||||
):
|
||||
_links = output_node["links"]
|
||||
output_links.update(_links)
|
||||
for link in self.workflow.get("links", []):
|
||||
if len(link) > 3 and link[0] in output_links:
|
||||
find_output_nodes(link[3])
|
||||
self.crypto_nodes_ids = (
|
||||
self.workflow_nodes_dict.keys()
|
||||
- self.input_nodes_ids
|
||||
- self.output_nodes_ids
|
||||
)
|
||||
self.crypto_nodes_ids = self.crypto_nodes_ids - {
|
||||
self.save_crypto_node_id,
|
||||
self.crypto_bridge_node_id,
|
||||
}
|
||||
|
||||
def calculate_crypto_result(self, crypto_file_name):
|
||||
self.crypto_result = {"prompt": {}, "workflow": {}, "outputs": []}
|
||||
for node_id in self.crypto_nodes_ids:
|
||||
if node_id in self.node_prompt_map:
|
||||
self.crypto_result["prompt"][node_id] = self.node_prompt_map[node_id]
|
||||
if node_id in self.workflow_nodes_dict:
|
||||
self.crypto_result["workflow"][node_id] = self.workflow_nodes_dict[
|
||||
node_id
|
||||
]
|
||||
crypto_bridge_node = self.node_prompt_map[self.crypto_bridge_node_id]
|
||||
for input_name, input_value in crypto_bridge_node.get("inputs", {}).items():
|
||||
if isinstance(input_value, list) and len(input_value) == 2:
|
||||
self.crypto_result["outputs"] = input_value[0], input_value[1]
|
||||
json_result = json.dumps(self.crypto_result, indent=4, ensure_ascii=False)
|
||||
save_dir = folder_paths.temp_directory
|
||||
with open(os.path.join(save_dir, crypto_file_name), "w", encoding="utf-8") as f:
|
||||
f.write(json_result)
|
||||
return save_dir
|
||||
|
||||
def output_workflow_simple_shell(self, output_workflow_name):
|
||||
simplify_workflow = copy.deepcopy(self.workflow)
|
||||
save_crypto_node = None
|
||||
crypto_bridge_node = None
|
||||
for node in simplify_workflow["nodes"]:
|
||||
if node["id"] == self.save_crypto_node_id:
|
||||
save_crypto_node = node
|
||||
if node["id"] == self.crypto_bridge_node_id:
|
||||
crypto_bridge_node = node
|
||||
except_nodes_ids = self.crypto_nodes_ids
|
||||
except_nodes_ids.add(self.crypto_bridge_node_id)
|
||||
simplify_workflow["nodes"] = [
|
||||
node
|
||||
for node in simplify_workflow["nodes"]
|
||||
if int(node["id"]) not in except_nodes_ids
|
||||
]
|
||||
if save_crypto_node is None:
|
||||
raise ValueError("SaveCryptoNode not found in workflow")
|
||||
if crypto_bridge_node is None:
|
||||
raise ValueError("CryptoBridgeNode not found in workflow")
|
||||
save_crypto_node["type"] = "DecodeCryptoNode"
|
||||
save_crypto_node["widgets_values"] = (
|
||||
[save_crypto_node.get("widgets_values", [None])[0]]
|
||||
if "widgets_values" in save_crypto_node
|
||||
else [None]
|
||||
)
|
||||
save_crypto_node["widgets_values"].append("")
|
||||
save_crypto_node["properties"] = {"Node name for S&R": "DecodeCryptoNode"}
|
||||
if "outputs" not in crypto_bridge_node:
|
||||
save_crypto_node["outputs"] = []
|
||||
else:
|
||||
self.workflow = copy.deepcopy(workflow)
|
||||
save_crypto_node["outputs"] = copy.deepcopy(crypto_bridge_node["outputs"])
|
||||
output_nodes_ids = [int(node["id"]) for node in simplify_workflow["nodes"]]
|
||||
filtered_links = []
|
||||
for link in simplify_workflow["links"]:
|
||||
if len(link) < 5:
|
||||
continue
|
||||
if link[1] == self.crypto_bridge_node_id:
|
||||
link[1] = self.save_crypto_node_id
|
||||
if link[1] in output_nodes_ids and link[3] in output_nodes_ids:
|
||||
filtered_links.append(link)
|
||||
else:
|
||||
pass
|
||||
simplify_workflow["links"] = filtered_links
|
||||
simplify_workflow.pop("groups", None)
|
||||
save_dir = folder_paths.output_directory
|
||||
with open(
|
||||
os.path.join(folder_paths.output_directory, output_workflow_name),
|
||||
"w",
|
||||
encoding="utf-8",
|
||||
) as f:
|
||||
json.dump(simplify_workflow, f, ensure_ascii=False, indent=4)
|
||||
return save_dir
|
||||
|
||||
self.output_images_link = 0
|
||||
def save_original_workflow(self, output_workflow_name, save_dir):
|
||||
with open(
|
||||
os.path.join(save_dir, output_workflow_name), "w", encoding="utf-8"
|
||||
) as f:
|
||||
json.dump(self.workflow, f, ensure_ascii=False, indent=4)
|
||||
return save_dir
|
||||
|
||||
nodes_dict = {node['id']: node for node in self.workflow['nodes']}
|
||||
self.max_id_index = max(nodes_dict.keys()) + 1
|
||||
def save_original_prompt(self, output_prompt_name, save_dir):
|
||||
with open(
|
||||
os.path.join(save_dir, output_prompt_name), "w", encoding="utf-8"
|
||||
) as f:
|
||||
json.dump(self.prompt, f, ensure_ascii=False, indent=4)
|
||||
return save_dir
|
||||
|
||||
|
||||
class DecodeCryptoWorkflow:
|
||||
def __init__(self, prompt, workflow, template_id):
|
||||
self.prompt = prompt
|
||||
self.workflow = workflow
|
||||
self.template_id = template_id
|
||||
self.crypto_result = {}
|
||||
self.input_anything_map = {}
|
||||
|
||||
def calculate_input_anything_map(self):
|
||||
self.input_anything_map.clear()
|
||||
for node_id, node in self.prompt.items():
|
||||
if node.get("class_type") == "DecodeCryptoNode":
|
||||
for input_name, input_value in node.get("inputs", {}).items():
|
||||
if input_name.startswith("input_anything"):
|
||||
if isinstance(input_value, list) and len(input_value) == 2:
|
||||
input_link_key = f"{input_value[0]}_{input_value[1]}"
|
||||
self.input_anything_map[input_link_key] = input_name
|
||||
return self.input_anything_map
|
||||
|
||||
def load_crypto_prompt(self, serial_number_token, user_token=None):
|
||||
if not user_token:
|
||||
user_token = AuthUnit().read_user_token()
|
||||
content = DownloadWorkflow().download_workflow(
|
||||
self.template_id, serial_number_token, user_token
|
||||
)
|
||||
if not isinstance(content, str) or content == "":
|
||||
raise ValueError("failed to download workflow")
|
||||
self.crypto_result = json.loads(content)
|
||||
return self.crypto_result["prompt"]
|
||||
|
||||
def get_hidden_input(self, input_value):
|
||||
if isinstance(input_value, list) and len(input_value) == 2:
|
||||
key = f"{input_value[0]}_{input_value[1]}"
|
||||
return self.input_anything_map.get(key, None)
|
||||
|
||||
def get_outputs(self):
|
||||
return self.crypto_result["outputs"]
|
||||
|
||||
|
||||
class WorkflowTrimHandler:
|
||||
@staticmethod
|
||||
def onprompt_handler(json_data: Dict[str, Any]) -> Dict[str, Any]:
|
||||
prompt = json_data["prompt"]
|
||||
has_new_component = False
|
||||
has_old_component = False
|
||||
for node in prompt.values():
|
||||
class_type = node.get("class_type")
|
||||
if class_type == "SaveCryptoNode":
|
||||
has_new_component = True
|
||||
break
|
||||
if class_type == "ExcuteCryptoNode":
|
||||
has_old_component = True
|
||||
break
|
||||
if has_new_component:
|
||||
user_token, error_msg = AuthUnit().get_user_token()
|
||||
if not user_token and (
|
||||
error_msg == "no token found" or error_msg == "login result error"
|
||||
):
|
||||
AuthUnit().login_dialog("输出CryptoCat加密工作流,请先完成登录")
|
||||
json_data["prompt"] = {}
|
||||
elif has_old_component:
|
||||
prompt = WorkflowTrimHandler.replace_prompt(prompt)
|
||||
json_data["prompt"] = prompt
|
||||
return json_data
|
||||
|
||||
@staticmethod
|
||||
def find_workflow_related_nodes(nodes, input_ids):
|
||||
found_ids = set()
|
||||
stack = list(input_ids)
|
||||
while stack:
|
||||
link_id = stack.pop()
|
||||
for node_id, node in nodes.items():
|
||||
outputs = node.get('outputs', None)
|
||||
if not outputs:
|
||||
continue
|
||||
for output in outputs:
|
||||
links = output.get('links', None)
|
||||
if not links:
|
||||
continue
|
||||
if link_id in links:
|
||||
if node_id not in found_ids:
|
||||
found_ids.add(node_id)
|
||||
inputs = node.get('inputs', [])
|
||||
for input_node in inputs:
|
||||
link_id = input_node.get('link')
|
||||
if link_id is not None:
|
||||
stack.append(link_id)
|
||||
break
|
||||
return found_ids
|
||||
|
||||
def trim_workflow(self):
|
||||
if not self.workflow:
|
||||
def replace_prompt(prompt: Dict[str, Any]) -> Dict[str, Any]:
|
||||
if not prompt:
|
||||
raise ValueError("Invalid JSON format.")
|
||||
|
||||
nodes_dict = {node['id']: node for node in self.workflow['nodes']}
|
||||
save_crypto_node_ids = [node_id for node_id, details in nodes_dict.items() if details.get('type') == 'SaveCryptoNode']
|
||||
if len(save_crypto_node_ids) == 1:
|
||||
save_crypto_node_id = save_crypto_node_ids[0]
|
||||
|
||||
# 获取所有以 'input_anything' 开头的输入链接
|
||||
input_ids = set()
|
||||
for input_node in nodes_dict[save_crypto_node_id].get('inputs', []):
|
||||
if input_node['name'] and input_node['name'].startswith('input_anything'):
|
||||
input_ids.add(input_node['link'])
|
||||
if input_node['name'] and input_node['name'] == "output_images":
|
||||
self.output_images_link = input_node['link']
|
||||
|
||||
# 只处理有效的链接 ID
|
||||
input_ids = {link_id for link_id in input_ids if link_id is not None}
|
||||
|
||||
# 找到与输入链接相关的节点
|
||||
related_node_ids = self.find_workflow_related_nodes(nodes_dict, input_ids)
|
||||
|
||||
# 添加 SaveCryptoNode 本身
|
||||
related_node_ids.add(save_crypto_node_id)
|
||||
|
||||
# # 打印和删除不相关的节点
|
||||
# for node_id in nodes_dict.keys():
|
||||
# if node_id not in related_node_ids:
|
||||
# print(f"Removing node ID: {node_id} - {nodes_dict[node_id]}")
|
||||
|
||||
# 更新节点和链接
|
||||
self.workflow['nodes'] = [details for node_id, details in nodes_dict.items() if node_id in related_node_ids]
|
||||
remaining_node_ids = {node['id'] for node in self.workflow['nodes']}
|
||||
self.workflow.pop('groups', None)
|
||||
self.workflow['output_images_id'] = self.output_images_link
|
||||
|
||||
|
||||
# 保留包含任一节点 ID 的链接
|
||||
self.workflow['links'] = [
|
||||
link for link in self.workflow['links']
|
||||
if link[1] in remaining_node_ids and link[3] in remaining_node_ids
|
||||
]
|
||||
|
||||
return self.workflow
|
||||
|
||||
elif len(save_crypto_node_ids) > 1:
|
||||
raise ValueError("Error: Multiple 'SaveCryptoNode' instances found.")
|
||||
else:
|
||||
raise ValueError("Error: No 'SaveCryptoNode' instances found.")
|
||||
|
||||
|
||||
|
||||
|
||||
def replace_workflow(self,hide_prompt_path):
|
||||
if not self.workflow:
|
||||
raise ValueError("Invalid JSON format.")
|
||||
|
||||
nodes_dict = {node['id']: node for node in self.workflow['nodes']}
|
||||
output_images_link = None
|
||||
|
||||
# 替换 SaveCryptoNode 为 ExcuteCryptoNode
|
||||
for node in self.workflow['nodes']:
|
||||
if node.get('type') == 'SaveCryptoNode':
|
||||
node['type'] = 'ExcuteCryptoNode'
|
||||
node['properties']['Node name for S&R'] = 'ExcuteCryptoNode'
|
||||
|
||||
# 移除 output_images 输入并获取 link
|
||||
for inp in node['inputs']:
|
||||
if inp['name'] == 'output_images':
|
||||
output_images_link = inp.get('link')
|
||||
node['inputs'] = [inp for inp in node['inputs'] if inp['name'] != 'output_images']
|
||||
node['widgets_values'] = [hide_prompt_path]
|
||||
|
||||
# 找到 ExcuteCryptoNode 的位置
|
||||
execute_node = next((node for node in self.workflow['nodes'] if node.get('type') == 'ExcuteCryptoNode'), None)
|
||||
|
||||
if not execute_node:
|
||||
raise ValueError("No 'ExcuteCryptoNode' instance found.")
|
||||
|
||||
# 创建新的 CryptoCatImage 节点
|
||||
execute_node_pos = execute_node['pos']
|
||||
self.max_id_index = max(max(nodes_dict.keys()) + 1, self.max_id_index)
|
||||
new_save_image_node = {
|
||||
"id": self.max_id_index, # 新节点 ID,确保唯一
|
||||
"type": "CryptoCatImage",
|
||||
"size": {
|
||||
"0": 210,
|
||||
"1": 162
|
||||
},
|
||||
"flags": {},
|
||||
"order": 50,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "images",
|
||||
"type": "IMAGE",
|
||||
"link": output_images_link, # 使用获取的 link
|
||||
"label": "images"
|
||||
}
|
||||
],
|
||||
"outputs": [], # 新节点没有输出
|
||||
"properties": {
|
||||
"Node name for S&R": "CryptoCatImage"
|
||||
}
|
||||
}
|
||||
# 将新节点添加到节点列表
|
||||
self.workflow['nodes'].append(new_save_image_node)
|
||||
|
||||
# 删除原有的链接以避免冲突
|
||||
self.workflow['links'] = [
|
||||
link for link in self.workflow['links'] if link[0] != output_images_link
|
||||
]
|
||||
|
||||
# 用新的 outputs 替换 ExcuteCryptoNode 的 outputs
|
||||
execute_node['outputs'] = [
|
||||
{
|
||||
"name": "IMAGE",
|
||||
"type": "IMAGE",
|
||||
"links": [output_images_link], # 连接到新节点
|
||||
"shape": 3,
|
||||
"label": "IMAGE",
|
||||
"slot_index": 0
|
||||
}
|
||||
]
|
||||
|
||||
# 添加链接信息
|
||||
self.workflow['links'].append([
|
||||
output_images_link, # ExcuteCryptoNode 的输出 ID
|
||||
execute_node['id'], # 新节点 ID
|
||||
0, # 新节点的输入索引
|
||||
new_save_image_node['id'], # ExcuteCryptoNode ID(旧节点 ID)
|
||||
0, # 旧节点的输出索引
|
||||
"IMAGE" # 数据类型
|
||||
])
|
||||
|
||||
return self.workflow
|
||||
|
||||
|
||||
def set_excute_crypto_node_path(self, path):
|
||||
if not self.workflow:
|
||||
raise ValueError("Invalid JSON format.")
|
||||
for node in self.workflow['nodes']:
|
||||
if node.get('type') == 'ExcuteCryptoNode':
|
||||
node['properties']['Path to executable file'] = path
|
||||
|
||||
|
||||
def get_remaining_node_ids(self):
|
||||
if not self.workflow:
|
||||
raise ValueError("Invalid JSON format.")
|
||||
remaining_node_ids = {node['id'] for node in self.workflow['nodes']}
|
||||
return remaining_node_ids
|
||||
|
||||
|
||||
|
||||
class PromptTrim():
|
||||
def __init__(self, prompt):
|
||||
self.prompt = prompt
|
||||
self.debug = True
|
||||
self.hide_part_prompt = {}
|
||||
self.show_part_prompt = {}
|
||||
|
||||
def split_prompt(self, related_node_ids):
|
||||
if not self.prompt:
|
||||
raise ValueError("Invalid JSON format.")
|
||||
|
||||
# 查找第一个 'SaveCryptoNode' 实例并转换为 int
|
||||
# save_crypto_node_id = next((int(node_id) for node_id, details in self.prompt.items() if details['class_type'] == 'SaveCryptoNode'), None)
|
||||
save_crypto_node_id = None
|
||||
output_images_ids = []
|
||||
for node_id, details in self.prompt.items():
|
||||
if details['class_type'] == 'SaveCryptoNode':
|
||||
output_images_ids = details['inputs'].get('output_images')
|
||||
save_crypto_node_id = int(node_id)
|
||||
break
|
||||
|
||||
if save_crypto_node_id is None:
|
||||
raise ValueError("Error: No 'SaveCryptoNode' instances found.")
|
||||
|
||||
if save_crypto_node_id not in related_node_ids:
|
||||
raise AssertionError("SaveCryptoNode not found in related node list.")
|
||||
|
||||
self.hide_part_prompt = {}
|
||||
self.show_part_prompt = {}
|
||||
|
||||
# 输出不在链中的节点
|
||||
for node_id in self.prompt.keys():
|
||||
node_id_int = int(node_id) # 转换为 int 类型
|
||||
if node_id_int not in related_node_ids:
|
||||
self.hide_part_prompt[node_id_int] = self.prompt[node_id]
|
||||
else:
|
||||
self.show_part_prompt[node_id_int] = self.prompt[node_id]
|
||||
|
||||
self.hide_part_prompt['output_images_ids'] = output_images_ids
|
||||
return self.show_part_prompt, self.hide_part_prompt
|
||||
|
||||
|
||||
def replace_prompt(self):
|
||||
if not self.prompt:
|
||||
raise ValueError("Invalid JSON format.")
|
||||
|
||||
crypto_file_path = ''
|
||||
crypto_file_path = ""
|
||||
excute_crypto_id = None
|
||||
|
||||
# 查找 ExcuteCryptoNode 并获取路径,同时构建需要删除的节点
|
||||
for node_id, node in list(self.prompt.items()):
|
||||
if node.get('class_type') == 'ExcuteCryptoNode':
|
||||
crypto_file_path = node['inputs'].get('crypto_file_path')
|
||||
for node_id, node in list(prompt.items()):
|
||||
if node.get("class_type") == "ExcuteCryptoNode":
|
||||
crypto_file_path = node["inputs"].get("crypto_file_path")
|
||||
excute_crypto_id = node_id
|
||||
# 删除当前节点
|
||||
del self.prompt[node_id]
|
||||
break
|
||||
|
||||
del prompt[node_id]
|
||||
break
|
||||
if not crypto_file_path:
|
||||
print("No 'ExcuteCryptoNode' found in prompt.")
|
||||
return self.prompt
|
||||
|
||||
# print(f"{crypto_file_path=}")
|
||||
|
||||
inject_json = FileCompressor.decompress_from_json(crypto_file_path, "19040822")
|
||||
output_images_ids = inject_json['output_images_ids']
|
||||
inject_json.pop('output_images_ids', None)
|
||||
for node_id, node in list(self.prompt.items()):
|
||||
if node.get('class_type') == 'CryptoCatImage':
|
||||
ids = node['inputs']['images'] #input_images
|
||||
if excute_crypto_id in ids:
|
||||
node['inputs']['images'] = output_images_ids
|
||||
|
||||
# 随机生成一个种子,避免缓存问题
|
||||
random_seed_node = next((node for node in inject_json.values() if node.get('class_type') == 'RandomSeedNode'), None)
|
||||
return prompt
|
||||
inject_json = FileCompressor.decompress_from_json(crypto_file_path, "19040822")
|
||||
output_images_ids = inject_json.pop("output_images_ids")
|
||||
for node in prompt.values():
|
||||
if node.get("class_type") == "CryptoCatImage":
|
||||
if excute_crypto_id in node["inputs"]["images"]:
|
||||
node["inputs"]["images"] = output_images_ids
|
||||
random_seed_node = next(
|
||||
(
|
||||
node
|
||||
for node in inject_json.values()
|
||||
if node.get("class_type") == "RandomSeedNode"
|
||||
),
|
||||
None,
|
||||
)
|
||||
if random_seed_node:
|
||||
random_seed_node["inputs"]["is_changed"] = random.randint(0, 999999)
|
||||
|
||||
|
||||
# 将 inject_json 的节点合并到当前 prompt
|
||||
# for node in inject_json.get('nodes', []):
|
||||
# self.prompt[node['id']] = node # 使用 ID 添加或更新节点
|
||||
self.prompt.update(inject_json)
|
||||
|
||||
if self.debug == True:
|
||||
temp_dir = os.environ.get('TEMP') or os.environ.get('TMP') or '/tmp'
|
||||
filename = os.path.basename(crypto_file_path) + "_prompt.json"
|
||||
with open(os.path.join(temp_dir, filename), "w", encoding="utf-8") as f:
|
||||
json.dump(self.prompt, f, indent=4)
|
||||
print(f"prompt len = {len(self.prompt)}")
|
||||
|
||||
return self.prompt
|
||||
|
||||
def has_crypto_node(self):
|
||||
if not self.prompt:
|
||||
return False
|
||||
return any(node.get('class_type') == 'ExcuteCryptoNode' for node in self.prompt.values())
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
|
||||
current_dir = os.path.dirname(os.path.abspath(__file__))
|
||||
parent_dir = os.path.dirname(os.path.dirname(current_dir))
|
||||
sys.path.append(parent_dir)
|
||||
import folder_paths
|
||||
|
||||
with open("D:\\work\\ComfyUI\\temp\\flux+lora简单版_original_workflow.json", "r", encoding="utf-8") as f:
|
||||
workflow = json.load(f)
|
||||
|
||||
wt = WorkflowTrim(workflow)
|
||||
wt.trim_workflow()
|
||||
replace_json = wt.replace_workflow('')
|
||||
|
||||
with open("D:\\work\\ComfyUI\\temp\\flux+lora简单版_trim.json", "w", encoding="utf-8") as f:
|
||||
json.dump(replace_json, f, indent=4)
|
||||
|
||||
|
||||
######################################################
|
||||
with open("D:\\work\\ComfyUI\\temp\\flux+lora简单版.txt", "r", encoding="utf-8") as f:
|
||||
prompt = json.load(f)
|
||||
|
||||
pt = PromptTrim(prompt)
|
||||
show_part_prompt, hide_part_prompt = pt.split_prompt(wt.get_remaining_node_ids())
|
||||
|
||||
with open("D:\\work\\ComfyUI\\temp\\flux+lora简单版_show_part_prompt.json", "w", encoding="utf-8") as f:
|
||||
json.dump(show_part_prompt, f, indent=4)
|
||||
|
||||
with open("D:\\work\\ComfyUI\\temp\\flux+lora简单版_hide_part_prompt.json", "w", encoding="utf-8") as f:
|
||||
json.dump(hide_part_prompt, f, indent=4)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
random_seed_node["inputs"]["is_changed"] = random.randint(0, 999999)
|
||||
prompt.update(inject_json)
|
||||
return prompt
|
||||
|
||||
@@ -0,0 +1,238 @@
|
||||
import json
|
||||
import os
|
||||
import requests
|
||||
from .url_config import CatUrlConfig, download_crypto_workflow, user_upload_workflow
|
||||
from .utils import combine_files, get_machine_id, get_local_app_setting_path
|
||||
from .auth_unit import AuthUnit
|
||||
from server import PromptServer
|
||||
from aiohttp import web
|
||||
import time
|
||||
|
||||
|
||||
class Cancelled(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class MessageHolder:
|
||||
stash = {}
|
||||
messages = {}
|
||||
cancelled = False
|
||||
|
||||
@classmethod
|
||||
def addMessage(cls, id, message):
|
||||
if message == "__cancel__":
|
||||
cls.messages = {}
|
||||
cls.cancelled = True
|
||||
elif message == "__start__":
|
||||
cls.messages = {}
|
||||
cls.stash = {}
|
||||
cls.cancelled = False
|
||||
else:
|
||||
cls.messages[str(id)] = message
|
||||
|
||||
@classmethod
|
||||
def waitForMessage(cls, id, period=0.1, timeout=60):
|
||||
sid = str(id)
|
||||
cls.messages.clear()
|
||||
start_time = time.time()
|
||||
while not sid in cls.messages and not "-1" in cls.messages:
|
||||
if cls.cancelled:
|
||||
cls.cancelled = False
|
||||
raise Cancelled()
|
||||
if time.time() - start_time > timeout:
|
||||
raise Cancelled("Operation timed out")
|
||||
time.sleep(period)
|
||||
if cls.cancelled:
|
||||
cls.cancelled = False
|
||||
raise Cancelled()
|
||||
message = cls.messages.pop(str(id), None) or cls.messages.pop("-1")
|
||||
return message.strip()
|
||||
|
||||
|
||||
routes = PromptServer.instance.routes
|
||||
|
||||
|
||||
@routes.post("/cryptocat/message")
|
||||
async def message_handler(request):
|
||||
post = await request.post()
|
||||
MessageHolder.addMessage(post.get("id"), post.get("message"))
|
||||
return web.json_response({})
|
||||
|
||||
|
||||
class UploadWorkflow:
|
||||
def __init__(self, user_token):
|
||||
self.user_token = user_token
|
||||
|
||||
def check_workflow(self, template_id):
|
||||
url = CatUrlConfig().workflow_url
|
||||
headers = {"Authorization": f"Bearer {self.user_token}"}
|
||||
response = requests.get(
|
||||
url, params={"template_id": template_id, "action": "check"}, headers=headers
|
||||
)
|
||||
if response.status_code == 200:
|
||||
response_data = response.json()
|
||||
error_code = response_data.get("code")
|
||||
error_msg = response_data.get("message")
|
||||
return error_code, error_msg
|
||||
else:
|
||||
return -1, ""
|
||||
|
||||
def upload_workflow(self, template_id, temp_dir):
|
||||
error_code, error_msg = self.check_workflow(template_id)
|
||||
if error_code == 1:
|
||||
auto_overwrite = UserWorkflowSetting().get_auto_overwrite()
|
||||
if not auto_overwrite:
|
||||
json_content = {
|
||||
"title": "已经存在相同template_id的数据,是否覆盖?",
|
||||
"icon": "info",
|
||||
"confirmButtonText": "覆盖",
|
||||
"cancelButtonText": "取消",
|
||||
"showCancelButton": True,
|
||||
"timer": 50000,
|
||||
}
|
||||
PromptServer.instance.send_sync(
|
||||
"cryptocat_dialog",
|
||||
{"json_content": json.dumps(json_content), "id": template_id},
|
||||
)
|
||||
msg_result = MessageHolder.waitForMessage(template_id, timeout=60000)
|
||||
try:
|
||||
result_code = int(msg_result)
|
||||
except ValueError:
|
||||
print("crypto cat upload cancel: Invalid response format")
|
||||
return False
|
||||
if result_code != 1:
|
||||
print("crypto cat upload cancel: User rejected overwrite")
|
||||
return False
|
||||
elif error_code != 0:
|
||||
print(f"crypto cat upload failed: {error_msg}")
|
||||
PromptServer.instance.send_sync(
|
||||
"cryptocat_toast", {"content": f"异常情况,{error_msg}", "type": "error"}
|
||||
)
|
||||
return False
|
||||
combine_target_path = os.path.join(temp_dir, f"cryptocat_{template_id}.zip")
|
||||
combine_file_paths = []
|
||||
for file in [
|
||||
f"crypto_{template_id}.json",
|
||||
f"original_workflow_{template_id}.json",
|
||||
f"original_prompt_{template_id}.json",
|
||||
]:
|
||||
src_path = os.path.join(temp_dir, file)
|
||||
combine_file_paths.append(src_path)
|
||||
combine_files(combine_file_paths, template_id, combine_target_path)
|
||||
for file_path in combine_file_paths:
|
||||
os.remove(file_path)
|
||||
success, message = user_upload_workflow(
|
||||
template_id, combine_target_path, self.user_token
|
||||
)
|
||||
if success:
|
||||
PromptServer.instance.send_sync(
|
||||
"cryptocat_toast", {"content": "上传成功", "type": "info", "duration": 5000}
|
||||
)
|
||||
else:
|
||||
PromptServer.instance.send_sync(
|
||||
"cryptocat_toast",
|
||||
{"content": f"上传失败: {message}", "type": "error", "duration": 5000},
|
||||
)
|
||||
return success
|
||||
|
||||
def generate_serial_number(self, template_id, count):
|
||||
url = CatUrlConfig().serial_number_url
|
||||
headers = {"Authorization": f"Bearer {self.user_token}"}
|
||||
response = requests.get(
|
||||
url, params={"template_id": template_id, "count": count}, headers=headers
|
||||
)
|
||||
if response.status_code == 200:
|
||||
response_data = response.json()
|
||||
error_code = response_data.get("code")
|
||||
error_msg = response_data.get("message", "")
|
||||
if error_code == 0:
|
||||
return response_data.get("data", [])
|
||||
else:
|
||||
raise ValueError(f"failed to generate serial number: {error_msg}")
|
||||
else:
|
||||
raise ValueError(
|
||||
f"failed to generate serial number: {response.status_code}"
|
||||
)
|
||||
|
||||
|
||||
class DownloadWorkflow:
|
||||
_instance = None
|
||||
_cache = {}
|
||||
|
||||
def __new__(cls):
|
||||
if cls._instance is None:
|
||||
cls._instance = super(DownloadWorkflow, cls).__new__(cls)
|
||||
return cls._instance
|
||||
|
||||
def __init__(self):
|
||||
if not hasattr(self, "_initialized"):
|
||||
self._initialized = True
|
||||
|
||||
def download_workflow(self, template_id, serial_number_token, user_token):
|
||||
if not serial_number_token:
|
||||
raise ValueError("serial_number_token is required")
|
||||
cache_key = f"{template_id}_{serial_number_token}_{user_token}"
|
||||
if cache_key in self._cache:
|
||||
return self._cache[cache_key]
|
||||
hardware_id = get_machine_id()
|
||||
success, message = download_crypto_workflow(
|
||||
template_id, hardware_id, serial_number_token, user_token
|
||||
)
|
||||
if success:
|
||||
self._cache[cache_key] = message
|
||||
return message
|
||||
elif message is not None:
|
||||
if message == "need login" or message == "需要登录":
|
||||
AuthUnit.login()
|
||||
else:
|
||||
raise ValueError(message)
|
||||
else:
|
||||
raise ValueError("failed to download workflow")
|
||||
|
||||
|
||||
class UserWorkflowSetting:
|
||||
_instance = None
|
||||
|
||||
def __new__(cls):
|
||||
if cls._instance is None:
|
||||
cls._instance = super(UserWorkflowSetting, cls).__new__(cls)
|
||||
return cls._instance
|
||||
|
||||
def __init__(self):
|
||||
if not hasattr(self, "initialized"):
|
||||
local_app_path = get_local_app_setting_path()
|
||||
local_app_path.mkdir(parents=True, exist_ok=True)
|
||||
self.config_path = local_app_path / "config.ini"
|
||||
self.section_name = "Workflow"
|
||||
self.initialized = True
|
||||
|
||||
def get_auto_overwrite(self):
|
||||
try:
|
||||
import configparser
|
||||
|
||||
config = configparser.ConfigParser()
|
||||
config.read(self.config_path, encoding="utf-8")
|
||||
if not config.has_section(self.section_name):
|
||||
return False
|
||||
return config.getboolean(
|
||||
self.section_name, "auto_overwrite", fallback=False
|
||||
)
|
||||
except Exception as e:
|
||||
print(f"Error reading auto_overwrite setting: {str(e)}")
|
||||
return False
|
||||
|
||||
def set_auto_overwrite(self, value):
|
||||
try:
|
||||
import configparser
|
||||
|
||||
config = configparser.ConfigParser()
|
||||
config.read(self.config_path, encoding="utf-8")
|
||||
if not config.has_section(self.section_name):
|
||||
config.add_section(self.section_name)
|
||||
config.set(self.section_name, "auto_overwrite", str(value).lower())
|
||||
with open(self.config_path, "w", encoding="utf-8") as configfile:
|
||||
config.write(configfile)
|
||||
return True
|
||||
except Exception as e:
|
||||
print(f"Error saving auto_overwrite setting: {str(e)}")
|
||||
return False
|
||||
@@ -0,0 +1,90 @@
|
||||
_E='template_id'
|
||||
_D='message'
|
||||
_C='code'
|
||||
_B='Authorization'
|
||||
_A=False
|
||||
from enum import IntEnum
|
||||
import json,os
|
||||
from PIL import Image
|
||||
from io import BytesIO
|
||||
import numpy as np,requests,pyzipper
|
||||
from requests.exceptions import RequestException,Timeout,HTTPError
|
||||
from urllib.parse import urljoin
|
||||
from.utils import get_local_app_setting_path
|
||||
DEFAULT_SUBDOMAIN='api'if os.getenv('DEBUG')!='true'else'test'
|
||||
DEFAULT_URL_PREFIX=f"https://{DEFAULT_SUBDOMAIN}.riceround.online"
|
||||
DEFAULT_WS_PREFIX=f"wss://{DEFAULT_SUBDOMAIN}.riceround.online"
|
||||
class UploadType(IntEnum):TEMPLATE_PUBLISH_IMAGE=1;USER_UPLOAD_TASK_IMAGE=2;MACHINE_TASK_RESULT=1000
|
||||
class CatUrlConfig:
|
||||
_instance=None;_initialized=_A
|
||||
def __new__(A,*B,**C):
|
||||
if A._instance is None:A._instance=super(CatUrlConfig,A).__new__(A)
|
||||
return A._instance
|
||||
def __init__(A):
|
||||
if not A._initialized:A._initialized=True
|
||||
def get_server_url(A,url_path):return urljoin(A.url_prefix,url_path)
|
||||
def get_ws_url(A,url_path):return urljoin(A.ws_prefix,url_path)
|
||||
@property
|
||||
def ws_prefix(self):return DEFAULT_WS_PREFIX
|
||||
@property
|
||||
def url_prefix(self):return DEFAULT_URL_PREFIX
|
||||
@property
|
||||
def login_api_url(self):return self.get_server_url('/api/workflow/get_info')
|
||||
@property
|
||||
def workflow_url(self):return self.get_server_url(f"/api/cryptocat/workflow")
|
||||
@property
|
||||
def serial_number_url(self):return self.get_server_url(f"/api/cryptocat/serial_number")
|
||||
@property
|
||||
def user_client_workflow(self):return self.get_server_url(f"/api/cryptocat/client_workflow")
|
||||
def user_upload_image(image,user_token):
|
||||
H='data';G='image/png';D=CatUrlConfig().user_upload_sign_url;I={_B:f"Bearer {user_token}"};J={'upload_type':UploadType.USER_UPLOAD_TASK_IMAGE.value,'file_type':G};A=requests.get(D,headers=I,params=J);E='';B=''
|
||||
if A.status_code==200:
|
||||
C=A.json()
|
||||
if C.get(_C)==0:E=C.get(H,{}).get('upload_sign_url','');B=C.get(H,{}).get('download_url','')
|
||||
else:raise ValueError(f"failed to upload image. Status code: {A.status_code}")
|
||||
if not E or not B:raise ValueError(f"failed to upload image. upload_sign_url is empty")
|
||||
K=255.*image.cpu().numpy();L=Image.fromarray(np.clip(K,0,255).astype(np.uint8));F=BytesIO();L.save(F,format='PNG',quality=95,compress_level=1);M=F.getvalue();A=requests.put(D,data=M,headers={'Content-Type':G})
|
||||
if A.status_code==200:return B
|
||||
else:print(f"failed to upload image. Status code: {A.status_code}");raise ValueError(f"failed to upload image. Status code: {A.status_code}")
|
||||
def user_upload_workflow(template_id,workflow_path,user_token,timeout=30):
|
||||
'\n 用户上传工作流文件到服务器。\n\n :param workflow_id: 工作流的模板ID\n :param workflow_path: 工作流文件的本地路径\n :param user_token: 用户的认证Token\n :param timeout: 请求超时时间(秒)\n :return: (bool, str) 成功与否及相关消息\n ';K='An unknown error occurred';J='Request timed out';I='Workflow uploaded successfully';A=workflow_path;B=CatUrlConfig().workflow_url;L={_B:f"Bearer {user_token}"};C=os.path.basename(A)
|
||||
if not os.path.isfile(A):print(f"File does not exist: {A}");return _A,'File does not exist'
|
||||
try:
|
||||
with open(A,'rb')as M:N=M.read()
|
||||
O={'file':(C,N,'application/octet-stream')};P={_E:template_id,'action':'upload'};print(f"Uploading file: {C} to {B}");D=requests.put(B,headers=L,files=O,data=P,timeout=timeout);D.raise_for_status()
|
||||
try:E=D.json()
|
||||
except ValueError:print('Response content is not valid JSON');return _A,'Server returned invalid response'
|
||||
if E.get(_C)==0:print(I);return True,I
|
||||
else:F=E.get(_D,'Upload failed');print(f"Upload failed: {F}");return _A,F
|
||||
except FileNotFoundError:print(f"File not found: {A}");return _A,'File not found'
|
||||
except Timeout:print(J);return _A,J
|
||||
except HTTPError as G:print(f"HTTP error occurred: {G}");return _A,f"HTTP error: {G}"
|
||||
except RequestException as H:print(f"Request exception occurred: {H}");return _A,f"Request exception: {H}"
|
||||
except Exception as Q:print(K);return _A,K
|
||||
def download_crypto_workflow(template_id,hardware_id,serial_number,user_token=None):
|
||||
'\n 下载并解密工作流的函数\n\n :param template_id: 模板ID\n :param hardware_id: 硬件ID\n :param serial_number: 序列号\n :param user_token: 用户凭证 (可选)\n :return: (status, content)\n - status: bool, 表示操作是否成功\n - content: 当status为True时,返回工作流内容;否则返回错误信息\n ';M='utf-8';H=user_token;G=serial_number
|
||||
if not G:raise ValueError('serial_number (序列号) 参数不能为空')
|
||||
N=CatUrlConfig();O=N.user_client_workflow;I={}
|
||||
if H:I[_B]=f"Bearer {H}"
|
||||
P={_E:template_id,'hardware_id':hardware_id,'serial_number':G}
|
||||
try:B=requests.post(O,data=P,headers=I)
|
||||
except requests.RequestException as D:A=f"网络请求出现异常: {str(D)}";print(A);return _A,A
|
||||
if B.status_code!=200:
|
||||
if B.status_code==500:
|
||||
try:C=B.json();A=C.get(_D,f"未知错误 (status_code={B.status_code})")
|
||||
except Exception:A=f"服务器内部错误 (status_code={B.status_code})"
|
||||
else:A=f"下载工作流失败: status_code={B.status_code}"
|
||||
print(A);return _A,A
|
||||
try:C=B.json()
|
||||
except ValueError:A='无法解析返回的 JSON 数据';print(A);return _A,A
|
||||
J=C.get(_C,-1)
|
||||
if J!=0:A=C.get(_D,f"未知错误 code={J}");print(A);return _A,A
|
||||
K=C.get('workflow_url','');L=C.get('password','')
|
||||
if not K or not L:A='下载工作流失败: workflow_url 或 password 缺失';print(A);return _A,A
|
||||
try:E=requests.get(K)
|
||||
except requests.RequestException as D:A=f"下载工作流内容出现异常: {str(D)}";print(A);return _A,A
|
||||
if E.status_code!=200:A=f"下载工作流失败: status_code={E.status_code}";print(A);return _A,A
|
||||
try:
|
||||
Q=BytesIO(E.content)
|
||||
with pyzipper.AESZipFile(Q)as F:F.setpassword(L.encode(M));R=F.namelist()[0];S=F.read(R);return True,S.decode(M)
|
||||
except Exception as D:A=f"解密工作流失败: {str(D)}";print(A);return _A,A
|
||||
@@ -0,0 +1,147 @@
|
||||
import configparser
|
||||
import hashlib
|
||||
from io import BytesIO
|
||||
import os
|
||||
from pathlib import Path
|
||||
import uuid
|
||||
import torch
|
||||
import numpy as np
|
||||
from PIL import Image
|
||||
import platform
|
||||
import subprocess
|
||||
import random
|
||||
import string
|
||||
|
||||
|
||||
def pil2tensor(images: Image.Image | list[Image.Image]) -> torch.Tensor:
|
||||
"Converts a PIL Image or a list of PIL Images to a tensor."
|
||||
|
||||
def single_pil2tensor(image: Image.Image) -> torch.Tensor:
|
||||
np_image = np.array(image).astype(np.float32) / 255.0
|
||||
if np_image.ndim == 2:
|
||||
return torch.from_numpy(np_image).unsqueeze(0)
|
||||
else:
|
||||
return torch.from_numpy(np_image).unsqueeze(0)
|
||||
|
||||
if isinstance(images, Image.Image):
|
||||
return single_pil2tensor(images)
|
||||
else:
|
||||
return torch.cat([single_pil2tensor(img) for img in images], dim=0)
|
||||
|
||||
|
||||
def calculate_machine_id():
|
||||
"\n 获取跨平台的机器唯一标识符,类似于 gopsutil 的 HostID\n"
|
||||
system = platform.system()
|
||||
if system == "Linux":
|
||||
try:
|
||||
with open("/etc/machine-id", "r") as f:
|
||||
return f.read().strip()
|
||||
except FileNotFoundError:
|
||||
try:
|
||||
with open("/var/lib/dbus/machine-id", "r") as f:
|
||||
return f.read().strip()
|
||||
except FileNotFoundError:
|
||||
pass
|
||||
try:
|
||||
output = subprocess.check_output(["cat", "/sys/class/dmi/id/product_uuid"])
|
||||
return output.decode().strip()
|
||||
except Exception:
|
||||
pass
|
||||
elif system == "Windows":
|
||||
try:
|
||||
import winreg
|
||||
|
||||
reg_key = "SOFTWARE\\Microsoft\\Cryptography"
|
||||
with winreg.OpenKey(winreg.HKEY_LOCAL_MACHINE, reg_key) as key:
|
||||
machine_guid, _ = winreg.QueryValueEx(key, "MachineGuid")
|
||||
return machine_guid
|
||||
except Exception:
|
||||
pass
|
||||
elif system == "Darwin":
|
||||
try:
|
||||
output = subprocess.check_output(
|
||||
["ioreg", "-rd1", "-c", "IOPlatformExpertDevice"]
|
||||
)
|
||||
for line in output.decode().splitlines():
|
||||
if "IOPlatformUUID" in line:
|
||||
return line.split("=")[-1].strip().strip('"')
|
||||
except Exception:
|
||||
pass
|
||||
return str(uuid.getnode())
|
||||
|
||||
|
||||
def normalize_machine_id(machine_id: str) -> str:
|
||||
"\n 接受一个机器标识符,并返回经过 MD5 哈希处理的规范化标识符\n"
|
||||
salt = "CryptoCat"
|
||||
trimmed_id = machine_id.strip()
|
||||
lowercase_id = trimmed_id.lower()
|
||||
salted_id = lowercase_id + salt
|
||||
hash_obj = hashlib.md5(salted_id.encode("utf-8"))
|
||||
return hash_obj.hexdigest()
|
||||
|
||||
|
||||
def get_local_app_setting_path() -> Path:
|
||||
home = Path.home()
|
||||
config_dir = home / "CryptoCat"
|
||||
return config_dir
|
||||
|
||||
|
||||
def get_machine_id() -> str:
|
||||
"\n 返回机器ID,为了兼容各个平台,各个语言,需要统一读写这个值\n"
|
||||
config_dir = get_local_app_setting_path()
|
||||
config_file = config_dir / "machine.ini"
|
||||
try:
|
||||
config_dir.mkdir(parents=True, exist_ok=True)
|
||||
except Exception as e:
|
||||
print(f"Error creating directory '{config_dir}': {e}")
|
||||
return ""
|
||||
config = configparser.ConfigParser()
|
||||
try:
|
||||
if config_file.exists():
|
||||
config.read(config_file, encoding="utf-8")
|
||||
if "Machine" in config and "machine_id" in config["Machine"]:
|
||||
return config["Machine"]["machine_id"]
|
||||
original_host_id = calculate_machine_id()
|
||||
machine_id = normalize_machine_id(original_host_id)
|
||||
if "Machine" not in config:
|
||||
config.add_section("Machine")
|
||||
config.set("Machine", "machine_id", machine_id)
|
||||
with open(config_file, "w", encoding="utf-8") as file:
|
||||
config.write(file)
|
||||
return machine_id
|
||||
except Exception as e:
|
||||
print(f"Error handling machine ID in '{config_file}': {e}")
|
||||
return ""
|
||||
|
||||
|
||||
def combine_files(files: list[str], password: str, zip_file_path: str) -> bool:
|
||||
import pyzipper
|
||||
|
||||
for file_path in files:
|
||||
if not os.path.exists(file_path):
|
||||
raise FileNotFoundError(f"file not found: {file_path}")
|
||||
try:
|
||||
with pyzipper.AESZipFile(
|
||||
zip_file_path,
|
||||
"w",
|
||||
compression=pyzipper.ZIP_DEFLATED,
|
||||
encryption=pyzipper.WZ_AES,
|
||||
) as zipf:
|
||||
if isinstance(password, str):
|
||||
password = password.encode("utf-8")
|
||||
else:
|
||||
raise ValueError("password must be a string")
|
||||
zipf.setpassword(password)
|
||||
for index, file_path in enumerate(files, start=1):
|
||||
arcname = f"{index}.bin"
|
||||
zipf.write(file_path, arcname)
|
||||
return True
|
||||
except Exception as e:
|
||||
print(f"Error creating zip: {str(e)}")
|
||||
return False
|
||||
|
||||
|
||||
def generate_random_string(length: int) -> str:
|
||||
"\n Generate a random string of specified length using uppercase and lowercase letters.\n \n Args:\n length (int): The desired length of the random string\n \n Returns:\n str: A random string of the specified length\n"
|
||||
letters = string.ascii_letters
|
||||
return "".join(random.choice(letters) for _ in range(length))
|
||||
@@ -0,0 +1,20 @@
|
||||
name: Publish to Comfy registry
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
paths:
|
||||
- "pyproject.toml"
|
||||
|
||||
jobs:
|
||||
publish-node:
|
||||
name: Publish Custom Node to registry
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Check out code
|
||||
uses: actions/checkout@v4
|
||||
- name: Publish Custom Node
|
||||
uses: Comfy-Org/publish-node-action@main
|
||||
with:
|
||||
personal_access_token: ${{ secrets.REGISTRY_ACCESS_TOKEN }}
|
||||
Reference in New Issue
Block a user