Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
408d85691e | ||
|
|
c66cd6901b | ||
|
|
aeadbc4f6d | ||
|
|
224136890e | ||
|
|
3669a1e86d | ||
|
|
d588b5b327 | ||
|
|
b705679098 | ||
|
|
f71a0b0da5 | ||
|
|
ebc2c76b6b | ||
|
|
2e3fff278e | ||
|
|
1f4bc5e089 | ||
|
|
52c38b10dd | ||
|
|
7047aa5456 | ||
|
|
33fe4019f7 | ||
|
|
80b9d97690 | ||
|
|
3c3c92723f | ||
|
|
037bd87006 | ||
|
|
f688310d28 | ||
|
|
c4d65e7a45 | ||
|
|
6f208b710d | ||
|
|
b599faaf85 | ||
|
|
6d991d20dc | ||
|
|
c87e0296f6 | ||
|
|
16cdb4c5b4 | ||
|
|
7631b8924d | ||
|
|
785d307ff3 | ||
|
|
8c713ff35e | ||
|
|
7d80493bef | ||
|
|
bff2760c3d | ||
|
|
a0f8848367 | ||
|
|
5b1cbcd8d5 | ||
|
|
05857a92d5 | ||
|
|
6bdc811286 | ||
|
|
469d50a5b8 | ||
|
|
ef86904bfb | ||
|
|
d4181ea67c | ||
|
|
1c6d17309f | ||
|
|
db293ec41d |
@@ -0,0 +1,21 @@
|
||||
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:
|
||||
## Add your own personal access token to your Github Repository secrets and reference it here.
|
||||
personal_access_token: ${{ secrets.REGISTRY_ACCESS_TOKEN }}
|
||||
@@ -10,7 +10,10 @@ ChatGPT 节点支持 Local LLM(llama.cpp),Phi3、llama3 都可以直接一
|
||||
|
||||
- 右键菜单支持 text-to-text,方便对 prompt 词补全
|
||||
|
||||
强烈推荐:[Phi-3-mini-4k-instruct-GGUF](https://huggingface.co/lmstudio-community/Phi-3-mini-4k-instruct-GGUF/tree/main),备选:[llama3_if_ai_sdpromptmkr_q2k](https://hf-mirror.com/impactframes/llama3_if_ai_sdpromptmkr_q2k/tree/main)
|
||||
强烈推荐:
|
||||
[Phi-3-mini-4k-instruct-function-calling-GGUF](https://huggingface.co/nold/Phi-3-mini-4k-instruct-function-calling-GGUF)
|
||||
|
||||
[Phi-3-mini-4k-instruct-GGUF](https://huggingface.co/lmstudio-community/Phi-3-mini-4k-instruct-GGUF/tree/main),备选:[llama3_if_ai_sdpromptmkr_q2k](https://hf-mirror.com/impactframes/llama3_if_ai_sdpromptmkr_q2k/tree/main)
|
||||
|
||||
- 右键菜单支持 image-to-text,使用多模态模型,多模态使用 [llava-phi-3-mini-gguf](https://huggingface.co/xtuner/llava-phi-3-mini-gguf/tree/main),注意需要把llava-phi-3-mini-mmproj-f16.gguf也下载
|
||||
|
||||
@@ -37,6 +40,7 @@ ChatGPT 节点支持 Local LLM(llama.cpp),Phi3、llama3 都可以直接一
|
||||
- 发布为 app 的 workflow,可以在右键里再次编辑了
|
||||
- web app 可以设置分类,在 comfyui 右键菜单可以编辑更新 web app
|
||||
- 支持动态提示
|
||||
- 支持把输出显示到comfyui背景(TouchDesigner 风格)
|
||||
|
||||

|
||||
|
||||
|
||||
+73
-4
@@ -8,6 +8,8 @@ import hashlib
|
||||
import datetime
|
||||
import folder_paths
|
||||
import logging
|
||||
import base64,io,re
|
||||
from PIL import Image
|
||||
from comfy.cli_args import args
|
||||
python = sys.executable
|
||||
|
||||
@@ -30,6 +32,8 @@ except:
|
||||
print("##nodes.ChatGPT ImportError")
|
||||
|
||||
|
||||
from .nodes.RembgNode import get_rembg_models,U2NET_HOME,run_briarmbg,run_rembg
|
||||
|
||||
from server import PromptServer
|
||||
|
||||
try:
|
||||
@@ -98,6 +102,26 @@ install_openai()
|
||||
current_path = os.path.abspath(os.path.dirname(__file__))
|
||||
|
||||
|
||||
def remove_base64_prefix(base64_str):
|
||||
"""
|
||||
去除 base64 字符串中的 data:image/*;base64, 前缀
|
||||
|
||||
Args:
|
||||
base64_str: base64 编码的字符串
|
||||
|
||||
Returns:
|
||||
去除前缀后的 base64 字符串
|
||||
"""
|
||||
|
||||
# 使用正则表达式匹配常见的前缀
|
||||
pattern = r'^data:image\/(.*);base64,(.+)$'
|
||||
match = re.match(pattern, base64_str)
|
||||
if match:
|
||||
# 如果匹配到常见的前缀,则去除前缀并返回
|
||||
return match.group(2)
|
||||
else:
|
||||
# 如果不匹配到常见的前缀,则直接返回
|
||||
return base64_str
|
||||
|
||||
def calculate_md5(string):
|
||||
encoded_string = string.encode()
|
||||
@@ -629,10 +653,53 @@ async def get_checkpoints(request):
|
||||
names=get_llama_models()
|
||||
except:
|
||||
print("llamafile none")
|
||||
|
||||
try:
|
||||
if data['type']=='rembg':
|
||||
names=get_rembg_models(U2NET_HOME)
|
||||
except:
|
||||
print("rembg none")
|
||||
|
||||
return web.json_response({"names":names,"types":list(folder_paths.folder_names_and_paths.keys())})
|
||||
|
||||
|
||||
@routes.post('/mixlab/rembg')
|
||||
async def rembg_hander(request):
|
||||
data = await request.json()
|
||||
model=data['model']
|
||||
result={}
|
||||
|
||||
data_base64=remove_base64_prefix(data['base64'])
|
||||
image_data = base64.b64decode(data_base64)
|
||||
|
||||
# 创建一个BytesIO对象
|
||||
image_stream = io.BytesIO(image_data)
|
||||
|
||||
# 使用PIL Image模块读取图像
|
||||
image = Image.open(image_stream)
|
||||
|
||||
if model=='briarmbg':
|
||||
_,rgba_images,_=run_briarmbg([image])
|
||||
else:
|
||||
_,rgba_images,_=run_rembg(model,[image])
|
||||
|
||||
with io.BytesIO() as buf:
|
||||
rgba_images[0].save(buf, format='PNG')
|
||||
img_bytes = buf.getvalue()
|
||||
img_base64 = base64.b64encode(img_bytes).decode('utf-8')
|
||||
|
||||
try:
|
||||
result={
|
||||
'data':img_base64,
|
||||
'model':model,
|
||||
'status':'success',
|
||||
}
|
||||
except Exception as e:
|
||||
print(e)
|
||||
|
||||
return web.json_response(result)
|
||||
|
||||
|
||||
@routes.post("/mixlab/prompt_result")
|
||||
async def post_prompt_result(request):
|
||||
data = await request.json()
|
||||
@@ -711,12 +778,14 @@ async def start_local_llm(data):
|
||||
|
||||
server_settings=ServerSettings(host=address,port=port)
|
||||
|
||||
name, ext = os.path.splitext(os.path.basename(model))
|
||||
print('#model',name)
|
||||
app = create_app(
|
||||
server_settings=server_settings,
|
||||
model_settings=[
|
||||
ModelSettings(
|
||||
model=model,
|
||||
model_alias=os.path.basename(model),
|
||||
model_alias=name,
|
||||
n_gpu_layers=n_gpu_layers,
|
||||
n_ctx=4098,
|
||||
chat_format=chat_format,
|
||||
@@ -750,12 +819,12 @@ async def start_local_llm(data):
|
||||
async def my_hander_method(request):
|
||||
data =await request.json()
|
||||
# print(data)
|
||||
if llama_port and llama_model and llama_chat_format:
|
||||
return web.json_response({"port":llama_port,"model":llama_model,"chat_format":llama_chat_format} )
|
||||
try:
|
||||
result=await start_local_llm(data)
|
||||
except:
|
||||
result={
|
||||
{"port":None,"model":"","llama_cpp_error":True}
|
||||
}
|
||||
result= {"port":None,"model":"","llama_cpp_error":True}
|
||||
print('start_local_llm error')
|
||||
|
||||
return web.json_response(result)
|
||||
|
||||
@@ -5201,7 +5201,7 @@
|
||||
"title_aux": "ComfyUI Stable Video Diffusion"
|
||||
}
|
||||
],
|
||||
"https://github.com/thedyze/save-image-extended-comfyui": [
|
||||
"https://github.com/audioscavenger/save-image-extended-comfyui": [
|
||||
[
|
||||
"SaveImageExtended"
|
||||
],
|
||||
@@ -5705,4 +5705,4 @@
|
||||
"title_aux": "SDXLCustomAspectRatio"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
+36
-22
@@ -467,15 +467,37 @@ class BriaRMBG(nn.Module):
|
||||
|
||||
|
||||
|
||||
def get_U2NET_model_path():
|
||||
try:
|
||||
return folder_paths.get_folder_paths('rembg')[0]
|
||||
except:
|
||||
return os.path.join(folder_paths.models_dir, "rembg")
|
||||
|
||||
|
||||
|
||||
|
||||
U2NET_HOME=os.path.join(folder_paths.models_dir, "rembg")
|
||||
U2NET_HOME=get_U2NET_model_path()
|
||||
os.environ["U2NET_HOME"] = U2NET_HOME
|
||||
|
||||
global _available
|
||||
_available=False
|
||||
|
||||
|
||||
def get_rembg_models(path):
|
||||
"""从目录中获取文件并提取文件名
|
||||
Args:
|
||||
path: 目录路径
|
||||
Returns:
|
||||
文件名列表
|
||||
"""
|
||||
filenames = []
|
||||
for root, _, files in os.walk(path):
|
||||
for filename in files:
|
||||
# 过滤隐藏文件
|
||||
if not filename.startswith('.'):
|
||||
name, ext = os.path.splitext(os.path.basename(filename))
|
||||
filenames.append(name)
|
||||
return filenames
|
||||
|
||||
|
||||
def is_installed(package):
|
||||
try:
|
||||
spec = importlib.util.find_spec(package)
|
||||
@@ -509,8 +531,8 @@ except:
|
||||
_available=False
|
||||
|
||||
|
||||
def briarmbg_run(images=[]):
|
||||
mroot=os.path.join(folder_paths.models_dir, "rembg")
|
||||
def run_briarmbg(images=[]):
|
||||
mroot=U2NET_HOME
|
||||
m=os.path.join(mroot,'briarmbg.pth')
|
||||
if os.path.exists(m)==False:
|
||||
# 下载
|
||||
@@ -573,14 +595,15 @@ def briarmbg_run(images=[]):
|
||||
return (masks,rgba_images,rgb_images)
|
||||
|
||||
|
||||
def run_bg(model_name= "unet",images=[]):
|
||||
def run_rembg(model_name= "unet",images=[],callback=None):
|
||||
# model_name = "unet" # "isnet-general-use"
|
||||
# print('#run_rembg',model_name)
|
||||
rembg_session = new_session(model_name)
|
||||
masks=[]
|
||||
rgba_images=[]
|
||||
rgb_images=[]
|
||||
# 进度条
|
||||
pbar = comfy.utils.ProgressBar(len(images) )
|
||||
pbar=callback
|
||||
for img in images:
|
||||
# use the post_process_mask argument to post process the mask to get better results.
|
||||
mask = remove(img, session=rembg_session,only_mask=True,post_process_mask=True)
|
||||
@@ -620,8 +643,9 @@ def run_bg(model_name= "unet",images=[]):
|
||||
rgb_image = Image.new("RGB", image_rgba.size, (0, 0, 0))
|
||||
rgb_image.paste(image_rgba, mask=image_rgba.split()[3])
|
||||
rgb_images.append(rgb_image)
|
||||
|
||||
pbar.update(1)
|
||||
|
||||
if pbar:
|
||||
pbar.update(1)
|
||||
return (masks,rgba_images,rgb_images)
|
||||
|
||||
|
||||
@@ -643,17 +667,7 @@ class RembgNode_:
|
||||
def INPUT_TYPES(s):
|
||||
return {"required": {
|
||||
"image": ("IMAGE",),
|
||||
"model_name": ([
|
||||
"briarmbg",
|
||||
"u2net",
|
||||
"u2netp",
|
||||
"u2net_human_seg",
|
||||
"u2net_cloth_seg",
|
||||
"silueta",
|
||||
"isnet-general-use",
|
||||
"isnet-anime",
|
||||
|
||||
],),
|
||||
"model_name": (get_rembg_models(U2NET_HOME),),
|
||||
|
||||
},
|
||||
}
|
||||
@@ -681,9 +695,9 @@ class RembgNode_:
|
||||
images.append(im)
|
||||
|
||||
if model_name=='briarmbg':
|
||||
masks,rgba_images,rgb_images=briarmbg_run(images)
|
||||
masks,rgba_images,rgb_images=run_briarmbg(images)
|
||||
else:
|
||||
masks,rgba_images,rgb_images=run_bg(model_name,images)
|
||||
masks,rgba_images,rgb_images=run_rembg(model_name,images, comfy.utils.ProgressBar(len(images) ))
|
||||
|
||||
masks=[pil2tensor(m) for m in masks]
|
||||
|
||||
|
||||
+9
-2
@@ -5,13 +5,20 @@ from PIL import Image
|
||||
import numpy as np
|
||||
import torch
|
||||
|
||||
from folder_paths import get_filename_list, get_full_path, get_save_image_path, get_output_directory,models_dir
|
||||
from folder_paths import get_folder_paths, get_full_path, get_save_image_path, get_output_directory,models_dir
|
||||
from comfy.model_management import get_torch_device
|
||||
from .tsr.system import TSR
|
||||
|
||||
import comfy.utils
|
||||
|
||||
triposr_model_path=path.join(models_dir,'triposr/model.ckpt')
|
||||
|
||||
def get_triposr_model_path():
|
||||
try:
|
||||
return path.join(get_folder_paths('triposr')[0],'model.ckpt')
|
||||
except:
|
||||
return path.join(path.join(models_dir, "triposr"),'model.ckpt')
|
||||
|
||||
triposr_model_path=get_triposr_model_path()
|
||||
|
||||
|
||||
# Tensor to PIL
|
||||
|
||||
+2
-4
@@ -284,7 +284,7 @@ class FloatSlider:
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("FLOAT",)
|
||||
RETURN_NAMES = ('weight(0-1)',)
|
||||
RETURN_NAMES = ('FLOAT',)
|
||||
FUNCTION = "run"
|
||||
|
||||
CATEGORY = "♾️Mixlab/Input"
|
||||
@@ -297,9 +297,7 @@ class FloatSlider:
|
||||
number = min_value
|
||||
elif number > max_value:
|
||||
number = max_value
|
||||
scaled_number = (number - min_value) / (max_value - min_value)
|
||||
return (scaled_number,)
|
||||
|
||||
return (number,)
|
||||
|
||||
class IntNumber:
|
||||
@classmethod
|
||||
|
||||
+2
-1
@@ -87,7 +87,7 @@ def split_video(video_path, video_segment_frames, transition_frames, output_dir)
|
||||
|
||||
folder_paths.folder_names_and_paths["video_formats"] = (
|
||||
[
|
||||
os.path.join(os.path.dirname(os.path.abspath(__file__)), "..", "video_formats"),
|
||||
os.path.join(os.path.dirname(os.path.abspath(__file__)), ".", "video_formats"),
|
||||
],
|
||||
[".json"]
|
||||
)
|
||||
@@ -433,6 +433,7 @@ class VideoCombine_Adv:
|
||||
ffmpeg_formats = ["video/"+x[:-5] for x in folder_paths.get_filename_list("video_formats")]
|
||||
else:
|
||||
ffmpeg_formats = []
|
||||
# ffmpeg_formats =["video/"+x for x in ['webm', 'mp4', 'mkv']]
|
||||
return {
|
||||
"required": {
|
||||
"image_batch": ("IMAGE",),
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"main_pass":
|
||||
[
|
||||
"-n", "-c:v", "libsvtav1",
|
||||
"-pix_fmt", "yuv420p10le",
|
||||
"-crf", "23"
|
||||
],
|
||||
"extension": "webm",
|
||||
"environment": {"SVT_LOG": "1"}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"main_pass":
|
||||
[
|
||||
"-n", "-c:v", "libx264",
|
||||
"-pix_fmt", "yuv420p",
|
||||
"-crf", "19"
|
||||
],
|
||||
"extension": "mp4"
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"main_pass":
|
||||
[
|
||||
"-n", "-c:v", "libx265",
|
||||
"-pix_fmt", "yuv420p10le",
|
||||
"-preset", "medium",
|
||||
"-crf", "22",
|
||||
"-x265-params", "log-level=quiet"
|
||||
],
|
||||
"extension": "mp4"
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"main_pass":
|
||||
[
|
||||
"-n",
|
||||
"-pix_fmt", "yuv420p",
|
||||
"-crf", "23"
|
||||
],
|
||||
"extension": "webm"
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
[project]
|
||||
name = "comfyui-mixlab-nodes"
|
||||
description = "3D, ScreenShareNode & FloatingVideoNode, SpeechRecognition & SpeechSynthesis, GPT, LoadImagesFromLocal, Layers, Other Nodes, ..."
|
||||
version = "0.28.3"
|
||||
license = "MIT"
|
||||
dependencies = ["numpy", "pyOpenSSL", "watchdog", "opencv-python-headless", "matplotlib", "openai", "simple-lama-inpainting", "clip-interrogator==0.6.0", "transformers>=4.36.0", "lark-parser", "imageio-ffmpeg", "rembg[gpu]", "omegaconf==2.3.0", "Pillow>=9.5.0", "einops==0.7.0", "trimesh>=4.0.5", "huggingface-hub", "scikit-image"]
|
||||
|
||||
[project.urls]
|
||||
Repository = "https://github.com/shadowcz007/comfyui-mixlab-nodes"
|
||||
# Used by Comfy Registry https://comfyregistry.org
|
||||
|
||||
[tool.comfy]
|
||||
PublisherId = "shadow"
|
||||
DisplayName = "comfyui-mixlab-nodes"
|
||||
Icon = ""
|
||||
+329
-105
@@ -24,6 +24,7 @@
|
||||
margin-left: 5%;
|
||||
user-select: none;
|
||||
margin-top: 32px;
|
||||
margin-bottom: 120px;
|
||||
}
|
||||
|
||||
.apps {
|
||||
@@ -65,9 +66,9 @@
|
||||
box-shadow: 0px 0px 10px 10px #fbe9f0
|
||||
}
|
||||
|
||||
.card:hover {
|
||||
/* .card:hover {
|
||||
box-shadow: 0px 0px 10px 10px #e9fbfa
|
||||
}
|
||||
} */
|
||||
|
||||
.card h5 {
|
||||
font-size: 14px;
|
||||
@@ -145,15 +146,6 @@
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
.status {
|
||||
background: black;
|
||||
color: white;
|
||||
display: flex;
|
||||
width: fit-content;
|
||||
padding: 4px;
|
||||
font-size: 12px;
|
||||
|
||||
}
|
||||
|
||||
.description {
|
||||
display: flex;
|
||||
@@ -247,7 +239,7 @@
|
||||
|
||||
.card textarea {
|
||||
width: 100%;
|
||||
height:'fit-content';
|
||||
height: 'fit-content';
|
||||
/* min-width: 300px; */
|
||||
margin-top: 12px;
|
||||
resize: none;
|
||||
@@ -263,18 +255,44 @@
|
||||
margin-top: 12px;
|
||||
}
|
||||
|
||||
.run_div {
|
||||
position: fixed;
|
||||
bottom: 28px;
|
||||
display: flex;
|
||||
left: 144px;
|
||||
z-index: 100;
|
||||
/* align-items: center; */
|
||||
/* justify-content: space-around; */
|
||||
/* width: calc(50% - 100px); */
|
||||
flex-direction: column;
|
||||
background-color: #f7f7f7;
|
||||
box-shadow: 3px 3px 8px #cacaca;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.status {
|
||||
/* background: #e7e7e7; */
|
||||
color: black;
|
||||
display: flex;
|
||||
width: 120px;
|
||||
padding-left: 11px;
|
||||
font-size: 12px;
|
||||
border-radius: 8px;
|
||||
justify-content: flex-start;
|
||||
align-items: center;
|
||||
height: 48px;
|
||||
}
|
||||
|
||||
.run_btn {
|
||||
background: black;
|
||||
color: white;
|
||||
width: 88px;
|
||||
height: 88px;
|
||||
position: fixed;
|
||||
bottom: 72px;
|
||||
left: calc(50% - 44px);
|
||||
border-radius: 100%;
|
||||
/* width: calc(50% - 100px); */
|
||||
min-width: 200px;
|
||||
max-width: 460px;
|
||||
height: 48px;
|
||||
border-radius: 8px;
|
||||
cursor: pointer;
|
||||
border: 3px solid;
|
||||
z-index: 100;
|
||||
}
|
||||
|
||||
button:hover {
|
||||
@@ -452,7 +470,7 @@
|
||||
<link href="/extensions/comfyui-mixlab-nodes/lib/photoswipe.min.css" rel="stylesheet">
|
||||
<link href="/extensions/comfyui-mixlab-nodes/lib/classic.min.css" rel="stylesheet">
|
||||
<script src="/extensions/comfyui-mixlab-nodes/lib/pickr.min.js"></script>
|
||||
<script src="/extensions/comfyui-mixlab-nodes/lib/filerobot-image-editor.min.js"></script>
|
||||
<!-- <script src="/extensions/comfyui-mixlab-nodes/lib/filerobot-image-editor.min.js"></script> -->
|
||||
<script type="module" src="/extensions/comfyui-mixlab-nodes/lib/model-viewer.min.js"></script>
|
||||
<link rel="stylesheet" href="/extensions/comfyui-mixlab-nodes/lib/login.css">
|
||||
</head>
|
||||
@@ -474,15 +492,14 @@
|
||||
<a class="link" href="https://www.mixcomfy.com" target="_blank">ComfyUI中文爱好者社区推荐</a>
|
||||
</div>
|
||||
|
||||
<a target="_blank" id="login_btn" href="https://www.mixcomfy.com/blog/" style="text-decoration: none;
|
||||
<a id="login_btn" target="_blank" href="https://discord.gg/xbP2GZF6gn" style="text-decoration: none;
|
||||
color: black;font-size:12px">
|
||||
<svg height="32" aria-hidden="true" viewBox="0 0 16 16" version="1.1" width="32" data-view-component="true"
|
||||
class="octicon octicon-mark-github v-align-middle color-fg-default">
|
||||
<path
|
||||
d="M8 0c4.42 0 8 3.58 8 8a8.013 8.013 0 0 1-5.45 7.59c-.4.08-.55-.17-.55-.38 0-.27.01-1.13.01-2.2 0-.75-.25-1.23-.54-1.48 1.78-.2 3.65-.88 3.65-3.95 0-.88-.31-1.59-.82-2.15.08-.2.36-1.02-.08-2.12 0 0-.67-.22-2.2.82-.64-.18-1.32-.27-2-.27-.68 0-1.36.09-2 .27-1.53-1.03-2.2-.82-2.2-.82-.44 1.1-.16 1.92-.08 2.12-.51.56-.82 1.28-.82 2.15 0 3.06 1.86 3.75 3.64 3.95-.23.2-.44.55-.51 1.07-.46.21-1.61.55-2.33-.66-.15-.24-.6-.83-1.23-.82-.67.01-.27.38.01.53.34.19.73.9.82 1.13.16.45.68 1.31 2.69.94 0 .67.01 1.3.01 1.49 0 .21-.15.45-.55.38A7.995 7.995 0 0 1 0 8c0-4.42 3.58-8 8-8Z">
|
||||
</path>
|
||||
</svg> Community</a>
|
||||
|
||||
</svg> HELP/帮助</a>
|
||||
|
||||
</div>
|
||||
<a id="author"></a>
|
||||
@@ -680,11 +697,89 @@
|
||||
}
|
||||
|
||||
|
||||
// 获取 rembg 模型
|
||||
async function get_rembg_models() {
|
||||
try {
|
||||
const response = await fetch(`${get_url()}/mixlab/folder_paths`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({
|
||||
type: 'rembg'
|
||||
})
|
||||
})
|
||||
|
||||
const data = await response.json()
|
||||
// console.log(data)
|
||||
return data.names
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
}
|
||||
}
|
||||
|
||||
//自动抠图
|
||||
async function run_rembg(model, base64) {
|
||||
try {
|
||||
const response = await fetch(`${get_url()}/mixlab/rembg`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({
|
||||
model,
|
||||
base64
|
||||
})
|
||||
})
|
||||
|
||||
const data = await response.json()
|
||||
// console.log(data)
|
||||
return data.data
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
}
|
||||
}
|
||||
|
||||
function convertImageToBlackBasedOnAlpha(image) {
|
||||
const canvas = document.createElement('canvas');
|
||||
const ctx = canvas.getContext('2d');
|
||||
|
||||
// Draw the image onto the canvas
|
||||
canvas.width = image.width;
|
||||
canvas.height = image.height;
|
||||
ctx.drawImage(image, 0, 0);
|
||||
|
||||
// Get the image data from the canvas
|
||||
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
|
||||
const pixels = imageData.data;
|
||||
|
||||
// Modify the RGB values based on the alpha channel
|
||||
for (let i = 0; i < pixels.length; i += 4) {
|
||||
const alpha = pixels[i + 3];
|
||||
if (alpha !== 0) {
|
||||
// Set non-transparent pixels to black
|
||||
pixels[i] = 0; // Red
|
||||
pixels[i + 1] = 0; // Green
|
||||
pixels[i + 2] = 0; // Blue
|
||||
}
|
||||
}
|
||||
|
||||
// Put the modified image data back onto the canvas
|
||||
ctx.putImageData(imageData, 0, 0);
|
||||
|
||||
// Convert the modified canvas to base64 data URL
|
||||
const base64ImageData = canvas.toDataURL('image/png'); // Replace 'png' with your desired image format
|
||||
|
||||
return base64ImageData;
|
||||
}
|
||||
|
||||
|
||||
|
||||
// 图像编辑
|
||||
async function editImage(image, data) {
|
||||
//判断mask是否有输出
|
||||
let isMask = data.options.hasMask;
|
||||
console.log(data)
|
||||
|
||||
//app
|
||||
document.body.querySelector('.app').style.display = 'none'
|
||||
document.body.querySelector('#author').style.display = 'none'
|
||||
@@ -693,21 +788,139 @@
|
||||
editor.style.display = 'block';
|
||||
const iframe = editor.querySelector('iframe');
|
||||
|
||||
const sleep = (t = 1000) => {
|
||||
return new Promise((res, rej) => {
|
||||
setTimeout(() => {
|
||||
res(true)
|
||||
}, t)
|
||||
})
|
||||
}
|
||||
|
||||
//清空 图层
|
||||
const removeAllLayer = () => {
|
||||
let Layers = iframe.contentWindow.Layers;
|
||||
//清空
|
||||
Layers.reset_layers()
|
||||
Layers.refresh_gui()
|
||||
}
|
||||
|
||||
// 复原
|
||||
const resetLayer = () => {
|
||||
let Layers = iframe.contentWindow.Layers;
|
||||
for (const layer of Layers.get_layers()) {
|
||||
layer.visible = true;
|
||||
}
|
||||
Layers.refresh_gui()
|
||||
}
|
||||
//取image
|
||||
const getImageBase64FromLayer = () => {
|
||||
let Layers = iframe.contentWindow.Layers;
|
||||
let tempCanvas = document.createElement("canvas");
|
||||
let tempCtx = tempCanvas.getContext("2d");
|
||||
let dim = Layers.get_dimensions();
|
||||
tempCanvas.width = dim.width;
|
||||
tempCanvas.height = dim.height;
|
||||
for (const layer of Layers.get_layers()) {
|
||||
if (layer.name === 'Image_' + data.id) {
|
||||
layer.visible = true;
|
||||
} else {
|
||||
layer.visible = false;
|
||||
}
|
||||
}
|
||||
Layers.refresh_gui()
|
||||
|
||||
Layers.convert_layers_to_canvas(tempCtx);
|
||||
return tempCanvas.toDataURL()
|
||||
}
|
||||
|
||||
//add mask
|
||||
const addMask = (id, name, image) => {
|
||||
let Layers = iframe.contentWindow.Layers;
|
||||
var new_mask_layer = {
|
||||
id,
|
||||
name,
|
||||
type: 'brush',
|
||||
data: [],
|
||||
render_function: ['brush', 'render'],
|
||||
width: image.naturalWidth || image.width,
|
||||
height: image.naturalHeight || image.height,
|
||||
|
||||
};
|
||||
Layers.insert(new_mask_layer);
|
||||
}
|
||||
|
||||
//add image
|
||||
const addImage = (id, name, image) => {
|
||||
let Layers = iframe.contentWindow.Layers;
|
||||
var new_mask_layer = {
|
||||
id,
|
||||
name,
|
||||
type: 'image',
|
||||
data: image,
|
||||
width: image.naturalWidth || image.width,
|
||||
height: image.naturalHeight || image.height,
|
||||
width_original: image.naturalWidth || image.width,
|
||||
height_original: image.naturalHeight || image.height,
|
||||
};
|
||||
Layers.insert(new_mask_layer);
|
||||
}
|
||||
|
||||
//默认的画笔size设置大
|
||||
// let inputSize = (iframe.contentDocument.getElementById('size')).querySelector('input');
|
||||
// inputSize.value=50;
|
||||
// (iframe.contentDocument.getElementById('size')).querySelector('.increase_number').click()
|
||||
|
||||
//自动抠图
|
||||
let autoMaskSelect = iframe.contentDocument.getElementById('automask_image_mixlab');
|
||||
const select = iframe.contentDocument.getElementById('automask_models_mixlab');
|
||||
if (select.children.length === 0) {
|
||||
let rembgModels = await get_rembg_models()
|
||||
// 遍历模型列表并创建选项
|
||||
for (const model of rembgModels) {
|
||||
const option = document.createElement('option');
|
||||
option.value = model;
|
||||
option.textContent = model;
|
||||
select.appendChild(option);
|
||||
}
|
||||
}
|
||||
|
||||
let autoMaskBtn = iframe.contentDocument.getElementById('automask_image_mixlab');
|
||||
if (!autoMaskBtn.getAttribute('init')) autoMaskBtn.addEventListener('click', async e => {
|
||||
//api请求
|
||||
let base64 = getImageBase64FromLayer()
|
||||
resetLayer()
|
||||
|
||||
let res = await run_rembg(select.value, base64)
|
||||
const match = res.match(/^data:image\/(\w+);base64,/);
|
||||
if (!match) {
|
||||
res = 'data:image/png;base64,' + res
|
||||
}
|
||||
|
||||
let image = await createImage(res)
|
||||
|
||||
let mb = convertImageToBlackBasedOnAlpha(image)
|
||||
let mask = await createImage(mb)
|
||||
let id = Layers.auto_increment;
|
||||
addImage(id, 'Mask_' + data.id + id, mask)
|
||||
})
|
||||
|
||||
autoMaskBtn.setAttribute('init', 1)
|
||||
let cancelImageBtn = iframe.contentDocument.getElementById('cancel_image_mixlab');
|
||||
cancelImageBtn.addEventListener('click', e => {
|
||||
if (!cancelImageBtn.getAttribute('init')) cancelImageBtn.addEventListener('click', e => {
|
||||
editor.style.display = 'none';
|
||||
document.body.querySelector('.app').style.display = 'flex'
|
||||
document.body.querySelector('#author').style.display = 'block'
|
||||
})
|
||||
|
||||
cancelImageBtn.setAttribute('init', 1)
|
||||
// 获取 id 为 "mix" 的 button 元素
|
||||
let saveImageBtn = iframe.contentDocument.getElementById('save_image_mixlab');
|
||||
saveImageBtn.addEventListener('click', async e => {
|
||||
saveImageBtn.style = `width: 98px;
|
||||
height: 36px;
|
||||
margin: 0 12px;
|
||||
background-color: var(--background-color-active);
|
||||
color: var(--text-color-active);`
|
||||
if (!saveImageBtn.getAttribute('init')) saveImageBtn.addEventListener('click', async e => {
|
||||
//保存,并更新图片
|
||||
e.preventDefault();
|
||||
|
||||
@@ -721,6 +934,7 @@
|
||||
tempCanvas.width = dim.width;
|
||||
tempCanvas.height = dim.height;
|
||||
|
||||
//todo 获取Image更新后的数据(暂不支持image的修改)
|
||||
|
||||
for (const layer of Layers.get_layers()) {
|
||||
if (layer.name !== 'Image_' + data.id) {
|
||||
@@ -734,13 +948,7 @@
|
||||
Layers.convert_layers_to_canvas(tempCtx);
|
||||
|
||||
// 复原
|
||||
for (const layer of Layers.get_layers()) {
|
||||
layer.visible = true;
|
||||
}
|
||||
Layers.refresh_gui()
|
||||
|
||||
|
||||
|
||||
resetLayer()
|
||||
|
||||
// 获取图像数据
|
||||
const imageData = tempCtx.getImageData(0, 0, dim.width, dim.height);
|
||||
@@ -766,10 +974,15 @@
|
||||
// // 获取读取的文件内容,即 Blob 对象
|
||||
let hashId = await calculateImageHash(fileBlob)
|
||||
|
||||
if (hashId == window._appData.data[data.id].hashId) return;
|
||||
if (hashId == window._appData.data[data.id].hashId) {
|
||||
document.body.querySelector('.app').style.display = 'flex'
|
||||
document.body.querySelector('#author').style.display = 'block'
|
||||
return;
|
||||
}
|
||||
|
||||
//底图
|
||||
const { url: imgurl } = await uploadImage(base64ToBlob(data.options.defaultImage))
|
||||
|
||||
//mask
|
||||
let { url, name } = await uploadMask(fileBlob, imgurl);
|
||||
// 在这里可以对 Blob 对象进行进一步处理
|
||||
// imageElement.src = url;
|
||||
@@ -777,31 +990,22 @@
|
||||
window._appData.data[data.id].hashId = hashId;
|
||||
|
||||
// console.log("上传的文件:", url, data.id, name);
|
||||
|
||||
|
||||
//更新图片
|
||||
|
||||
const canvas = document.createElement("canvas");
|
||||
canvas.width = dim.width;
|
||||
canvas.height = dim.height;
|
||||
|
||||
const ctx = canvas.getContext('2d');
|
||||
|
||||
const defaultImage = new Image();
|
||||
defaultImage.src = data.options.defaultImage;
|
||||
defaultImage.onload = function () {
|
||||
ctx.drawImage(defaultImage, 0, 0, dim.width, dim.height);
|
||||
const defaultImage = await createImage(data.options.defaultImage)
|
||||
ctx.drawImage(defaultImage, 0, 0, dim.width, dim.height);
|
||||
|
||||
// 绘制base64图片
|
||||
const base64Image = base64
|
||||
const base64ImageObj = new Image();
|
||||
base64ImageObj.onload = function () {
|
||||
ctx.globalCompositeOperation = 'destination-in';
|
||||
ctx.drawImage(base64ImageObj, 0, 0, dim.width, dim.height);
|
||||
image.src = canvas.toDataURL();
|
||||
};
|
||||
base64ImageObj.src = base64;
|
||||
};
|
||||
// 绘制base64图片
|
||||
// const base64Image = base64
|
||||
const base64ImageObj = await createImage(base64)
|
||||
ctx.globalCompositeOperation = 'destination-in';
|
||||
ctx.drawImage(base64ImageObj, 0, 0, dim.width, dim.height);
|
||||
image.src = canvas.toDataURL();
|
||||
|
||||
}
|
||||
|
||||
@@ -809,46 +1013,34 @@
|
||||
document.body.querySelector('#author').style.display = 'block'
|
||||
})
|
||||
|
||||
saveImageBtn.setAttribute('init', 1)
|
||||
|
||||
var Layers = iframe.contentWindow.Layers;
|
||||
// console.log(Layers)
|
||||
|
||||
//判断是否已经存在
|
||||
let layers1 = Layers.get_layers()
|
||||
let layers1 = Layers.get_layers();
|
||||
|
||||
if (!layers1.filter(l => l.name == 'Image_' + data.id)[0]) {
|
||||
var new_layer = {
|
||||
id: (Layers.get_layers()).length,
|
||||
name: 'Image_' + data.id,
|
||||
type: 'image',
|
||||
data: image,
|
||||
width: image.naturalWidth || image.width,
|
||||
height: image.naturalHeight || image.height,
|
||||
width_original: image.naturalWidth || image.width,
|
||||
height_original: image.naturalHeight || image.height,
|
||||
};
|
||||
|
||||
Layers.insert(new_layer);
|
||||
//通过layer.link.src 判断是否图片更新
|
||||
console.log(layers1.filter(l => l.name == 'Image_' + data.id)[0]?.link?.currentSrc !== data.options.defaultImage)
|
||||
if (layers1.filter(l => l.name == 'Image_' + data.id)[0]
|
||||
&& layers1.filter(l => l.name == 'Image_' + data.id)[0].link.src !== data.options.defaultImage) {
|
||||
//清空图层
|
||||
removeAllLayer();
|
||||
layers1 = [];
|
||||
await sleep()
|
||||
}
|
||||
|
||||
|
||||
let im = await createImage(data.options.defaultImage)
|
||||
// console.log(layers1, layers1.length)
|
||||
if (!layers1.filter(l => l.name == 'Image_' + data.id)[0]) {
|
||||
addImage(0, 'Image_' + data.id, im)
|
||||
}
|
||||
|
||||
if (isMask) {
|
||||
if (!layers1.filter(l => l.name == 'Mask_' + data.id)[0]) {
|
||||
var new_mask_layer = {
|
||||
id: (Layers.get_layers()).length,
|
||||
name: 'Mask_' + data.id,
|
||||
type: 'brush',
|
||||
data: [],
|
||||
render_function: ['brush', 'render'],
|
||||
width: image.naturalWidth || image.width,
|
||||
height: image.naturalHeight || image.height,
|
||||
|
||||
};
|
||||
|
||||
Layers.insert(new_mask_layer);
|
||||
addMask(1, 'Mask_' + data.id, im)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1041,7 +1233,7 @@
|
||||
}
|
||||
|
||||
|
||||
function createOutputs(outputData, link) {
|
||||
async function createOutputs(outputData, link) {
|
||||
const url = new URL(window.location.href);
|
||||
const params = new URLSearchParams(url.search);
|
||||
const innerApp = params.get("innerApp");
|
||||
@@ -1162,20 +1354,21 @@
|
||||
"TransparentImage"].includes(node.class_type)) {
|
||||
|
||||
const url = node.options?.defaultImage || window._appData?.icon || base64Df;
|
||||
let a = document.createElement('a');
|
||||
let a = document.createElement('div');
|
||||
a.id = `output_${node.id}`
|
||||
a.setAttribute('data-pswp-width', "200");
|
||||
a.setAttribute('data-pswp-height', "200");
|
||||
a.setAttribute('target', "_blank");
|
||||
a.setAttribute('href', url);
|
||||
a.setAttribute('title', node.title);
|
||||
|
||||
let img = new Image();
|
||||
// img;
|
||||
img.src = url;
|
||||
let img = await createImage(url)
|
||||
|
||||
a.appendChild(img)
|
||||
// a.setAttribute('data-pswp-width', img.naturalWidth);
|
||||
// a.setAttribute('data-pswp-height', img.naturalHeight);
|
||||
// a.setAttribute('target', "_blank");
|
||||
// a.setAttribute('href', url);
|
||||
// a.setAttribute('title', node.title);
|
||||
|
||||
output_card.appendChild(a);
|
||||
isShowImageFn = true;
|
||||
|
||||
}
|
||||
|
||||
//3d
|
||||
@@ -1297,8 +1490,9 @@
|
||||
|
||||
if (hashId == window._appData.data[data.id].hashId) return
|
||||
|
||||
let base64 = await blobToBase64(fileBlob)
|
||||
|
||||
if (data.class_type === 'LoadImagesToBatch') {
|
||||
let base64 = await blobToBase64(fileBlob)
|
||||
createBase64ImageForLoadImageToBatch(imageElement, data.id, base64)
|
||||
} else {
|
||||
let { url, name } = await uploadImage(fileBlob);
|
||||
@@ -1307,6 +1501,14 @@
|
||||
window._appData.data[data.id].inputs.image = name;
|
||||
window._appData.data[data.id].hashId = hashId;
|
||||
console.log("上传的文件:", url, data.id, name);
|
||||
|
||||
//更换option里的default image
|
||||
window._appData.input = Array.from(window._appData.input, inp => {
|
||||
if (inp.id === data.id) {
|
||||
inp.options.defaultImage = base64;
|
||||
}
|
||||
return inp
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1608,9 +1810,9 @@
|
||||
//上传,返回url
|
||||
let { url, name } = await uploadImage(fileBlob, '.' + file.type.split('/')[1])
|
||||
|
||||
let base64 = await parseImageToBase64(url);
|
||||
|
||||
if (data.class_type === 'ImagesPrompt_') {
|
||||
//
|
||||
let base64 = await parseImageToBase64(url);
|
||||
uploadContainer.querySelector('.images_prompt_main').src = base64
|
||||
window._appData.data[data.id].inputs.image_base64 = base64;
|
||||
} else {
|
||||
@@ -1620,10 +1822,17 @@
|
||||
// 在这里可以对 Blob 对象进行进一步处理
|
||||
imageElement.src = url;
|
||||
|
||||
|
||||
if (isVideoUpload) {
|
||||
window._appData.data[data.id].inputs.video = name;
|
||||
} else {
|
||||
//更换option里的default image
|
||||
window._appData.input = Array.from(window._appData.input, inp => {
|
||||
if (inp.id === data.id) {
|
||||
inp.options.defaultImage = base64;
|
||||
}
|
||||
return inp
|
||||
})
|
||||
|
||||
window._appData.data[data.id].inputs.image = name;
|
||||
}
|
||||
|
||||
@@ -2207,7 +2416,7 @@
|
||||
})
|
||||
}
|
||||
|
||||
function createUI(data, share = true) {
|
||||
async function createUI(data, share = true) {
|
||||
// appData.input, appData.output, appData.seed, share, appData.link
|
||||
if (!data) return
|
||||
const { input: inputData, output: outputData, data: workflow, seed, seedTitle, link, name } = data;
|
||||
@@ -2348,30 +2557,44 @@
|
||||
}
|
||||
|
||||
|
||||
statusDiv.appendChild(status);
|
||||
// statusDiv.appendChild(status);
|
||||
statusDiv.appendChild(seeds);
|
||||
|
||||
// 创建输入框
|
||||
var input1 = createInputs(inputData)
|
||||
|
||||
var output = createOutputs(outputData, link)
|
||||
var output = await createOutputs(outputData, link)
|
||||
|
||||
// 创建提交按钮
|
||||
let submitDiv = document.createElement('div');
|
||||
submitDiv.appendChild(statusDiv);
|
||||
|
||||
let submitDivBtn = document.createElement('div');
|
||||
submitDivBtn.style.display = 'flex'
|
||||
submitDiv.appendChild(submitDivBtn);
|
||||
|
||||
submitDivBtn.appendChild(status);
|
||||
|
||||
submitDiv.className = 'run_div';
|
||||
var submitButton = document.createElement('button');
|
||||
submitButton.textContent = 'Create';
|
||||
submitButton.className = 'run_btn'
|
||||
|
||||
// 将所有UI元素添加到页面中
|
||||
leftDiv.appendChild(titleDiv);
|
||||
leftDiv.appendChild(iconDes);
|
||||
// leftDiv.appendChild(titleDiv);
|
||||
// leftDiv.appendChild(iconDes);
|
||||
// leftDiv.appendChild(des);
|
||||
leftDiv.appendChild(statusDiv);
|
||||
// leftDiv.appendChild(statusDiv);
|
||||
leftDiv.appendChild(input1);
|
||||
|
||||
if (typeof (data.data) == 'object') mainDiv.appendChild(submitButton);
|
||||
submitDivBtn.appendChild(submitButton);
|
||||
if (typeof (data.data) == 'object') mainDiv.appendChild(submitDiv);
|
||||
|
||||
rightDiv.appendChild(output);
|
||||
|
||||
// mainDiv.appendChild(titleDiv);
|
||||
// mainDiv.appendChild(iconDes);
|
||||
|
||||
mainDiv.appendChild(leftDetails);
|
||||
mainDiv.appendChild(rightDiv);
|
||||
|
||||
@@ -2719,7 +2942,7 @@
|
||||
async function createApp(appData, share = true) {
|
||||
// console.log(appData)
|
||||
// 使用示例:
|
||||
var ui = createUI(appData, share);
|
||||
var ui = await createUI(appData, share);
|
||||
|
||||
// 更新标题
|
||||
ui.title.update(appData.name || 'Mixlab APP');
|
||||
@@ -2859,7 +3082,8 @@
|
||||
const lightbox = new PhotoSwipeLightbox({
|
||||
gallery: '.output_card',
|
||||
children: 'a',
|
||||
pswpModule: () => import('/extensions/comfyui-mixlab-nodes/lib/photoswipe.esm.min.js')
|
||||
pswpModule: () => import('/extensions/comfyui-mixlab-nodes/lib/photoswipe.esm.min.js'),
|
||||
// appendToEl: document.querySelector('#result')
|
||||
});
|
||||
lightbox.on('uiRegister', function () {
|
||||
lightbox.pswp.ui.registerElement({
|
||||
@@ -2904,8 +3128,8 @@
|
||||
if (appData.author) {
|
||||
let div = document.body.querySelector('#author');
|
||||
if (appData.author.link) div.href = appData.author.link
|
||||
div.style = `z-index:20;display: flex;flex-direction: column;position: fixed;bottom: 12px;right: 24px;cursor: pointer;text-decoration: none;color: black;`
|
||||
div.innerHTML = `<p style="font-size:12px">Author:</p>
|
||||
div.style = `z-index:20;display: flex;flex-direction: column;position: fixed;bottom: 24px;left: 24px;cursor: pointer;text-decoration: none;color: black;`
|
||||
div.innerHTML = `<p style="font-size:12px;margin: 8px 0;">Author:</p>
|
||||
<div style="display: flex;"> <img style="width:32px;height:32px;border-radius: 100%;"
|
||||
src="${appData.author.avatar || base64Df}"/>
|
||||
<p style="margin-left:8px;font-size:12px;font-weight:800">${appData.author.name || '-'}</p></div>`
|
||||
|
||||
@@ -2,6 +2,8 @@ import { app } from '../../../scripts/app.js'
|
||||
import { $el } from '../../../scripts/ui.js'
|
||||
import { api } from '../../../scripts/api.js'
|
||||
|
||||
import { td_bg } from './td_background.js'
|
||||
console.log('td_bg', td_bg)
|
||||
//本机安装的插件节点全集
|
||||
window._nodesAll = null
|
||||
|
||||
@@ -503,6 +505,22 @@ app.registerExtension({
|
||||
}
|
||||
})
|
||||
|
||||
//td bg
|
||||
const tdBG = document.createElement('button')
|
||||
tdBG.innerText = 'TDBG'
|
||||
tdBG.style = style
|
||||
tdBG.style.marginLeft = '12px'
|
||||
|
||||
tdBG.addEventListener('click', () => {
|
||||
td_bg.toggle();
|
||||
if(td_bg.running){
|
||||
tdBG.style.background = 'yellow'
|
||||
}else{
|
||||
tdBG.style.background = 'transparent'
|
||||
}
|
||||
|
||||
})
|
||||
|
||||
// author
|
||||
let author = document.createElement('div')
|
||||
// author.style=`display: flex`
|
||||
@@ -659,6 +677,7 @@ app.registerExtension({
|
||||
|
||||
btns.appendChild(btn)
|
||||
btns.appendChild(download)
|
||||
btns.appendChild(tdBG)
|
||||
|
||||
document.body.appendChild(widget.div)
|
||||
this.addCustomWidget(widget)
|
||||
@@ -687,9 +706,8 @@ app.registerExtension({
|
||||
}
|
||||
|
||||
const div = this.widgets.filter(w => w.div)[0].div
|
||||
Array.from(
|
||||
div.querySelectorAll('button'),
|
||||
b => (b.style.background = 'yellow')
|
||||
Array.from(div.querySelectorAll('button'), b =>
|
||||
b.innerText != 'TDBG' ? (b.style.background = 'yellow') : ''
|
||||
)
|
||||
} catch (error) {}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ async function* completion (url, messages, controller) {
|
||||
let data = {
|
||||
model: 'gpt-3.5-turbo-16k',
|
||||
messages,
|
||||
temperature: 0.6,
|
||||
temperature: 0.05,
|
||||
stream: true
|
||||
}
|
||||
// if (imageNode) {
|
||||
|
||||
@@ -3,7 +3,7 @@ import { app } from '../../../scripts/app.js'
|
||||
const repoOwner = 'shadowcz007' // 替换为仓库的所有者
|
||||
const repoName = 'comfyui-mixlab-nodes' // 替换为仓库的名称
|
||||
|
||||
const version = 'v0.27.0'
|
||||
const version = 'v0.29.0'
|
||||
|
||||
fetch(`https://api.github.com/repos/${repoOwner}/${repoName}/releases/latest`)
|
||||
.then(response => response.json())
|
||||
|
||||
@@ -0,0 +1,323 @@
|
||||
// touchdesigner的背景效果,把appinfo的输出,选择一张图片作为背景
|
||||
|
||||
window._bg_img = null
|
||||
|
||||
/**
|
||||
* draws the back canvas (the one containing the background and the connections)
|
||||
* @method drawBackCanvas
|
||||
**/
|
||||
LGraphCanvas.prototype.drawBackCanvas = function () {
|
||||
var canvas = this.bgcanvas
|
||||
if (
|
||||
canvas.width != this.canvas.width ||
|
||||
canvas.height != this.canvas.height
|
||||
) {
|
||||
canvas.width = this.canvas.width
|
||||
canvas.height = this.canvas.height
|
||||
}
|
||||
|
||||
if (!this.bgctx) {
|
||||
this.bgctx = this.bgcanvas.getContext('2d')
|
||||
}
|
||||
var ctx = this.bgctx
|
||||
if (ctx.start) {
|
||||
ctx.start()
|
||||
}
|
||||
|
||||
var viewport = this.viewport || [0, 0, ctx.canvas.width, ctx.canvas.height]
|
||||
|
||||
//clear
|
||||
if (this.clear_background) {
|
||||
ctx.clearRect(viewport[0], viewport[1], viewport[2], viewport[3])
|
||||
}
|
||||
|
||||
//show subgraph stack header
|
||||
if (this._graph_stack && this._graph_stack.length) {
|
||||
ctx.save()
|
||||
var parent_graph = this._graph_stack[this._graph_stack.length - 1]
|
||||
var subgraph_node = this.graph._subgraph_node
|
||||
ctx.strokeStyle = subgraph_node.bgcolor
|
||||
ctx.lineWidth = 10
|
||||
ctx.strokeRect(1, 1, canvas.width - 2, canvas.height - 2)
|
||||
ctx.lineWidth = 1
|
||||
ctx.font = '40px Arial'
|
||||
ctx.textAlign = 'center'
|
||||
ctx.fillStyle = subgraph_node.bgcolor || '#AAA'
|
||||
var title = ''
|
||||
for (var i = 1; i < this._graph_stack.length; ++i) {
|
||||
title += this._graph_stack[i]._subgraph_node.getTitle() + ' >> '
|
||||
}
|
||||
ctx.fillText(title + subgraph_node.getTitle(), canvas.width * 0.5, 40)
|
||||
ctx.restore()
|
||||
}
|
||||
|
||||
var bg_already_painted = false
|
||||
if (this.onRenderBackground) {
|
||||
bg_already_painted = this.onRenderBackground(canvas, ctx)
|
||||
}
|
||||
|
||||
//reset in case of error
|
||||
if (!this.viewport) {
|
||||
ctx.restore()
|
||||
ctx.setTransform(1, 0, 0, 1, 0, 0)
|
||||
}
|
||||
this.visible_links.length = 0
|
||||
|
||||
if (this.graph) {
|
||||
//apply transformations
|
||||
ctx.save()
|
||||
this.ds.toCanvasContext(ctx)
|
||||
|
||||
//render BG
|
||||
if (
|
||||
this.ds.scale < 1 &&
|
||||
!bg_already_painted &&
|
||||
this.clear_background_color
|
||||
) {
|
||||
ctx.fillStyle = this.clear_background_color
|
||||
ctx.fillRect(
|
||||
this.visible_area[0],
|
||||
this.visible_area[1],
|
||||
this.visible_area[2],
|
||||
this.visible_area[3]
|
||||
)
|
||||
}
|
||||
|
||||
// 主要修改
|
||||
if (this.background_image && this.ds.scale > 0.5 && !bg_already_painted) {
|
||||
if (this.zoom_modify_alpha) {
|
||||
//使得 alpha 越接近0时变化越缓慢。
|
||||
let alpha = (1.0 - 0.5 / this.ds.scale) * this.editor_alpha
|
||||
ctx.globalAlpha = Math.min(Math.max(0, Math.sqrt(alpha)), 1)
|
||||
// console.log((1.0 - 0.5 / this.ds.scale) * this.editor_alpha)
|
||||
} else {
|
||||
ctx.globalAlpha = this.editor_alpha
|
||||
}
|
||||
ctx.imageSmoothingEnabled = ctx.imageSmoothingEnabled = false // ctx.mozImageSmoothingEnabled =
|
||||
if (!this._bg_img || this._bg_img.name != this.background_image) {
|
||||
this._bg_img = new Image()
|
||||
this._bg_img.name = this.background_image
|
||||
this._bg_img.src = this.background_image
|
||||
var that = this
|
||||
this._bg_img.onload = function () {
|
||||
that.draw(true, true)
|
||||
}
|
||||
}
|
||||
|
||||
var pattern = null
|
||||
if (this._pattern == null && this._bg_img.width > 0) {
|
||||
pattern = ctx.createPattern(this._bg_img, 'repeat')
|
||||
this._pattern_img = this._bg_img
|
||||
this._pattern = pattern
|
||||
} else {
|
||||
pattern = this._pattern
|
||||
}
|
||||
|
||||
if (pattern) {
|
||||
ctx.fillStyle = pattern
|
||||
ctx.fillRect(
|
||||
this.visible_area[0],
|
||||
this.visible_area[1],
|
||||
this.visible_area[2],
|
||||
this.visible_area[3]
|
||||
)
|
||||
ctx.fillStyle = 'transparent'
|
||||
}
|
||||
|
||||
ctx.globalAlpha = 1.0
|
||||
ctx.imageSmoothingEnabled = ctx.imageSmoothingEnabled = true //= ctx.mozImageSmoothingEnabled
|
||||
}
|
||||
|
||||
//groups
|
||||
if (this.graph._groups.length && !this.live_mode) {
|
||||
this.drawGroups(canvas, ctx)
|
||||
}
|
||||
|
||||
if (this.onDrawBackground) {
|
||||
this.onDrawBackground(ctx, this.visible_area)
|
||||
}
|
||||
if (this.onBackgroundRender) {
|
||||
//LEGACY
|
||||
console.error(
|
||||
'WARNING! onBackgroundRender deprecated, now is named onDrawBackground '
|
||||
)
|
||||
this.onBackgroundRender = null
|
||||
}
|
||||
|
||||
//DEBUG: show clipping area
|
||||
//ctx.fillStyle = "red";
|
||||
//ctx.fillRect( this.visible_area[0] + 10, this.visible_area[1] + 10, this.visible_area[2] - 20, this.visible_area[3] - 20);
|
||||
|
||||
//bg
|
||||
if (this.render_canvas_border) {
|
||||
ctx.strokeStyle = '#235'
|
||||
ctx.strokeRect(0, 0, canvas.width, canvas.height)
|
||||
}
|
||||
|
||||
if (this.render_connections_shadows) {
|
||||
ctx.shadowColor = '#000'
|
||||
ctx.shadowOffsetX = 0
|
||||
ctx.shadowOffsetY = 0
|
||||
ctx.shadowBlur = 6
|
||||
} else {
|
||||
ctx.shadowColor = 'rgba(0,0,0,0)'
|
||||
}
|
||||
|
||||
//draw connections
|
||||
if (!this.live_mode) {
|
||||
this.drawConnections(ctx)
|
||||
}
|
||||
|
||||
ctx.shadowColor = 'rgba(0,0,0,0)'
|
||||
|
||||
//restore state
|
||||
ctx.restore()
|
||||
}
|
||||
|
||||
if (ctx.finish) {
|
||||
ctx.finish()
|
||||
}
|
||||
|
||||
this.dirty_bgcanvas = false
|
||||
this.dirty_canvas = true //to force to repaint the front canvas with the bgcanvas
|
||||
}
|
||||
|
||||
function imgToCanvasBase64 (img) {
|
||||
const canvas = document.createElement('canvas')
|
||||
const ctx = canvas.getContext('2d')
|
||||
canvas.width = img.width
|
||||
canvas.height = img.height
|
||||
ctx.drawImage(img, 0, 0)
|
||||
const base64 = canvas.toDataURL('image/png')
|
||||
|
||||
return base64
|
||||
}
|
||||
|
||||
// 使用示例
|
||||
function convertImageToBase64 (img) {
|
||||
// const img = new Image()
|
||||
// img.src = 'path/to/your/image.jpg' // 替换为你的图片路径
|
||||
// console.log('convertImageToBase64',img)
|
||||
try {
|
||||
const base64 = imgToCanvasBase64(img)
|
||||
return base64
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
}
|
||||
}
|
||||
|
||||
function getInputsAndOutputs () {
|
||||
const outputs =
|
||||
`PreviewImage,SaveImage,TransparentImage,VHS_VideoCombine,VideoCombine_Adv,Image Save,SaveImageAndMetadata_`.split(
|
||||
','
|
||||
)
|
||||
|
||||
let outputsId = []
|
||||
|
||||
for (let node of app.graph._nodes) {
|
||||
if (outputs.includes(node.type)) {
|
||||
outputsId.push(node.id)
|
||||
}
|
||||
}
|
||||
|
||||
return outputsId
|
||||
}
|
||||
|
||||
function getRandomElement (arr) {
|
||||
const randomIndex = Math.floor(Math.random() * arr.length)
|
||||
return arr[randomIndex]
|
||||
}
|
||||
|
||||
async function getBG () {
|
||||
var outputs = []
|
||||
|
||||
for (let id of app.graph
|
||||
.getNodeById(50)
|
||||
.widgets.filter(w => w.name === 'output_ids')[0]
|
||||
.value.split('\n')) {
|
||||
if (getInputsAndOutputs().map(Number).includes(Number(id))) {
|
||||
if (app.graph.getNodeById(id).imgs && app.graph.getNodeById(id).imgs[0]) {
|
||||
let b = convertImageToBase64(app.graph.getNodeById(id).imgs[0])
|
||||
// console.log(b)
|
||||
outputs.push(b)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var BACKGROUND_IMAGE = getRandomElement(outputs),
|
||||
CLEAR_BACKGROUND_COLOR = 'rgba(0,0,0,0.9)'
|
||||
|
||||
if (!window._bg_img) {
|
||||
window._bg_img = app.canvas._bg_img.src
|
||||
}
|
||||
// let img=new Image();
|
||||
// img.src=BACKGROUND_IMAGE;
|
||||
|
||||
//去掉透明度过度
|
||||
// app.canvas.zoom_modify_alpha=false;
|
||||
//整体透明度
|
||||
app.canvas.editor_alpha = 1.1
|
||||
// app.canvas._pattern=ctx.createPattern(img, "no-repeat");
|
||||
app.canvas.updateBackground(BACKGROUND_IMAGE, CLEAR_BACKGROUND_COLOR)
|
||||
app.canvas.draw(true, true)
|
||||
}
|
||||
|
||||
class BgRunner {
|
||||
constructor () {
|
||||
this.intervalId = null
|
||||
this.running = false
|
||||
}
|
||||
|
||||
// 要运行的方法
|
||||
bg () {
|
||||
console.log('方法bg正在运行')
|
||||
getBG()
|
||||
}
|
||||
|
||||
// 启动bg方法每秒运行一次
|
||||
start () {
|
||||
if (!this.running) {
|
||||
this.intervalId = setInterval(() => this.bg(), 1500)
|
||||
this.running = true
|
||||
}
|
||||
}
|
||||
|
||||
// 停止bg方法的运行
|
||||
stop () {
|
||||
if (this.running) {
|
||||
clearInterval(this.intervalId)
|
||||
this.intervalId = null
|
||||
this.running = false
|
||||
|
||||
if (window._bg_img) {
|
||||
var BACKGROUND_IMAGE = window._bg_img,
|
||||
CLEAR_BACKGROUND_COLOR = 'rgba(0,0,0,1)'
|
||||
app.canvas.editor_alpha = 1
|
||||
|
||||
app.canvas.updateBackground(BACKGROUND_IMAGE, CLEAR_BACKGROUND_COLOR)
|
||||
app.canvas.draw(true, true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 切换start和stop
|
||||
toggle () {
|
||||
if (this.running) {
|
||||
this.stop()
|
||||
} else {
|
||||
this.start()
|
||||
}
|
||||
}
|
||||
|
||||
// 获取运行状态
|
||||
isRunning () {
|
||||
return this.running
|
||||
}
|
||||
}
|
||||
|
||||
// 示例用法
|
||||
// const runner = new BgRunner();
|
||||
// runner.start();
|
||||
// setTimeout(() => runner.stop(), 5000);
|
||||
|
||||
export const td_bg = new BgRunner()
|
||||
+120
-36
@@ -26,7 +26,35 @@ function showTextByLanguage (key, json) {
|
||||
}
|
||||
|
||||
//系统prompt
|
||||
const systemPrompt = `You are a prompt creator, your task is to create prompts for the user input request, the prompts are image descriptions that include keywords for (an adjective, type of image, framing/composition, subject, subject appearance/action, environment, lighting situation, details of the shoot/illustration, visuals aesthetics and artists), brake keywords by comas, provide high quality, non-verboose, coherent, brief, concise, and not superfluous prompts, the subject from the input request must be included verbatim on the prompt,the prompt is english`
|
||||
// const systemPrompt = `You are a prompt creator, your task is to create prompts for the user input request, the prompts are image descriptions that include keywords for (an adjective, type of image, framing/composition, subject, subject appearance/action, environment, lighting situation, details of the shoot/illustration, visuals aesthetics and artists), brake keywords by comas, provide high quality, non-verboose, coherent, brief, concise, and not superfluous prompts, the subject from the input request must be included verbatim on the prompt,the prompt is english`
|
||||
|
||||
let tool ={
|
||||
"name": "create_prompt",
|
||||
"description": "Create a prompt with a given subject, content, and style based on user input for image descriptions.",
|
||||
"parameter": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"subject": {
|
||||
"type": "string",
|
||||
"description": "The subject of the prompt, included verbatim from the input request.",
|
||||
"required": true
|
||||
},
|
||||
"content": {
|
||||
"type": "string",
|
||||
"description": "The content of the prompt, primarily focusing on the scene and objects, including keywords for adjective, type of image, framing/composition, subject appearance/action, and environment.",
|
||||
"required": true
|
||||
},
|
||||
"style": {
|
||||
"type": "string",
|
||||
"description": "The style of the prompt, including lighting situation, details of the shoot/illustration, visual aesthetics, and artists. Ensure it is high quality, non-verbose, coherent, brief, concise, and not superfluous.",
|
||||
"required": true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const systemPrompt=`You are a helpful assistant with access to the following functions. Use them if required - ${JSON.stringify(tool,null,2)}`
|
||||
|
||||
|
||||
if (!localStorage.getItem('_mixlab_system_prompt')) {
|
||||
localStorage.setItem('_mixlab_system_prompt', systemPrompt)
|
||||
@@ -46,7 +74,7 @@ async function get_llamafile_models () {
|
||||
})
|
||||
|
||||
const data = await response.json()
|
||||
console.log(data)
|
||||
// console.log(data)
|
||||
return data.names
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
@@ -135,7 +163,7 @@ async function createMenu () {
|
||||
|
||||
// appsButton.onclick = () =>
|
||||
appsButton.onclick = async () => {
|
||||
if (window._mixlab_llamacpp) {
|
||||
if (window._mixlab_llamacpp&&window._mixlab_llamacpp.model&&window._mixlab_llamacpp.model.length>0) {
|
||||
//显示运行的模型
|
||||
createModelsModal([
|
||||
window._mixlab_llamacpp.url,
|
||||
@@ -539,10 +567,6 @@ injectCSS(`::-webkit-scrollbar {
|
||||
border-left: 2px solid var(--input-text);
|
||||
}
|
||||
|
||||
.litegraph{
|
||||
background: var(--bg-color)!important;
|
||||
}
|
||||
|
||||
`)
|
||||
|
||||
async function getCustomnodeMappings (mode = 'url') {
|
||||
@@ -774,6 +798,22 @@ async function fetchReadmeContent (url) {
|
||||
}
|
||||
}
|
||||
|
||||
async function startLLM (model) {
|
||||
let res = await start_llama(model)
|
||||
window._mixlab_llamacpp = res||{ model:[] }
|
||||
|
||||
localStorage.setItem('_mixlab_llama_select', res?.model||'')
|
||||
|
||||
if (document.body.querySelector('#mixlab_chatbot_by_llamacpp')&&window._mixlab_llamacpp?.url) {
|
||||
document.body
|
||||
.querySelector('#mixlab_chatbot_by_llamacpp')
|
||||
.setAttribute('title', window._mixlab_llamacpp.url)
|
||||
}
|
||||
if (document.body.querySelector('#llm_status_btn')&&window._mixlab_llamacpp) {
|
||||
document.body.querySelector('#llm_status_btn').innerText = window._mixlab_llamacpp.model
|
||||
}
|
||||
}
|
||||
|
||||
function createModelsModal (models) {
|
||||
var div =
|
||||
document.querySelector('#model-modal') || document.createElement('div')
|
||||
@@ -829,7 +869,7 @@ function createModelsModal (models) {
|
||||
color: var(--descrip-text);
|
||||
font-size: 18px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
align-items: flex-start;
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
text-decoration: none;
|
||||
@@ -848,6 +888,27 @@ function createModelsModal (models) {
|
||||
})
|
||||
linkIcon.style.padding = '4px'
|
||||
|
||||
const statusIcon = document.createElement('small')
|
||||
statusIcon.textContent = showTextByLanguage('Status', {
|
||||
Status: 'OFF'
|
||||
})
|
||||
statusIcon.id = 'llm_status_btn'
|
||||
statusIcon.style=`padding: 4px;
|
||||
background-color: rgb(102, 255, 108);
|
||||
color: black;
|
||||
font-size: 12px;
|
||||
margin-left: 12px;`
|
||||
if (window._mixlab_llamacpp?.url) {
|
||||
statusIcon.textContent = window._mixlab_llamacpp.model
|
||||
statusIcon.style.backgroundColor = '#66ff6c'
|
||||
statusIcon.style.color = 'black'
|
||||
} else {
|
||||
}
|
||||
statusIcon.addEventListener('click', e => {
|
||||
e.stopPropagation()
|
||||
// startLLM()
|
||||
})
|
||||
|
||||
const n_gpu = document.createElement('input')
|
||||
n_gpu.type = 'number'
|
||||
n_gpu.setAttribute('min', -1)
|
||||
@@ -882,20 +943,22 @@ function createModelsModal (models) {
|
||||
const title = document.createElement('p')
|
||||
title.innerText = 'Models'
|
||||
title.style = `font-size: 18px;
|
||||
margin-right: 8px;`
|
||||
margin-right: 8px;
|
||||
margin-top: 0;`
|
||||
|
||||
const left_d = document.createElement('div')
|
||||
left_d.style = `display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
font-size: 12px;`
|
||||
align-items: flex-start;
|
||||
font-size: 12px;
|
||||
flex-direction: column; `
|
||||
left_d.appendChild(title)
|
||||
|
||||
title.appendChild(statusIcon)
|
||||
left_d.appendChild(linkIcon)
|
||||
|
||||
left_d.appendChild(n_gpu_div)
|
||||
headTitleElement.appendChild(left_d)
|
||||
|
||||
headTitleElement.appendChild(n_gpu_div)
|
||||
// headTitleElement.appendChild(n_gpu_div)
|
||||
|
||||
//重启
|
||||
const reStart = document.createElement('small')
|
||||
@@ -903,7 +966,11 @@ function createModelsModal (models) {
|
||||
restart: '重启'
|
||||
})
|
||||
|
||||
reStart.style.padding = '4px'
|
||||
reStart.style=`padding: 8px;
|
||||
font-size: 16px;
|
||||
outline: 1px solid;
|
||||
padding-top: 4px;
|
||||
padding-bottom: 4px;`
|
||||
|
||||
headTitleElement.appendChild(reStart)
|
||||
|
||||
@@ -962,29 +1029,22 @@ function createModelsModal (models) {
|
||||
|
||||
modalContent.appendChild(input)
|
||||
|
||||
for (const m of models) {
|
||||
let d = document.createElement('div')
|
||||
d.innerText = m
|
||||
d.className = `mix_tag`
|
||||
if (!window._mixlab_llamacpp||(window._mixlab_llamacpp?.model?.length==0)) {
|
||||
for (const m of models) {
|
||||
let d = document.createElement('div')
|
||||
d.innerText = `${showTextByLanguage('Run', {
|
||||
Run: '运行'
|
||||
})} ${m}`
|
||||
d.className = `mix_tag`
|
||||
|
||||
if (!window._mixlab_llamacpp) {
|
||||
d.addEventListener('click', async e => {
|
||||
e.stopPropagation()
|
||||
div.remove()
|
||||
let res = await start_llama(m)
|
||||
window._mixlab_llamacpp = res
|
||||
|
||||
localStorage.setItem('_mixlab_llama_select', res.model)
|
||||
|
||||
if (document.body.querySelector('#mixlab_chatbot_by_llamacpp')) {
|
||||
document.body
|
||||
.querySelector('#mixlab_chatbot_by_llamacpp')
|
||||
.setAttribute('title', window._mixlab_llamacpp.url)
|
||||
}
|
||||
startLLM(m)
|
||||
})
|
||||
}
|
||||
|
||||
modalContent.appendChild(d)
|
||||
modalContent.appendChild(d)
|
||||
}
|
||||
}
|
||||
modal.appendChild(modalContent)
|
||||
|
||||
@@ -998,8 +1058,8 @@ function createModelsModal (models) {
|
||||
cursor: pointer;
|
||||
font-size: 12px;
|
||||
color: white;`
|
||||
helpInfo.href="https://discord.gg/cXs9vZSqeK"
|
||||
helpInfo.target="_blank"
|
||||
helpInfo.href = 'https://discord.gg/cXs9vZSqeK'
|
||||
helpInfo.target = '_blank'
|
||||
modal.appendChild(helpInfo)
|
||||
|
||||
document.body.appendChild(div)
|
||||
@@ -1353,6 +1413,8 @@ app.registerExtension({
|
||||
.querySelector('#mixlab_chatbot_by_llamacpp')
|
||||
.setAttribute('title', res.url)
|
||||
})
|
||||
}else{
|
||||
startLLM('')
|
||||
}
|
||||
|
||||
LGraphCanvas.prototype.helpAboutNode = async function (node) {
|
||||
@@ -1361,7 +1423,13 @@ app.registerExtension({
|
||||
? nodesMap
|
||||
: await getCustomnodeMappings('url')
|
||||
|
||||
console.log('node & node map', node, nodesMap, nodesMap[node.type])
|
||||
console.log(
|
||||
'%c### node & node map',
|
||||
'background: yellow; color: black',
|
||||
node,
|
||||
nodesMap,
|
||||
nodesMap[node.type]
|
||||
)
|
||||
let repo = nodesMap[node.type]
|
||||
if (repo) {
|
||||
let markdown = await fetchReadmeContent(repo.url)
|
||||
@@ -1392,6 +1460,7 @@ app.registerExtension({
|
||||
let userInput = widget.value
|
||||
widget.value = widget.value.trim()
|
||||
widget.value += '\n'
|
||||
let jsonStr="";
|
||||
try {
|
||||
await completion_(
|
||||
window._mixlab_llamacpp.url + '/v1/chat/completions',
|
||||
@@ -1406,6 +1475,7 @@ app.registerExtension({
|
||||
t => {
|
||||
// console.log(t)
|
||||
widget.value += t
|
||||
jsonStr+=t
|
||||
}
|
||||
)
|
||||
} catch (error) {
|
||||
@@ -1431,13 +1501,27 @@ app.registerExtension({
|
||||
t => {
|
||||
// console.log(t)
|
||||
widget.value += t
|
||||
jsonStr+=t
|
||||
}
|
||||
)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
widget.value = widget.value.trim()
|
||||
let json=null;
|
||||
|
||||
try {
|
||||
json=JSON.parse(jsonStr.trim())
|
||||
} catch (error) {
|
||||
json=JSON.parse(jsonStr.trim()+"}")
|
||||
}
|
||||
|
||||
if(json){
|
||||
widget.value = [json.subject,json.content,json.style].join('\n')
|
||||
}else{
|
||||
widget.value = widget.value.trim()
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
{
|
||||
"presets": ["@babel/preset-env"],
|
||||
"plugins": [
|
||||
["@babel/plugin-transform-runtime", {
|
||||
"regenerator": true
|
||||
}]
|
||||
]
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
# OS generated files #
|
||||
.DS_Store
|
||||
.DS_Store?
|
||||
._*
|
||||
.Trashes
|
||||
ehthumbs.db
|
||||
Thumbs.db
|
||||
nbproject/
|
||||
.idea/
|
||||
.git/
|
||||
.vscode
|
||||
/.project
|
||||
*.log
|
||||
|
||||
/node_modules/
|
||||
*.js.ignore
|
||||
|
||||
@@ -1,21 +0,0 @@
|
||||
Copyright (c) ViliusL
|
||||
https://github.com/viliusle
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of this software and associated documentation files (the
|
||||
"Software"), to deal in the Software without restriction, including
|
||||
without limitation the rights to use, copy, modify, merge, publish,
|
||||
distribute, sublicense, and/or sell copies of the Software, and to
|
||||
permit persons to whom the Software is furnished to do so, subject to
|
||||
the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be
|
||||
included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
@@ -1,63 +0,0 @@
|
||||
# miniPaint
|
||||
|
||||
Online image editor lets you create, edit images using HTML5 technologies.
|
||||
No need to buy, download, install or have obsolete flash. No ads.
|
||||
Key features: layers, filters, HTML5, open source, Photoshop alternative.
|
||||
|
||||
miniPaint operates directly in the browser. You can create images, paste from the clipboard (ctrl+v)
|
||||
or upload from the computer (using menu or drag & drop). Nothing will be sent to any server. Everything stays in your
|
||||
browser.
|
||||
|
||||
## URL:
|
||||
**https://viliusle.github.io/miniPaint/**
|
||||
|
||||
## Preview:
|
||||

|
||||
(generated using miniPaint)
|
||||
|
||||
**Change log:** [/miniPaint/releases](https://github.com/viliusle/miniPaint/releases)
|
||||
|
||||
## Browser Support
|
||||
- Chrome
|
||||
- Firefox
|
||||
- Opera
|
||||
- Edge
|
||||
- Safari
|
||||
|
||||
## Features
|
||||
|
||||
- **Files**: open images, directories, URL, data URL, drag and drop, save (PNG, JPG, BMP, WEBP, animated GIF, TIFF, JSON
|
||||
(layers data), print.
|
||||
- **Edit**: Undo, cut, copy, paste, selection, paste from clipboard.
|
||||
- **Image**: information, EXIF, trim, zoom, resize (Hermite resample, default resize), rotate, flip,
|
||||
color corrections (brightness, contrast, hue, saturation, luminance), auto adjust colors, grid, histogram, negative.
|
||||
- **Layers**: multiple layers system, differences, merge, flatten, Transparency support.
|
||||
- **Effects**: Black and White, Blur (box, Gaussian, stack, zoom), Bulge/Pinch, Denoise, Desaturate, Dither, Dot Screen,
|
||||
Edge, Emboss, Enrich, Gamma, Grains, GrayScale, Heatmap, JPG Compression, Mosaic, Oil, Sepia, Sharpen, Solarize,
|
||||
Tilt Shift, Vignette, Vibrance, Vintage, Blueprint, Night Vision, Pencil, also Instagram Filters: 1977, Aden, Clarendon,
|
||||
Gingham, Inkwell, Lo-fi, Toaster, Valencia, X-Pro II.
|
||||
- **Tools**: pencil, brush, magic wand, erase, fill, color picker, letters, crop, blur, sharpen, desaturate, clone,
|
||||
borders, sprites, key-points, color zoom, replace color, restore alpha, content fill.
|
||||
- **Help**: keyboard shortcuts, translations.
|
||||
|
||||
## Embed
|
||||
To embed this app in the other page, use this HTML code:
|
||||
|
||||
<iframe style="width:100%; height:1000px;" id="miniPaint" src="https://viliusle.github.io/miniPaint/" allow="camera"></iframe>
|
||||
|
||||
## Build instructions
|
||||
See [Wiki > Build instructions](https://github.com/viliusle/miniPaint/wiki/Build-instructions)
|
||||
|
||||
## Wiki
|
||||
See [Wiki](https://github.com/viliusle/miniPaint/wiki)
|
||||
|
||||
## Contributors
|
||||
<a align="center" href="https://github.com/viliusle/miniPaint/graphs/contributors">
|
||||
<img src="https://contrib.rocks/image?repo=viliusle/miniPaint" />
|
||||
</a>
|
||||
|
||||
## License
|
||||
MIT License
|
||||
|
||||
## Support
|
||||
Please use the GitHub issues for support, features, issues or use mail www.viliusl@gmail.com for contacts.
|
||||
@@ -1,15 +0,0 @@
|
||||
# Security Policy
|
||||
|
||||
## Supported Versions
|
||||
|
||||
Use this section to tell people about which versions of your project are
|
||||
currently being supported with security updates.
|
||||
|
||||
| Version | Supported |
|
||||
| ------- | ------------------ |
|
||||
| latest | :white_check_mark: |
|
||||
| < latest | :x: |
|
||||
|
||||
## Reporting a Vulnerability
|
||||
|
||||
Please send details to www.viliusl@gmail.com
|
||||
@@ -34,7 +34,7 @@
|
||||
content="miniPaint is free online image editor using HTML5. Edit, adjust your images, add effects online in your browser, without installing anything..." />
|
||||
<meta property="og:site_name" content="miniPaint" />
|
||||
|
||||
<script src="dist/bundle.js"></script>
|
||||
<script src="dist/bundle.ejs"></script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
@@ -45,6 +45,15 @@
|
||||
<div class="submenu">
|
||||
<!-- <a class="logo" href="#">miniPaint</a> -->
|
||||
<div class="block attributes" id="action_attributes"></div>
|
||||
|
||||
<select id="automask_models_mixlab"></select>
|
||||
|
||||
<button id="automask_image_mixlab" type="button" style="width: 98px;
|
||||
height: 36px;margin-right: 18px;
|
||||
color: white;">
|
||||
RemoveBg
|
||||
</button>
|
||||
|
||||
<button id="cancel_image_mixlab" type="button" style="width: 98px;
|
||||
height: 36px;
|
||||
color: white;">
|
||||
@@ -55,6 +64,13 @@
|
||||
color: white;">
|
||||
Save
|
||||
</button>
|
||||
|
||||
<a
|
||||
target="_blank" href="https://discord.gg/xbP2GZF6gn"
|
||||
style="width: 98px;
|
||||
color: white;
|
||||
text-decoration: none;"> Help/帮助 </a>
|
||||
|
||||
<button class="undo_button" id="undo_button" type="button">
|
||||
<span class="sr_only">Undo</span>
|
||||
</button>
|
||||
@@ -82,7 +98,7 @@
|
||||
</div>
|
||||
|
||||
<div class="sidebar_right">
|
||||
<div class="preview block">
|
||||
<div class="preview block" style="display: none;">
|
||||
<h2 class="trn toggle" data-target="toggle_preview">Preview</h2>
|
||||
<div id="toggle_preview"></div>
|
||||
</div>
|
||||
@@ -92,7 +108,7 @@
|
||||
<div class="content" id="toggle_colors"></div>
|
||||
</div>
|
||||
|
||||
<div class="block" id="info_base">
|
||||
<div class="block" id="info_base" style="display: none;">
|
||||
<h2 class="trn toggle toggle-full" data-target="toggle_info">Information</h2>
|
||||
<div class="content" id="toggle_info"></div>
|
||||
</div>
|
||||
|
||||
@@ -1,41 +0,0 @@
|
||||
{
|
||||
"name": "miniPaint",
|
||||
"short_name": "miniPaint",
|
||||
"start_url": "/",
|
||||
"display": "standalone",
|
||||
"orientation": "landscape",
|
||||
"background_color": "#666d6f",
|
||||
"description": "miniPaint is free online image editor using HTML5.",
|
||||
"icons": [
|
||||
{
|
||||
"src": "images/manifest/48x48.png",
|
||||
"sizes": "48x48",
|
||||
"type": "image/png"
|
||||
},
|
||||
{
|
||||
"src": "images/manifest/72x72.png",
|
||||
"sizes": "72x72",
|
||||
"type": "image/png"
|
||||
},
|
||||
{
|
||||
"src": "images/manifest/96x96.png",
|
||||
"sizes": "96x96",
|
||||
"type": "image/png"
|
||||
},
|
||||
{
|
||||
"src": "images/manifest/144x144.png",
|
||||
"sizes": "144x144",
|
||||
"type": "image/png"
|
||||
},
|
||||
{
|
||||
"src": "images/manifest/168x168.png",
|
||||
"sizes": "168x168",
|
||||
"type": "image/png"
|
||||
},
|
||||
{
|
||||
"src": "images/manifest/192x192.png",
|
||||
"sizes": "192x192",
|
||||
"type": "image/png"
|
||||
}
|
||||
]
|
||||
}
|
||||
-10750
File diff suppressed because it is too large
Load Diff
@@ -1,51 +0,0 @@
|
||||
{
|
||||
"name": "miniPaint",
|
||||
"version": "4.14.2",
|
||||
"author": "Vilius L.",
|
||||
"description": "Online graphics editing tool lets create, edit images using HTML5 technologies.",
|
||||
"keywords": [
|
||||
"canvas",
|
||||
"drawing",
|
||||
"paint",
|
||||
"layers",
|
||||
"effects"
|
||||
],
|
||||
"scripts": {
|
||||
"server": "webpack serve --mode development --env development --open",
|
||||
"dev": "webpack --mode development",
|
||||
"build": "webpack --mode production"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/viliusle/miniPaint"
|
||||
},
|
||||
"homepage": "https://github.com/viliusle/miniPaint",
|
||||
"license": "MIT",
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.14.5",
|
||||
"@babel/plugin-transform-runtime": "^7.14.5",
|
||||
"@babel/preset-env": "^7.14.5",
|
||||
"babel-loader": "^8.2.2",
|
||||
"css-loader": "^5.2.6",
|
||||
"source-map-loader": "^3.0.0",
|
||||
"style-loader": "^2.0.0",
|
||||
"webpack": "^5.76.0",
|
||||
"webpack-cli": "^4.7.2",
|
||||
"webpack-dev-server": "^4.3.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.14.5",
|
||||
"alertifyjs": "^1.13.1",
|
||||
"blueimp-canvas-to-blob": "^3.28.0",
|
||||
"exif-js": "^2.3.0",
|
||||
"file-saver": "^2.0.5",
|
||||
"fuzzysort": "^1.1.4",
|
||||
"gif.js.optimized": "^1.0.1",
|
||||
"hermite-resize": "git+https://github.com/viliusle/Hermite-resize.git",
|
||||
"jquery": "^3.5.1",
|
||||
"pica": "^7.0.0",
|
||||
"semver-compare": "^1.0.0",
|
||||
"uuid": "^8.3.2",
|
||||
"webfontloader": "^1.6.28"
|
||||
}
|
||||
}
|
||||
@@ -1,68 +0,0 @@
|
||||
|
||||
//IMPORTANT - this file is not used !!!
|
||||
|
||||
|
||||
// use a cacheName for cache versioning
|
||||
var cacheName = 'v1:static';
|
||||
|
||||
// during the install phase you usually want to cache static assets
|
||||
self.addEventListener('install', function(e) {
|
||||
// once the SW is installed, go ahead and fetch the resources to make this work offline
|
||||
e.waitUntil(
|
||||
caches.open(cacheName).then(function(cache) {
|
||||
return cache.addAll([
|
||||
'./',
|
||||
'./dist/bundle.js',
|
||||
'./images/favicon.png',
|
||||
'./images/logo.svg',
|
||||
'./images/logo-colors.png',
|
||||
'./images/icons/animation.svg',
|
||||
'./images/icons/blur.svg',
|
||||
'./images/icons/bold.svg',
|
||||
'./images/icons/brush.svg',
|
||||
'./images/icons/bulge_pinch.svg',
|
||||
'./images/icons/clone.svg',
|
||||
'./images/icons/crop.svg',
|
||||
'./images/icons/delete.svg',
|
||||
'./images/icons/desaturate.svg',
|
||||
'./images/icons/erase.svg',
|
||||
'./images/icons/external.png',
|
||||
'./images/icons/fill.svg',
|
||||
'./images/icons/gradient.png',
|
||||
'./images/icons/grid.png',
|
||||
'./images/icons/italic.svg',
|
||||
'./images/icons/magic_erase.svg',
|
||||
'./images/icons/media.svg',
|
||||
'./images/icons/menu.svg',
|
||||
'./images/icons/pencil.svg',
|
||||
'./images/icons/pick_color.svg',
|
||||
'./images/icons/refresh.svg',
|
||||
'./images/icons/select.svg',
|
||||
'./images/icons/selection.svg',
|
||||
'./images/icons/shape.svg',
|
||||
'./images/icons/sharpen.svg',
|
||||
'./images/icons/strikethrough.svg',
|
||||
'./images/icons/text.svg',
|
||||
'./images/icons/underline.svg',
|
||||
'./images/icons/view.svg'
|
||||
]).then(function() {
|
||||
self.skipWaiting();
|
||||
});
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
// when the browser fetches a url
|
||||
self.addEventListener('fetch', function(event) {
|
||||
// either respond with the cached object or go ahead and fetch the actual url
|
||||
event.respondWith(
|
||||
caches.match(event.request).then(function(response) {
|
||||
if (response) {
|
||||
// retrieve from cache
|
||||
return response;
|
||||
}
|
||||
// fetch as normal
|
||||
return fetch(event.request);
|
||||
})
|
||||
);
|
||||
});
|
||||
@@ -1,56 +0,0 @@
|
||||
var webpack = require('webpack');
|
||||
var path = require('path');
|
||||
|
||||
module.exports = {
|
||||
entry: [
|
||||
'./src/js/main.js',
|
||||
],
|
||||
output: {
|
||||
path: path.resolve(__dirname, 'dist'),
|
||||
filename: 'bundle.js',
|
||||
publicPath: '/dist/'
|
||||
},
|
||||
resolve: {
|
||||
extensions: ['.js', '.css'],
|
||||
alias: {
|
||||
Utilities: path.resolve(__dirname, './../node_modules/')
|
||||
}
|
||||
},
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: /\.css$/,
|
||||
use: [
|
||||
'style-loader',
|
||||
{
|
||||
loader: 'css-loader',
|
||||
options: {url: false}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
test: /\.js$/,
|
||||
exclude: /(node_modules|bower_components)/,
|
||||
use: ['babel-loader']
|
||||
},
|
||||
]
|
||||
},
|
||||
plugins: [
|
||||
new webpack.ProvidePlugin({
|
||||
$: "jquery",
|
||||
jQuery: "jquery",
|
||||
"window.jQuery": "jquery"
|
||||
}),
|
||||
new webpack.DefinePlugin({
|
||||
VERSION: JSON.stringify(require("./package.json").version)
|
||||
}),
|
||||
],
|
||||
devtool: "cheap-module-source-map",
|
||||
devServer: {
|
||||
// host: '0.0.0.0',
|
||||
//contentBase: "./",
|
||||
static: {
|
||||
directory: path.resolve(__dirname, "./"),
|
||||
},
|
||||
}
|
||||
};
|
||||
Reference in New Issue
Block a user