Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cd0dcfbb8c | ||
|
|
fcc9e30eae | ||
|
|
5f66218a43 | ||
|
|
61ef4f9a0f | ||
|
|
f0a8734b42 | ||
|
|
4fe95ef4ec | ||
|
|
2a5148845b | ||
|
|
8fa562caaf | ||
|
|
cab5620cd5 | ||
|
|
be38d36677 | ||
|
|
69236fca89 | ||
|
|
4a4f376bfd | ||
|
|
fd9718fe24 | ||
|
|
26a6e11212 | ||
|
|
de1a669f6e | ||
|
|
e482c9e5c4 | ||
|
|
4e96a77a41 | ||
|
|
3346290e5c | ||
|
|
dcac593efe | ||
|
|
7248d0de02 | ||
|
|
8ad3ce632c | ||
|
|
9398b02562 | ||
|
|
164e4da99d | ||
|
|
0025ea6119 | ||
|
|
eef53a5165 | ||
|
|
9ea066d948 | ||
|
|
4bd900c4a1 | ||
|
|
736cd2bebd | ||
|
|
cd658c2a60 | ||
|
|
9730658f21 | ||
|
|
3fa107acb1 | ||
|
|
7da22179a0 | ||
|
|
fc7b71ee78 | ||
|
|
b79573bf1f | ||
|
|
a01db6f7c0 | ||
|
|
117d58c58e | ||
|
|
ff6626ed89 | ||
|
|
10c798a440 | ||
|
|
9097d87819 | ||
|
|
d901f503d1 | ||
|
|
65f2b6ce6f | ||
|
|
d949fe8bf1 | ||
|
|
15cfb48550 | ||
|
|
447dc6d4c3 |
@@ -1,8 +1,7 @@
|
||||
> 适配了最新版comfyui的py3.11 ,torch 2.1.2+cu121
|
||||
|
||||
> [Mixlab nodes discord](https://discord.gg/cXs9vZSqeK)
|
||||
|
||||
####
|
||||
#### `相关插件推荐`
|
||||
[comfyui-sd-prompt-mixlab](https://github.com/shadowcz007/comfyui-sd-prompt-mixlab)
|
||||
|
||||
[comfyui-Image-reward](https://github.com/shadowcz007/comfyui-Image-reward)
|
||||
@@ -13,6 +12,20 @@
|
||||
|
||||
<!-- [comfyui-CLIPSeg](https://github.com/shadowcz007/comfyui-CLIPSeg) -->
|
||||
|
||||
##### `最新`:
|
||||
ChatGPT节点支持 Local LLM(llama.cpp),Phi3、llama3 都可以直接一个节点运行了。
|
||||
|
||||
Model download,move to :```models/llamafile/```
|
||||
|
||||
强烈推荐:[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)
|
||||
|
||||
|
||||
> 右键菜单支持 text-to-text,方便对prompt词补全
|
||||

|
||||

|
||||
|
||||
|
||||
## 🚀🚗🚚🏃 Workflow-to-APP
|
||||
- 新增AppInfo节点,可以通过简单的配置,把workflow转变为一个Web APP。
|
||||
@@ -79,13 +92,41 @@ https://github.com/shadowcz007/comfyui-mixlab-nodes/assets/12645064/e7e77f90-e43
|
||||
[Voice + Real-time Face Swap Workflow](./workflow/语音+实时换脸workflow.json)
|
||||
|
||||
### GPT
|
||||
> Support for calling multiple GPTs.ChatGPT、ChatGLM3 、ChatGLM4 , Some code provided by rui. If you are using OpenAI's service, fill in https://api.openai.com/v1 . If you are using a local LLM service, fill in http://127.0.0.1:xxxx/v1 . Azure OpenAI:https://xxxx.openai.azure.com
|
||||
> Support for calling multiple GPTs.Local LLM(llama.cpp)、 ChatGPT、ChatGLM3 、ChatGLM4 , Some code provided by rui. If you are using OpenAI's service, fill in https://api.openai.com/v1 . If you are using a local LLM service, fill in http://127.0.0.1:xxxx/v1 . Azure OpenAI:https://xxxx.openai.azure.com
|
||||
|
||||

|
||||
|
||||
[workflow-5](./workflow/5-gpt-workflow.json)
|
||||
|
||||
|
||||
最新:ChatGPT节点支持 Local LLM(llama.cpp),Phi3、llama3 都可以直接一个节点运行了。
|
||||
|
||||
Model download,move to :```models/llamafile/```
|
||||
|
||||
强烈推荐:[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)
|
||||
|
||||
|
||||
> 如果碰到安装失败,可以尝试手动安装
|
||||
```
|
||||
../../../python_embeded/python.exe -s -m pip install llama-cpp-python --extra-index-url https://abetlen.github.io/llama-cpp-python/whl/cu121
|
||||
|
||||
../../../python_embeded/python.exe -s -m pip install llama-cpp-python[server]
|
||||
|
||||
```
|
||||
|
||||
> [Mac](https://llama-cpp-python.readthedocs.io/en/latest/install/macos/)
|
||||
```
|
||||
pip uninstall llama-cpp-python -y
|
||||
CMAKE_ARGS="-DLLAMA_METAL=on" pip install -U llama-cpp-python --no-cache-dir
|
||||
pip install 'llama-cpp-python[server]'
|
||||
```
|
||||
```
|
||||
pip install llama-cpp-python \
|
||||
--extra-index-url https://abetlen.github.io/llama-cpp-python/whl/metal
|
||||
```
|
||||
|
||||
## Prompt
|
||||
> PromptSlide
|
||||

|
||||
|
||||
+200
-43
@@ -7,9 +7,27 @@ import urllib
|
||||
import hashlib
|
||||
import datetime
|
||||
import folder_paths
|
||||
|
||||
import logging
|
||||
from comfy.cli_args import args
|
||||
python = sys.executable
|
||||
|
||||
#修复 sys.stdout.isatty() object has no attribute 'isatty'
|
||||
try:
|
||||
sys.stdout.isatty()
|
||||
except:
|
||||
print('#fix sys.stdout.isatty')
|
||||
sys.stdout.isatty = lambda: False
|
||||
|
||||
llama_port=None
|
||||
llama_model=""
|
||||
|
||||
try:
|
||||
from .nodes.ChatGPT import get_llama_models,get_llama_model_path,llama_cpp_client
|
||||
llama_cpp_client("")
|
||||
|
||||
except:
|
||||
print("##nodes.ChatGPT ImportError")
|
||||
|
||||
|
||||
from server import PromptServer
|
||||
|
||||
@@ -42,7 +60,7 @@ def is_installed(package, package_overwrite=None):
|
||||
print(f"Couldn't install\nCommand: {command}\nError code: {result.returncode}")
|
||||
else:
|
||||
print(package+'## OK')
|
||||
|
||||
|
||||
try:
|
||||
import OpenSSL
|
||||
except ImportError:
|
||||
@@ -414,37 +432,86 @@ async def new_start(self, address, port, verbose=True, call_on_start=None):
|
||||
runner = web.AppRunner(self.app, access_log=None)
|
||||
await runner.setup()
|
||||
|
||||
if not await check_port_available(address, port):
|
||||
raise RuntimeError(f"Port {port} is already in use.")
|
||||
# if not await check_port_available(address, port):
|
||||
# raise RuntimeError(f"Port {port} is already in use.")
|
||||
|
||||
http_success = False
|
||||
http_port=port
|
||||
for i in range(11): # 尝试最多11次
|
||||
if await check_port_available(address, port + i):
|
||||
http_port = port + i
|
||||
site = web.TCPSite(runner, address, http_port)
|
||||
await site.start()
|
||||
http_success = True
|
||||
break
|
||||
|
||||
site = web.TCPSite(runner, address, port)
|
||||
await site.start()
|
||||
if not http_success:
|
||||
raise RuntimeError(f"Ports {port} to {port + 10} are all in use.")
|
||||
|
||||
|
||||
# site = web.TCPSite(runner, address, port)
|
||||
# await site.start()
|
||||
|
||||
ssl_context = None
|
||||
scheme = "http"
|
||||
try:
|
||||
# 跟着本体修改
|
||||
if args.tls_keyfile and args.tls_certfile:
|
||||
scheme = "https"
|
||||
ssl_context = ssl.SSLContext(protocol=ssl.PROTOCOL_TLS_SERVER, verify_mode=ssl.CERT_NONE)
|
||||
ssl_context.load_cert_chain(certfile=args.tls_certfile,
|
||||
keyfile=args.tls_keyfile)
|
||||
else:
|
||||
# 如果没传,则自动创建
|
||||
import ssl
|
||||
crt, key = create_for_https()
|
||||
ssl_context = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH)
|
||||
ssl_context.load_cert_chain(crt, key)
|
||||
except:
|
||||
import ssl
|
||||
crt, key = create_for_https()
|
||||
ssl_context = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH)
|
||||
ssl_context.load_cert_chain(crt, key)
|
||||
|
||||
import ssl
|
||||
crt, key = create_for_https()
|
||||
ssl_context = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH)
|
||||
ssl_context.load_cert_chain(crt, key)
|
||||
|
||||
success = False
|
||||
for i in range(10): # 尝试最多10次
|
||||
if await check_port_available(address, port + 1 + i):
|
||||
https_port = port + 1 + i
|
||||
for i in range(11): # 尝试最多11次
|
||||
if await check_port_available(address, http_port + 1 + i):
|
||||
https_port = http_port + 1 + i
|
||||
site2 = web.TCPSite(runner, address, https_port, ssl_context=ssl_context)
|
||||
await site2.start()
|
||||
success = True
|
||||
break
|
||||
|
||||
if not success:
|
||||
raise RuntimeError(f"Ports {port + 1} to {port + 10} are all in use.")
|
||||
raise RuntimeError(f"Ports {http_port + 1} to {http_port + 10} are all in use.")
|
||||
|
||||
if address == '':
|
||||
address = '0.0.0.0'
|
||||
address = '127.0.0.1'
|
||||
if address=='0.0.0.0':
|
||||
address = '127.0.0.1'
|
||||
|
||||
if verbose:
|
||||
print("\033[93mStarting server\n")
|
||||
print("\033[93mTo see the GUI go to: http://{}:{}".format(address, port))
|
||||
print("\033[93mTo see the GUI go to: https://{}:{}\033[0m".format(address, https_port))
|
||||
|
||||
logging.info("\n")
|
||||
logging.info("\n\nStarting server")
|
||||
|
||||
# print("\033[93mStarting server\n")
|
||||
logging.info("\033[93mTo see the GUI go to: http://{}:{}".format(address, http_port))
|
||||
logging.info("\033[93mTo see the GUI go to: https://{}:{}\033[0m".format(address, https_port))
|
||||
|
||||
# print("\033[93mTo see the GUI go to: http://{}:{}".format(address, http_port))
|
||||
# print("\033[93mTo see the GUI go to: https://{}:{}\033[0m".format(address, https_port))
|
||||
|
||||
if call_on_start is not None:
|
||||
call_on_start(address, port)
|
||||
try:
|
||||
if scheme=='https':
|
||||
call_on_start(scheme,address, https_port)
|
||||
else:
|
||||
call_on_start(scheme,address, http_port)
|
||||
except:
|
||||
call_on_start(address,http_port)
|
||||
|
||||
|
||||
except Exception as e:
|
||||
print(f"Error starting the server: {e}")
|
||||
@@ -455,7 +522,6 @@ async def new_start(self, address, port, verbose=True, call_on_start=None):
|
||||
# webbrowser.open(f"https://{address}")
|
||||
# webbrowser.open(f"http://{address}:{port}")
|
||||
|
||||
|
||||
PromptServer.start=new_start
|
||||
|
||||
# 创建路由表
|
||||
@@ -550,13 +616,19 @@ async def nodes_map_hander(request):
|
||||
async def get_checkpoints(request):
|
||||
data = await request.json()
|
||||
t="checkpoints"
|
||||
names=[]
|
||||
try:
|
||||
t=data['type']
|
||||
names = folder_paths.get_filename_list(t)
|
||||
except Exception as e:
|
||||
print('/mixlab/folder_paths',False,e)
|
||||
|
||||
names = folder_paths.get_filename_list(t)
|
||||
|
||||
try:
|
||||
if data['type']=='llamafile':
|
||||
names=get_llama_models()
|
||||
except:
|
||||
print("llamafile none")
|
||||
|
||||
return web.json_response({"names":names,"types":list(folder_paths.folder_names_and_paths.keys())})
|
||||
|
||||
|
||||
@@ -577,15 +649,98 @@ async def post_prompt_result(request):
|
||||
|
||||
return web.json_response({"result":res})
|
||||
|
||||
# 扩展api接口
|
||||
# from server import PromptServer
|
||||
# from aiohttp import web
|
||||
|
||||
# @routes.post('/ws_image')
|
||||
# async def my_hander_method(request):
|
||||
# post = await request.post()
|
||||
# x = post.get("something")
|
||||
# return web.json_response({})
|
||||
async def start_local_llm(data):
|
||||
global llama_port,llama_model
|
||||
if llama_port and llama_model:
|
||||
return {"port":llama_port,"model":llama_model}
|
||||
|
||||
import threading
|
||||
import uvicorn
|
||||
from llama_cpp.server.app import create_app
|
||||
from llama_cpp.server.settings import (
|
||||
Settings,
|
||||
ServerSettings,
|
||||
ModelSettings,
|
||||
ConfigFileSettings,
|
||||
)
|
||||
|
||||
if not "model" in data and "model_path" in data:
|
||||
data['model']= os.path.basename(data["model_path"])
|
||||
model=data["model_path"]
|
||||
|
||||
elif "model" in data:
|
||||
model=get_llama_model_path(data['model'])
|
||||
|
||||
|
||||
address="127.0.0.1"
|
||||
port=9090
|
||||
success = False
|
||||
for i in range(11): # 尝试最多11次
|
||||
if await check_port_available(address, port + i):
|
||||
port = port + i
|
||||
success = True
|
||||
break
|
||||
|
||||
if success == False:
|
||||
return {"port":None,"model":""}
|
||||
|
||||
|
||||
server_settings=ServerSettings(host=address,port=port)
|
||||
|
||||
app = create_app(
|
||||
server_settings=server_settings,
|
||||
model_settings=[
|
||||
ModelSettings(
|
||||
model=model,
|
||||
n_gpu_layers=-1,
|
||||
n_ctx=4098,
|
||||
chat_format="chatml"
|
||||
)])
|
||||
|
||||
def run_uvicorn():
|
||||
uvicorn.run(
|
||||
app,
|
||||
host=os.getenv("HOST", server_settings.host),
|
||||
port=int(os.getenv("PORT", server_settings.port)),
|
||||
ssl_keyfile=server_settings.ssl_keyfile,
|
||||
ssl_certfile=server_settings.ssl_certfile,
|
||||
)
|
||||
|
||||
# 创建一个子线程
|
||||
thread = threading.Thread(target=run_uvicorn)
|
||||
|
||||
# 启动子线程
|
||||
thread.start()
|
||||
|
||||
llama_port=port
|
||||
llama_model=data['model']
|
||||
|
||||
return {"port":llama_port,"model":llama_model}
|
||||
|
||||
# llam服务的开启
|
||||
@routes.post('/mixlab/start_llama')
|
||||
async def my_hander_method(request):
|
||||
data =await request.json()
|
||||
# print(data)
|
||||
try:
|
||||
result=await start_local_llm(data)
|
||||
except:
|
||||
result={
|
||||
{"port":None,"model":"","llama_cpp_error":True}
|
||||
}
|
||||
print('start_local_llm error')
|
||||
|
||||
return web.json_response(result)
|
||||
|
||||
# parser.add_argument("--llm-model", type=str, help="Path to Model file (gguf). Run the Local LLM by MixlabNodes")
|
||||
# if args.llm_model and os.path.exists(args.llm_model):
|
||||
# start_local_llm({
|
||||
# "model_path":args.llm_model
|
||||
# })
|
||||
# print("Local LLM Start")
|
||||
|
||||
|
||||
|
||||
|
||||
# 导入节点
|
||||
@@ -713,7 +868,7 @@ NODE_DISPLAY_NAME_MAPPINGS = {
|
||||
"VAEDecodeConsistencyDecoder":"Consistency Decoder Decode",
|
||||
"ScreenShare":"Screen Share ♾️Mixlab",
|
||||
"FloatingVideo":"FloatingVideo ♾️Mixlab",
|
||||
"ChatGPTOpenAI":"ChatGPT ♾️Mixlab",
|
||||
"ChatGPTOpenAI":"ChatGPT & Local LLM ♾️Mixlab",
|
||||
"ShowTextForGPT":"Show Text ♾️MixlabApp",
|
||||
"MergeLayers":"Merge Layers ♾️Mixlab",
|
||||
"SpeechSynthesis":"SpeechSynthesis ♾️Mixlab",
|
||||
@@ -756,42 +911,44 @@ NODE_DISPLAY_NAME_MAPPINGS = {
|
||||
# web ui的节点功能
|
||||
WEB_DIRECTORY = "./web"
|
||||
|
||||
print('--------------')
|
||||
print('\033[91m ### Mixlab Nodes: \033[93mLoaded')
|
||||
|
||||
logging.info('--------------')
|
||||
logging.info('\033[91m ### Mixlab Nodes: \033[93mLoaded')
|
||||
# print('\033[91m ### Mixlab Nodes: \033[93mLoaded')
|
||||
|
||||
try:
|
||||
from .nodes.Lama import LaMaInpainting
|
||||
print('LaMaInpainting.available',LaMaInpainting.available)
|
||||
logging.info('LaMaInpainting.available {}'.format(LaMaInpainting.available))
|
||||
if LaMaInpainting.available:
|
||||
NODE_CLASS_MAPPINGS['LaMaInpainting']=LaMaInpainting
|
||||
except Exception as e:
|
||||
print('LaMaInpainting.available',False,e)
|
||||
logging.info('LaMaInpainting.available False')
|
||||
|
||||
try:
|
||||
from .nodes.ClipInterrogator import ClipInterrogator
|
||||
print('ClipInterrogator.available',ClipInterrogator.available)
|
||||
logging.info('ClipInterrogator.available {}'.format(ClipInterrogator.available))
|
||||
if ClipInterrogator.available:
|
||||
NODE_CLASS_MAPPINGS['ClipInterrogator']=ClipInterrogator
|
||||
except Exception as e:
|
||||
print('ClipInterrogator.available',False,e)
|
||||
logging.info('ClipInterrogator.available False')
|
||||
|
||||
try:
|
||||
from .nodes.TextGenerateNode import PromptGenerate,ChinesePrompt
|
||||
print('PromptGenerate.available',PromptGenerate.available)
|
||||
logging.info('PromptGenerate.available {}'.format(PromptGenerate.available))
|
||||
if PromptGenerate.available:
|
||||
NODE_CLASS_MAPPINGS['PromptGenerate_Mix']=PromptGenerate
|
||||
print('ChinesePrompt.available',ChinesePrompt.available)
|
||||
logging.info('ChinesePrompt.available {}'.format(ChinesePrompt.available))
|
||||
if ChinesePrompt.available:
|
||||
NODE_CLASS_MAPPINGS['ChinesePrompt_Mix']=ChinesePrompt
|
||||
except Exception as e:
|
||||
print('TextGenerateNode.available',False,e)
|
||||
logging.info('TextGenerateNode.available False')
|
||||
|
||||
try:
|
||||
from .nodes.RembgNode import RembgNode_
|
||||
print('RembgNode_.available',RembgNode_.available)
|
||||
logging.info('RembgNode_.available {}'.format(RembgNode_.available))
|
||||
if RembgNode_.available:
|
||||
NODE_CLASS_MAPPINGS['RembgNode_Mix']=RembgNode_
|
||||
except Exception as e:
|
||||
print('RembgNode_.available',False,e)
|
||||
logging.info('RembgNode_.available False' )
|
||||
|
||||
print('\033[93m -------------- \033[0m')
|
||||
logging.info('\033[93m -------------- \033[0m')
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 75 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 63 KiB |
@@ -10,6 +10,12 @@ if exist "%python_exec%" (
|
||||
for /f "delims=" %%i in (%requirements_txt%) do (
|
||||
%python_exec% -s -m pip install "%%i" -i https://pypi.tuna.tsinghua.edu.cn/simple
|
||||
)
|
||||
|
||||
%python_exec% -s -m pip install --upgrade --force llama-cpp-python --extra-index-url https://abetlen.github.io/llama-cpp-python/whl/cu121
|
||||
|
||||
%python_exec% -s -m pip install --upgrade --force llama-cpp-python[server]
|
||||
|
||||
|
||||
) else (
|
||||
echo Installing with system Python
|
||||
for /f "delims=" %%i in (%requirements_txt%) do (
|
||||
|
||||
+109
-17
@@ -87,16 +87,102 @@ def ZhipuAI_client(key):
|
||||
return client
|
||||
|
||||
|
||||
# 优先使用phi
|
||||
def phi_sort(lst):
|
||||
return sorted(lst, key=lambda x: x.lower().count('phi'), reverse=True)
|
||||
|
||||
def get_llama_path():
|
||||
try:
|
||||
return folder_paths.get_folder_paths('llamafile')[0]
|
||||
except:
|
||||
return os.path.join(folder_paths.models_dir, "llamafile")
|
||||
|
||||
def get_llama_models():
|
||||
res=[]
|
||||
|
||||
model_path=get_llama_path()
|
||||
if os.path.exists(model_path):
|
||||
files = os.listdir(model_path)
|
||||
for file in files:
|
||||
if os.path.isfile(os.path.join(model_path, file)):
|
||||
res.append(file)
|
||||
res=phi_sort(res)
|
||||
return res
|
||||
|
||||
llama_modes_list=get_llama_models()
|
||||
|
||||
def get_llama_model_path(file_name):
|
||||
model_path=get_llama_path()
|
||||
mp=os.path.join(model_path,file_name)
|
||||
return mp
|
||||
|
||||
def llama_cpp_client(file_name):
|
||||
try:
|
||||
if is_installed('llama_cpp')==False:
|
||||
import subprocess
|
||||
|
||||
# 安装
|
||||
print('#pip install llama-cpp-python')
|
||||
|
||||
result = subprocess.run([sys.executable, '-s', '-m', 'pip',
|
||||
'install',
|
||||
'llama-cpp-python',
|
||||
'--extra-index-url',
|
||||
'https://abetlen.github.io/llama-cpp-python/whl/cu121'
|
||||
], capture_output=True, text=True)
|
||||
|
||||
#检查命令执行结果
|
||||
if result.returncode == 0:
|
||||
print("#install success")
|
||||
from llama_cpp import Llama
|
||||
|
||||
subprocess.run([sys.executable, '-s', '-m', 'pip',
|
||||
'install',
|
||||
'llama-cpp-python[server]'
|
||||
], capture_output=True, text=True)
|
||||
|
||||
else:
|
||||
print("#install error")
|
||||
|
||||
else:
|
||||
from llama_cpp import Llama
|
||||
except:
|
||||
print("#install llama-cpp-python error")
|
||||
|
||||
if file_name:
|
||||
mp=get_llama_model_path(file_name)
|
||||
# file_name=get_llama_models()[0]
|
||||
# model_path=os.path.join(folder_paths.models_dir, "llamafile")
|
||||
# mp=os.path.join(model_path,file_name)
|
||||
|
||||
llm = Llama(model_path=mp, chat_format="chatml",n_gpu_layers=-1,n_ctx=512)
|
||||
|
||||
return llm
|
||||
|
||||
|
||||
|
||||
|
||||
def chat(client, model_name,messages ):
|
||||
|
||||
try_count = 0
|
||||
while True:
|
||||
try_count += 1
|
||||
try:
|
||||
response = client.chat.completions.create(
|
||||
model=model_name,
|
||||
messages=messages
|
||||
)
|
||||
if hasattr(client, "chat"):
|
||||
response = client.chat.completions.create(
|
||||
model=model_name,
|
||||
messages=messages
|
||||
)
|
||||
else:
|
||||
# 是llama的
|
||||
response = client.create_chat_completion_openai_v1(
|
||||
messages=messages,
|
||||
# response_format={
|
||||
# "type": "json_object",
|
||||
# },
|
||||
# temperature=0.7,
|
||||
)
|
||||
|
||||
break
|
||||
except openai.AuthenticationError as ex:
|
||||
raise ex
|
||||
@@ -105,7 +191,8 @@ def chat(client, model_name,messages ):
|
||||
raise ex
|
||||
time.sleep(3)
|
||||
continue
|
||||
|
||||
|
||||
# print(response.keys())
|
||||
finish_reason = response.choices[0].finish_reason
|
||||
if finish_reason != "stop":
|
||||
raise RuntimeError("API finished with unexpected reason: " + finish_reason)
|
||||
@@ -128,6 +215,16 @@ class ChatGPTNode:
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
model_list=llama_modes_list+[
|
||||
"gpt-3.5-turbo",
|
||||
"gpt-3.5-turbo-0125",
|
||||
"gpt-35-turbo",
|
||||
"gpt-3.5-turbo-16k",
|
||||
"gpt-3.5-turbo-16k-0613",
|
||||
"gpt-4-0613",
|
||||
"gpt-4-1106-preview",
|
||||
"glm-4"
|
||||
]
|
||||
return {
|
||||
"required": {
|
||||
"api_key":("KEY", {"default": "", "multiline": True,"dynamicPrompts": False}),
|
||||
@@ -138,16 +235,8 @@ class ChatGPTNode:
|
||||
"default": "You are ChatGPT, a large language model trained by OpenAI. Answer as concisely as possible.",
|
||||
"multiline": True,"dynamicPrompts": False
|
||||
}),
|
||||
"model": ([
|
||||
"gpt-3.5-turbo",
|
||||
"gpt-3.5-turbo-0125",
|
||||
"gpt-35-turbo",
|
||||
"gpt-3.5-turbo-16k",
|
||||
"gpt-3.5-turbo-16k-0613",
|
||||
"gpt-4-0613",
|
||||
"gpt-4-1106-preview",
|
||||
"glm-4"],
|
||||
{"default": "gpt-3.5-turbo"}),
|
||||
"model": ( model_list,
|
||||
{"default": model_list[0]}),
|
||||
"seed": ("INT", {"default": 0, "min": 0, "max": 0xffffffffffffffff, "step": 1}),
|
||||
"context_size":("INT", {"default": 1, "min": 0, "max":30, "step": 1}),
|
||||
},
|
||||
@@ -170,8 +259,8 @@ class ChatGPTNode:
|
||||
api_url,
|
||||
prompt,
|
||||
system_content,
|
||||
model,
|
||||
seed,context_size,unique_id = None, extra_pnginfo=None):
|
||||
model,
|
||||
seed,context_size,unique_id = None, extra_pnginfo=None):
|
||||
# print(api_key!='',api_url,prompt,system_content,model,seed)
|
||||
# 可以选择保留会话历史以维持上下文记忆
|
||||
# 或者在此处清除会话历史 self.session_history.clear()
|
||||
@@ -193,6 +282,9 @@ class ChatGPTNode:
|
||||
if model == "glm-4" :
|
||||
client = ZhipuAI_client(api_key) # 使用 Zhipuai 的接口
|
||||
print('using Zhipuai interface')
|
||||
elif model in llama_modes_list:
|
||||
#
|
||||
client=llama_cpp_client(model)
|
||||
else :
|
||||
client = openai_client(api_key,api_url) # 使用 ChatGPT 的接口
|
||||
print('using ChatGPT interface')
|
||||
|
||||
@@ -70,14 +70,20 @@ def load_caption_model(model_path,config,t='blip-base'):
|
||||
return (caption_model,caption_processor)
|
||||
|
||||
|
||||
def get_clip_interrogator_path():
|
||||
try:
|
||||
return folder_paths.get_folder_paths('clip_interrogator')[0]
|
||||
except:
|
||||
return os.path.join(folder_paths.models_dir, "clip_interrogator")
|
||||
|
||||
caption_model_path=os.path.join(folder_paths.models_dir, "clip_interrogator/Salesforce/blip-image-captioning-base")
|
||||
|
||||
cache_path=get_clip_interrogator_path()
|
||||
|
||||
caption_model_path=os.path.join(cache_path, "Salesforce/blip-image-captioning-base")
|
||||
if not os.path.exists(caption_model_path):
|
||||
print(f"## clip_interrogator_model not found: {caption_model_path}, pls download from https://huggingface.co/Salesforce/blip-image-captioning-base")
|
||||
caption_model_path='Salesforce/blip-image-captioning-base'
|
||||
|
||||
cache_path=os.path.join(folder_paths.models_dir, "clip_interrogator")
|
||||
|
||||
|
||||
# Tensor to PIL
|
||||
def tensor2pil(image):
|
||||
|
||||
+189
-37
@@ -29,23 +29,122 @@ def opencv_to_pil(image):
|
||||
return pil_image
|
||||
|
||||
|
||||
def composite_images(foreground, background, mask):
|
||||
def composite_images(foreground, background, mask,is_multiply_blend=False,position="overall"):
|
||||
width,height=foreground.size
|
||||
|
||||
bg_image=background
|
||||
|
||||
bwidth,bheight=bg_image.size
|
||||
|
||||
# 按z-index排序
|
||||
layer = {
|
||||
"x":0,
|
||||
"y":0,
|
||||
"width":width,
|
||||
"height":height,
|
||||
"z_index":88,
|
||||
"scale_option":'overall',
|
||||
"image":foreground,
|
||||
"mask":mask
|
||||
}
|
||||
if position=="overall":
|
||||
layer = {
|
||||
"x":0,
|
||||
"y":0,
|
||||
"width":bwidth,
|
||||
"height":bheight,
|
||||
"z_index":88,
|
||||
"scale_option":'overall',
|
||||
"image":foreground,
|
||||
"mask":mask
|
||||
}
|
||||
|
||||
elif position=='center_bottom':
|
||||
|
||||
scale = int(bwidth*0.25) / width
|
||||
new_height = int(height * scale)
|
||||
|
||||
layer = {
|
||||
"x":int(bwidth*0.75*0.5),
|
||||
"y":bheight-new_height-24,
|
||||
"width":int(bwidth*0.25),
|
||||
"height":int(bheight*0.25),
|
||||
"z_index":88,
|
||||
"scale_option":'width',
|
||||
"image":foreground,
|
||||
"mask":mask
|
||||
}
|
||||
|
||||
elif position=='right_bottom':
|
||||
|
||||
scale = int(bwidth*0.25) / width
|
||||
new_height = int(height * scale)
|
||||
|
||||
layer = {
|
||||
"x":bwidth-int(bwidth*0.25)-24,
|
||||
"y":bheight-new_height-24,
|
||||
"width":int(bwidth*0.25),
|
||||
"height":int(bheight*0.25),
|
||||
"z_index":88,
|
||||
"scale_option":'width',
|
||||
"image":foreground,
|
||||
"mask":mask
|
||||
}
|
||||
|
||||
|
||||
elif position=='center_top':
|
||||
|
||||
scale = int(bwidth*0.25) / width
|
||||
new_height = int(height * scale)
|
||||
|
||||
layer = {
|
||||
"x":int( bwidth*0.75*0.5),
|
||||
"y":24,
|
||||
"width":int(bwidth*0.25),
|
||||
"height":int(bheight*0.25),
|
||||
"z_index":88,
|
||||
"scale_option":'width',
|
||||
"image":foreground,
|
||||
"mask":mask
|
||||
}
|
||||
|
||||
elif position=='right_top':
|
||||
|
||||
scale = int(bwidth*0.25) / width
|
||||
new_height = int(height * scale)
|
||||
|
||||
layer = {
|
||||
"x":bwidth-int(bwidth*0.25)-24,
|
||||
"y":24,
|
||||
"width":int(bwidth*0.25),
|
||||
"height":int(bheight*0.25),
|
||||
"z_index":88,
|
||||
"scale_option":'width',
|
||||
"image":foreground,
|
||||
"mask":mask
|
||||
}
|
||||
elif position=='left_top':
|
||||
|
||||
scale = int(bwidth*0.25) / width
|
||||
new_height = int(height * scale)
|
||||
|
||||
layer = {
|
||||
"x":24,
|
||||
"y":24,
|
||||
"width":int(bwidth*0.25),
|
||||
"height":int(bheight*0.25),
|
||||
"z_index":88,
|
||||
"scale_option":'width',
|
||||
"image":foreground,
|
||||
"mask":mask
|
||||
}
|
||||
elif position=='left_bottom':
|
||||
|
||||
scale = int(bwidth*0.25) / width
|
||||
new_height = int(height * scale)
|
||||
|
||||
layer = {
|
||||
"x":24,
|
||||
"y":bheight-new_height-24,
|
||||
"width":int(bwidth*0.25),
|
||||
"height":int(bheight*0.25),
|
||||
"z_index":88,
|
||||
"scale_option":'width',
|
||||
"image":foreground,
|
||||
"mask":mask
|
||||
}
|
||||
|
||||
width, height = bg_image.size
|
||||
# width, height = bg_image.size
|
||||
|
||||
layer_image=layer['image']
|
||||
layer_mask=layer['mask']
|
||||
@@ -53,12 +152,12 @@ def composite_images(foreground, background, mask):
|
||||
bg_image=merge_images(bg_image,
|
||||
layer_image,
|
||||
layer_mask,
|
||||
layer['x'],
|
||||
layer['y'],
|
||||
layer['width'],
|
||||
layer['height'],
|
||||
layer['scale_option']
|
||||
)
|
||||
layer['x'],
|
||||
layer['y'],
|
||||
layer['width'],
|
||||
layer['height'],
|
||||
layer['scale_option'],
|
||||
is_multiply_blend )
|
||||
|
||||
bg_image=bg_image.convert('RGB')
|
||||
|
||||
@@ -704,7 +803,6 @@ def multiply_blend(image1, image2):
|
||||
# cv2.imwrite('result.jpg', result)
|
||||
|
||||
|
||||
|
||||
def merge_images(bg_image, layer_image, mask, x, y, width, height, scale_option,is_multiply_blend=False):
|
||||
# 打开底图
|
||||
bg_image = bg_image.convert("RGBA")
|
||||
@@ -730,21 +828,42 @@ def merge_images(bg_image, layer_image, mask, x, y, width, height, scale_option,
|
||||
# 整体缩放
|
||||
layer_image = layer_image.resize((width, height))
|
||||
|
||||
elif scale_option == "longest":
|
||||
original_width, original_height = layer_image.size
|
||||
if original_width > original_height:
|
||||
new_width=width
|
||||
scale = width / original_width
|
||||
new_height = int(original_height * scale)
|
||||
x=0
|
||||
y=int((height-new_height)*0.5)
|
||||
else:
|
||||
new_height=height
|
||||
scale = height / original_height
|
||||
new_width = int(original_height * scale)
|
||||
x=int((width-new_width)*0.5)
|
||||
y=0
|
||||
# elif side == "shortest":
|
||||
# if width < height:
|
||||
#
|
||||
# else:
|
||||
#
|
||||
|
||||
|
||||
# 调整mask的大小
|
||||
nw, nh = layer_image.size
|
||||
mask = mask.resize((nw, nh))
|
||||
|
||||
# 分离出a通道
|
||||
r, g, b, alpha = layer_image.split()
|
||||
alpha = ImageOps.invert(alpha)
|
||||
# 创建一个新的RGB图像
|
||||
new_rgb_image = Image.new("RGB", layer_image.size)
|
||||
# 将透明通道粘贴到新的RGB图像上
|
||||
new_rgb_image.paste(layer_image, (0, 0), mask=alpha)
|
||||
# # 分离出a通道
|
||||
# r, g, b, alpha = layer_image.split()
|
||||
# alpha = ImageOps.invert(alpha)
|
||||
# # 创建一个新的RGB图像
|
||||
# new_rgb_image = Image.new("RGB", layer_image.size)
|
||||
# # 将透明通道粘贴到新的RGB图像上
|
||||
# new_rgb_image.paste(layer_image, (0, 0), mask=alpha)
|
||||
|
||||
new_rgb_image.paste(layer_image, (x, y), mask=mask)
|
||||
mask=new_rgb_image.convert('L')
|
||||
mask = ImageOps.invert(mask)
|
||||
# new_rgb_image.paste(layer_image, (x, y), mask=mask)
|
||||
# mask=new_rgb_image.convert('L')
|
||||
# mask = ImageOps.invert(mask)
|
||||
|
||||
if is_multiply_blend:
|
||||
bg_image_white=Image.new("RGB", bg_image.size,(255, 255, 255))
|
||||
@@ -753,8 +872,11 @@ def merge_images(bg_image, layer_image, mask, x, y, width, height, scale_option,
|
||||
bg_image=multiply_blend(bg_image_white,bg_image)
|
||||
bg_image=bg_image.convert("RGBA")
|
||||
else:
|
||||
# 在底图上粘贴图层
|
||||
bg_image.paste(layer_image, (x, y), mask=mask)
|
||||
transparent_img = Image.new("RGBA",layer_image.size, (255, 255, 255, 0))
|
||||
transparent_img.paste(layer_image,(0, 0), mask)
|
||||
# transparent_img.save('test.png')
|
||||
bg_image.paste(transparent_img, (x, y), transparent_img)
|
||||
|
||||
|
||||
# 输出合成后的图片
|
||||
return bg_image
|
||||
@@ -802,6 +924,31 @@ def resize_image(layer_image, scale_option, width, height,color="white"):
|
||||
resized_image = resized_image.convert("RGB")
|
||||
resized_image=resize_2(resized_image)
|
||||
return resized_image
|
||||
elif scale_option == "longest":
|
||||
#暂时不用,
|
||||
if original_width > original_height:
|
||||
new_width=width
|
||||
scale = width / original_width
|
||||
new_height = int(original_height * scale)
|
||||
x=0
|
||||
y=int((new_height-height)*0.5)
|
||||
resized_image = Image.new("RGB", (new_width, new_height), color=color)
|
||||
resized_image.paste(layer_image.resize((new_width, new_height)), (x,y))
|
||||
resized_image = resized_image.convert("RGB")
|
||||
resized_image=resize_2(resized_image)
|
||||
return resized_image
|
||||
else:
|
||||
new_height=height
|
||||
scale = height / original_height
|
||||
new_width = int(original_height * scale)
|
||||
x=int((new_width-width)*0.5)
|
||||
y=0
|
||||
resized_image = Image.new("RGB", (new_width, new_height), color=color)
|
||||
resized_image.paste(layer_image.resize((new_width, new_height)), (x,y))
|
||||
resized_image = resized_image.convert("RGB")
|
||||
resized_image=resize_2(resized_image)
|
||||
return resized_image
|
||||
|
||||
|
||||
layer_image=resize_2(layer_image)
|
||||
return layer_image
|
||||
@@ -1703,10 +1850,15 @@ class CompositeImages:
|
||||
def INPUT_TYPES(s):
|
||||
return {
|
||||
"required": {
|
||||
"foreground": ("IMAGE",),
|
||||
"mask":("MASK",),
|
||||
"background": ("IMAGE",),
|
||||
},
|
||||
"foreground": (any_type,),
|
||||
"mask":("MASK",),
|
||||
"background": ("IMAGE",),
|
||||
},
|
||||
"optional":{
|
||||
|
||||
"is_multiply_blend": ("BOOLEAN", {"default": False}),
|
||||
"position": (['overall',"center_bottom","center_top","right_bottom","left_bottom","right_top","left_top"],),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("IMAGE",)
|
||||
@@ -1718,11 +1870,11 @@ class CompositeImages:
|
||||
|
||||
# OUTPUT_IS_LIST = (True,)
|
||||
|
||||
def run(self, foreground,mask,background):
|
||||
def run(self, foreground,mask,background,is_multiply_blend,position):
|
||||
foreground= tensor2pil(foreground)
|
||||
mask= tensor2pil(mask)
|
||||
background= tensor2pil(background)
|
||||
res=composite_images(foreground,background,mask)
|
||||
res=composite_images(foreground,background,mask,is_multiply_blend,position)
|
||||
|
||||
return (pil2tensor(res),)
|
||||
|
||||
|
||||
+7
-2
@@ -42,8 +42,13 @@ else:
|
||||
_available=True
|
||||
|
||||
|
||||
|
||||
llma_model_path=os.path.join(folder_paths.models_dir, "lama/big-lama.pt")
|
||||
def get_lama_path():
|
||||
try:
|
||||
return folder_paths.get_folder_paths('lama')[0]
|
||||
except:
|
||||
return os.path.join(folder_paths.models_dir, "lama")
|
||||
|
||||
llma_model_path=os.path.join(get_lama_path(), "big-lama.pt")
|
||||
if not os.path.exists(llma_model_path):
|
||||
os.environ['LAMA_MODEL']=''
|
||||
print(f"## lama torchscript model not found: {llma_model_path},pls download from https://github.com/enesmsahin/simple-lama-inpainting/releases/download/v0.1.0/big-lama.pt")
|
||||
|
||||
@@ -18,19 +18,25 @@ from lark import Lark, Transformer, v_args
|
||||
global _available
|
||||
_available=True
|
||||
|
||||
def get_text_generator_path():
|
||||
try:
|
||||
return folder_paths.get_folder_paths('prompt_generator')[0]
|
||||
except:
|
||||
return os.path.join(folder_paths.models_dir, "prompt_generator")
|
||||
|
||||
text_generator_model_path=os.path.join(folder_paths.models_dir, "prompt_generator/text2image-prompt-generator")
|
||||
prompt_generator=get_text_generator_path()
|
||||
|
||||
text_generator_model_path=os.path.join(prompt_generator, "text2image-prompt-generator")
|
||||
if not os.path.exists(text_generator_model_path):
|
||||
print(f"## text_generator_model not found: {text_generator_model_path}, pls download from https://huggingface.co/succinctly/text2image-prompt-generator/tree/main")
|
||||
text_generator_model_path='succinctly/text2image-prompt-generator'
|
||||
|
||||
zh_en_model_path=os.path.join(folder_paths.models_dir, "prompt_generator/opus-mt-zh-en")
|
||||
zh_en_model_path=os.path.join(prompt_generator, "opus-mt-zh-en")
|
||||
if not os.path.exists(zh_en_model_path):
|
||||
print(f"## zh_en_model not found: {zh_en_model_path}, pls download from https://huggingface.co/Helsinki-NLP/opus-mt-zh-en/tree/main")
|
||||
zh_en_model_path='Helsinki-NLP/opus-mt-zh-en'
|
||||
|
||||
|
||||
|
||||
def is_installed(package):
|
||||
try:
|
||||
spec = importlib.util.find_spec(package)
|
||||
|
||||
+23
-13
@@ -247,10 +247,10 @@
|
||||
|
||||
.card textarea {
|
||||
width: 100%;
|
||||
/* height: 200px; */
|
||||
height:'fit-content';
|
||||
/* min-width: 300px; */
|
||||
margin-top: 12px;
|
||||
resize: vertical;
|
||||
resize: none;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
@@ -404,9 +404,9 @@
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* 定义滚动条轨道的背景颜色 */
|
||||
/* 定义滚动条轨道的背景颜色 */
|
||||
|
||||
::-webkit-scrollbar-track {
|
||||
::-webkit-scrollbar-track {
|
||||
background-color: #f1f1f1;
|
||||
/* 轨道背景颜色 */
|
||||
}
|
||||
@@ -434,14 +434,18 @@
|
||||
/* 角落颜色 */
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
.dynamic_prompt::after {
|
||||
content: attr(title);
|
||||
position: absolute;
|
||||
position: absolute;
|
||||
color: black;
|
||||
padding: 4px;
|
||||
padding-left: 25px;
|
||||
border-radius: 4px;
|
||||
} */
|
||||
|
||||
summary {
|
||||
user-select: none;
|
||||
}
|
||||
</style>
|
||||
<!-- <script src="../../../scripts/api.js" type="module"></script> -->
|
||||
@@ -948,6 +952,10 @@
|
||||
|
||||
|
||||
function createOutputs(outputData, link) {
|
||||
const url = new URL(window.location.href);
|
||||
const params = new URLSearchParams(url.search);
|
||||
const innerApp = params.get("innerApp");
|
||||
|
||||
const container = document.createElement('div');
|
||||
container.className = "output";
|
||||
|
||||
@@ -956,11 +964,11 @@
|
||||
|
||||
const copyHTML = document.createElement('button');
|
||||
copyHTML.innerText = 'copy as html'
|
||||
action.appendChild(copyHTML)
|
||||
if (!innerApp) action.appendChild(copyHTML)
|
||||
|
||||
const copyImage = document.createElement('button');
|
||||
copyImage.innerText = 'copy image'
|
||||
action.appendChild(copyImage)
|
||||
if (!innerApp) action.appendChild(copyImage)
|
||||
copyImage.style.marginLeft = '18px';
|
||||
|
||||
let isURL = false;
|
||||
@@ -1062,18 +1070,19 @@
|
||||
"Image Save",
|
||||
"SaveImageAndMetadata_",
|
||||
"TransparentImage"].includes(node.class_type)) {
|
||||
console.log('output#image', node)
|
||||
|
||||
const url = node.options?.defaultImage || window._appData?.icon || base64Df;
|
||||
let a = document.createElement('a');
|
||||
a.id = `output_${node.id}`
|
||||
a.setAttribute('data-pswp-width', "200");
|
||||
a.setAttribute('data-pswp-height', "200");
|
||||
a.setAttribute('target', "_blank");
|
||||
a.setAttribute('href', base64Df);
|
||||
a.setAttribute('href', url);
|
||||
a.setAttribute('title', node.title);
|
||||
|
||||
let img = new Image();
|
||||
// img;
|
||||
img.src = window._appData?.icon || base64Df;
|
||||
img.src = url;
|
||||
a.appendChild(img)
|
||||
output_card.appendChild(a);
|
||||
isShowImageFn = true;
|
||||
@@ -2267,7 +2276,8 @@
|
||||
// leftDiv.appendChild(des);
|
||||
leftDiv.appendChild(statusDiv);
|
||||
leftDiv.appendChild(input1);
|
||||
mainDiv.appendChild(submitButton);
|
||||
|
||||
if (typeof (data.data) == 'object') mainDiv.appendChild(submitButton);
|
||||
|
||||
rightDiv.appendChild(output);
|
||||
|
||||
@@ -2624,7 +2634,7 @@
|
||||
ui.title.update(appData.name || 'Mixlab APP');
|
||||
|
||||
// 更新应用图标
|
||||
ui.icon.update(appData.icon || base64Df);
|
||||
ui.icon.update(appData.icon || appData.output[0]?.options?.defaultImage || base64Df);
|
||||
|
||||
ui.des.update(appData.description || '-');
|
||||
|
||||
|
||||
@@ -207,9 +207,27 @@ async function extractInputAndOutputData (
|
||||
// input.push()
|
||||
}
|
||||
if (outputIds.includes(id)) {
|
||||
let options = {}
|
||||
//输出的默认图
|
||||
if (
|
||||
node.type === 'SaveImageAndMetadata_' &&
|
||||
app.graph.getNodeById(id).imgs
|
||||
) {
|
||||
// SaveImageAndMetadata_的默认图,转为base64
|
||||
let imgurl = app.graph.getNodeById(id).imgs[0].src
|
||||
|
||||
options.defaultImage = await drawImageToCanvas(imgurl, 512)
|
||||
console.log('#SaveImageAndMetadata_的默认图', options)
|
||||
}
|
||||
|
||||
// let node = app.graph.getNodeById(id)
|
||||
// output.push()
|
||||
output[outputIds.indexOf(id)] = { ...data[id], title: node.title, id }
|
||||
output[outputIds.indexOf(id)] = {
|
||||
...data[id],
|
||||
title: node.title,
|
||||
id,
|
||||
options
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
|
||||
@@ -0,0 +1,108 @@
|
||||
async function* completion (url, messages, controller) {
|
||||
let data = {
|
||||
model: 'gpt-3.5-turbo-16k',
|
||||
messages,
|
||||
temperature: 0.6,
|
||||
stream: true
|
||||
}
|
||||
// if (imageNode) {
|
||||
// data = { ...data, image_data: [imageNode] }
|
||||
// }
|
||||
|
||||
// let controller = new AbortController()
|
||||
|
||||
let response = await fetch(url, {
|
||||
method: 'POST',
|
||||
body: JSON.stringify(data),
|
||||
headers: {
|
||||
Connection: 'keep-alive',
|
||||
'Content-Type': 'application/json',
|
||||
Accept: 'text/event-stream'
|
||||
},
|
||||
signal: controller.signal
|
||||
})
|
||||
|
||||
const reader = response.body.getReader()
|
||||
const decoder = new TextDecoder()
|
||||
|
||||
let content = ''
|
||||
let leftover = '' // Buffer for partially read lines
|
||||
|
||||
try {
|
||||
let cont = true
|
||||
while (cont) {
|
||||
let result = await reader.read()
|
||||
if (result.done) {
|
||||
break
|
||||
}
|
||||
|
||||
|
||||
// Add any leftover data to the current chunk of data
|
||||
const text = leftover + decoder.decode(result.value)
|
||||
|
||||
// Check if the last character is a line break
|
||||
const endsWithLineBreak = text.endsWith('\n')
|
||||
|
||||
// Split the text into lines
|
||||
let lines = text.split('\n')
|
||||
|
||||
// If the text doesn't end with a line break, then the last line is incomplete
|
||||
// Store it in leftover to be added to the next chunk of data
|
||||
if (!endsWithLineBreak) {
|
||||
leftover = lines.pop()
|
||||
} else {
|
||||
leftover = '' // Reset leftover if we have a line break at the end
|
||||
}
|
||||
|
||||
// Parse all sse events and add them to result
|
||||
const regex = /^(\S+):\s(.*)$/gm
|
||||
for (const line of lines) {
|
||||
const match = regex.exec(line)
|
||||
if (match) {
|
||||
result[match[1]] = match[2]
|
||||
// since we know this is llama.cpp, let's just decode the json in data
|
||||
if (result.data) {
|
||||
result.data = JSON.parse(result.data)
|
||||
// console.log('#result.data',result.data)
|
||||
|
||||
content += result.data.choices[0].delta?.content||''
|
||||
|
||||
// yield
|
||||
yield result
|
||||
|
||||
// if we got a stop token from server, we will break here
|
||||
if (result.data.choices[0].finish_reason=="stop") {
|
||||
if (result.data.generation_settings) {
|
||||
// generation_settings = result.data.generation_settings;
|
||||
}
|
||||
cont = false
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('llama error: ', e)
|
||||
throw e
|
||||
} finally {
|
||||
controller.abort()
|
||||
}
|
||||
|
||||
return content
|
||||
// return (await response.json()).content
|
||||
}
|
||||
|
||||
export async function completion_ (url, messages, controller, callback) {
|
||||
let request = await completion(url, messages, controller)
|
||||
for await (const chunk of request) {
|
||||
|
||||
let content=chunk.data.choices[0].delta.content||""
|
||||
if(chunk.data.choices[0].role=="assistant"){
|
||||
//开始
|
||||
content=""
|
||||
}
|
||||
|
||||
if (callback) callback(content)
|
||||
}
|
||||
}
|
||||
@@ -3,7 +3,7 @@ import { app } from '../../../scripts/app.js'
|
||||
const repoOwner = 'shadowcz007' // 替换为仓库的所有者
|
||||
const repoName = 'comfyui-mixlab-nodes' // 替换为仓库的名称
|
||||
|
||||
const version = 'v0.23.0'
|
||||
const version = 'v0.25.1'
|
||||
|
||||
fetch(`https://api.github.com/repos/${repoOwner}/${repoName}/releases/latest`)
|
||||
.then(response => response.json())
|
||||
|
||||
+383
-71
@@ -9,6 +9,94 @@ import {
|
||||
|
||||
import { smart_init, addSmartMenu } from './smart_connect.js'
|
||||
|
||||
import { completion_ } from './chat.js'
|
||||
|
||||
//系统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`
|
||||
|
||||
if (!localStorage.getItem('_mixlab_system_prompt')) {
|
||||
localStorage.setItem('_mixlab_system_prompt', systemPrompt)
|
||||
}
|
||||
|
||||
// 获取llama 模型
|
||||
async function get_llamafile_models () {
|
||||
try {
|
||||
const response = await fetch('/mixlab/folder_paths', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({
|
||||
type: 'llamafile'
|
||||
})
|
||||
})
|
||||
|
||||
const data = await response.json()
|
||||
console.log(data)
|
||||
return data.names
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
}
|
||||
}
|
||||
// 运行llama
|
||||
async function start_llama (model = 'Phi-3-mini-4k-instruct-Q5_K_S.gguf') {
|
||||
try {
|
||||
const response = await fetch('/mixlab/start_llama', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({
|
||||
model
|
||||
})
|
||||
})
|
||||
|
||||
const data = await response.json();
|
||||
if(data.llama_cpp_error){
|
||||
return
|
||||
}
|
||||
|
||||
return { url: `http://127.0.0.1:${data.port}`, model: data.model }
|
||||
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
}
|
||||
}
|
||||
|
||||
// 菜单入口
|
||||
async function createMenu () {
|
||||
const menu = document.querySelector('.comfy-menu')
|
||||
const separator = document.createElement('div')
|
||||
separator.style = `margin: 20px 0px;
|
||||
width: 100%;
|
||||
height: 1px;
|
||||
background: var(--border-color);
|
||||
`
|
||||
menu.append(separator)
|
||||
|
||||
if (!menu.querySelector('#mixlab_chatbot_by_llamacpp')) {
|
||||
const appsButton = document.createElement('button')
|
||||
appsButton.id = 'mixlab_chatbot_by_llamacpp'
|
||||
appsButton.textContent = '♾️Mixlab'
|
||||
|
||||
// appsButton.onclick = () =>
|
||||
appsButton.onclick = async () => {
|
||||
if (window._mixlab_llamacpp) {
|
||||
//显示运行的模型
|
||||
createModelsModal([
|
||||
window._mixlab_llamacpp.url,
|
||||
window._mixlab_llamacpp.model
|
||||
])
|
||||
} else {
|
||||
let ms = await get_llamafile_models()
|
||||
ms = ms.filter(m => !m.match('-mmproj-'))
|
||||
if (ms.length > 0) createModelsModal(ms)
|
||||
}
|
||||
}
|
||||
menu.append(appsButton)
|
||||
}
|
||||
}
|
||||
|
||||
let isScriptLoaded = {}
|
||||
|
||||
function loadExternalScript (url) {
|
||||
@@ -299,8 +387,9 @@ async function get_my_app (filename = null, category = '') {
|
||||
data = []
|
||||
|
||||
for (const res of result.data) {
|
||||
let { app, workflow } = res.data;
|
||||
if (app?.filename) data.push({
|
||||
let { app, workflow } = res.data
|
||||
if (app?.filename)
|
||||
data.push({
|
||||
...app,
|
||||
data: workflow,
|
||||
date: res.date
|
||||
@@ -344,6 +433,33 @@ injectCSS(`::-webkit-scrollbar {
|
||||
width: 2px;
|
||||
}
|
||||
|
||||
#mixlab_chatbot_by_llamacpp{
|
||||
font-size:14px
|
||||
}
|
||||
|
||||
#mixlab_chatbot_by_llamacpp::before {
|
||||
content: attr(title);
|
||||
position: absolute;
|
||||
margin-top: 24px;
|
||||
font-size: 10px;
|
||||
}
|
||||
|
||||
.mix_tag{
|
||||
padding:8px;cursor: pointer;font-size: 14px;
|
||||
color: var(--input-text);
|
||||
background-color: var(--comfy-input-bg);
|
||||
border-radius: 8px;
|
||||
border-color: var(--border-color);
|
||||
border-style: solid;
|
||||
margin-top: 2px;
|
||||
margin-bottom: 14px;
|
||||
}
|
||||
|
||||
.mix_tag:hover{
|
||||
background-color: #101c19;
|
||||
color: aquamarine;
|
||||
}
|
||||
|
||||
@keyframes loading_mixlab {
|
||||
0% {
|
||||
background-color: green;
|
||||
@@ -586,13 +702,160 @@ async function fetchReadmeContent (url) {
|
||||
}
|
||||
}
|
||||
|
||||
function createModelsModal (models) {
|
||||
var div =
|
||||
document.querySelector('#model-modal') || document.createElement('div')
|
||||
div.id = 'model-modal'
|
||||
div.innerHTML = ''
|
||||
div.style.cssText = `
|
||||
width: 100%;
|
||||
z-index: 9990;
|
||||
height: 100vh;
|
||||
display: flex;
|
||||
color: var(--descrip-text);
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
background: #000000a8;
|
||||
`
|
||||
|
||||
var modal = document.createElement('div')
|
||||
|
||||
div.addEventListener('click', e => {
|
||||
e.stopPropagation()
|
||||
div.remove()
|
||||
})
|
||||
|
||||
div.appendChild(modal)
|
||||
modal.classList.add('modal-body')
|
||||
// Set modal styles
|
||||
modal.style.cssText = `
|
||||
color: var(--descrip-text);
|
||||
background-color: var(--comfy-menu-bg);
|
||||
position: fixed;
|
||||
overflow:hidden;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
z-index: 9999;
|
||||
border-radius: 4px;
|
||||
box-shadow: 4px 4px 14px rgba(255,255,255,0.2);
|
||||
`
|
||||
|
||||
// Create modal header
|
||||
const headerElement = document.createElement('div')
|
||||
headerElement.classList.add('modal-header')
|
||||
headerElement.style.cssText = `
|
||||
display: flex;
|
||||
padding: 20px 24px 8px 24px;
|
||||
justify-content: space-between;
|
||||
`
|
||||
|
||||
const headTitleElement = document.createElement('a')
|
||||
headTitleElement.classList.add('header-title')
|
||||
headTitleElement.style.cssText = `
|
||||
color: var(--descrip-text);
|
||||
font-size: 18px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
text-decoration: none;
|
||||
font-weight: bold;
|
||||
justify-content: space-between;
|
||||
padding: 20px;
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
`
|
||||
|
||||
headTitleElement.textContent = 'Models'
|
||||
// headTitleElement.href = 'https://github.com/shadowcz007/comfyui-mixlab-nodes'
|
||||
// headTitleElement.target = '_blank'
|
||||
const linkIcon = document.createElement('small')
|
||||
linkIcon.textContent = '自动开启'
|
||||
linkIcon.style.padding = '4px'
|
||||
|
||||
headTitleElement.appendChild(linkIcon)
|
||||
headerElement.appendChild(headTitleElement)
|
||||
if (localStorage.getItem('_mixlab_auto_llama_open')) {
|
||||
linkIcon.style.backgroundColor = '#66ff6c'
|
||||
linkIcon.style.color = 'black'
|
||||
}
|
||||
linkIcon.addEventListener('click', e => {
|
||||
e.stopPropagation()
|
||||
if (localStorage.getItem('_mixlab_auto_llama_open')) {
|
||||
localStorage.setItem('_mixlab_auto_llama_open', '')
|
||||
linkIcon.style.backgroundColor = ''
|
||||
linkIcon.style.color = 'var(--descrip-text)'
|
||||
} else {
|
||||
localStorage.setItem('_mixlab_auto_llama_open', 'true')
|
||||
linkIcon.style.backgroundColor = '#66ff6c'
|
||||
linkIcon.style.color = 'black'
|
||||
}
|
||||
})
|
||||
|
||||
modal.appendChild(headTitleElement)
|
||||
|
||||
// Create modal content area
|
||||
var modalContent = document.createElement('div')
|
||||
modalContent.classList.add('modal-content')
|
||||
|
||||
var input = document.createElement('textarea')
|
||||
input.className = 'comfy-multiline-input'
|
||||
input.style = ` height: 260px;
|
||||
width: 480px;
|
||||
font-size: 16px;
|
||||
padding: 18px;`
|
||||
input.value = localStorage.getItem('_mixlab_system_prompt')
|
||||
|
||||
input.addEventListener('change', e => {
|
||||
e.stopPropagation()
|
||||
localStorage.setItem('_mixlab_system_prompt', input.value)
|
||||
})
|
||||
|
||||
input.addEventListener('click', e => {
|
||||
e.stopPropagation()
|
||||
})
|
||||
|
||||
modalContent.appendChild(input)
|
||||
|
||||
for (const m of models) {
|
||||
let d = document.createElement('div')
|
||||
d.innerText = 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)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
modalContent.appendChild(d)
|
||||
}
|
||||
modal.appendChild(modalContent)
|
||||
|
||||
document.body.appendChild(div)
|
||||
}
|
||||
|
||||
function createModal (url, markdown, title) {
|
||||
// Create modal element
|
||||
var div =
|
||||
document.querySelector('#mix-modal') || document.createElement('div')
|
||||
div.id = 'mix-modal'
|
||||
div.innerHTML = ''
|
||||
div.style.cssText = `width: 100%;
|
||||
div.style.cssText = `
|
||||
width: 100%;
|
||||
z-index: 9990;
|
||||
height: 100vh;
|
||||
display: flex;
|
||||
@@ -898,6 +1161,17 @@ function drawBadge (node, orig, restArgs) {
|
||||
app.registerExtension({
|
||||
name: 'Comfy.Mixlab.ui',
|
||||
init () {
|
||||
//是否要自动加载模型
|
||||
if (localStorage.getItem('_mixlab_auto_llama_open')) {
|
||||
let model = localStorage.getItem('_mixlab_llama_select')
|
||||
start_llama(model).then(res => {
|
||||
window._mixlab_llamacpp = res
|
||||
document.body
|
||||
.querySelector('#mixlab_chatbot_by_llamacpp')
|
||||
.setAttribute('title', res.url)
|
||||
})
|
||||
}
|
||||
|
||||
LGraphCanvas.prototype.helpAboutNode = async function (node) {
|
||||
nodesMap =
|
||||
nodesMap && Object.keys(nodesMap).length > 0
|
||||
@@ -922,75 +1196,64 @@ app.registerExtension({
|
||||
|
||||
smart_init()
|
||||
|
||||
const getNodeMenuOptions = LGraphCanvas.prototype.getNodeMenuOptions // store the existing method
|
||||
LGraphCanvas.prototype.getNodeMenuOptions = function (node) {
|
||||
// replace it
|
||||
const options = getNodeMenuOptions.apply(this, arguments) // start by calling the stored one
|
||||
node.setDirtyCanvas(true, true) // force a redraw of (foreground, background)
|
||||
console.log('getNodeMenuOptions', node.type == 'CLIPTextEncode')
|
||||
LGraphCanvas.prototype.text2text = async function (node) {
|
||||
// console.log(node)
|
||||
let widget = node.widgets.filter(
|
||||
w => w.name === 'text' && typeof w.value == 'string'
|
||||
)[0]
|
||||
if (widget) {
|
||||
let controller = new AbortController()
|
||||
let ends = []
|
||||
let userInput = widget.value
|
||||
widget.value = widget.value.trim()
|
||||
widget.value += '\n'
|
||||
try {
|
||||
await completion_(
|
||||
window._mixlab_llamacpp.url + '/v1/chat/completions',
|
||||
[
|
||||
{
|
||||
role: 'system',
|
||||
content: localStorage.getItem('_mixlab_system_prompt')
|
||||
},
|
||||
{ role: 'user', content: userInput }
|
||||
],
|
||||
controller,
|
||||
t => {
|
||||
// console.log(t)
|
||||
widget.value += t
|
||||
}
|
||||
)
|
||||
} catch (error) {
|
||||
//是否要自动加载模型
|
||||
if (localStorage.getItem('_mixlab_auto_llama_open')) {
|
||||
let model = localStorage.getItem('_mixlab_llama_select')
|
||||
start_llama(model).then(async res => {
|
||||
window._mixlab_llamacpp = res
|
||||
document.body
|
||||
.querySelector('#mixlab_chatbot_by_llamacpp')
|
||||
.setAttribute('title', res.url)
|
||||
|
||||
let opts = [
|
||||
{
|
||||
content: 'Help ♾️Mixlab', // with a name
|
||||
callback: () => {
|
||||
LGraphCanvas.prototype.helpAboutNode(node)
|
||||
} // and the callback
|
||||
},
|
||||
{
|
||||
content: 'Fix node v2', // with a name
|
||||
callback: () => {
|
||||
LGraphCanvas.prototype.fixTheNode(node)
|
||||
await completion_(
|
||||
window._mixlab_llamacpp.url + '/v1/chat/completions',
|
||||
[
|
||||
{
|
||||
role: 'system',
|
||||
content: localStorage.getItem('_mixlab_system_prompt')
|
||||
},
|
||||
{ role: 'user', content: userInput }
|
||||
],
|
||||
controller,
|
||||
t => {
|
||||
// console.log(t)
|
||||
widget.value += t
|
||||
}
|
||||
)
|
||||
})
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
if (node.widgets) {
|
||||
// let text_widget = node.widgets.filter(
|
||||
// w => w.name === 'text' && typeof w.value == 'string'
|
||||
// )
|
||||
// if (text_widget && text_widget.length == 1) {
|
||||
// opts.push({
|
||||
// content: 'Text-to-Text ♾️Mixlab', // with a name
|
||||
// callback: () => {
|
||||
// LGraphCanvas.prototype.text2text(node)
|
||||
// } // and the callback
|
||||
// })
|
||||
// }
|
||||
widget.value = widget.value.trim()
|
||||
}
|
||||
|
||||
opts = addSmartMenu(opts, node)
|
||||
|
||||
// if (node.type == 'CLIPTextEncode') {
|
||||
// // 则出现 randomPrompt
|
||||
// // CLIPTextEncode 的widget ,name== 'text'
|
||||
// let node_widget_name = 'text'
|
||||
// const widget = node.widgets.filter(w => w.name === node_widget_name)[0]
|
||||
|
||||
// let mixlab_nodes_smart_connect= [{node_type:'CLIPTextEncode',
|
||||
// node_widget_name:'text',
|
||||
// inputNodeName:'RandomPrompt',
|
||||
// inputNode_output_type:'STRING'}]
|
||||
|
||||
// if (widget) {
|
||||
// opts = [
|
||||
// {
|
||||
// content: 'RandomPrompt',
|
||||
// callback: () => {
|
||||
// LGraphCanvas.prototype._createNodeForInput(
|
||||
// node, //当前node
|
||||
// widget,//当前node里需要自动连线的widget
|
||||
// 'RandomPrompt',//作为input的node type
|
||||
// 'STRING'// 作为input的node的outputs的type. the input slot type of the target node
|
||||
// )
|
||||
// }
|
||||
// },
|
||||
// null,
|
||||
// ...opts
|
||||
// ]
|
||||
// }
|
||||
// }
|
||||
|
||||
return [...opts, null, ...options] // and return the options
|
||||
}
|
||||
|
||||
const getGroupMenuOptions = LGraphCanvas.prototype.getGroupMenuOptions // store the existing method
|
||||
@@ -1139,6 +1402,56 @@ app.registerExtension({
|
||||
this.setDirty(true, true)
|
||||
}
|
||||
|
||||
const getNodeMenuOptions=LGraphCanvas.prototype.getNodeMenuOptions;
|
||||
LGraphCanvas.prototype.getNodeMenuOptions = function (node) {
|
||||
// replace it
|
||||
const options = getNodeMenuOptions.apply(this, arguments) // start by calling the stored one
|
||||
node.setDirtyCanvas(true, true) // force a redraw of (foreground, background)
|
||||
|
||||
let opts = [
|
||||
{
|
||||
content: 'Help ♾️Mixlab', // with a name
|
||||
callback: () => {
|
||||
LGraphCanvas.prototype.helpAboutNode(node)
|
||||
} // and the callback
|
||||
},
|
||||
{
|
||||
content: 'Fix node v2', // with a name
|
||||
callback: () => {
|
||||
LGraphCanvas.prototype.fixTheNode(node)
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
if (node.widgets) {
|
||||
let text_widget = node.widgets.filter(
|
||||
w => w.name === 'text' && typeof w.value == 'string'
|
||||
)
|
||||
|
||||
let text_input = node.inputs?.filter(
|
||||
inp => inp.name == 'text' && inp.type == 'STRING'
|
||||
)
|
||||
|
||||
if (
|
||||
text_input&&
|
||||
text_input.length == 0 &&
|
||||
text_widget &&
|
||||
text_widget.length == 1 &&
|
||||
window._mixlab_llamacpp &&
|
||||
node.type != 'ShowTextForGPT'
|
||||
) {
|
||||
opts.push({
|
||||
content: 'Text-to-Text ♾️Mixlab', // with a name
|
||||
callback: () => {
|
||||
LGraphCanvas.prototype.text2text(node)
|
||||
} // and the callback
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return [...opts, null, ...options] // and return the options
|
||||
}
|
||||
|
||||
// 支持app模式的json
|
||||
const loadAppJson = async data => {
|
||||
let workflow
|
||||
@@ -1173,8 +1486,6 @@ app.registerExtension({
|
||||
event.preventDefault()
|
||||
event.stopPropagation()
|
||||
|
||||
|
||||
|
||||
// Dragging from Chrome->Firefox there is a file but its a bmp, so ignore that
|
||||
if (
|
||||
event.dataTransfer.files.length &&
|
||||
@@ -1182,13 +1493,14 @@ app.registerExtension({
|
||||
) {
|
||||
const reader = new FileReader()
|
||||
reader.onload = async () => {
|
||||
|
||||
loadAppJson(reader.result)
|
||||
}
|
||||
reader.readAsText(event.dataTransfer.files[0])
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
createMenu()
|
||||
},
|
||||
setup () {
|
||||
setTimeout(async () => {
|
||||
@@ -1198,7 +1510,7 @@ app.registerExtension({
|
||||
const apps = await get_my_app()
|
||||
if (!apps) return
|
||||
|
||||
console.log('apps',apps)
|
||||
console.log('apps', apps)
|
||||
|
||||
let apps_map = { 0: [] }
|
||||
|
||||
|
||||
Reference in New Issue
Block a user