Compare commits

...
Author SHA1 Message Date
shadowcz007 8a46647d8c Update index.html 2024-01-04 14:02:19 +08:00
shadowcz007 406a255db0 v0.10.0 增加 ClipInterrogator、优化APP功能 2024-01-04 13:37:07 +08:00
shadowcz007 574557810e Update index.html 2024-01-04 13:24:50 +08:00
shadowcz007 998a02c3a4 上一次输入记录 2024-01-04 13:12:02 +08:00
shadowcz007 c6f964c921 textarea输入,增加上一次 输入记录 2024-01-04 12:57:55 +08:00
shadowcz007 efb0e147c5 Update index.html 2024-01-04 12:45:00 +08:00
shadowcz007 9cf7356f98 Update index.html 2024-01-04 12:33:59 +08:00
shadowcz007 af05c43174 支持image的batch输出 2024-01-04 12:31:45 +08:00
shadowcz007 380c68ff2b EnhanceImage节点支持batch多张输入和输出 2024-01-04 12:03:27 +08:00
shadowcz007 068b00b99f update 2024-01-04 11:24:00 +08:00
shadowcz007 cd6a42ab64 clip-interrogator 2024-01-04 11:03:25 +08:00
shadowcz007 f115abec92 add clip interrogator 2024-01-04 11:01:08 +08:00
shadowcz007 f0e23cf878 AIPC大赛模板 2024-01-03 22:28:38 +08:00
shadowcz007 a94f11d809 Update index.html 2024-01-03 20:22:24 +08:00
shadowcz007 38972bea5f 更新AIPC大赛模板-直接合成,免去ps 2024-01-03 18:03:33 +08:00
shadowcz007 a761ff552a Merge branch 'main' of https://github.com/shadowcz007/comfyui-mixlab-nodes 2024-01-03 17:54:25 +08:00
shadowcz007 dbeb84ea9a resizeImage 缩放图像新增center模式,多余的背景可以设定填充颜色 2024-01-03 17:54:23 +08:00
shadow 0a4938f39a Merge pull request #105 from shadowcz007/v0.9.2-中断生成
修复3d image的bug,未上传bg图也可以运行了
2024-01-03 14:21:26 +08:00
shadowcz007 b2182c716d 修复3d image的bug,未上传bg图也可以运行了 2024-01-03 14:20:48 +08:00
shadow 8253be73f6 Merge pull request #104 from shadowcz007/v0.9.2-中断生成
添加中断生成的功能
2024-01-03 09:35:12 +08:00
shadowcz007 20318e296e 添加中断生成的功能 2024-01-03 09:32:29 +08:00
shadow cea1b69286 Merge pull request #103 from shadowcz007/v0.9.1-优化app模式
V0.9.1 优化app模式
2024-01-02 23:56:18 +08:00
shadowcz007 ab8aa69389 v0.9.1
web app可以设置分类,在comfyui右键菜单可以编辑更新web app

The web app can be configured with categories, and the web app can be edited and updated in the right-click menu of ComfyUI.

暂时支持8种节点作为界面上的输入节点:Load Image、CLIPTextEncode、PromptSlide、TextInput_、Color、FloatSlider、IntNumber、CheckpointLoaderSimple、LoraLoader
2024-01-02 23:54:24 +08:00
shadowcz007 681491f1d0 v0.9.1 2024-01-02 23:51:19 +08:00
shadowcz007 c2fb815074 更新示例:TwinShot 2024-01-02 23:50:39 +08:00
shadowcz007 fffa14dc44 修复了randomprompt里的一个小bug 2024-01-02 17:48:41 +08:00
shadowcz007 df37166d42 Switch节点增加flat功能,可以把list里的某个元素取出来单独处理 2024-01-02 17:44:17 +08:00
shadowcz007 25fa3a8f6a 支持按照分类隔离应用 2024-01-02 16:19:01 +08:00
shadowcz007 b11507c5e6 样式 2024-01-02 15:11:29 +08:00
shadowcz007 f06d02489f app支持color组件 2024-01-02 14:59:59 +08:00
shadowcz007 c203af2f71 优化 2024-01-02 13:42:39 +08:00
shadowcz007 c715155a70 渐变节点 2024-01-02 13:25:48 +08:00
shadowcz007 8163133294 优化颜色选择器 2024-01-02 12:17:41 +08:00
shadowcz007 765be5dab4 1 2024-01-02 11:03:22 +08:00
shadowcz007 7c1523389d Update index.html 2024-01-02 09:53:25 +08:00
shadowcz007 7a2b1ba166 支持category 2024-01-02 09:32:46 +08:00
shadowcz007 45b4dcfcd0 Update index.html 2024-01-01 22:46:10 +08:00
shadowcz007 3b2e535566 add photoswipe 2024-01-01 22:34:58 +08:00
shadowcz007 db556d13a3 1 2024-01-01 21:33:54 +08:00
shadowcz007 a987063c68 nodes map - appinfo 2024-01-01 21:08:38 +08:00
shadowcz007 4ce30ef899 Update ui_mixlab.js 2024-01-01 20:33:00 +08:00
shadowcz007 d988282d98 增加种子生成模式切换 2024-01-01 20:24:12 +08:00
shadowcz007 695fdf7ceb update 2024-01-01 20:08:39 +08:00
shadowcz007 0befe164cc v0.9.0 2024-01-01 16:12:58 +08:00
shadowcz007 d506c68a80 promptslide-appinfo-workflow.svg 2024-01-01 16:10:46 +08:00
shadowcz007 c59c429b75 update 2024-01-01 16:05:19 +08:00
shadowcz007 c726b6e4a2 prompt weight 提供选项 2024-01-01 15:56:07 +08:00
shadowcz007 96075ad4e1 Update ImageNode.py 2024-01-01 14:17:58 +08:00
shadowcz007 7a8dc07a8a Update ui_mixlab.js 2024-01-01 11:46:21 +08:00
shadowcz007 1fdac0bc09 Update index.html 2024-01-01 11:32:06 +08:00
shadowcz007 804b942a36 Update index.html 2024-01-01 11:16:56 +08:00
shadowcz007 6335d4378b 优化 2024-01-01 10:55:09 +08:00
shadowcz007 cfc2189616 v0.8.1 2023-12-31 23:58:16 +08:00
shadowcz007 e06e032701 fixbug 2023-12-31 23:56:30 +08:00
shadowcz007 c9499c2c79 update 2023-12-31 23:43:17 +08:00
shadowcz007 2bf43541bb Update appinfo-workflow.svg 2023-12-31 23:42:23 +08:00
shadowcz007 0386c7266d Update app_mixlab.js 2023-12-31 23:41:12 +08:00
shadowcz007 7aa6ed9a5d fixbug 2023-12-31 23:36:57 +08:00
shadowcz007 b71325afa5 fixbug 2023-12-31 22:50:40 +08:00
shadowcz007 2cc29bdf77 适配了最新版comfyui的py3.11 ,torch 2.1.2+cu121 2023-12-31 22:48:11 +08:00
shadowcz007 b5d602abc4 Update index.html 2023-12-31 22:01:26 +08:00
shadowcz007 33c45637ac app 2023-12-31 21:39:50 +08:00
shadowcz007 662d4478d0 Update ui_mixlab.js 2023-12-31 21:31:02 +08:00
shadowcz007 31d3809572 Update ui_mixlab.js 2023-12-31 21:28:45 +08:00
shadowcz007 024ff4a309 Update Lama.py 2023-12-31 21:24:40 +08:00
shadowcz007 9ae8d30b6b llma 2023-12-31 21:23:26 +08:00
shadowcz007 44349c10b0 Update index.html 2023-12-31 20:09:34 +08:00
shadowcz007 506520a3c4 Create Prompt-weight-workflow.json 2023-12-31 17:58:06 +08:00
shadowcz007 50f7020977 prompt-weight 2023-12-31 17:50:47 +08:00
shadowcz007 02a27a03cc Update PromptNode.py 2023-12-31 16:22:57 +08:00
shadowcz007 4ad6bacf7b 优化 2023-12-31 16:20:36 +08:00
shadowcz007 26ecc0fa44 新增 PromptSlide节点,实现滑块调节prompt的权重 2023-12-31 16:04:30 +08:00
shadowcz007 2619befca6 Update README.md 2023-12-31 13:14:29 +08:00
shadowcz007 ea24f52b13 Update checkVersion_mixlab.js 2023-12-31 13:12:34 +08:00
shadowcz007 4abfc47346 ### Update 0.8.0
v0.8.0 🚀🚗🚚🏃‍ LaMaInpainting
- 新增 LaMaInpainting
- 优化color节点的输出
- 修复高清显示屏上定位节点不准的情况

- Add LaMaInpainting
- Optimize the output of the color node
- Fix the issue of inaccurate positioning node on high-definition display screens
2023-12-31 13:11:31 +08:00
shadowcz007 3b95010d06 新增LaMaInpainting & 优化color节点的输出 2023-12-31 13:04:28 +08:00
shadow b3c1b96088 Merge pull request #97 from shadowcz007/fix_hidpi_node_move_center
Fix:node can't move to center on HiDPI device
2023-12-31 10:07:07 +08:00
shadowcz007 a9f1326873 update 2023-12-30 23:52:50 +08:00
shadowcz007 75a696fb64 update 2023-12-30 23:39:36 +08:00
shadowcz007 24aaacba6d update 2023-12-30 23:38:52 +08:00
shadowcz007 e3cd7d5f91 更新示例 2023-12-30 21:05:07 +08:00
shadow 2e228e8db5 Merge pull request #95 from shadowcz007/v0.7-apps
V0.7 apps
2023-12-30 20:53:19 +08:00
shadowcz007 5c9dd80370 fixbug 2023-12-30 20:51:31 +08:00
shadowcz007 727f5f2e48 upate 2023-12-30 20:37:09 +08:00
shadowcz007 1c238f7697 sharebutton 2023-12-30 20:16:51 +08:00
shadowcz007 119d7cce15 0.7.0 2023-12-30 18:19:18 +08:00
shadowcz007 4afc8f6083 Support multiple web app switching. 支持多个web app 切换 2023-12-30 18:16:06 +08:00
shadowcz007 a9ec3af066 改进input range 2023-12-30 18:07:43 +08:00
shadowcz007 9edae81fee update 2023-12-30 17:48:59 +08:00
shadowcz007 b941b12f12 update 2023-12-30 17:40:28 +08:00
shadowcz007 3069de188a 1 2023-12-30 17:02:44 +08:00
shadowcz007 a968f08abd update 2023-12-30 14:16:44 +08:00
shadowcz007 4153d3e5ff 1 2023-12-30 12:37:03 +08:00
shadowcz007 9d9c1a6c84 update 2023-12-30 12:29:01 +08:00
shadowcz007 16ef10a4d9 优化node map 2023-12-30 10:10:21 +08:00
shadowcz007 b3766e440a VHS_VideoCombine 2023-12-30 09:56:08 +08:00
gold3bear 6359c3f70f Fix:node can't move to center on HiDPI device 2023-12-30 02:09:45 +08:00
shadowcz007 efe73fb965 Update README.md 2023-12-29 10:39:01 +08:00
shadowcz007 c45a962fcc workflow-to-app支持checkpoints和lora 2023-12-29 10:38:22 +08:00
shadowcz007 f98a03e2e9 Update README.md 2023-12-29 00:00:16 +08:00
shadowcz007 5b6257814d 优化 2023-12-28 23:56:48 +08:00
shadowcz007 69a445d4ed 新增切换节点 2023-12-28 23:18:25 +08:00
shadowcz007 e82c786b8a 增加了从剪切板获取图片的控件 2023-12-28 18:36:34 +08:00
shadowcz007 eec2225c89 支持视频 2023-12-28 16:02:25 +08:00
shadowcz007 f7355e0b71 update 2023-12-28 14:33:59 +08:00
shadowcz007 6c6a99cfe4 优化LoadImagefromlocal ,新增LoadImageFromURL 2023-12-28 13:24:05 +08:00
shadowcz007 b4634e2e0d 修复clipseg的bug 2023-12-28 12:09:36 +08:00
shadowcz007 3f4cba0612 fixbug:textimage的高宽不对 2023-12-27 21:45:35 +08:00
shadowcz007 38db99cc75 支持showtext作为输出。GPT聊天也可以实现workflow-to-app了 2023-12-27 20:39:21 +08:00
shadowcz007 4d5906394b 优化newlayer的可视化效果 2023-12-27 20:12:55 +08:00
shadowcz007 2fc212b156 update 2023-12-27 19:38:39 +08:00
shadowcz007 53fbb5b027 fixbug 2023-12-27 17:48:49 +08:00
shadowcz007 4f24721450 Update README.md 2023-12-27 16:48:29 +08:00
shadow 83043727b5 Merge pull request #83 from shadowcz007/v0.6---simple-app
V0.6   simple app
2023-12-27 16:29:27 +08:00
shadowcz007 2d336afb85 v0.6.0 2023-12-27 16:29:00 +08:00
shadowcz007 4d309435c8 Update index.html 2023-12-26 17:15:33 +08:00
shadowcz007 099ce9cdfd 1 2023-12-26 16:32:01 +08:00
shadowcz007 8914e60cb8 初步打通 2023-12-26 16:23:43 +08:00
shadowcz007 dbd30a40e9 init 2023-12-26 12:06:55 +08:00
shadowcz007 c9a598fd59 更新下workflow示例 2023-12-26 10:56:14 +08:00
shadowcz007 e331e588cf v0.5.2
The bug of missing texture mapping for 3D nodes has been fixed.
2023-12-25 22:28:53 +08:00
shadowcz007 2011557771 fixbug 2023-12-25 22:24:56 +08:00
shadowcz007 f0ba45d14e GLB can export 2023-12-25 09:14:39 +08:00
shadowcz007 8352a521b7 v0.5.1 2023-12-24 23:07:01 +08:00
shadowcz007 aa3d4d79f8 fixbug 2023-12-24 23:04:16 +08:00
shadowcz007 4f650d760c fixbug-mergeLayer的多图片支持 2023-12-24 22:57:43 +08:00
shadowcz007 ea4b792627 v0.5.0 2023-12-24 11:16:34 +08:00
shadow 883605239a Merge pull request #75 from shadowcz007/v0.5_delay_node
V0.5 delay node
2023-12-24 10:57:21 +08:00
shadowcz007 5b8cab920c 增加示例 2023-12-24 10:56:56 +08:00
shadowcz007 8d3d327335 Update Utils.py 2023-12-24 10:53:05 +08:00
shadowcz007 32574050c4 增加从语音识别发送到chatgpt的方法 2023-12-24 10:44:25 +08:00
shadowcz007 8d45a90d9b Update Utils.py 2023-12-24 09:31:42 +08:00
gold3bear f66862a422 update DynamicDelayProcessor 2023-12-24 00:06:40 +08:00
shadowcz007 6a56be3a9b clone group & save to templete 2023-12-23 23:35:04 +08:00
gold3bear ebf6395de2 delay by text processor 2023-12-23 23:16:56 +08:00
shadowcz007 5df9fbf50d 图层支持视频合成(多image 2023-12-23 17:06:04 +08:00
shadowcz007 ff961155c9 Merge branch 'main' of https://github.com/shadowcz007/comfyui-mixlab-nodes 2023-12-23 15:59:20 +08:00
shadowcz007 14838d06a8 增加noise_image节点 2023-12-23 15:59:16 +08:00
shadow 99def24dd8 Merge pull request #73 from shadowcz007/v0.5-GamePal
支持换行的textimage
2023-12-23 14:13:31 +08:00
shadowcz007 f5b210d142 支持换行的textimage 2023-12-23 14:13:04 +08:00
shadow a137a23b48 Merge pull request #72 from shadowcz007/v0.5-GamePal
TextToNumber&audio input control
2023-12-23 13:16:08 +08:00
shadowcz007 6bbf06d9e9 TextToNumber&audio input control 2023-12-23 13:15:47 +08:00
shadowcz007 0b614b40cf Merge branch 'main' of https://github.com/shadowcz007/comfyui-mixlab-nodes 2023-12-23 10:49:27 +08:00
shadowcz007 27673561bd 使用comfyui的ui来控制刷新率 2023-12-23 10:49:24 +08:00
shadow 6e2070410d Merge pull request #70 from shadowcz007/v0.3.2-3DImage
Delete layers-test-workflow.json
2023-12-22 20:36:39 +08:00
shadowcz007 7ccf21f74f Delete layers-test-workflow.json 2023-12-22 20:36:04 +08:00
shadow 3b2710f285 Merge pull request #69 from shadowcz007/v0.3.2-3DImage
v0.4.2
2023-12-22 20:28:33 +08:00
shadowcz007 c4b277235b 1 2023-12-22 20:27:53 +08:00
shadowcz007 a55318add1 v0.4.2 2023-12-22 20:24:19 +08:00
shadowcz007 b57123a4fe Update 3D-workflow.json 2023-12-22 20:21:57 +08:00
shadowcz007 04dcc00670 增加可视化选区 2023-12-22 20:20:03 +08:00
shadowcz007 746a02b49f test- 2023-12-22 12:16:39 +08:00
shadowcz007 bdbe3db2a9 Update Vae.py 2023-12-21 10:39:32 +08:00
shadowcz007 27ae99ad86 Update __init__.py 2023-12-21 10:26:16 +08:00
shadowcz007 38add89547 update style 2023-12-21 10:24:01 +08:00
shadowcz007 a9612fbb2f 增加一个resize节点 2023-12-20 16:12:09 +08:00
shadowcz007 429cc29b5b test 2023-12-20 15:08:13 +08:00
shadowcz007 8eca94e405 test 2023-12-20 14:32:57 +08:00
shadowcz007 ad71daafb6 Merge branch 'v0.3.2-3DImage' of https://github.com/shadowcz007/comfyui-mixlab-nodes into v0.3.2-3DImage 2023-12-20 12:18:41 +08:00
shadowcz007 c936d83688 1 2023-12-20 12:18:38 +08:00
shadow c6684d680f Merge pull request #66 from shadowcz007/main
0.4.1
2023-12-20 10:50:26 +08:00
shadow fe358b0e13 0.4.1 2023-12-20 08:59:33 +08:00
shadowcz007 897f259a2a Merge branch 'v0.3.2-3DImage' of https://github.com/shadowcz007/comfyui-mixlab-nodes into v0.3.2-3DImage 2023-12-20 00:10:05 +08:00
shadowcz007 f3302c1b3a update 2023-12-20 00:08:05 +08:00
shadow 573feeaaab Merge pull request #64 from shadowcz007/main
1
2023-12-20 00:05:39 +08:00
shadowcz007 019c98ecc1 update default style 2023-12-19 21:56:49 +08:00
shadowcz007 ad6a51a4b5 Update ImageNode.py 2023-12-19 12:59:59 +08:00
61 changed files with 21839 additions and 3953 deletions
+3 -1
View File
@@ -1,4 +1,6 @@
__pycache__/
https/
nodes/config.json
workflow/my_workflow.json
workflow/my_workflow.json
workflow/my_workflow_app.json
app/*
+91 -24
View File
@@ -1,18 +1,42 @@
##
v0.4.0 🚀🚗🚚🏃‍
- Add "help" option to the context menu for each node.
- Add "find the node" option to the global context menu.
- Optimize the 3D Image node and add workflow.
> 适配了最新版comfyui的py3.11 ,torch 2.1.2+cu121
### 3D
![](./assets/3dimage.png)
[workflow](./workflow/3D-workflow.json)
## 🚀🚗🚚🏃 Workflow-to-APP
- 新增AppInfo节点,可以通过简单的配置,把workflow转变为一个Web APP。
- 支持多个web app 切换
- 发布为app的workflow,可以在右键里再次编辑了
- web app可以设置分类,在comfyui右键菜单可以编辑更新web app
### ScreenShareNode & FloatingVideoNode
> Now comfyui supports capturing screen pixel streams from any software and can be used for LCM-Lora integration. Let's get started with implementation and design! 💻🌐
- Support multiple web app switching.
- Add the AppInfo node, which allows you to transform the workflow into a web app by simple configuration.
- The workflow, which is now released as an app, can also be edited again by right-clicking.
- The web app can be configured with categories, and the web app can be edited and updated in the right-click menu of ComfyUI.
![](./assets/0-m-app.png)
![](./assets/appinfo-readme.png)
![](./assets/appinfo-2.png)
Example:
- workflow
![APP info](./workflow/appinfo-workflow.svg)
[text-to-image](./workflow/Text-to-Image-app.json)
APP-JSON:
- [text-to-image](./example/Text-to-Image_3.json)
- [image-to-image](./example/Image-to-Image_2.json)
- text-to-text
> 暂时支持8种节点作为界面上的输入节点:Load Image、CLIPTextEncode、PromptSlide、TextInput_、Color、FloatSlider、IntNumber、CheckpointLoaderSimple、LoraLoader
> 输出节点:PreviewImage 、SaveImage、ShowTextForGPT、VHS_VideoCombine
## 🏃🚗🚚🚀 Real-time Design
> ScreenShareNode & FloatingVideoNode. Now comfyui supports capturing screen pixel streams from any software and can be used for LCM-Lora integration. Let's get started with implementation and design! 💻🌐
>
![screenshare](./assets/screenshare.png)
https://github.com/shadowcz007/comfyui-mixlab-nodes/assets/12645064/e7e77f90-e43e-410a-ab3a-1952b7b4e7da
@@ -23,6 +47,7 @@ https://github.com/shadowcz007/comfyui-mixlab-nodes/assets/12645064/e7e77f90-e43
!! Please use the address with HTTPS (https://127.0.0.1).
### SpeechRecognition & SpeechSynthesis
![f](./assets/audio-workflow.svg)
@@ -31,18 +56,24 @@ https://github.com/shadowcz007/comfyui-mixlab-nodes/assets/12645064/e7e77f90-e43
### GPT
> Support for calling multiple GPTs.ChatGPT、ChatGLM3 , 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
![gpt-workflow.svg](./assets/gpt-workflow.svg)
[workflow-5](./workflow/5-gpt-workflow.json)
### LoadImagesFromLocal
> Monitor changes to images in a local folder, and trigger real-time execution of workflows, supporting common image formats, especially PSD format, in conjunction with Photoshop.
![watch](./assets/4-loadfromlocal-watcher-workflow.svg)
## Prompt
> PromptSlide
![](./assets/prompt_weight.png)
[workflow-4](./workflow/4-loadfromlocal-watcher-workflow.json)
![](./workflow/promptslide-appinfo-workflow.svg)
> randomPrompt
![randomPrompt](./assets/randomPrompt.png)
> ClipInterrogator
[add clip-interrogator](https://github.com/pharmapsychotic/clip-interrogator)
### Layers
> A new layer class node has been added, allowing you to separate the image into layers. After merging the images, you can input the controlnet for further processing.
@@ -51,6 +82,31 @@ https://github.com/shadowcz007/comfyui-mixlab-nodes/assets/12645064/e7e77f90-e43
![poster](./assets/poster-workflow.svg)
### 3D
![](./assets/3dimage.png)
[workflow](./workflow/3D-workflow.json)
### LoadImagesFromLocal
> Monitor changes to images in a local folder, and trigger real-time execution of workflows, supporting common image formats, especially PSD format, in conjunction with Photoshop.
![watch](./assets/4-loadfromlocal-watcher-workflow.svg)
[workflow-4](./workflow/4-loadfromlocal-watcher-workflow.json)
### LoadImagesFromURL
> Conveniently load images from a fixed address on the internet to ensure that default images in the workflow can be executed.
## Utils
> The Color node provides a color picker for easy color selection, the Font node offers built-in font selection for use with TextImage to generate text images, and the DynamicDelayByText node allows delayed execution based on the length of the input text.
- [添加了DynamicDelayByText功能,可以根据输入文本的长度进行延迟执行。](./workflow/audio-chatgpt-workflow.json)
- [Added DynamicDelayByText, enabling delayed execution based on input text length.](./workflow/audio-chatgpt-workflow.json)
## Other Nodes
![main](./assets/all-workflow.svg)
@@ -58,9 +114,7 @@ https://github.com/shadowcz007/comfyui-mixlab-nodes/assets/12645064/e7e77f90-e43
[workflow-1](./workflow/1-workflow.json)
> randomPrompt
![randomPrompt](./assets/randomPrompt.png)
> TransparentImage
@@ -83,11 +137,15 @@ Add edges to an image.
![FeatheredMask](./assets/FlVou_Y6kaGWYoEj1Tn0aTd4AjMI.jpg)
> LaMaInpainting
from [simple-lama-inpainting](https://github.com/enesmsahin/simple-lama-inpainting)
### Improvement
- Add "help" option to the context menu for each node.
- Add "find the node" option to the global context menu.
- Add "Nodes Map" option to the global context menu.
An improvement has been made to directly redirect to GitHub to search for missing nodes when loading the graph.
@@ -96,13 +154,23 @@ An improvement has been made to directly redirect to GitHub to search for missin
![node-not-found](./assets/node-not-found.png)
### Update
v0.8.0 🚀🚗🚚🏃‍ LaMaInpainting
- 新增 LaMaInpainting
- 优化color节点的输出
- 修复高清显示屏上定位节点不准的情况
- Add LaMaInpainting
- Optimize the output of the color node
- Fix the issue of inaccurate positioning node on high-definition display screens
### Models
[Download CLIPSeg](https://huggingface.co/CIDAS/clipseg-rd64-refined/tree/main), move to : model/clipseg
<!-- ### Workflow
[Workflow](./workflow.md) -->
[Download CLIPSeg](https://huggingface.co/CIDAS/clipseg-rd64-refined/tree/main), move to : models/clipseg
[Download lama](https://github.com/enesmsahin/simple-lama-inpainting/releases/download/v0.1.0/big-lama.pt), move to : models/lama
[Download Salesforce\blip-image-captioning-base](https://huggingface.co/Salesforce/blip-image-captioning-base), move to : models/clip_interrogator/Salesforce/blip-image-captioning-base
## Installation
@@ -137,7 +205,6 @@ pip3 install -r requirements.txt
访问 [www.mixcomfy.com](https://www.mixcomfy.com),获得更多内测功能,关注微信公众号:Mixlab无界社区
#### Thanks:
[ComfyUI-CLIPSeg](https://github.com/biegert/ComfyUI-CLIPSeg/tree/main)
+224 -12
View File
@@ -4,7 +4,7 @@ import subprocess
import importlib.util
import sys,json
import urllib
import hashlib
import datetime
@@ -79,6 +79,13 @@ install_openai()
current_path = os.path.abspath(os.path.dirname(__file__))
def calculate_md5(string):
encoded_string = string.encode()
md5_hash = hashlib.md5(encoded_string).hexdigest()
return md5_hash
def create_key(key_p,crt_p):
import OpenSSL
# 生成自签名证书
@@ -126,8 +133,38 @@ def create_for_https():
return (crt,key)
# workflow 目录下的所有json
def read_workflow_json_files_all(folder_path):
print('#read_workflow_json_files_all',folder_path)
json_files = []
for root, dirs, files in os.walk(folder_path):
for file in files:
if file.endswith('.json'):
json_files.append(os.path.join(root, file))
data = []
for file_path in json_files:
try:
with open(file_path) as json_file:
json_data = json.load(json_file)
creation_time = datetime.datetime.fromtimestamp(os.path.getctime(file_path))
numeric_timestamp = creation_time.timestamp()
file_info = {
'filename': os.path.basename(file_path),
'category': os.path.dirname(file_path),
'data': json_data,
'date': numeric_timestamp
}
data.append(file_info)
except Exception as e:
print(e)
sorted_data = sorted(data, key=lambda x: x['date'], reverse=True)
return sorted_data
# workflow
def read_workflow_json_files(folder_path):
def read_workflow_json_files(folder_path ):
json_files = []
for filename in os.listdir(folder_path):
if filename.endswith('.json'):
@@ -162,12 +199,125 @@ def get_workflows():
workflows=read_workflow_json_files(workflow_path)
return workflows
def get_my_workflow_for_app(filename="my_workflow_app.json",category="",is_all=False):
app_path=os.path.join(current_path, "app")
if not os.path.exists(app_path):
os.mkdir(app_path)
category_path=os.path.join(app_path,category)
if not os.path.exists(category_path):
os.mkdir(category_path)
apps=[]
if filename==None:
#TODO 支持目录内遍历
if is_all:
data=read_workflow_json_files_all(category_path)
else:
data=read_workflow_json_files(category_path)
i=0
for item in data:
# print(item)
try:
x=item["data"]
if i==0:
apps.append({
"filename":item["filename"],
# "category":item['category'],
"data":x,
"date":item["date"],
})
else:
category=''
if 'category' in x['app']:
category=x['app']['category']
apps.append({
"filename":item["filename"],
"category":category,
"data":{
"app":{
"category":category,
"description":x['app']['description'],
"filename":(x['app']['filename'] if 'filename' in x['app'] else "") ,
"icon":(x['app']['icon'] if 'icon' in x['app'] else None),
"name":x['app']['name'],
"version":x['app']['version'],
}
},
"date":item["date"]
})
i+=1
except Exception as e:
print("发生异常:", str(e))
else:
app_workflow_path=os.path.join(category_path, filename)
# print('app_workflow_path: ',app_workflow_path)
try:
with open(app_workflow_path) as json_file:
apps = [{
'filename':filename,
'data':json.load(json_file)
}]
except Exception as e:
print("发生异常:", str(e))
if len(apps)==1 and category!='' and category!=None:
data=read_workflow_json_files(category_path)
for item in data:
x=item["data"]
# print(apps[0]['filename'] ,item["filename"])
if apps[0]['filename']!=item["filename"]:
category=''
if 'category' in x['app']:
category=x['app']['category']
apps.append({
"filename":item["filename"],
# "category":category,
"data":{
"app":{
"category":category,
"description":x['app']['description'],
"filename":(x['app']['filename'] if 'filename' in x['app'] else "") ,
"icon":(x['app']['icon'] if 'icon' in x['app'] else None),
"name":x['app']['name'],
"version":x['app']['version'],
}
},
"date":item["date"]
})
return apps
def save_workflow_json(data):
workflow_path=os.path.join(current_path, "workflow/my_workflow.json")
with open(workflow_path, 'w') as file:
json.dump(data, file)
return workflow_path
def save_workflow_for_app(data,filename="my_workflow_app.json",category=""):
app_path=os.path.join(current_path, "app")
if not os.path.exists(app_path):
os.mkdir(app_path)
category_path=os.path.join(app_path,category)
if not os.path.exists(category_path):
os.mkdir(category_path)
app_workflow_path=os.path.join(category_path, filename)
try:
output_str = json.dumps(data['output'])
data['app']['id']=calculate_md5(output_str)
# id=data['app']['id']
except Exception as e:
print("发生异常:", str(e))
with open(app_workflow_path, 'w') as file:
json.dump(data, file)
return filename
def get_nodes_map():
# print("#####path::", current_path)
@@ -253,6 +403,18 @@ async def mixlab_hander(request):
print(e)
return web.json_response(data)
@routes.get('/mixlab/app')
async def mixlab_app_handler(request):
html_file = os.path.join(current_path, "web/index.html")
if os.path.exists(html_file):
with open(html_file, 'r', encoding='utf-8', errors='ignore') as f:
html_data = f.read()
return web.Response(text=html_data, content_type='text/html')
else:
return web.Response(text="HTML file not found", status=404)
@routes.post('/mixlab/workflow')
async def mixlab_workflow_hander(request):
data = await request.json()
@@ -265,6 +427,29 @@ async def mixlab_workflow_hander(request):
'status':'success',
'file_path':file_path
}
elif data['task']=='save_app':
category=""
if "category" in data:
category=data['category']
file_path=save_workflow_for_app(data['data'],data['filename'],category)
result={
'status':'success',
'file_path':file_path
}
elif data['task']=='my_app':
filename=None
category=""
admin=False
if 'filename' in data:
filename=data['filename']
if 'category' in data:
category=data['category']
if 'admin' in data:
admin=data['admin']
result={
'data':get_my_workflow_for_app(filename,category,admin),
'status':'success',
}
elif data['task']=='list':
result={
'data':get_workflows(),
@@ -289,6 +474,7 @@ async def nodes_map_hander(request):
return web.json_response(result)
# 把插件自定义的路由添加到comfyui server里
def new_add_routes(self):
import nodes
self.app.add_routes(routes)
@@ -317,27 +503,34 @@ PromptServer.add_routes=new_add_routes
# 导入节点
from .nodes.PromptNode import RandomPrompt
from .nodes.ImageNode import TransparentImage,LoadImagesFromPath,TextImage,SvgImage,Image3D,EmptyLayer,ShowLayer,NewLayer,MergeLayers,AreaToMask,SmoothMask,FeatheredMask,SplitLongMask,ImageCropByAlpha,EnhanceImage,FaceToMask
from .nodes.PromptNode import RandomPrompt,PromptSlide
from .nodes.ImageNode import NoiseImage,TransparentImage,GradientImage,LoadImagesFromPath,LoadImagesFromURL,ResizeImage,TextImage,SvgImage,Image3D,ShowLayer,NewLayer,MergeLayers,AreaToMask,SmoothMask,FeatheredMask,SplitLongMask,ImageCropByAlpha,EnhanceImage,FaceToMask
from .nodes.Vae import VAELoader,VAEDecode
from .nodes.ScreenShareNode import ScreenShareNode,FloatingVideo
from .nodes.Clipseg import CLIPSeg,CombineMasks
from .nodes.ChatGPT import ChatGPTNode,ShowTextForGPT,CharacterInText
from .nodes.Audio import SpeechRecognition,SpeechSynthesis
from .nodes.Utils import ColorInput,FontInput
from .nodes.Audio import GamePal,SpeechRecognition,SpeechSynthesis
from .nodes.Utils import AppInfo,IntNumber,FloatSlider,TextInput,ColorInput,FontInput,TextToNumber,DynamicDelayProcessor,LimitNumber,SwitchByIndex,GetImageSize_,MultiplicationNode
from .nodes.Lama import LaMaInpainting
from .nodes.ClipInterrogator import ClipInterrogator
# 要导出的所有节点及其名称的字典
# 注意:名称应全局唯一
NODE_CLASS_MAPPINGS = {
"AppInfo":AppInfo,
"RandomPrompt":RandomPrompt,
"PromptSlide":PromptSlide,
"ClipInterrogator":ClipInterrogator,
"NoiseImage":NoiseImage,
"GradientImage":GradientImage,
"TransparentImage":TransparentImage,
"ResizeImageMixlab":ResizeImage,
"LoadImagesFromPath":LoadImagesFromPath,
"LoadImagesFromURL":LoadImagesFromURL,
"TextImage":TextImage,
"EnhanceImage":EnhanceImage,
"SvgImage":SvgImage,
"3DImage":Image3D,
"EmptyLayer":EmptyLayer,
"3DImage":Image3D,
"ShowLayer":ShowLayer,
"NewLayer":NewLayer,
"MergeLayers":MergeLayers,
@@ -359,11 +552,24 @@ NODE_CLASS_MAPPINGS = {
"SpeechRecognition":SpeechRecognition,
"SpeechSynthesis":SpeechSynthesis,
"Color":ColorInput,
"Font":FontInput
"FloatSlider":FloatSlider,
"IntNumber":IntNumber,
"TextInput_":TextInput,
"Font":FontInput,
"TextToNumber":TextToNumber,
"DynamicDelayProcessor":DynamicDelayProcessor,
"MultiplicationNode":MultiplicationNode,
"GetImageSize_":GetImageSize_,
"SwitchByIndex":SwitchByIndex,
"LimitNumber":LimitNumber,
"LaMaInpainting":LaMaInpainting
# "GamePal":GamePal
}
# 一个包含节点友好/可读的标题的字典
NODE_DISPLAY_NAME_MAPPINGS = {
"AppInfo":"AppInfo ♾️Mixlab",
"ResizeImageMixlab":"ResizeImage ♾️Mixlab",
"RandomPrompt": "Random Prompt ♾️Mixlab",
"SplitLongMask":"Splitting a long image into sections",
"VAELoaderConsistencyDecoder":"Consistency Decoder Loader",
@@ -374,12 +580,18 @@ NODE_DISPLAY_NAME_MAPPINGS = {
"ShowTextForGPT":"ShowTextForGPT ♾️Mixlab",
"MergeLayers":"MergeLayers ♾️Mixlab",
"SpeechSynthesis":"SpeechSynthesis ♾️Mixlab",
"SpeechRecognition":"SpeechRecognition ♾️Mixlab"
"SpeechRecognition":"SpeechRecognition ♾️Mixlab",
"3DImage":"3DImage ♾️Mixlab",
"DynamicDelayProcessor":"DynamicDelayByText ♾️Mixlab",
"LaMaInpainting":"LaMaInpainting ♾️Mixlab",
"PromptSlide":"PromptSlide ♾️Mixlab"
# "GamePal":"GamePal ♾️Mixlab"
}
# web ui的节点功能
WEB_DIRECTORY = "./web"
print('--------------')
print('\033[91mMixlab Nodes: \033[93mLoaded\033[0m')
print('\033[91m ### Mixlab Nodes: \033[93mLoaded\033[0m')
print('--------------')
Binary file not shown.

After

Width:  |  Height:  |  Size: 101 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 240 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 254 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 7.4 MiB

After

Width:  |  Height:  |  Size: 7.1 MiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 8.7 MiB

After

Width:  |  Height:  |  Size: 9.9 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 477 KiB

+10
View File
@@ -0,0 +1,10 @@
Chibi Anime Style
Gakuen Anime Style
Gekiga Anime Style
Jidaimono Anime Style
Kawaii Anime Style
Mecha Anime Style
Realistic Anime Style
Semi-Realistic Anime Style
Shoji Anime Style
Kemonomimi Anime Style
+2052
View File
File diff suppressed because it is too large Load Diff
+23
View File
@@ -0,0 +1,23 @@
GoPro
Drone
polaroid
black and white film
Kodachrome
shot on 8mm
shot on 16mm
shot on 35mm
Microscopic
Fisheye Lens
Wide Angle
Ultra-Wide Angle
Panorama
Short Exposure
Long Exposure
Double Exposure
f2.8
Depth of Field
Soft Focus
Deep Focus
Shallow Focus
Vanishing Point
Vantage Point
+16 -5
View File
@@ -4762,25 +4762,31 @@
"https://github.com/shadowcz007/comfyui-mixlab-nodes": [
[
"3DImage",
"AppInfo",
"IntNumber",
"FloatSlider",
"ResizeImage",
"NoiseImage",
"AreaToMask",
"CLIPSeg",
"CLIPSeg_",
"CharacterInText",
"ChatGPTOpenAI",
"Color",
"CombineMasks_",
"CombineSegMasks",
"EmptyLayer",
"EnhanceImage",
"GradientImage",
"FaceToMask",
"FeatheredMask",
"FloatingVideo",
"Font",
"ImageCropByAlpha",
"LoadImagesFromPath",
"LoadImagesFromURL",
"MergeLayers",
"NewLayer",
"RandomPrompt",
"PromptSlide",
"ClipInterrogator",
"ScreenShare",
"ShowLayer",
"ShowTextForGPT",
@@ -4790,12 +4796,17 @@
"SplitLongMask",
"SvgImage",
"TextImage",
"ResizeImageMixlab",
"TransparentImage",
"VAEDecodeConsistencyDecoder",
"VAELoaderConsistencyDecoder"
"VAELoaderConsistencyDecoder",
"TextToNumber",
"TextInput_",
"DynamicDelayProcessor",
"LaMaInpainting"
],
{
"title_aux": "comfyui-mixlab-nodes [WIP]"
"title_aux": "comfyui-mixlab-nodes"
}
],
"https://github.com/shiimizu/ComfyUI_smZNodes": [
+16
View File
@@ -0,0 +1,16 @@
Mood Lighting
Moody Lighting
Studio Lighting
Cove Lighting
Soft Lighting
Hard Lighting
Volumetric Lighting
Low-Key Lighting
High-Key Lighting
Epic Light
Rembrandt Lighting
Contre-Jour
Veiling Flare
Crepuscular Rays
Rays of Shimmering Light
Godrays
+132
View File
@@ -0,0 +1,132 @@
Aaron Siskind
Alessio Albi
Alfred Eisenstaedt
Alfred Stieglitz
Alyssa Monks
André Kertész
Andreas Gursky
Andrew Wyeth
Anne Geddes
Annie Leibovitz
Ansel Adams
Arnold Newman
August Sander
Balthus
Berenice Abbott
Bill Brandt
Bill Henson
Brassaï (Gyula Halász)
Brooke Shaden
Bruce Davidson
Bruce Weber
Bunny Yeager
Carleton Watkins
Carrie Mae Weems
Chuck Close
Cindy Sherman
Clarence H. White
Claude Cahun
Danny Lyon
David LaChapelle
Dawoud Bey
Diane Arbus
Don McCullin
Dora Maar
Dorothea Lange
Duane Michals
Eadweard Muybridge
Edward Burtynsky
Edward Curtis
Edward Ruscha
Edward Steichen
Edward Weston
Elliott Erwitt
Ernst Haas
Eugene Atget
Fan Ho
Francesca Woodman
Frans Lanting
Garry Winogrand
Georges Melies
Gerda Taro
Gertrude Käsebier
Gordon Parks
Graciela Iturbide
Gregory Crewdson
Harold Edgerton
Helen Levitt
Helmut Newton
Hendrik Kerstens
Henri Cartier-Bresson
Hugh Kretschmer
Irving Penn
Jacques Henri Lartigue
James Nachtwey
James Van Der Zee
Jay Maisel
Jerry Uelsmann
Joel Peter Witkin
Joel Sartore
John Frederick William Herschel
Josef Sudek
Julia Margaret Cameron
Karl Blossfeldt
Larry Burrows
László Moholy-Nagy (photography)
Lee Jeffries
Lewis Hine
Lorna Simpson
Lynsey Addario
Margaret Bourke-White
Mario Testino
Martin Parr
Martin Schoeller
Mary Ellen Mark
Mathew B. Brady
Méret Oppenheim
Meryl McMaster
Mick Rock
Miles Aldridge
Minor Martin White
Nan Goldin
Nathan Wirth
Olive Cotton
Olivier Rousteing
Patrick Demarchelier
Paul Nicklen
Paul Outerbridge
Paul Strand
Pete Souza
Peter Dombrovskis
Peter Henry Emerson
Peter Lik
Peter Lindbergh
Philip-Lorca diCorcia
Philippe Halsman
Ralph Gibson
Richard Avedon
Robert Adams
Robert Bechtle
Robert Capa
Robert Frank
Robert Mapplethorpe
Roger Fenton
Ruth Bernhard
Sally Mann
Sebastião Salgado
Shirin Neshat
Stefan Gesell
Steven Meisel
Susan Meiselas
Vivian Maier
Vivian Maier
Viviane Sassen
Walker Evans
Wes Anderson
William Eggleston
William Eugene Smith
William Henry Fox Talbot
Yinka Shonibare
Yousuf Karsh
Man Ray
Robert Mapplethorpe
+135
View File
@@ -0,0 +1,135 @@
Vintage
Grain
Sepia
High Key
Low Key
High Dynamic Range
Cross Process
Radial Blur
Infrared
Lomo
Photocopy
Pencil Sketch
Pop Art
Orton
Mosaic
Selective Black and White
Torn Paper
Tilt-Shift
Double Exposure
Polaroid
Liquid Ink
Color Splash
Sketch
Water Drops
Polarizer
Chinese Painting
Water Droplets
Polarization
Color Inversion
Fish-eye
Soft Focus
Solarization
Posterize
Comic Book
Duotone
Gradient Map
Edge Detection
Oil Painting
Reflection
Mirror
ASCII Art
Glitch
Time-Lapse
Day to Night
Surreal
Black and White
Sepia Tone
Vintage Film
Grainy Texture
High Key Lighting
Low Key Lighting
Cross Processed Film
Infrared Photography
Photocopy
Pencil Drawing
Pop Art Filter
Mosaic Filter
Selective Desaturation
Torn Paper
Tilt-Shift Photography
Double Exposure
Polaroid Style Frame
Water Drops Texture
Polarizer
Chinese Painting
Water Droplets Texture
Polarization
Color Inversion
Fish-eye Lens
Soft Focus
Solarize Filter
Edge Detection
Oil Painting
Reflection
Mirror Image
Time-Lapse Photography
Day to Night Transition
Surreal Art Style
Abstract Expressionism
Acrylic Painting
Anime
Art Deco
Biomorphic Abstraction
Black and White Photograph
Cartoon
Charcoal Sketch
Chibi Anime
Chinese Painting
Classicist Painting
Collage
Concept Art
Cyberpunk
Dada Art
Digital Art
Fantasy Art
Fashion Art
Fashion Sketch
Fish-Eye lens Photograph
Goth Art
Graffiti
Harlem Renaissance
High Key Photograph
Hyperrealist Pencil Sketch
Impressionist Painting
Josei Anime
Long Exposure Photograph
Low Key Photograph
Macro Photograph
Manga
Metal Sculpture
Mid Century Modern Illustration
Mixed Media
Modern Art
Moe Anime
Nihonga
Origami
Paper Mache
Pen and Ink
Pencil Sketch
Photograph
Photorealism
Pinup Art
Romanticist Painting
Sci-Fi Art
Semi Realistic Fantasy Art
Semi Realistic Cyberpunk Art
Shallow Depth of Field Photograph
Steam Punk Art
Stone Sculpture
Superhero Comic
Surrealist Art
Tempura Painting
Underground Comic
Watercolor Painting
Zulu Urban Art
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+56 -2
View File
@@ -7,6 +7,16 @@ class SpeechRecognition:
def INPUT_TYPES(s):
return {"required": {
"upload":("AUDIOINPUTMIX",), },
"optional":{
"start_by":("INT", {
"default": 0,
"min": 0, #Minimum value
"max": 2048, #Maximum value
"step": 1, #Slider's step
"display": "number" # Cosmetic only: display as "number" or "slider"
}),
}
}
RETURN_TYPES = ("STRING",)
@@ -19,8 +29,8 @@ class SpeechRecognition:
INPUT_IS_LIST = False
OUTPUT_IS_LIST = (False,)
def run(self,upload):
return (upload,)
def run(self,upload,start_by):
return {"ui": {"start_by": [start_by]}, "result": (upload,)}
class SpeechSynthesis:
@@ -44,3 +54,47 @@ class SpeechSynthesis:
# print(session_history)
return {"ui": {"text": text}, "result": (text,)}
#
class GamePal:
@classmethod
def INPUT_TYPES(s):
return {
"required": {
"input_text": ("STRING",{"multiline": True,"default": ""}),
},
"optional": {
"input_num": ("INT",{
"default":100,
"min": -1, #Minimum value
"max": 0xffffffffffffffff, #Maximum value
"step": 1, #Slider's step
"display": "slider" # Cosmetic only: display as "number" or "slider"
}),
"python_code": ("STRING",{"multiline": True,"default": "result= 1 if 'Mixlab' in input_text else 0"}),
}
}
INPUT_IS_LIST = False
RETURN_TYPES = ("INT",)
FUNCTION = "run"
OUTPUT_NODE = True
OUTPUT_IS_LIST = (False,)
CATEGORY = "♾️Mixlab/audio"
def run(self, input_text,input_num,python_code):
exec(python_code)
res=None
try:
# 可能会引发异常的代码
res=result
except:
# 处理异常的代码
print('')
print(res)
# print(session_history)
return {"ui": {"text": [input_text],"num":[input_num]}, "result": (res,)}
+6 -6
View File
@@ -75,15 +75,15 @@ class ChatGPTNode:
"required": {
"api_key":("KEY", {"default": "", "multiline": True}),
"api_url":("URL", {"default": "", "multiline": True}),
"prompt": ("STRING", {"multiline": True}),
"prompt": ("STRING", {"multiline": True,"dynamicPrompts": False}),
"system_content": ("STRING",
{
"default": "You are ChatGPT, a large language model trained by OpenAI. Answer as concisely as possible.",
"multiline": True
"multiline": True,"dynamicPrompts": False
}),
"model": (["gpt-3.5-turbo","gpt-35-turbo","gpt-3.5-turbo-16k", "gpt-3.5-turbo-16k-0613", "gpt-4-0613","gpt-4-1106-preview"],
{"default": "gpt-3.5-turbo"}),
"seed": ("INT", {"default": 0, "min": 0, "max": 10000, "step": 1}),
"seed": ("INT", {"default": 0, "min": 0, "max": 0xffffffffffffffff, "step": 1}),
"context_size":("INT", {"default": 1, "min": 0, "max":30, "step": 1}),
},
"hidden": {
@@ -167,7 +167,7 @@ class ShowTextForGPT:
def INPUT_TYPES(s):
return {
"required": {
"text": ("STRING", {"forceInput": True}),
"text": ("STRING", {"forceInput": True,"dynamicPrompts": False}),
}
}
@@ -189,8 +189,8 @@ class CharacterInText:
def INPUT_TYPES(s):
return {
"required": {
"text": ("STRING", {"multiline": True}),
"character": ("STRING", {"multiline": True}),
"text": ("STRING", {"multiline": True,"dynamicPrompts": False}),
"character": ("STRING", {"multiline": True,"dynamicPrompts": False}),
"start_index": ("INT", {
"default": 1,
"min": 0, #Minimum value
+158
View File
@@ -0,0 +1,158 @@
import os
import folder_paths
from PIL import Image
import comfy.utils
import numpy as np
import json
import torch
from transformers import AutoProcessor, BlipForConditionalGeneration
from clip_interrogator import Config, Interrogator
def load_caption_model(model_path,config,t='blip-base'):
dtype=torch.float16 if config.device == 'cuda' else torch.float32
caption_model = BlipForConditionalGeneration.from_pretrained(model_path, torch_dtype=dtype)
caption_processor = AutoProcessor.from_pretrained(model_path)
caption_model.eval()
if not config.caption_offload:
caption_model = caption_model.to(config.device)
return (caption_model,caption_processor)
caption_model_path=os.path.join(folder_paths.models_dir, "clip_interrogator/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")
cache_path=os.path.join(folder_paths.models_dir, "clip_interrogator")
# Tensor to PIL
def tensor2pil(image):
return Image.fromarray(np.clip(255. * image.cpu().numpy().squeeze(), 0, 255).astype(np.uint8))
# Convert PIL to Tensor
def pil2tensor(image):
return torch.from_numpy(np.array(image).astype(np.float32) / 255.0).unsqueeze(0)
def image_analysis(ci,image):
image = image.convert('RGB')
image_features = ci.image_to_features(image)
top_mediums = ci.mediums.rank(image_features, 5)
top_artists = ci.artists.rank(image_features, 5)
top_movements = ci.movements.rank(image_features, 5)
top_trendings = ci.trendings.rank(image_features, 5)
top_flavors = ci.flavors.rank(image_features, 5)
medium_ranks = {medium: sim for medium, sim in zip(top_mediums, ci.similarities(image_features, top_mediums))}
artist_ranks = {artist: sim for artist, sim in zip(top_artists, ci.similarities(image_features, top_artists))}
movement_ranks = {movement: sim for movement, sim in zip(top_movements, ci.similarities(image_features, top_movements))}
trending_ranks = {trending: sim for trending, sim in zip(top_trendings, ci.similarities(image_features, top_trendings))}
flavor_ranks = {flavor: sim for flavor, sim in zip(top_flavors, ci.similarities(image_features, top_flavors))}
return medium_ranks, artist_ranks, movement_ranks, trending_ranks, flavor_ranks
def image_to_prompt(ci,image, mode):
ci.config.chunk_size = 2048 if ci.config.clip_model_name == "ViT-L-14/openai" else 1024
ci.config.flavor_intermediate_count = 2048 if ci.config.clip_model_name == "ViT-L-14/openai" else 1024
image = image.convert('RGB')
if mode == 'best':
return ci.interrogate(image)
elif mode == 'classic':
return ci.interrogate_classic(image)
elif mode == 'fast':
return ci.interrogate_fast(image)
elif mode == 'negative':
return ci.interrogate_negative(image)
# image = Image.open(image_path).convert('RGB')
# ci = Interrogator(Config(clip_model_name="ViT-L-14/openai"))
# print(ci.interrogate(image))
class ClipInterrogator:
@classmethod
def INPUT_TYPES(s):
return {"required": {
"image": ("IMAGE",),
"prompt_mode": (['fast','classic','best','negative'],),
"image_analysis": (["off","on"],),
},
}
RETURN_TYPES = ("STRING","STRING",)
RETURN_NAMES = ("prompt","analysis",)
FUNCTION = "run"
CATEGORY = "♾️Mixlab/prompt"
INPUT_IS_LIST = True
OUTPUT_IS_LIST = (True,)
global ci
ci = None
def run(self,image,prompt_mode,image_analysis):
global ci
prompt_mode=prompt_mode[0]
analysis=image_analysis[0]
prompt_result=[]
analysis_result=[]
# 进度条
pbar = comfy.utils.ProgressBar(len(image)*(2 if analysis=='on' else 1))
if ci==None:
config=Config(
clip_model_name="ViT-L-14/openai",
device="cuda" if torch.cuda.is_available() else "cpu",
download_cache=True,
clip_model_path=cache_path,
cache_path=cache_path
)
config.apply_low_vram_defaults()
caption_model,caption_processor=load_caption_model(caption_model_path,config)
config.caption_model= caption_model
config.caption_processor= caption_processor
ci = Interrogator(config)
# else:
# simple_lama.model.to("cuda" if torch.cuda.is_available() else "cpu")
for i in range(len(image)):
im=image[i]
im=tensor2pil(im)
im=im.convert('RGB')
if analysis=='on':
analysis_res=image_analysis(ci,im)
analysis_result.append(json.dumps(analysis_res))
pbar.update(1)
prompt=image_to_prompt(ci,im,prompt_mode)
pbar.update(1)
prompt_result.append(prompt)
# result.save("inpainted.png")
if ci.config.clip_offload and not ci.clip_offloaded:
ci.clip_model = ci.clip_model.to('cpu')
ci.clip_offloaded = True
if ci.config.caption_offload and not ci.caption_offloaded:
ci.caption_model = ci.caption_model.to('cpu')
ci.caption_offloaded = True
return {"ui":{"prompt": prompt_result,"analysis":analysis_result},"result": (prompt_result,analysis_result,)}
+19 -8
View File
@@ -31,10 +31,20 @@ logger = logging.getLogger('CLIPSeg nodes')
clipseg_model_dir = os.path.join(folder_paths.models_dir, "clipseg")
if not os.path.exists(clipseg_model_dir):
print(f"## clipseg model not found: {clipseg_model_dir},pls download from https://huggingface.co/CIDAS/clipseg-rd64-refined/tree/main")
clipseg_model_dir='CIDAS/clipseg-rd64-refined'
"""Helper methods for CLIPSeg nodes"""
# Tensor to PIL
def tensor2pil(image):
return Image.fromarray(np.clip(255. * image.cpu().numpy().squeeze(), 0, 255).astype(np.uint8))
# Convert PIL to Tensor
def pil2tensor(image):
return torch.from_numpy(np.array(image).astype(np.float32) / 255.0).unsqueeze(0)
def tensor_to_numpy(tensor: torch.Tensor) -> np.ndarray:
"""Convert a tensor to a numpy array and scale its values to 0-255."""
array = tensor.numpy().squeeze()
@@ -91,7 +101,7 @@ class CLIPSeg:
return {"required":
{
"image": ("IMAGE",),
"text": ("STRING", {"multiline": False}),
"text": ("STRING", {"multiline": False,"dynamicPrompts": False}),
},
"optional":
@@ -107,7 +117,7 @@ class CLIPSeg:
RETURN_NAMES = ("Mask","Heatmap Mask", "BW Mask")
# INPUT_IS_LIST = True
# OUTPUT_IS_LIST = (True,)
OUTPUT_IS_LIST = (False,False,False,)
FUNCTION = "segment_image"
def segment_image(self, image: torch.Tensor, text: str, blur: float, threshold: float, dilation_factor: int) -> Tuple[torch.Tensor, torch.Tensor, torch.Tensor]:
@@ -180,12 +190,13 @@ class CLIPSeg:
binary_mask_image = Image.fromarray(binary_mask_resized[..., 0])
# convert PIL image to numpy array
tensor_bw = binary_mask_image.convert("RGB")
tensor_bw = np.array(tensor_bw).astype(np.float32) / 255.0
tensor_bw = torch.from_numpy(tensor_bw)[None,]
tensor_bw = tensor_bw.squeeze(0)[..., 0]
tensor_bw = binary_mask_image.convert("L")
tensor_bw=pil2tensor(tensor_bw)
# tensor_bw = np.array(tensor_bw).astype(np.float32) / 255.0
# tensor_bw = torch.from_numpy(tensor_bw)[None,]
# tensor_bw = tensor_bw.squeeze(0)[..., 0]
return tensor_bw, image_out_heatmap, image_out_binary
return (tensor_bw, image_out_heatmap, image_out_binary,)
#OUTPUT_NODE = False
@@ -235,7 +246,7 @@ class CombineMasks:
# Resize heatmap and binary mask to match the original image dimensions
dimensions = (image_np.shape[1], image_np.shape[0])
print('heatmap',heatmap)
# print('heatmap',heatmap)
if dimensions is None or dimensions[0] == 0 or dimensions[1] == 0:
raise ValueError("Invalid dimensions")
+704 -99
View File
File diff suppressed because it is too large Load Diff
+84
View File
@@ -0,0 +1,84 @@
import os
import folder_paths
from simple_lama_inpainting import SimpleLama
from PIL import Image
import numpy as np
import torch
llma_model_path=os.path.join(folder_paths.models_dir, "lama/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")
else:
os.environ['LAMA_MODEL'] = llma_model_path
# Tensor to PIL
def tensor2pil(image):
return Image.fromarray(np.clip(255. * image.cpu().numpy().squeeze(), 0, 255).astype(np.uint8))
# Convert PIL to Tensor
def pil2tensor(image):
return torch.from_numpy(np.array(image).astype(np.float32) / 255.0).unsqueeze(0)
# simple_lama = SimpleLama()
# img_path = "image.png"
# mask_path = "mask.png"
# image = Image.open(img_path)
# mask = Image.open(mask_path).convert('L')
# result = simple_lama(image, mask)
# result.save("inpainted.png")
class LaMaInpainting:
@classmethod
def INPUT_TYPES(s):
return {"required": {
"image": ("IMAGE",),
"mask": ("MASK",),
},
}
RETURN_TYPES = ("IMAGE",)
FUNCTION = "run"
CATEGORY = "♾️Mixlab/image"
INPUT_IS_LIST = True
OUTPUT_IS_LIST = (True,)
global simple_lama
simple_lama = None
def run(self,image,mask):
global simple_lama
result=[]
if simple_lama==None:
simple_lama = SimpleLama()
else:
simple_lama.model.to("cuda" if torch.cuda.is_available() else "cpu")
for i in range(len(image)):
im=image[i]
ma=mask[i]
im=tensor2pil(im)
ma=tensor2pil(ma)
ma =ma.convert('L')
res = simple_lama(im, ma)
res=pil2tensor(res)
result.append(res)
# result.save("inpainted.png")
if simple_lama.device=='cuda':
simple_lama.model.to('cpu')
return (result,)
+121 -53
View File
@@ -4,12 +4,11 @@ import json
from urllib import request, parse
def queue_prompt(prompt_workflow):
p = {"prompt": prompt_workflow}
data = json.dumps(p).encode('utf-8')
req = request.Request("http://127.0.0.1:8188/prompt", data=data)
request.urlopen(req)
# def queue_prompt(prompt_workflow):
# p = {"prompt": prompt_workflow}
# data = json.dumps(p).encode('utf-8')
# req = request.Request("http://127.0.0.1:8188/prompt", data=data)
# request.urlopen(req)
default_prompt1='''Swing
@@ -45,6 +44,73 @@ default_prompt1='''Swing
'''
default_prompt1="\n".join([p.strip() for p in default_prompt1.split('\n') if p.strip()!=''])
def addWeight(text, weight=1):
if weight == 1:
return text
else:
return f"({text}:{round(weight,2)})"
class PromptSlide:
@classmethod
def INPUT_TYPES(s):
return {
"required": {
"prompt_keyword": ("STRING",
{
"multiline": False,
"default": '',
"dynamicPrompts": False
}),
"weight":("FLOAT", {"default": 1, "min": -3,"max": 3,
"step": 0.01,
"display": "slider"}),
# "min_value":("FLOAT", {
# "default": -2,
# "min": -10,
# "max": 0xffffffffffffffff,
# "step": 0.01,
# "display": "number"
# }),
# "max_value":("FLOAT", {
# "default": 2,
# "min": -10,
# "max": 0xffffffffffffffff,
# "step": 0.01,
# "display": "number"
# }),
}
}
RETURN_TYPES = ("STRING",)
RETURN_NAMES = ("prompt",)
FUNCTION = "run"
CATEGORY = "♾️Mixlab/prompt"
INPUT_IS_LIST = False
OUTPUT_IS_LIST = (False,)
OUTPUT_NODE = False
# 运行的函数
def run(self,prompt_keyword,weight):
# if weight < min_value:
# weight= min_value
# elif weight > max_value:
# weight= max_value
p=addWeight(prompt_keyword,weight)
return (p,)
class RandomPrompt:
'''
@@ -87,7 +153,7 @@ class RandomPrompt:
# 运行的函数
def run(self,max_count,mutable_prompt,immutable_prompt,random_sample):
print('#运行的函数',mutable_prompt,immutable_prompt,max_count,random_sample)
# print('#运行的函数',mutable_prompt,immutable_prompt,max_count,random_sample)
# Split the text into an array of words
words1 = mutable_prompt.split("\n")
@@ -106,6 +172,8 @@ class RandomPrompt:
w1=w1.strip()
for w2 in words2:
w2=w2.strip()
if '``' not in w2:
w2=w2+',``'
if w1!='' and w2!='':
prompts.append(w2.replace('``', w1))
pbar.update(1)
@@ -126,62 +194,62 @@ class RandomPrompt:
class RunWorkflow:
@classmethod
def INPUT_TYPES(s):
return {
"required": {
"workflow": ("STRING", {
"multiline": False,
"default": ''
}),
"prompt": ("STRING", {
"multiline": False,
"default": ''
}),
"image": ("IMAGE",),
"input_node": ("STRING", {
"multiline": False,
"default": ''
}),
"output_node": ("STRING", {
"multiline": False,
"default": ''
}),
},
# class RunWorkflow:
# @classmethod
# def INPUT_TYPES(s):
# return {
# "required": {
# "workflow": ("STRING", {
# "multiline": False,
# "default": ''
# }),
# "prompt": ("STRING", {
# "multiline": False,
# "default": ''
# }),
# "image": ("IMAGE",),
# "input_node": ("STRING", {
# "multiline": False,
# "default": ''
# }),
# "output_node": ("STRING", {
# "multiline": False,
# "default": ''
# }),
# },
}
# }
RETURN_TYPES = ("IMAGE","STRING",)
# RETURN_TYPES = ("IMAGE","STRING",)
FUNCTION = "run"
# FUNCTION = "run"
CATEGORY = "♾️Mixlab/workflow"
# CATEGORY = "♾️Mixlab/workflow"
OUTPUT_IS_LIST = (True,)
OUTPUT_NODE = True
# OUTPUT_IS_LIST = (True,)
# OUTPUT_NODE = True
# 运行的函数
def run(self,workflow,prompt,image,input_node,output_node):
print('#运行的函数',prompt,image,input_node,output_node)
workflow=json.loads(workflow)
input_node=input_node.split(".")
workflow[input_node[0]][input_node[1]][input_node[2]]=prompt
# # 运行的函数
# def run(self,workflow,prompt,image,input_node,output_node):
# print('#运行的函数',prompt,image,input_node,output_node)
# workflow=json.loads(workflow)
# input_node=input_node.split(".")
# workflow[input_node[0]][input_node[1]][input_node[2]]=prompt
workflow_new={}
# 遍历,seed设为随机
for key, value in workflow.items():
if 'inputs' in value:
if 'seed' in value['inputs']:
value['inputs']['seed']= random.randint(1, 18446744073709551614)
workflow_new[key]=value
# workflow_new={}
# # 遍历,seed设为随机
# for key, value in workflow.items():
# if 'inputs' in value:
# if 'seed' in value['inputs']:
# value['inputs']['seed']= random.randint(1, 18446744073709551614)
# workflow_new[key]=value
queue_prompt(workflow_new)
print('#运行的函数',workflow_new[input_node[0]])
# queue_prompt(workflow_new)
# print('#运行的函数',workflow_new[input_node[0]])
# return (new_prompt)
return {"ui":{"images": []},"result": ([image],['text'],)}
# # return (new_prompt)
# return {"ui":{"images": []},"result": ([image],['text'],)}
+5 -3
View File
@@ -79,11 +79,13 @@ class ScreenShareNode:
def INPUT_TYPES(s):
return { "required":{
"image_base64": ("CHEESE",),
"refresh_rate": ("INT", {"default": 500, "min": 0,"step": 50, "max": 0xffffffffffffffff}),
},
"optional":{
"prompt": ("PROMPT",),
"slide": ("SLIDE",),
"seed": ("SEED",),
# "seed": ("INT", {"default": 1, "min": 0, "max": 0xffffffffffffffff}),
} }
@@ -97,11 +99,11 @@ class ScreenShareNode:
OUTPUT_IS_LIST = (False,False,False,False)
# 运行的函数
def run(self,image_base64,prompt,slide,seed):
def run(self,image_base64,refresh_rate ,prompt,slide,seed):
im,mask=base64_save(image_base64)
# print('##########prompt',prompt)
return (im,prompt,slide,seed)
return {"ui":{"refresh_rate": [refresh_rate]},"result": (im,prompt,slide,seed,)}
class FloatingVideo:
@classmethod
+537 -8
View File
@@ -1,9 +1,53 @@
import os
import re,random
from PIL import Image
import numpy as np
# FONT_PATH= os.path.abspath(os.path.join(os.path.dirname(__file__),'../assets/王汉宗颜楷体繁.ttf'))
import folder_paths
import matplotlib.font_manager as fm
# import json
# import hashlib
# def get_json_hash(json_content):
# json_string = json.dumps(json_content, sort_keys=True)
# hash_object = hashlib.sha256(json_string.encode())
# hash_value = hash_object.hexdigest()
# return hash_value
def tensor2pil(image):
return Image.fromarray(np.clip(255. * image.cpu().numpy().squeeze(), 0, 255).astype(np.uint8))
def create_temp_file(image):
output_dir = folder_paths.get_temp_directory()
(
full_output_folder,
filename,
counter,
subfolder,
_,
) = folder_paths.get_save_image_path('tmp', output_dir)
im=tensor2pil(image)
image_file = f"{filename}_{counter:05}.png"
image_path=os.path.join(full_output_folder, image_file)
im.save(image_path,compress_level=4)
return [{
"filename": image_file,
"subfolder": subfolder,
"type": "temp"
}]
def get_font_files(directory):
font_files = {}
@@ -35,6 +79,16 @@ font_files = get_font_files(r_directory)
# print(font_files)
def flatten_list(nested_list):
flat_list = []
for item in nested_list:
if isinstance(item, list):
flat_list.extend(flatten_list(item))
else:
flat_list.append(item)
return flat_list
class ColorInput:
@classmethod
def INPUT_TYPES(s):
@@ -44,18 +98,23 @@ class ColorInput:
},
}
RETURN_TYPES = ("STRING",)
# RETURN_NAMES = ("WIDTH","HEIGHT","X","Y",)
RETURN_TYPES = ("STRING","INT","INT","INT","FLOAT",)
RETURN_NAMES = ("hex","r","g","b","a",)
FUNCTION = "run"
CATEGORY = "♾️Mixlab/utils"
INPUT_IS_LIST = False
OUTPUT_IS_LIST = (False,False,)
OUTPUT_IS_LIST = (False,False,False,False,False,)
def run(self,color):
return (color,)
h=color['hex']
r=color['r']
g=color['g']
b=color['b']
a=color['a']
return (h,r,g,b,a,)
@@ -76,8 +135,478 @@ class FontInput:
CATEGORY = "♾️Mixlab/utils"
INPUT_IS_LIST = False
OUTPUT_IS_LIST = (False,False,)
OUTPUT_IS_LIST = (False,)
def run(self,font):
return (font_files[font],)
class TextToNumber:
@classmethod
def INPUT_TYPES(s):
return {"required": {
"text": ("STRING",{"multiline": False,"default": "1"}),
"random_number": (["enable", "disable"],),
"number":("INT", {
"default": 0,
"min": 0, #Minimum value
"max": 10000000000, #Maximum value
"step": 1, #Slider's step
"display": "number" # Cosmetic only: display as "number" or "slider"
}),
},
}
RETURN_TYPES = ("INT",)
# RETURN_NAMES = ("WIDTH","HEIGHT","X","Y",)
FUNCTION = "run"
CATEGORY = "♾️Mixlab/utils"
INPUT_IS_LIST = False
OUTPUT_IS_LIST = (False,)
def run(self,text,random_number,number):
numbers = re.findall(r'\d+', text)
result=0
for n in numbers:
result = int(n)
# print(result)
if random_number=='enable' and result>0:
result= random.randint(1, 10000000000)
return {"ui": {"text": [text],"num":[result]}, "result": (result,)}
class FloatSlider:
@classmethod
def INPUT_TYPES(s):
return {"required": {
"number":("FLOAT", {
"default": 0,
"min": 0, #Minimum value
"max": 1, #Maximum value
"step": 0.001, #Slider's step
"display": "slider" # Cosmetic only: display as "number" or "slider"
}),
"min_value":("FLOAT", {
"default": 0,
"min": -0xffffffffffffffff,
"max": 0xffffffffffffffff,
"step": 0.001,
"display": "number"
}),
"max_value":("FLOAT", {
"default": 1,
"min": -0xffffffffffffffff,
"max": 0xffffffffffffffff,
"step": 0.001,
"display": "number"
}),
"step":("FLOAT", {
"default": 0.001,
"min": -0xffffffffffffffff,
"max": 0xffffffffffffffff,
"step": 0.001,
"display": "number"
}),
},
}
RETURN_TYPES = ("FLOAT",)
FUNCTION = "run"
CATEGORY = "♾️Mixlab/utils"
INPUT_IS_LIST = False
OUTPUT_IS_LIST = (False,)
def run(self,number,min_value,max_value,step):
if number < min_value:
number= min_value
elif number > max_value:
number= max_value
return (number,)
class IntNumber:
@classmethod
def INPUT_TYPES(s):
return {"required": {
"number":("INT", {
"default": 0,
"min": -1, #Minimum value
"max": 0xffffffffffffffff,
"step": 1,
"display": "number"
}),
"min_value":("INT", {
"default": 0,
"min": -0xffffffffffffffff,
"max": 0xffffffffffffffff,
"step": 1,
"display": "number"
}),
"max_value":("INT", {
"default": 1,
"min": -0xffffffffffffffff,
"max": 0xffffffffffffffff,
"step": 1,
"display": "number"
}),
"step":("INT", {
"default": 1,
"min": -0xffffffffffffffff,
"max": 0xffffffffffffffff,
"step":1,
"display": "number"
}),
},
}
RETURN_TYPES = ("INT",)
FUNCTION = "run"
CATEGORY = "♾️Mixlab/utils"
INPUT_IS_LIST = False
OUTPUT_IS_LIST = (False,)
def run(self,number,min_value,max_value,step):
if number < min_value:
number= min_value
elif number > max_value:
number= max_value
return (number,)
class MultiplicationNode:
@classmethod
def INPUT_TYPES(s):
return {"required": {
"numberA":(any_type,),
"numberB":("FLOAT", {
"default": 0,
"min": -1, #Minimum value
"max": 0xffffffffffffffff,
"step": 0.1, #Slider's step
"display": "number" # Cosmetic only: display as "number" or "slider"
})
},
}
RETURN_TYPES = ("FLOAT","INT",)
FUNCTION = "run"
CATEGORY = "♾️Mixlab/utils"
INPUT_IS_LIST = False
OUTPUT_IS_LIST = (False,False,)
def run(self,numberA,numberB):
b=int(numberA*numberB)
a=float(numberA*numberB)
return (a,b,)
class TextInput:
@classmethod
def INPUT_TYPES(s):
return {"required": {
"text": ("STRING",{"multiline": True,"default": ""}),
},
}
RETURN_TYPES = ("STRING",)
FUNCTION = "run"
CATEGORY = "♾️Mixlab/utils"
INPUT_IS_LIST = False
OUTPUT_IS_LIST = (False,)
def run(self,text):
return (text,)
# 接收一个值,然后根据字符串或数值长度计算延迟时间,用户可以自定义延迟"字/s",延迟之后将转化
import comfy.samplers
import folder_paths
# import time
class AnyType(str):
"""A special class that is always equal in not equal comparisons. Credit to pythongosssss"""
def __ne__(self, __value: object) -> bool:
return False
any_type = AnyType("*")
import time
class DynamicDelayProcessor:
@classmethod
def INPUT_TYPES(cls):
# print("print INPUT_TYPES",cls)
return {
"required":{
"delay_seconds":("INT",{
"default":1,
"min": 0,
"max": 1000000,
}),
},
"optional":{
"any_input":(any_type,),
"delay_by_text":("STRING",{"multiline":True,}),
"words_per_seconds":("FLOAT",{ "default":1.50,"min": 0.0,"max": 1000.00,"display":"Chars per second?"}),
"replace_output": (["disable","enable"],),
"replace_value":("INT",{ "default":-1,"min": 0,"max": 1000000,"display":"Replacement value"})
}
}
@classmethod
def calculate_words_length(cls,text):
chinese_char_pattern = re.compile(r'[\u4e00-\u9fff]')
english_word_pattern = re.compile(r'\b[a-zA-Z]+\b')
number_pattern = re.compile(r'\b[0-9]+\b')
words_length = 0
for segment in text.split():
if chinese_char_pattern.search(segment):
# 中文字符,每个字符计为 1
words_length += len(segment)
elif number_pattern.match(segment):
# 数字,每个字符计为 1
words_length += len(segment)
elif english_word_pattern.match(segment):
# 英文单词,整个单词计为 1
words_length += 1
return words_length
FUNCTION = "run"
RETURN_TYPES = (any_type,)
RETURN_NAMES = ('output',)
CATEGORY = "♾️Mixlab/utils"
def run(self,any_input,delay_seconds,delay_by_text,words_per_seconds,replace_output,replace_value):
# print(f"Delay text:",delay_by_text )
# 获取开始时间戳
start_time = time.time()
# 计算延迟时间
delay_time = delay_seconds
if delay_by_text and isinstance(delay_by_text, str) and words_per_seconds > 0:
words_length = self.calculate_words_length(delay_by_text)
print(f"Delay text: {delay_by_text}, Length: {words_length}")
delay_time += words_length / words_per_seconds
# 延迟执行
print(f"延迟执行: {delay_time}")
time.sleep(delay_time)
# 获取结束时间戳并计算间隔
end_time = time.time()
elapsed_time = end_time - start_time
print(f"实际延迟时间: {elapsed_time} 秒")
# 根据 replace_output 决定输出值
return (max(0, replace_value),) if replace_output == "enable" else (any_input,)
# app 配置节点
class AppInfo:
@classmethod
def INPUT_TYPES(s):
return {"required": {
"name": ("STRING",{"multiline": False,"default": "Mixlab-App","dynamicPrompts": False}),
"image": ("IMAGE",),
"input_ids":("STRING",{"multiline": True,"default": "\n".join(["1","2","3"]),"dynamicPrompts": False}),
"output_ids":("STRING",{"multiline": True,"default": "\n".join(["5","9"]),"dynamicPrompts": False}),
},
"optional":{
"description":("STRING",{"multiline": True,"default": "","dynamicPrompts": False}),
"version":("INT", {
"default": 1,
"min": 1,
"max": 10000,
"step": 1,
"display": "number"
}),
"share_prefix":("STRING",{"multiline": False,"default": "","dynamicPrompts": False}),
"link":("STRING",{"multiline": False,"default": "https://","dynamicPrompts": False}),
"category":("STRING",{"multiline": False,"default": "","dynamicPrompts": False}),
}
}
RETURN_TYPES = ("IMAGE",)
RETURN_NAMES = ("IMAGE",)
FUNCTION = "run"
CATEGORY = "♾️Mixlab"
INPUT_IS_LIST = True
OUTPUT_IS_LIST = (True,)
def run(self,name,image,input_ids,output_ids,description,version,share_prefix,link,category):
name=name[0]
im=image[0][0]
# image [img,] img[batch,w,h,a] 列表里面是batch,
input_ids=input_ids[0]
output_ids=output_ids[0]
description=description[0]
version=version[0]
share_prefix=share_prefix[0]
link=link[0]
category=category[0]
#TODO batch 的方式需要处理
im=create_temp_file(im)
# id=get_json_hash([name,im,input_ids,output_ids,description,version])
return {"ui": {"json": [name,im,input_ids,output_ids,description,version,share_prefix,link,category]}, "result": (image,)}
class GetImageSize_:
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"image": ("IMAGE",),
}
}
RETURN_TYPES = ("INT", "INT")
RETURN_NAMES = ("width", "height")
FUNCTION = "get_size"
CATEGORY = "♾️Mixlab/utils"
def get_size(self, image):
_, height, width, _ = image.shape
return (width, height)
class SwitchByIndex:
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"A":(any_type,),
"B":(any_type,),
"index":("INT", {
"default": -1,
"min": -1,
"max": 1000,
"step": 1,
"display": "number"
}),
"flat": (['off',"on"],),
}
}
RETURN_TYPES = (any_type,)
RETURN_NAMES = ("C",)
FUNCTION = "run"
CATEGORY = "♾️Mixlab/utils"
INPUT_IS_LIST = True
OUTPUT_IS_LIST = (True,)
def run(self, A,B,index,flat):
flat=flat[0]
C=[]
index=index[0]
for a in A:
C.append(a)
for b in B:
C.append(b)
if flat=='on':
C=flatten_list(C)
if index>-1:
try:
C=[C[index]]
except Exception as e:
C=[]
return (C,)
class LimitNumber:
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"number":(any_type,),
"min_value":("INT", {
"default": 0,
"min": 0,
"max": 0xffffffffffffffff,
"step": 1,
"display": "number"
}),
"max_value":("INT", {
"default": 1,
"min": 1,
"max": 0xffffffffffffffff,
"step": 1,
"display": "number"
}),
}
}
RETURN_TYPES = (any_type,)
RETURN_NAMES = ("number",)
FUNCTION = "run"
CATEGORY = "♾️Mixlab/utils"
INPUT_IS_LIST = False
OUTPUT_IS_LIST = (False,)
def run(self, number, min_value, max_value):
nn=number
if isinstance(number, int):
min_value=int(min_value)
max_value=int(max_value)
if isinstance(number, float):
min_value=float(min_value)
max_value=float(max_value)
if number < min_value:
nn= min_value
elif number > max_value:
nn= max_value
return (nn,)
return (font_files[font],)
+2 -2
View File
@@ -145,7 +145,7 @@ class VAELoader:
RETURN_TYPES = ("VAE",)
FUNCTION = "load_vae"
CATEGORY = "♾️Mixlab/ConsistencyDecoder"
CATEGORY = "♾️Mixlab/_test"
#TODO: scale factor?
def load_vae(self, vae_name):
@@ -165,7 +165,7 @@ class VAEDecode:
RETURN_TYPES = ("IMAGE",)
FUNCTION = "decode"
CATEGORY = "♾️Mixlab/ConsistencyDecoder"
CATEGORY = "♾️Mixlab/_test"
def decode(self, vae, samples):
image = vae.decode(samples["samples"].to("cuda:0"))
+3 -1
View File
@@ -3,4 +3,6 @@ pyOpenSSL
watchdog
opencv-python-headless
matplotlib
openai
openai
simple-lama-inpainting
clip-interrogator==0.6.0
+1891
View File
File diff suppressed because it is too large Load Diff
+630
View File
@@ -0,0 +1,630 @@
import { app } from '../../../scripts/app.js'
import { api } from '../../../scripts/api.js'
import { $el } from '../../../scripts/ui.js'
const getLocalData = key => {
let data = {}
try {
data = JSON.parse(localStorage.getItem(key)) || {}
} catch (error) {
return {}
}
return data
}
function getContentTypeFromBase64 (base64Data) {
const regex = /^data:(.+);base64,/
const matches = base64Data.match(regex)
if (matches && matches.length >= 2) {
return matches[1]
}
return null
}
function base64ToBlobFromURL (base64URL, contentType) {
return fetch(base64URL).then(response => response.blob())
}
const setLocalDataOfWin = (key, value) => {
localStorage.setItem(key, JSON.stringify(value))
// window[key] = value
}
async function uploadImage (blob, fileType = '.svg', filename) {
// const blob = await (await fetch(src)).blob();
const body = new FormData()
body.append(
'image',
new File([blob], (filename || new Date().getTime()) + fileType)
)
const resp = await api.fetchApi('/upload/image', {
method: 'POST',
body
})
// console.log(resp)
let data = await resp.json()
let { name, subfolder } = data
let src = api.apiURL(
`/view?filename=${encodeURIComponent(
name
)}&type=input&subfolder=${subfolder}${app.getPreviewFormatParam()}${app.getRandParam()}`
)
return src
}
function createImage (url) {
let im = new Image()
return new Promise((res, rej) => {
im.onload = () => res(im)
im.src = url
})
}
const parseImage = url => {
return new Promise((res, rej) => {
fetch(url)
.then(response => response.blob())
.then(blob => {
const reader = new FileReader()
reader.onloadend = () => {
const base64data = reader.result
res(base64data)
// 在这里可以将base64数据用于进一步处理或显示图片
}
reader.readAsDataURL(blob)
})
.catch(error => {
console.log('发生错误:', error)
})
})
}
function get_position_style (ctx, widget_width, y, node_height) {
const MARGIN = 4 // the margin around the html element
/* Create a transform that deals with all the scrolling and zooming */
const elRect = ctx.canvas.getBoundingClientRect()
const transform = new DOMMatrix()
.scaleSelf(
elRect.width / ctx.canvas.width,
elRect.height / ctx.canvas.height
)
.multiplySelf(ctx.getTransform())
.translateSelf(MARGIN, MARGIN + y)
return {
transformOrigin: '0 0',
transform: transform,
left: `0`,
top: `0`,
cursor: 'pointer',
position: 'absolute',
maxWidth: `${widget_width - MARGIN * 2}px`,
// maxHeight: `${node_height - MARGIN * 2}px`, // we're assuming we have the whole height of the node
width: `${widget_width - MARGIN * 2}px`,
// height: `${node_height * 0.3 - MARGIN * 2}px`,
// background: '#EEEEEE',
display: 'flex',
flexDirection: 'column',
// alignItems: 'center',
justifyContent: 'space-around'
}
}
async function extractMaterial (
modelViewerVariants,
selectMaterial,
material_img
) {
// 材质
const materialsNames = []
for (
let index = 0;
index < modelViewerVariants.model.materials.length;
index++
) {
let m = modelViewerVariants.model.materials[index]
let thumbUrl
try {
thumbUrl =
await m.pbrMetallicRoughness.baseColorTexture.texture.source.createThumbnail(
1024,
1024
)
} catch (error) {}
if (thumbUrl)
materialsNames.push({
value: m.name,
text: `#${index} ${m.name}`,
index,
thumbUrl
})
}
selectMaterial.innerHTML = ''
material_img.innerHTML = ''
for (let index = 0; index < materialsNames.length; index++) {
const name = materialsNames[index]
const option = document.createElement('option')
option.value = name.thumbUrl
option.textContent = name.text
option.setAttribute('data-index', index)
selectMaterial.appendChild(option)
let img = new Image()
img.src = name.thumbUrl
// img.setAttribute('data-index',name.index)
img.style.width = '40px'
material_img.appendChild(img)
if (index == 0) {
material_img.setAttribute('src', name.thumbUrl)
}
}
}
async function changeMaterial (
modelViewerVariants,
targetMaterial,
newImageUrl
) {
const targetTexture = await modelViewerVariants.createTexture(newImageUrl)
// 用图片创建纹理
targetMaterial.pbrMetallicRoughness.baseColorTexture.setTexture(targetTexture)
}
app.registerExtension({
name: 'Mixlab.3D.3DImage',
async getCustomWidgets (app) {
return {
THREED (node, inputName, inputData, app) {
// console.log('##node', node, inputName, inputData)
const widget = {
type: inputData[0], // the type, CHEESE
name: inputName, // the name, slice
size: [128, 88], // a default size
draw (ctx, node, width, y) {},
computeSize (...args) {
return [128, 88] // a method to compute the current size of the widget
},
async serializeValue (nodeId, widgetIndex) {
let d = getLocalData('_mixlab_3d_image')
// console.log('serializeValue', node)
if (d && d[node.id]) {
let { url, bg, material } = d[node.id]
let data = {}
if (url) {
data.image = await parseImage(url)
}
if (bg) {
data.bg_image = await parseImage(bg)
if (!data.bg_image.match('data:image/')) {
delete data.bg_image
}
}
if (material) {
data.material = await parseImage(material)
}
return JSON.parse(JSON.stringify(data))
} else {
return {}
}
}
}
node.addCustomWidget(widget)
return widget
}
}
},
async beforeRegisterNodeDef (nodeType, nodeData, app) {
if (nodeType.comfyClass == '3DImage') {
const orig_nodeCreated = nodeType.prototype.onNodeCreated
nodeType.prototype.onNodeCreated = async function () {
orig_nodeCreated?.apply(this, arguments)
const uploadWidget = this.widgets.filter(w => w.name == 'upload')[0]
const widget = {
type: 'div',
name: 'upload-preview',
draw (ctx, node, widget_width, y, widget_height) {
Object.assign(
this.div.style,
get_position_style(ctx, widget_width, 88, node.size[1])
)
}
}
widget.div = $el('div', {})
widget.div.style.width = `120px`
document.body.appendChild(widget.div)
const inputDiv = (key, placeholder, preview) => {
let div = document.createElement('div')
const ip = document.createElement('input')
ip.type = 'file'
ip.className = `${'comfy-multiline-input'} ${placeholder}`
div.style = `display: flex;
align-items: center;
margin: 6px 8px;
margin-top: 0;`
ip.placeholder = placeholder
// ip.value = value
ip.style = `outline: none;
border: none;
padding: 4px;
width: 60%;cursor: pointer;
height: 32px;`
const label = document.createElement('label')
label.style = 'font-size: 10px;min-width:32px'
label.innerText = placeholder
div.appendChild(label)
div.appendChild(ip)
let that = this,
filename = new Date().getTime()
ip.addEventListener('change', async event => {
const file = event.target.files[0]
const reader = new FileReader()
filename = new Date().getTime()
// 读取文件内容
reader.onload = async e => {
const fileURL = URL.createObjectURL(file)
// console.log('文件URL: ', fileURL)
let html = `<model-viewer src="${fileURL}"
min-field-of-view="0deg" max-field-of-view="180deg"
shadow-intensity="1"
camera-controls
touch-action="pan-y">
<div class="controls">
<div>Variant: <select class="variant"></select></div>
<div>Material: <select class="material"></select></div>
<div>Material: <div class="material_img"> </div></div>
<div><button class="bg">BG</button></div>
<div><button class="export">Export GLB</button></div>
</div></model-viewer>`
preview.innerHTML = html
if (that.size[1] < 400) {
that.setSize([that.size[0], that.size[1] + 300])
app.canvas.draw(true, true)
}
const modelViewerVariants = preview.querySelector('model-viewer')
const select = preview.querySelector('.variant')
const selectMaterial = preview.querySelector('.material')
const material_img = preview.querySelector('.material_img')
const bg = preview.querySelector('.bg')
const exportGLB = preview.querySelector('.export')
if (modelViewerVariants) {
modelViewerVariants.style.width = `${that.size[0] - 24}px`
modelViewerVariants.style.height = `${that.size[1] - 48}px`
}
modelViewerVariants.addEventListener('load', async () => {
const names = modelViewerVariants.availableVariants
// 变量
for (const name of names) {
const option = document.createElement('option')
option.value = name
option.textContent = name
select.appendChild(option)
}
// Adds a default option.
if (names.length === 0) {
const option = document.createElement('option')
option.value = 'default'
option.textContent = 'Default'
select.appendChild(option)
}
// 材质
extractMaterial(
modelViewerVariants,
selectMaterial,
material_img
)
})
let timer = null
const delay = 500 // 延迟时间,单位为毫秒
async function checkCameraChange () {
let dd = getLocalData(key)
let base64Data = modelViewerVariants.toDataURL()
const contentType = getContentTypeFromBase64(base64Data)
const blob = await base64ToBlobFromURL(base64Data, contentType)
// const fileBlob = new Blob([e.target.result], { type: file.type });
let url = await uploadImage(blob, '.png')
// console.log(url)
let bg_blob = await base64ToBlobFromURL(
'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mN88uXrPQAFwwK/6xJ6CQAAAABJRU5ErkJggg=='
)
let url_bg = await uploadImage(bg_blob, '.png')
// console.log('url_bg',url_bg)
if (!dd[that.id]) {
dd[that.id] = { url, bg: url_bg }
} else {
dd[that.id] = { ...dd[that.id], url }
}
// 材质贴图
let thumbUrl = material_img.getAttribute('src')
if (thumbUrl) {
let tb = await base64ToBlobFromURL(thumbUrl)
let tUrl = await uploadImage(tb, '.png')
// console.log('材质贴图', tUrl, thumbUrl)
dd[that.id].material = tUrl
}
setLocalDataOfWin(key, dd)
}
function startTimer () {
if (timer) clearTimeout(timer)
timer = setTimeout(checkCameraChange, delay)
}
modelViewerVariants.addEventListener('camera-change', startTimer)
select.addEventListener('input', async event => {
modelViewerVariants.variantName =
event.target.value === 'default' ? null : event.target.value
// 材质
await extractMaterial(
modelViewerVariants,
selectMaterial,
material_img
)
checkCameraChange()
})
selectMaterial.addEventListener('input', event => {
// console.log(selectMaterial.value)
material_img.setAttribute('src', selectMaterial.value)
if (selectMaterial.getAttribute('data-new-material')) {
let index =
~~selectMaterial.selectedOptions[0].getAttribute(
'data-index'
)
changeMaterial(
modelViewerVariants,
modelViewerVariants.model.materials[index],
selectMaterial.getAttribute('data-new-material')
)
}
checkCameraChange()
})
bg.addEventListener('click', () => {
// 创建一个input元素
var input = document.createElement('input')
input.type = 'file'
// 监听input的change事件
input.addEventListener('change', function () {
// 获取上传的文件
var file = input.files[0]
// 创建一个FileReader对象来读取文件
var reader = new FileReader()
// 监听FileReader的load事件
reader.addEventListener('load', async () => {
let base64 = reader.result
// 将读取的文件内容设置为div的背景
preview.style.backgroundImage = 'url(' + base64 + ')'
const contentType = getContentTypeFromBase64(base64)
const blob = await base64ToBlobFromURL(base64, contentType)
// const fileBlob = new Blob([e.target.result], { type: file.type });
let bg_url = await uploadImage(blob, '.png')
let bg_img = await createImage(base64)
let dd = getLocalData(key)
// console.log(dd[that.id],bg_url)
if (!dd[that.id]) dd[that.id] = { url: '', bg: bg_url }
dd[that.id] = {
...dd[that.id],
bg: bg_url,
bg_w: bg_img.naturalWidth,
bg_h: bg_img.naturalHeight
}
setLocalDataOfWin(key, dd)
// 更新尺寸
let w = that.size[0] - 24,
h = (w * bg_img.naturalHeight) / bg_img.naturalWidth
if (modelViewerVariants) {
modelViewerVariants.style.width = `${w}px`
modelViewerVariants.style.height = `${h}px`
}
preview.style.width = `${w}px`
})
// 读取文件
reader.readAsDataURL(file)
})
// 触发input的点击事件
input.click()
})
exportGLB.addEventListener('click', async () => {
const glTF = await modelViewerVariants.exportScene()
const file = new File([glTF], 'export.glb')
const link = document.createElement('a')
link.download = file.name
link.href = URL.createObjectURL(file)
link.click()
})
uploadWidget.value = await uploadWidget.serializeValue()
// 更新尺寸
let dd = getLocalData(key)
// console.log(dd[that.id],bg_url)
if (dd[that.id]) {
const { bg_w, bg_h } = dd[that.id]
if (bg_h && bg_w) {
let w = that.size[0] - 24,
h = (w * bg_h) / bg_w
if (modelViewerVariants) {
modelViewerVariants.style.width = `${w}px`
modelViewerVariants.style.height = `${h}px`
}
preview.style.width = `${w}px`
}
}
}
// 以文本形式读取文件
reader.readAsDataURL(file)
})
return div
}
let preview = document.createElement('div')
preview.className = 'preview'
preview.style = `margin-top: 12px;display: flex;
justify-content: center;
align-items: center;background-repeat: no-repeat;background-size: contain;`
let upload = inputDiv('_mixlab_3d_image', '3D Model', preview)
widget.div.appendChild(upload)
widget.div.appendChild(preview)
this.addCustomWidget(widget)
const onResize = this.onResize
let that = this
this.onResize = function () {
let modelViewerVariants = preview.querySelector('model-viewer')
// 更新尺寸
let dd = getLocalData('_mixlab_3d_image')
// console.log(dd[that.id],bg_url)
if (dd[that.id]) {
const { bg_w, bg_h } = dd[that.id]
if (bg_h && bg_w) {
let w = that.size[0] - 24,
h = (w * bg_h) / bg_w
if (modelViewerVariants) {
modelViewerVariants.style.width = `${w}px`
modelViewerVariants.style.height = `${h}px`
}
preview.style.width = `${w}px`
}
}
return onResize?.apply(this, arguments)
}
const onRemoved = this.onRemoved
this.onRemoved = () => {
upload.remove()
preview.remove()
widget.div.remove()
return onRemoved?.()
}
if (this.onResize) {
this.onResize(this.size)
}
// this.isVirtualNode = true
this.serialize_widgets = false //需要保存参数
}
const onExecuted = nodeType.prototype.onExecuted
nodeType.prototype.onExecuted = function (message) {
const r = onExecuted?.apply?.(this, arguments)
let div = this.widgets.filter(d => d.div)[0]?.div
console.log('Test', this.widgets)
let material = message.material[0]
if (material) {
const { filename, subfolder, type } = material
let src = api.apiURL(
`/view?filename=${encodeURIComponent(
filename
)}&type=${type}&subfolder=${subfolder}${app.getPreviewFormatParam()}${app.getRandParam()}`
)
const modelViewerVariants = div.querySelector('model-viewer')
const selectMaterial = div.querySelector('.material')
let index =
~~selectMaterial.selectedOptions[0].getAttribute('data-index')
selectMaterial.setAttribute('data-new-material', src)
changeMaterial(
modelViewerVariants,
modelViewerVariants.model.materials[index],
src
)
}
this.onResize?.(this.size)
return r
}
}
},
async loadedGraphNode (node, app) {
// Fires every time a node is constructed
// You can modify widgets/add handlers/etc here
const sleep = (t = 1000) => {
return new Promise((res, rej) => {
setTimeout(() => res(1), t)
})
}
if (node.type === '3DImage') {
// await sleep(0)
let widget = node.widgets.filter(w => w.name === 'upload-preview')[0]
let dd = getLocalData('_mixlab_3d_image')
let id = node.id
// console.log('3dImage load', node.widgets[0], node.widgets)
if (!dd[id]) return
let { url, bg } = dd[id]
if (!url) return
// let base64 = await parseImage(url)
let pre = widget.div.querySelector('.preview')
pre.style.width = `${node.size[0]}px`
pre.innerHTML = `
${url ? `<img src="${url}" style="width:100%"/>` : ''}
`
pre.style.backgroundImage = 'url(' + bg + ')'
const uploadWidget = node.widgets.filter(w => w.name == 'upload')[0]
uploadWidget.value = await uploadWidget.serializeValue()
}
}
})
+351
View File
@@ -0,0 +1,351 @@
import { app } from '../../../scripts/app.js'
import { $el } from '../../../scripts/ui.js'
import { api } from '../../../scripts/api.js'
function get_position_style (ctx, widget_width, y, node_height) {
const MARGIN = 12 // the margin around the html element
/* Create a transform that deals with all the scrolling and zooming */
const elRect = ctx.canvas.getBoundingClientRect()
const transform = new DOMMatrix()
.scaleSelf(
elRect.width / ctx.canvas.width,
elRect.height / ctx.canvas.height
)
.multiplySelf(ctx.getTransform())
.translateSelf(MARGIN, MARGIN + y)
return {
transformOrigin: '0 0',
transform: transform,
left: `0`,
top: `0`,
cursor: 'pointer',
position: 'absolute',
maxWidth: `${widget_width - MARGIN * 2}px`,
// maxHeight: `${node_height - MARGIN * 2}px`, // we're assuming we have the whole height of the node
width: `${widget_width - MARGIN * 2}px`,
// height: `${node_height * 0.3 - MARGIN * 2}px`,
// background: '#EEEEEE',
display: 'flex',
flexDirection: 'row',
// alignItems: 'center',
justifyContent: 'flex-start'
}
}
async function drawImageToCanvas (imageUrl) {
var canvas = document.createElement('canvas')
var ctx = canvas.getContext('2d')
var img = new Image()
await new Promise((resolve, reject) => {
img.onload = function () {
var scaleFactor = 320 / img.width
var canvasWidth = img.width * scaleFactor
var canvasHeight = img.height * scaleFactor
canvas.width = canvasWidth
canvas.height = canvasHeight
ctx.drawImage(img, 0, 0, canvasWidth, canvasHeight)
resolve()
}
img.onerror = function () {
reject(new Error('Failed to load image'))
}
img.src = imageUrl
})
var base64 = canvas.toDataURL('image/jpeg')
// console.log(base64); // 输出Base64数据
return base64
// 可以在这里执行其他操作,比如将Base64数据保存到服务器或显示在页面上
}
function extractInputAndOutputData (jsonData, inputIds = [], outputIds = []) {
const data = jsonData
const input = []
const output = []
const seed = {}
for (const id in data) {
if (data.hasOwnProperty(id)) {
let node = app.graph.getNodeById(id)
if (inputIds.includes(id)) {
// let node = app.graph.getNodeById(id)
let options = []
// 模型
try {
if (node.type === 'CheckpointLoaderSimple') {
options = node.widgets.filter(w => w.name === 'ckpt_name')[0]
.options.values
} else if (node.type === 'LoraLoader') {
options = node.widgets.filter(w => w.name === 'lora_name')[0]
.options.values
}
} catch (error) {}
if (node.type == 'IntNumber' || node.type == 'FloatSlider') {
// min max step
let [v, min, max, step] = Array.from(node.widgets, w => w.value)
options = { min, max, step }
// node.widgets.filter(w => w.type === 'number')[0].options
}
if (node.type == 'PromptSlide') {
// min max step
options = node.widgets.filter(w => w.type === 'slider')[0].options
// 备选的keywords清单
let ks = getLocalData(`_mixlab_PromptSlide`)
let keywords = ks[id]
// console.log('keywords',keywords)
if (keywords && keywords[0]) {
options.keywords = keywords
}
}
if(node.type=='Color'){
}
input[inputIds.indexOf(id)] = {
...data[id],
title: node.title,
id,
options
}
// input.push()
}
if (outputIds.includes(id)) {
// let node = app.graph.getNodeById(id)
// output.push()
output[outputIds.indexOf(id)] = { ...data[id], title: node.title, id }
}
if (node.type === 'KSampler') {
// seed 的类型收集
try {
seed[id] = node.widgets.filter(
w => w.name === 'seed'
)[0].linkedWidgets[0].value
} catch (error) {}
}
}
}
return { input, output, seed }
}
function getUrl () {
let api_host = `${window.location.hostname}:${window.location.port}`
let api_base = ''
let url = `${window.location.protocol}//${api_host}${api_base}`
return url
}
const getLocalData = key => {
let data = {}
try {
data = JSON.parse(localStorage.getItem(key)) || {}
} catch (error) {
return {}
}
return data
}
async function save_app (json) {
let url = getUrl()
const res = await fetch(`${url}/mixlab/workflow`, {
method: 'POST',
body: JSON.stringify({
data: json,
task: 'save_app',
filename: json.app.filename,
category: json.app.category
})
})
return await res.json()
}
function downloadJsonFile (jsonData, fileName = 'mix_app.json') {
const dataString = JSON.stringify(jsonData)
const blob = new Blob([dataString], { type: 'application/json' })
const url = URL.createObjectURL(blob)
const link = document.createElement('a')
link.href = url
link.download = fileName
link.click()
// 释放URL对象
setTimeout(() => {
URL.revokeObjectURL(url)
}, 0)
}
async function save (json, download = false) {
const name = json[0],
version = json[5],
share_prefix = json[6], //用于分享的功能扩展
link = json[7], //用于创建界面上的跳转链接
category = json[8] || '', //用于分类
description = json[4],
inputIds = json[2].split('\n').filter(f => f),
outputIds = json[3].split('\n').filter(f => f)
const iconData = json[1][0]
let { filename, subfolder, type } = iconData
let iconUrl = api.apiURL(
`/view?filename=${encodeURIComponent(
filename
)}&type=${type}&subfolder=${subfolder}${app.getPreviewFormatParam()}${app.getRandParam()}`
)
try {
let data = await app.graphToPrompt()
const { input, output, seed } = extractInputAndOutputData(
data.output,
inputIds,
outputIds
)
data.app = {
name,
description,
version,
input,
output,
seed, //控制是fixed 还是random
share_prefix,
link,
category,
filename: `${name}_${version}.json`
}
try {
data.app.icon = await drawImageToCanvas(iconUrl)
} catch (error) {}
// console.log(data.app)
// let http_workflow = app.graph.serialize()
await save_app(data)
if (download) {
await downloadJsonFile(data, data.app.filename)
}
let open = window.confirm(
`You can now access the standalone application on a new page!\n${getUrl()}/mixlab/app?filename=${encodeURIComponent(
data.app.filename
)}&category=${encodeURIComponent(data.app.category)}`
)
if (open)
window.open(
`${getUrl()}/mixlab/app?filename=${encodeURIComponent(
data.app.filename
)}&category=${encodeURIComponent(data.app.category)}`
)
} catch (error) {
console.log('###error', error)
}
}
app.registerExtension({
name: 'Mixlab.utils.AppInfo',
async beforeRegisterNodeDef (nodeType, nodeData, app) {
if (nodeType.comfyClass == 'AppInfo') {
const orig_nodeCreated = nodeType.prototype.onNodeCreated
nodeType.prototype.onNodeCreated = function () {
orig_nodeCreated?.apply(this, arguments)
console.log('#orig_nodeCreated', this)
const widget = {
type: 'div',
name: 'AppInfoRun',
draw (ctx, node, widget_width, y, widget_height) {
Object.assign(
this.div.style,
get_position_style(
ctx,
widget_width,
node.size[1] - widget_height,
node.size[1]
)
)
}
}
const style = `
flex-direction: row;
background-color: var(--comfy-input-bg);
border-radius: 8px;
border-color: var(--border-color);
border-style: solid;
color: var(--descrip-text);`
widget.div = $el('div', {})
const btn = document.createElement('button')
btn.innerText = 'Save & Open'
btn.style = style
btn.addEventListener('click', () => {
// console.log('hahhah')
if (window._mixlab_app_json) {
save(window._mixlab_app_json)
} else {
alert('Please run the workflow before saving')
// app.queuePrompt(0, 1)
this.widgets.filter(w => w.name === 'version')[0].value += 1
}
})
const download = document.createElement('button')
download.innerText = 'Download For App'
download.style = style
download.style.marginLeft = '12px'
download.addEventListener('click', () => {
// console.log('hahhah')
if (window._mixlab_app_json) {
save(window._mixlab_app_json, true)
} else {
alert('Please run the workflow before saving')
// app.queuePrompt(0, 1)
this.widgets.filter(w => w.name === 'version')[0].value += 1
}
})
document.body.appendChild(widget.div)
widget.div.appendChild(btn)
widget.div.appendChild(download)
this.addCustomWidget(widget)
const onRemoved = this.onRemoved
this.onRemoved = () => {
widget.div.remove()
return onRemoved?.()
}
this.serialize_widgets = true //需要保存参数
}
const onExecuted = nodeType.prototype.onExecuted
nodeType.prototype.onExecuted = function (message) {
onExecuted?.apply(this, arguments)
console.log(message.json)
window._mixlab_app_json = message.json
try {
const div = this.widgets.filter(w => w.div)[0].div
Array.from(
div.querySelectorAll('button'),
b => (b.style.background = 'yellow')
)
} catch (error) {}
}
}
}
})
+102 -15
View File
@@ -68,9 +68,8 @@ function speakText (text) {
// speakText('Hello, how are you?');
// #MixCopilot
const start = (element, id, startBtn) => {
startBtn.className='loading_mixlab'
const start = (element, id, startBtn, node) => {
startBtn.className = 'loading_mixlab'
window.recognition = new webkitSpeechRecognition()
@@ -95,15 +94,26 @@ const start = (element, id, startBtn) => {
localStorage.setItem('_mixlab_speech_recognition', JSON.stringify(data))
if (timeoutId) clearTimeout(timeoutId)
if (!window.recognition) return
timeoutId = setTimeout(function () {
console.log('结果传递::', result)
app.queuePrompt(0, 1)
// 把数据发送到chatgpt的输入prompt里
try {
const sendToId = node.widgets.filter(
w => w.name === 'Send to ChatGPT #'
)[0].value
app.graph
.getNodeById(sendToId)
.widgets.filter(w => w.name === 'prompt')[0].value = result
} catch (error) {}
setTimeout(() => app.queuePrompt(0, 1), 100)
window.recognition?.stop()
window.recognition = null;
startBtn.className=''
window.recognition = null
startBtn.className = ''
startBtn.innerText = 'START'
timeoutId = null
@@ -114,7 +124,7 @@ const start = (element, id, startBtn) => {
!window.recognition &&
window._mixlab_speech_synthesis_onend
) {
start(element, id, startBtn)
start(element, id, startBtn, node)
startBtn.innerText = 'STOP'
if (intervalId) {
clearInterval(intervalId)
@@ -171,13 +181,21 @@ app.registerExtension({
nodeType.prototype.onNodeCreated = function () {
orig_nodeCreated?.apply(this, arguments)
const sendTo = ComfyWidgets.INT(
this,
'Send to ChatGPT #',
['INT', { default: 0 }],
app
)
// console.log('sendTo',sendTo)
const widget = {
type: 'div',
name: 'chatgptdiv',
draw (ctx, node, widget_width, y, widget_height) {
Object.assign(
this.div.style,
get_position_style(ctx, widget_width, 44, node.size[1])
get_position_style(ctx, widget_width, 78, node.size[1])
)
}
}
@@ -189,7 +207,14 @@ app.registerExtension({
const inputDiv = (key, placeholder) => {
let div = document.createElement('div')
const startBtn = document.createElement('button')
const textArea = document.createElement('textarea')
textArea.placeholder = 'speak text'
// sendTo.type='range';
// sendTo.min=0;
// sendTo.max=2000;
// sendTo.step=1;
// sendTo.className='comfy-multiline-input'
textArea.className = `${'comfy-multiline-input'} ${placeholder}`
@@ -201,13 +226,18 @@ app.registerExtension({
margin: 0px 8px 6px;`
startBtn.style = `
outline: none;
border: none;
padding: 4px; `
margin-top:48px;
background-color: var(--comfy-input-bg);
border-radius: 8px;
border-color: var(--border-color);
border-style: solid;
color: var(--descrip-text);
`
startBtn.innerText = 'START'
div.appendChild(startBtn)
// div.appendChild(sendTo);
div.appendChild(textArea)
startBtn.addEventListener('click', () => {
@@ -215,13 +245,17 @@ app.registerExtension({
window.recognition.stop()
window.recognition = null
startBtn.innerText = 'START'
startBtn.className=''
startBtn.className = ''
} else {
start(textArea, this.id, startBtn)
start(textArea, this.id, startBtn, this)
startBtn.innerText = 'STOP'
}
})
// sendTo.addEventListener('change',()=>{
// console.log(sendTo.value)
// })
return div
}
@@ -239,16 +273,69 @@ app.registerExtension({
this.serialize_widgets = true //需要保存参数
}
// const onGraphConfigured=nodeType.prototype.onGraphConfigured;
// nodeType.prototype.onGraphConfigured = function (message) {
// onGraphConfigured?.apply(this, arguments)
// console.log('###SpeechRecognition onGraphConfigured',this,message)
// }
const onExecuted = nodeType.prototype.onExecuted
nodeType.prototype.onExecuted = function (message) {
onExecuted?.apply(this, arguments)
// console.log('this.widgets', this.widgets)
try {
// 是否根据start by 开启
let open = message.start_by[0] > 0
if (open) {
const div = this.widgets.filter(w => w.name == 'chatgptdiv')[0].div
const startBtn = div.querySelector('button')
let textArea = div.querySelector('textarea')
if (open && !window.recognition) {
start(textArea, this.id, startBtn, this)
startBtn.innerText = 'STOP'
} else if (!open && window.recognition) {
window.recognition.stop()
window.recognition = null
startBtn.innerText = 'START'
startBtn.className = ''
}
}
} catch (error) {
console.log('###SpeechRecognition', error)
}
}
}
},
async loadedGraphNode (node, app) {
if (node.type === 'SpeechRecognition') {
let data = getLocalData('_mixlab_speech_recognition')
// console.log('_mixlab_speech_recognition', node.widgets)
// console.log('_mixlab_speech_recognition', node )
let div = node.widgets.filter(f => f.type === 'div')[0]
if (div && data[node.id]) {
div.div.querySelector('textarea').value = data[node.id]
}
try {
let open = node.widgets_values[1] > 0
if (open) {
const div = node.widgets.filter(w => w.name == 'chatgptdiv')[0].div
const startBtn = div.querySelector('button')
let textArea = div.querySelector('textarea')
if (open && !window.recognition) {
start(textArea, node.id, startBtn, node)
startBtn.innerText = 'STOP'
} else if (!open && window.recognition) {
window.recognition.stop()
window.recognition = null
startBtn.innerText = 'START'
startBtn.className = ''
}
}
} catch (error) {
console.log('###SpeechRecognition', error)
}
}
}
})
+1 -1
View File
@@ -3,7 +3,7 @@ import { app } from '../../../scripts/app.js'
const repoOwner = 'shadowcz007' // 替换为仓库的所有者
const repoName = 'comfyui-mixlab-nodes' // 替换为仓库的名称
const version = 'v0.4.0'
const version = 'v0.10.0'
fetch(`https://api.github.com/repos/${repoOwner}/${repoName}/releases/latest`)
.then(response => response.json())
+1 -1
View File
@@ -61,7 +61,7 @@ app.registerExtension({
async getCustomWidgets (app) {
return {
KEY (node, inputName, inputData, app) {
// console.log('##node', node)
console.log('##inputData', inputData)
const widget = {
type: inputData[0], // the type, CHEESE
name: inputName, // the name, slice
+10 -391
View File
@@ -3,9 +3,7 @@ import { api } from '../../../scripts/api.js'
import { ComfyWidgets } from '../../../scripts/widgets.js'
import { $el } from '../../../scripts/ui.js'
async function uploadImage (blob, fileType = '.svg', filename) {
// const blob = await (await fetch(src)).blob();
const body = new FormData()
body.append(
@@ -195,6 +193,11 @@ const parseSvg = async svgContent => {
svgWidth = viewBox.width
svgHeight = viewBox.height
} else {
try {
svgWidth = ~~svgWidth.replace('px', '')
svgHeight = ~~svgHeight.replace('px', '')
} catch (error) {}
}
// 创建一个新的canvas元素
@@ -358,7 +361,7 @@ app.registerExtension({
setLocalDataOfWin(key, dd)
// console.log(this.id, ip.value.trim())
svgElement.style = `width: 90%;padding: 5%;`
svgElement.style = `width: 90%;padding: 5%;height: auto;`
// 将提取的SVG元素显示在页面上
svgContainer.innerHTML = ''
@@ -403,7 +406,9 @@ app.registerExtension({
this.serialize_widgets = true //需要保存参数
}
}
};
},
async loadedGraphNode (node, app) {
// Fires every time a node is constructed
@@ -426,7 +431,7 @@ app.registerExtension({
let svgStr = await dt.text()
const { svgElement, data, image } = await parseSvg(svgStr)
svgElement.style = `width: 90%;padding: 5%;`
svgElement.style = `width: 90%;padding: 5%;height:auto`
// 将提取的SVG元素显示在页面上
widget.div.querySelector('.preview').innerHTML = ''
@@ -434,392 +439,6 @@ app.registerExtension({
const uploadWidget = node.widgets.filter(w => w.name == 'upload')[0]
uploadWidget.value = await uploadWidget.serializeValue()
// let h=~~getComputedStyle(widget.div).height.replace('px','');
// let w=~~getComputedStyle(widget.div).width.replace('px','');
// // console.log('svg', w,h,node.size)
// node.setSize([
// w,h
// ])
// app.graph.setDirtyCanvas(true)
// console.log(node.widgets_values)
}
}
})
app.registerExtension({
name: 'Mixlab.image.3DImage',
async getCustomWidgets (app) {
return {
THREED (node, inputName, inputData, app) {
// console.log('##node', node, inputName, inputData)
const widget = {
type: inputData[0], // the type, CHEESE
name: inputName, // the name, slice
size: [128, 88], // a default size
draw (ctx, node, width, y) {},
computeSize (...args) {
return [128, 88] // a method to compute the current size of the widget
},
async serializeValue (nodeId, widgetIndex) {
let d = getLocalData('_mixlab_3d_image')
// console.log('serializeValue',d)
if (d && d[node.id]) {
let { url, bg } = d[node.id]
let base64 = await parseImage(url)
let bg_base64 = await parseImage(bg)
return JSON.parse(
JSON.stringify({ image: base64, bg_image: bg_base64 })
)
} else {
return {}
}
}
}
node.addCustomWidget(widget)
return widget
}
}
},
async beforeRegisterNodeDef (nodeType, nodeData, app) {
if (nodeType.comfyClass == '3DImage') {
const orig_nodeCreated = nodeType.prototype.onNodeCreated
nodeType.prototype.onNodeCreated = async function () {
orig_nodeCreated?.apply(this, arguments)
const uploadWidget = this.widgets.filter(w => w.name == 'upload')[0]
// console.log('3d nodeData', this.inputs)
const widget = {
type: 'div',
name: 'upload-preview',
draw (ctx, node, widget_width, y, widget_height) {
Object.assign(
this.div.style,
get_position_style(ctx, widget_width, 88, node.size[1])
)
}
}
widget.div = $el('div', {})
widget.div.style.width = `120px`
document.body.appendChild(widget.div)
const inputDiv = (key, placeholder, preview) => {
let div = document.createElement('div')
const ip = document.createElement('input')
ip.type = 'file'
ip.className = `${'comfy-multiline-input'} ${placeholder}`
div.style = `display: flex;
align-items: center;
margin: 6px 8px;
margin-top: 0;`
ip.placeholder = placeholder
// ip.value = value
ip.style = `outline: none;
border: none;
padding: 4px;
width: 60%;cursor: pointer;
height: 32px;`
const label = document.createElement('label')
label.style = 'font-size: 10px;min-width:32px'
label.innerText = placeholder
div.appendChild(label)
div.appendChild(ip)
let that = this,
filename = new Date().getTime()
ip.addEventListener('change', event => {
const file = event.target.files[0]
const reader = new FileReader()
filename = new Date().getTime()
// 读取文件内容
reader.onload = async e => {
const fileURL = URL.createObjectURL(file)
// console.log('文件URL: ', fileURL)
let html = `<model-viewer src="${fileURL}"
min-field-of-view="0deg" max-field-of-view="180deg"
shadow-intensity="1"
camera-controls
touch-action="pan-y">
<div class="controls">
<div>Variant: <select class="variant"></select></div>
<div><button class="bg">BG</button></div>
</div></model-viewer>`
preview.innerHTML = html
if (that.size[1] < 400) {
that.setSize([that.size[0], that.size[1] + 300])
app.canvas.draw(true, true)
};
const modelViewerVariants = preview.querySelector('model-viewer')
const select = preview.querySelector('.variant')
const bg = preview.querySelector('.bg')
if (modelViewerVariants) {
modelViewerVariants.style.width = `${that.size[0] - 24}px`
modelViewerVariants.style.height = `${that.size[1] - 48}px`
}
modelViewerVariants.addEventListener('load', () => {
const names = modelViewerVariants.availableVariants
for (const name of names) {
const option = document.createElement('option')
option.value = name
option.textContent = name
select.appendChild(option)
}
// Adds a default option.
const option = document.createElement('option')
option.value = 'default'
option.textContent = 'Default'
select.appendChild(option)
})
let timer = null
const delay = 500 // 延迟时间,单位为毫秒
async function checkCameraChange () {
let dd = getLocalData(key)
let w, h
let base64Data = modelViewerVariants.toDataURL()
// if (dd[that.id]) {
// w = dd[that.id].bg_w
// h = dd[that.id].bg_h
// }
// // 在这里触发相机停止变化的事件
// // console.log('在这里触发相机停止变化的事件')
// // let base64Data = modelViewerVariants.toDataURL()
// if (w && h) {
// base64Data = await exportModelViewerImage(
// modelViewerVariants.displaycanvas,
// w,
// h
// )
// } else {
// }
const contentType = getContentTypeFromBase64(base64Data)
const blob = await base64ToBlobFromURL(base64Data, contentType)
// const fileBlob = new Blob([e.target.result], { type: file.type });
let url = await uploadImage(blob, '.png')
// console.log(url)
if (!dd[that.id]) dd[that.id] = { url, bg: '' }
dd[that.id] = { ...dd[that.id], url }
setLocalDataOfWin(key, dd)
}
function startTimer () {
if (timer) clearTimeout(timer)
timer = setTimeout(checkCameraChange, delay)
}
modelViewerVariants.addEventListener('camera-change', startTimer)
select.addEventListener('input', event => {
modelViewerVariants.variantName =
event.target.value === 'default' ? null : event.target.value
checkCameraChange()
})
bg.addEventListener('click', () => {
// 创建一个input元素
var input = document.createElement('input')
input.type = 'file'
// 监听input的change事件
input.addEventListener('change', function () {
// 获取上传的文件
var file = input.files[0]
// 创建一个FileReader对象来读取文件
var reader = new FileReader()
// 监听FileReader的load事件
reader.addEventListener('load', async () => {
let base64 = reader.result
// 将读取的文件内容设置为div的背景
preview.style.backgroundImage = 'url(' + base64 + ')'
const contentType = getContentTypeFromBase64(base64)
const blob = await base64ToBlobFromURL(base64, contentType)
// const fileBlob = new Blob([e.target.result], { type: file.type });
let bg_url = await uploadImage(blob, '.png')
let bg_img = await createImage(base64)
let dd = getLocalData(key)
// console.log(dd[that.id],bg_url)
if (!dd[that.id]) dd[that.id] = { url: '', bg: bg_url }
dd[that.id] = {
...dd[that.id],
bg: bg_url,
bg_w: bg_img.naturalWidth,
bg_h: bg_img.naturalHeight
}
setLocalDataOfWin(key, dd)
// 更新尺寸
let w = that.size[0] - 24,
h = (w * bg_img.naturalHeight) / bg_img.naturalWidth
if (modelViewerVariants) {
modelViewerVariants.style.width = `${w}px`
modelViewerVariants.style.height = `${h}px`
}
preview.style.width = `${w}px`
})
// 读取文件
reader.readAsDataURL(file)
})
// 触发input的点击事件
input.click()
})
uploadWidget.value = await uploadWidget.serializeValue()
// 更新尺寸
let dd = getLocalData(key)
// console.log(dd[that.id],bg_url)
if (dd[that.id]) {
const { bg_w, bg_h } = dd[that.id]
if (bg_h && bg_w) {
let w = that.size[0] - 24,
h = (w * bg_h) / bg_w
if (modelViewerVariants) {
modelViewerVariants.style.width = `${w}px`
modelViewerVariants.style.height = `${h}px`
}
preview.style.width = `${w}px`
}
}
}
// 以文本形式读取文件
reader.readAsDataURL(file)
})
return div
}
let preview = document.createElement('div')
preview.className = 'preview'
preview.style = `margin-top: 12px;display: flex;
justify-content: center;
align-items: center;background-repeat: no-repeat;background-size: contain;`
let upload = inputDiv('_mixlab_3d_image', '3D Model', preview)
widget.div.appendChild(upload)
widget.div.appendChild(preview)
this.addCustomWidget(widget)
const onResize = this.onResize
let that=this;
this.onResize = function () {
let modelViewerVariants = preview.querySelector('model-viewer')
// 更新尺寸
let dd = getLocalData('_mixlab_3d_image')
// console.log(dd[that.id],bg_url)
if (dd[that.id]) {
const { bg_w, bg_h } = dd[that.id]
if (bg_h && bg_w) {
let w = that.size[0] - 24,
h = (w * bg_h) / bg_w
if (modelViewerVariants) {
modelViewerVariants.style.width = `${w}px`
modelViewerVariants.style.height = `${h}px`
}
preview.style.width = `${w}px`
}
}
return onResize?.apply(this, arguments)
}
const onRemoved = this.onRemoved
this.onRemoved = () => {
upload.remove()
preview.remove()
widget.div.remove()
return onRemoved?.()
}
if (this.onResize) {
this.onResize(this.size)
}
this.serialize_widgets = true //需要保存参数
}
}
},
async loadedGraphNode (node, app) {
// Fires every time a node is constructed
// You can modify widgets/add handlers/etc here
const sleep = (t = 1000) => {
return new Promise((res, rej) => {
setTimeout(() => res(1), t)
})
}
if (node.type === '3DImage') {
// await sleep(0)
let widget = node.widgets.filter(w => w.name === 'upload-preview')[0]
let dd = getLocalData('_mixlab_3d_image')
let id = node.id
// console.log('3dImage load', node.widgets[0], node.widgets)
if (!dd[id]) return
let { url, bg } = dd[id]
if (!url) return
// let base64 = await parseImage(url)
let pre = widget.div.querySelector('.preview')
pre.style.width = `${node.size[0]}px`
pre.innerHTML = `
${url ? `<img src="${url}" style="width:100%"/>` : ''}
`
pre.style.backgroundImage = 'url(' + bg + ')'
const uploadWidget = node.widgets.filter(w => w.name == 'upload')[0]
uploadWidget.value = await uploadWidget.serializeValue()
// let h=~~getComputedStyle(widget.div).height.replace('px','');
// let w=~~getComputedStyle(widget.div).width.replace('px','');
// // console.log('svg', w,h,node.size)
// node.setSize([
// w,h
// ])
// app.graph.setDirtyCanvas(true)
// console.log(node.widgets_values)
}
}
})
+279 -20
View File
@@ -69,12 +69,12 @@ const parseSvg = async svgContent => {
Array.from(rectElements, (rectElement, i) => {
// 获取rect元素的属性值
var x = ~~(rectElement.getAttribute('x')||0);
var y = ~~(rectElement.getAttribute('y')||0);
var x = ~~(rectElement.getAttribute('x') || 0)
var y = ~~(rectElement.getAttribute('y') || 0)
var width = ~~rectElement.getAttribute('width')
var height = ~~rectElement.getAttribute('height')
// console.log('rectElements',rectElement,x,y,width,height)
if (x != undefined && y != undefined&&width&&height) {
if (x != undefined && y != undefined && width && height) {
// 创建一个新的canvas元素
var canvas = document.createElement('canvas')
canvas.width = width
@@ -101,7 +101,7 @@ const parseSvg = async svgContent => {
image: base64,
mask: base64,
type: 'base64',
_t:'rect'
_t: 'rect'
}
// 将处理后的数据添加到数组中
@@ -115,9 +115,9 @@ const parseSvg = async svgContent => {
if (!(svgWidth && svgHeight)) {
// viewBox
let viewBox = svgElement.viewBox.baseVal
svgWidth =viewBox.width
svgHeight =viewBox.height
svgWidth = viewBox.width
svgHeight = viewBox.height
}
// 创建一个新的canvas元素
@@ -147,15 +147,186 @@ const parseSvg = async svgContent => {
image: base64,
mask: base64,
type: 'base64',
_t:'canvas'
_t: 'canvas'
}
data.push(rectData)
// 打印处理后的数据
console.log('layers',{ data, image: base64, svgElement })
console.log('layers', { data, image: base64, svgElement })
return { data, image: base64, svgElement }
}
async function setArea (cw, ch, topBase64, base64, data, fn) {
let displayHeight = Math.round(window.screen.availHeight * 0.8)
let div = document.createElement('div')
div.innerHTML = `
<div id='ml_overlay' style='position: absolute;top:0;background: #251f1fc4;
height: 100vh;
z-index:999999;
width: 100%;'>
<img id='ml_video' style='position: absolute;
height: ${displayHeight}px;user-select: none;
-webkit-user-drag: none;
outline: 2px solid #eaeaea;
box-shadow: 8px 9px 17px #575757;' />
<div id='ml_selection' style='position: absolute;
border: 2px dashed red;
pointer-events: none;
background-image: url("${topBase64}");
background-repeat: no-repeat;
background-size: cover;
'></div>
<div class="mx_close"> X </div>
</div>`
// document.body.querySelector('#ml_overlay')
document.body.appendChild(div)
// let canvas = document.createElement('canvas')
// canvas.width = cw
// canvas.height = ch
let img = div.querySelector('#ml_video')
// let overlay = div.querySelector('#ml_overlay')
let selection = div.querySelector('#ml_selection')
let close = div.querySelector('.mx_close')
let startX, startY, endX, endY
let start = false
let setDone = false
// Set video source
img.src = base64
// canvas.toDataURL();
close.style = `cursor: pointer;
position: fixed;
left: 12px;
top: 12px;
z-index: 99999999;
background: black;
width: 44px;
height: 44px;
text-align: center;
line-height: 44px;`
// init area
// const data = getSetAreaData()
let x = 0,
y = 0,
width = (cw * displayHeight) / ch,
height = displayHeight
let imgWidth = cw
let imgHeight = ch
if (data && data.width > 0 && data.height > 0) {
// 相同尺寸窗口,恢复选区
x = (width * data.x) / imgWidth
y = (height * data.y) / imgHeight
width = (width * data.width) / imgWidth
height = (height * data.height) / imgHeight
}
selection.style.left = x + 'px'
selection.style.top = y + 'px'
selection.style.width = width + 'px'
selection.style.height = height + 'px'
// Add mouse events
img.addEventListener('mousedown', startSelection)
img.addEventListener('mousemove', updateSelection)
img.addEventListener('mouseup', endSelection)
const removeDiv = () => {
div.remove()
close.removeEventListener('click', removeDiv)
img.removeEventListener('mousedown', startSelection)
img.removeEventListener('mousemove', updateSelection)
img.removeEventListener('mouseup', endSelection)
img.removeEventListener('mousedown', setDoneCheck)
}
close.addEventListener('click', removeDiv)
const setDoneCheck = event => {
console.log(setDone)
if (setDone) {
img.addEventListener('mousedown', startSelection)
img.addEventListener('mousemove', updateSelection)
img.addEventListener('mouseup', endSelection)
setDone = false
start = false
startX = event.clientX
startY = event.clientY
}
}
img.addEventListener('mousedown', setDoneCheck)
function remove () {
img.removeEventListener('mousedown', startSelection)
img.removeEventListener('mousemove', updateSelection)
img.removeEventListener('mouseup', endSelection)
setDone = true
// div.remove()
}
function startSelection (event) {
if (start == false) {
startX = event.clientX
startY = event.clientY
updateSelection(event)
start = true
} else {
}
}
function updateSelection (event) {
endX = event.clientX
endY = event.clientY
// Calculate width, height, and coordinates
let width = Math.abs(endX - startX)
let height = Math.abs(endY - startY)
let left = Math.min(startX, endX)
let top = Math.min(startY, endY)
// Set selection style
selection.style.left = left + 'px'
selection.style.top = top + 'px'
selection.style.width = width + 'px'
selection.style.height = height + 'px'
}
function endSelection (event) {
endX = event.clientX
endY = event.clientY
// 获取img元素的真实宽度和高度
let imgWidth = img.naturalWidth
let imgHeight = img.naturalHeight
// 换算起始坐标
let realStartX = (startX / img.offsetWidth) * imgWidth
let realStartY = (startY / img.offsetHeight) * imgHeight
// 换算起始坐标
let realEndX = (endX / img.offsetWidth) * imgWidth
let realEndY = (endY / img.offsetHeight) * imgHeight
startX = realStartX
startY = realStartY
endX = realEndX
endY = realEndY
// Calculate width, height, and coordinates
let width = Math.round(Math.abs(endX - startX))
let height = Math.round(Math.abs(endY - startY))
let left = Math.round(Math.min(startX, endX))
let top = Math.round(Math.min(startY, endY))
if (width <= 0 && height <= 0) return remove()
if (fn) fn(left, top, width, height)
remove()
}
}
app.registerExtension({
name: 'Mixlab.layer.ShowLayer',
async getCustomWidgets (app) {
@@ -199,8 +370,7 @@ app.registerExtension({
const findNode = nodeId => {
let node = app.graph._nodes_by_id[nodeId]
if (node?.type == 'Reroute') {
let linkId =node.inputs.filter(i=>i.type=='*')[0].link
let linkId = node.inputs.filter(i => i.type == '*')[0].link
nodeId = app.graph.links.filter(link => link.id == linkId)[0]
?.origin_id
return findNode(nodeId)
@@ -211,15 +381,17 @@ app.registerExtension({
// 获取layers数据
const getLayers = async () => {
console.log('getLayers1',this.inputs.filter(ip => ip.name === 'layers'))
console.log(
'getLayers1',
this.inputs.filter(ip => ip.name === 'layers')
)
let linkId = this.inputs.filter(ip => ip.name === 'layers')[0].link
let nodeId = app.graph.links?.filter(link => link.id == linkId)[0]
?.origin_id;
?.origin_id
if(nodeId){
if (nodeId) {
nodeId = findNode(nodeId)
}
// let node = app.graph._nodes_by_id[nodeId]
// if (node?.type == 'Reroute') {
@@ -227,18 +399,18 @@ app.registerExtension({
// nodeId = app.graph.links.filter(link => link.id == linkId)[0]
// ?.origin_id
// }
let d = getLocalData('_mixlab_svg_image')
console.log('test',d[nodeId])
console.log('test', d[nodeId])
if (d[nodeId]) {
let url = d[nodeId]
let dt = await fetch(url)
let svgStr = await dt.text()
const { data } = (await parseSvg(svgStr)) || {}
console.log('fetch',data)
console.log('fetch', data)
return data
} else {
return []
@@ -350,3 +522,90 @@ app.registerExtension({
}
}
})
app.registerExtension({
name: 'Mixlab.layer.NewLayer',
async beforeRegisterNodeDef (nodeType, nodeData, app) {
if (nodeData.name === 'NewLayer') {
const orig_nodeCreated = nodeType.prototype.onNodeCreated
nodeType.prototype.onNodeCreated = async function () {
orig_nodeCreated?.apply(this, arguments)
let b = this.widgets.filter(w => w.type === 'button')[0]
// const [w, h, base64] = canvas
if (!b) {
const updateValue = (x1, y1, w1, h1) => {
if (this.widgets) {
for (const widget of this.widgets) {
if (widget.name === 'x') {
widget.value = x1
}
if (widget.name === 'y') {
widget.value = y1
}
if (widget.name === 'width') {
widget.value = w1
}
if (widget.name === 'height') {
widget.value = h1
}
}
}
}
this.addWidget('button', 'Set Area', '', () => {
let data = {}
for (const widget of this.widgets) {
if (widget.name === 'x') {
data.x = widget.value
}
if (widget.name === 'y') {
data.y = widget.value
}
if (widget.name === 'width') {
data.width = widget.value
}
if (widget.name === 'height') {
data.height = widget.value
}
}
try {
console.log('this.inputs', this.inputs)
let topLinkId = this.inputs[0].link
let topNodeId = app.graph.links[topLinkId].origin_id
let topIm = app.graph.getNodeById(topNodeId).imgs[0]
let linkId = this.inputs[3].link
let nodeId = app.graph.links[linkId].origin_id
// console.log(linkId,this.inputs)
let im = app.graph.getNodeById(nodeId).imgs[0]
// let src = im.src
setArea(
im.naturalWidth,
im.naturalHeight,
topIm.src,
im.src,
data,
updateValue
)
} catch (error) {}
})
}
}
const onRemoved = this.onRemoved
this.onRemoved = () => {
// let b = this.widgets.filter(w => w.type === 'button')[0];
return onRemoved?.()
}
if (this.onResize) {
this.onResize(this.size)
}
this.serialize_widgets = true //需要保存参数
}
}
})
+110 -73
View File
@@ -461,7 +461,7 @@ async function requestCamera () {
/*
A method that returns the required style for the html
*/
function get_position_style (ctx, widget_width, y, node_height) {
function get_position_style (ctx, widget_width, y, node_height, top) {
const MARGIN = 4 // the margin around the html element
/* Create a transform that deals with all the scrolling and zooming */
@@ -478,14 +478,14 @@ function get_position_style (ctx, widget_width, y, node_height) {
transformOrigin: '0 0',
transform: transform,
left: `0`,
top: `0`,
top: `${top}px`,
cursor: 'pointer',
position: 'absolute',
maxWidth: `${widget_width - MARGIN * 2}px`,
// maxHeight: `${node_height - MARGIN * 2}px`, // we're assuming we have the whole height of the node
width: `${widget_width - MARGIN * 2}px`,
// height: `${node_height - MARGIN * 2}px`,
background: '#EEEEEE',
// background: '#EEEEEE',
display: 'flex',
flexDirection: 'column',
// alignItems: 'center',
@@ -585,9 +585,6 @@ app.registerExtension({
},
async beforeRegisterNodeDef (nodeType, nodeData, app) {
if (nodeType.comfyClass == 'ScreenShare') {
/*
Hijack the onNodeCreated call to add our widget
*/
const orig_nodeCreated = nodeType.prototype.onNodeCreated
nodeType.prototype.onNodeCreated = function () {
orig_nodeCreated?.apply(this, arguments)
@@ -596,22 +593,29 @@ app.registerExtension({
type: 'HTML', // whatever
name: 'sreen_share', // whatever
draw (ctx, node, widget_width, y, widget_height) {
console.log('ScreenSHare', y, widget_height)
// console.log('ScreenSHare', y, widget_height)
Object.assign(
this.card.style,
get_position_style(
ctx,
widget_width,
widget_height * 5,
node.size[1]
node.size[1],
40
)
)
}
}
widget.card = $el('div', {})
widget.card = $el('div', {
color: 'var(--descrip-text)',
backgroundColor: 'var(--comfy-input-bg)'
})
widget.previewCard = $el('div', {})
widget.previewCard = $el('div', {
color: 'var(--descrip-text)',
backgroundColor: 'var(--comfy-input-bg)'
})
widget.preview = $el('video', {
style: {
@@ -623,7 +627,10 @@ app.registerExtension({
})
widget.previewArea = $el('div', {
style: {}
style: {
color: 'var(--descrip-text)',
backgroundColor: 'var(--comfy-input-bg)'
}
})
widget.shareDiv = $el('div', {
@@ -631,7 +638,9 @@ app.registerExtension({
style: {
cursor: 'pointer',
fontWeight: '300',
display: 'flex'
display: 'flex',
color: 'var(--descrip-text)',
backgroundColor: 'var(--comfy-input-bg)'
}
})
@@ -642,7 +651,12 @@ app.registerExtension({
padding: '8px 0',
fontWeight: '300',
margin: '2px',
width: '100%'
width: '100%',
color: 'var(--descrip-text)',
backgroundColor: 'var(--comfy-input-bg)',
borderRadius: '8px',
borderColor: 'var(--border-color)',
borderStyle: 'solid'
}
})
@@ -653,7 +667,12 @@ app.registerExtension({
padding: '8px 0',
fontWeight: '300',
margin: '2px',
width: '100%'
width: '100%',
color: 'var(--descrip-text)',
backgroundColor: 'var(--comfy-input-bg)',
borderRadius: '8px',
borderColor: 'var(--border-color)',
borderStyle: 'solid'
}
})
@@ -663,29 +682,43 @@ app.registerExtension({
cursor: 'pointer',
padding: '8px 0',
fontWeight: '300',
margin: '2px'
margin: '2px',
color: 'var(--descrip-text)',
backgroundColor: 'var(--comfy-input-bg)',
borderRadius: '8px',
borderColor: 'var(--border-color)',
borderStyle: 'solid'
}
})
widget.refreshInput = $el('input', {
placeholder: ' Refresh rate:200 ms',
type: 'number',
min: 100,
step: 100,
style: {
cursor: 'pointer',
padding: '8px 0',
fontWeight: '300',
margin: '2px'
}
})
// widget.refreshInput = $el('input', {
// placeholder: ' Refresh rate:200 ms',
// type: 'number',
// min: 100,
// step: 100,
// style: {
// cursor: 'pointer',
// padding: '8px 24px',
// fontWeight: '300',
// margin: '2px',
// color: 'var(--descrip-text)',
// backgroundColor: 'var(--comfy-input-bg)'
// }
// });
// widget.refreshInput.className='comfy-multiline-input'
widget.liveBtn = $el('button', {
innerText: 'Live Run',
style: {
cursor: 'pointer',
padding: '8px 0',
fontWeight: '300',
margin: '2px'
margin: '2px',
color: 'var(--descrip-text)',
backgroundColor: 'var(--comfy-input-bg)',
borderRadius: '8px',
borderColor: 'var(--border-color)',
borderStyle: 'solid'
}
})
@@ -699,7 +732,7 @@ app.registerExtension({
widget.shareDiv.appendChild(widget.shareBtn)
widget.shareDiv.appendChild(widget.shareOfWebCamBtn)
widget.card.appendChild(widget.openFloatingWinBtn)
widget.card.appendChild(widget.refreshInput)
// widget.card.appendChild(widget.refreshInput)
widget.card.appendChild(widget.liveBtn)
const toggleShare = async (isCamera = false) => {
@@ -859,11 +892,11 @@ app.registerExtension({
toggleShare()
})
widget.refreshInput.addEventListener('change', async () => {
window._mixlab_screen_refresh_rate = Math.round(
widget.refreshInput.value
)
})
// widget.refreshInput.addEventListener('change', async () => {
// window._mixlab_screen_refresh_rate = Math.round(
// widget.refreshInput.value
// )
// })
widget.liveBtn.addEventListener('click', async () => {
if (window._mixlab_stopLive) {
@@ -890,7 +923,7 @@ app.registerExtension({
})
// console.log('widget.inputEl',widget.inputEl)
this.setSize([this.size[0],this.size[1]+450])
this.setSize([this.size[0], this.size[1] + 450])
app.canvas.draw(true, true)
/*
@@ -904,12 +937,21 @@ app.registerExtension({
widget.shareBtn.remove()
widget.liveBtn.remove()
widget.card.remove()
widget.refreshInput.remove()
// widget.refreshInput.remove()
widget.previewArea.remove()
widget.previewCard.remove()
}
this.serialize_widgets = true
}
const onExecuted = nodeType.prototype.onExecuted
nodeType.prototype.onExecuted = function (message) {
onExecuted?.apply(this, arguments)
// console.log('###ScreenShare', this, message.refresh_rate)
window._mixlab_screen_refresh_rate = Math.round(
message.refresh_rate[0] || 500
)
}
}
}
})
@@ -1001,6 +1043,7 @@ async function setArea (src) {
div.innerHTML = `
<div id='ml_overlay' style='position: absolute;top:0;background: #251f1fc4;
height: 100vh;
z-index:999999;
width: 100%;'>
<img id='ml_video' style='position: absolute;
height: ${displayHeight}px;user-select: none;
@@ -1032,12 +1075,7 @@ async function setArea (src) {
height = displayHeight
let imgWidth = im.naturalWidth
let imgHeight = im.naturalHeight
// console.log(
// '#screen_share::使用上一次选区 selection',
// data,
// imgWidth,
// img.width
// )
if (
data &&
data.width > 0 &&
@@ -1051,9 +1089,6 @@ async function setArea (src) {
y = (img.height * data.y) / data.imgHeight
width = (img.width * data.width) / data.imgWidth
height = (img.height * data.height) / data.imgHeight
// imgWidth = data.imgWidth
// imgHeight = data.imgHeight;
// console.log('#screen_share::使用上一次选区 selection', x, y, width, height)
}
selection.style.left = x + 'px'
@@ -1118,9 +1153,6 @@ async function setArea (src) {
let realEndX = (endX / img.offsetWidth) * imgWidth
let realEndY = (endY / img.offsetHeight) * imgHeight
// 输出结果到控制台
// console.log('真实宽度: ' + realWidth)
// console.log('真实高度: ' + realHeight)
startX = realStartX
startY = realStartY
endX = realEndX
@@ -1130,19 +1162,6 @@ async function setArea (src) {
let height = Math.abs(endY - startY)
let left = Math.min(startX, endX)
let top = Math.min(startY, endY)
// Output results to console
// console.log('坐标位置: (' + left + ', ' + top + ')')
// console.log('宽度: ' + width)
// console.log('高度: ' + height)
// img.removeEventListener('mousedown', startSelection)
// img.removeEventListener('mousemove', updateSelection)
// img.removeEventListener('mouseup', endSelection)
// window._mixlab_screen_x = left
// window._mixlab_screen_y = top
// window._mixlab_screen_width = width
// window._mixlab_screen_height = height
if (width <= 0 && height <= 0) return remove()
@@ -1154,7 +1173,6 @@ async function setArea (src) {
window._mixlab_screen_webcamVideo,
!window._mixlab_screen_live
)
remove()
}
}
@@ -1203,7 +1221,7 @@ app.registerExtension({
draw (ctx, node, widget_width, y, widget_height) {
Object.assign(
this.card.style,
get_position_style(ctx, widget_width, y, node.size[1])
get_position_style(ctx, widget_width, y, node.size[1], 0)
)
}
}
@@ -1255,7 +1273,12 @@ app.registerExtension({
cursor: 'pointer',
padding: '8px 0',
fontWeight: '300',
margin: '2px'
margin: '2px',
color: 'var(--descrip-text)',
backgroundColor: 'var(--comfy-input-bg)',
borderRadius: '8px',
borderColor: 'var(--border-color)',
borderStyle: 'solid'
}
})
@@ -1421,7 +1444,8 @@ app.registerExtension({
)
try {
pipWindow.document.querySelector('#info').innerText =window._mixlab_screen_seed_input
pipWindow.document.querySelector('#info').innerText =
window._mixlab_screen_seed_input
} catch (error) {
console.log(error)
}
@@ -1776,6 +1800,14 @@ const updateUI = node => {
pw.inputEl.title = `Total of ${prompts.length} prompts`
} else {
// 动态添加
// console.log('ComfyWidgets',ComfyWidgets.STRING(
// node,
// 'prompts',
// ['STRING', { multiline: true }]
// ))
// ComfyWidgets.STRING(this, "", ["", {default:this.properties.text, multiline: true}], app)
const w = ComfyWidgets.STRING(
node,
'prompts',
@@ -2061,13 +2093,13 @@ const node = {
name: 'RandomPrompt',
async init (app) {
// Any initial setup to run as soon as the page loads
console.log('[logging]', 'extension init')
// console.log('[logging]', 'extension init')
if (window.location.href.match('/?')) {
const { workflow } = getURLParameters(window.location.href)
if (workflow)
get_my_workflow().then(data => {
console.log('#get_my_workflow', data)
// console.log('#get_my_workflow', data)
let my_workflow = data.filter(
d => d.filename == 'my_workflow.json'
)[0]
@@ -2103,10 +2135,15 @@ const node = {
// }
},
loadedGraphNode (node, app) {
// Fires for each node when loading/dragging/etc a workflow json or png
// If you break something in the backend and want to patch workflows in the frontend
// This is the place to do this
// console.log("[logging]", "loaded graph node: ", exportGraph(node.graph));
if (node.type === 'RandomPrompt') {
try {
let max_count = node.widgets.filter(w => w.name === "max_count")[0];
max_count.value= node.widgets_values[0]
// console.log('RandomPrompt',max_count,node.widgets_values[0])
} catch (error) {
console.log(error)
}
}
},
async nodeCreated (node) {
if (node.type === 'RandomPrompt') {
@@ -2199,7 +2236,7 @@ const node = {
const r = onExecuted?.apply?.(this, arguments)
let prompts = message.prompts
console.log('executed', message)
// console.log('executed', message)
// console.log('#RandomPrompt', this.widgets)
const pw = this.widgets.filter(w => w.name === 'prompts')[0]
@@ -2210,7 +2247,7 @@ const node = {
} else {
// 动态添加
const w = ComfyWidgets.STRING(
node,
this,
'prompts',
['STRING', { multiline: true }],
app
+204
View File
@@ -0,0 +1,204 @@
import { app } from '../../../scripts/app.js'
import { api } from '../../../scripts/api.js'
import { ComfyWidgets } from '../../../scripts/widgets.js'
import { $el } from '../../../scripts/ui.js'
function get_position_style (ctx, widget_width, y, node_height) {
const MARGIN = 4 // the margin around the html element
/* Create a transform that deals with all the scrolling and zooming */
const elRect = ctx.canvas.getBoundingClientRect()
const transform = new DOMMatrix()
.scaleSelf(
elRect.width / ctx.canvas.width,
elRect.height / ctx.canvas.height
)
.multiplySelf(ctx.getTransform())
.translateSelf(MARGIN, MARGIN + y)
return {
transformOrigin: '0 0',
transform: transform,
left: `0`,
top: `0`,
cursor: 'pointer',
position: 'absolute',
maxWidth: `${widget_width - MARGIN * 2}px`,
// maxHeight: `${node_height - MARGIN * 2}px`, // we're assuming we have the whole height of the node
width: `${widget_width - MARGIN * 2 - 24}px`,
// height: `${node_height * 0.3 - MARGIN * 2}px`,
// background: '#EEEEEE',
paddingLeft: '12px',
display: 'flex',
flexDirection: 'row',
// alignItems: 'center',
justifyContent: 'space-between'
}
}
const getLocalData = key => {
let data = {}
try {
data = JSON.parse(localStorage.getItem(key)) || {}
} catch (error) {
return {}
}
return data
}
const setLocalDataOfWin = (key, value) => {
localStorage.setItem(key, JSON.stringify(value))
// window[key] = value
}
const createSelect = (select, opts, targetWidget) => {
select.style.display = 'block'
let html = ''
let isMatch = false
for (const opt of opts) {
html += `<option value='${opt}' ${
targetWidget.value === opt ? 'selected' : ''
}>${opt}</option>`
if (targetWidget.value === opt) isMatch = true
}
select.innerHTML = html
if (!isMatch) targetWidget.value = opts[0]
// 添加change事件监听器
select.addEventListener('change', function () {
// 获取选中的选项的值
var selectedOption = select.options[select.selectedIndex].value
targetWidget.value = selectedOption
// console.log(widget,selectedOption)
})
}
app.registerExtension({
name: 'Mixlab.prompt.PromptSlide',
async beforeRegisterNodeDef (nodeType, nodeData, app) {
if (nodeType.comfyClass == 'PromptSlide') {
const orig_nodeCreated = nodeType.prototype.onNodeCreated
nodeType.prototype.onNodeCreated = async function () {
orig_nodeCreated?.apply(this, arguments)
const prompt_keyword = this.widgets.filter(
w => w.name == 'prompt_keyword'
)[0]
// console.log('PromptSlide nodeData', prompt_keyword)
const widget = {
type: 'div',
name: 'upload',
draw (ctx, node, widget_width, y, widget_height) {
Object.assign(
this.div.style,
get_position_style(ctx, widget_width, y, node.size[1])
)
}
}
widget.div = $el('div', {})
const btn = document.createElement('button')
btn.innerText = 'Upload Keywords'
btn.style = `cursor: pointer;
font-weight: 300;
margin: 2px;
color: var(--descrip-text);
background-color: var(--comfy-input-bg);
border-radius: 8px;
border-color: var(--border-color);
border-style: solid; height: 30px;min-width: 122px;
`
const select = document.createElement('select')
select.style = `display:none;cursor: pointer;
font-weight: 300;
margin: 2px;
color: var(--descrip-text);
background-color: var(--comfy-input-bg);
border-radius: 8px;
border-color: var(--border-color);
border-style: solid; height: 30px;min-width: 100px;
`
widget.select = select
// const btn=document.createElement('button');
// btn.innerText='Upload'
btn.addEventListener('click', () => {
let inp = document.createElement('input')
inp.type = 'file'
inp.accept = '.txt'
inp.click()
inp.addEventListener('change', event => {
// 获取选择的文件
const file = event.target.files[0];
this.title=file.name.split('.')[0];
// console.log(file.name.split('.')[0])
// 创建文件读取器
const reader = new FileReader()
// 定义读取完成事件的回调函数
reader.onload = event => {
// 读取完成后的文本内容
const fileContent = event.target.result.split('\n')
const keywords = Array.from(fileContent, f => f.trim()).filter(
f => f
)
// 打印文件内容
// console.log(keywords)
// widget.value = keywords
let ks = getLocalData(`_mixlab_PromptSlide`)
ks[this.id] = keywords
setLocalDataOfWin(`_mixlab_PromptSlide`, ks)
createSelect(select, keywords, prompt_keyword)
inp.remove()
}
// 以文本方式读取文件
reader.readAsText(file)
})
})
widget.div.appendChild(btn)
widget.div.appendChild(select)
document.body.appendChild(widget.div)
this.addCustomWidget(widget)
const onRemoved = this.onRemoved
this.onRemoved = () => {
widget.div.remove()
return onRemoved?.()
}
if (this.onResize) {
this.onResize(this.size)
}
this.serialize_widgets = true //需要保存参数
}
}
},
async loadedGraphNode (node, app) {
if (node.type === 'PromptSlide') {
try {
let prompt = node.widgets.filter(w => w.name === 'prompt_keyword')[0]
let ks = getLocalData(`_mixlab_PromptSlide`)
let keywords = ks[node.id]
// console.log('keywords',keywords)
let widget = node.widgets.filter(w => w.select)[0]
if (keywords && keywords[0]) {
// let widget = node.widgets.filter(w => w.select)[0]
// console.log('select',widget,widget.value)
widget.select.style.display = 'block'
createSelect(widget.select, keywords, prompt)
}
} catch (error) {}
}
}
})
+539 -219
View File
@@ -2,7 +2,12 @@ import { app } from '../../../scripts/app.js'
import { api } from '../../../scripts/api.js'
import { ComfyWidgets } from '../../../scripts/widgets.js'
import { $el } from '../../../scripts/ui.js'
import {closeIcon} from './svg_icons.js'
import { closeIcon } from './svg_icons.js'
import {
GroupNodeConfig,
GroupNodeHandler
} from '../../../extensions/core/groupNode.js'
function deepEqual (obj1, obj2) {
if (typeof obj1 !== typeof obj2) {
@@ -43,16 +48,51 @@ async function get_nodes_map () {
return await res.json()
}
function loadCSS(url) {
var link = document.createElement('link');
link.rel = 'stylesheet';
link.type = 'text/css';
link.href = url;
document.getElementsByTagName('head')[0].appendChild(link);
function get_url () {
let api_host = `${window.location.hostname}:${window.location.port}`
let api_base = ''
let url = `${window.location.protocol}//${api_host}${api_base}`
return url
}
var cssURL = 'https://cdnjs.cloudflare.com/ajax/libs/github-markdown-css/5.5.0/github-markdown-light.min.css';
loadCSS(cssURL);
async function get_my_app (filename = null, category = '') {
let url = get_url()
const res = await fetch(`${url}/mixlab/workflow`, {
method: 'POST',
body: JSON.stringify({
task: 'my_app',
filename,
category,
admin: true
})
})
let result = await res.json()
let data = []
try {
for (const res of result.data) {
let { app, workflow } = res.data
if (app.filename)
data.push({
...app,
data: workflow,
date: res.date
})
}
} catch (error) {}
return data
}
function loadCSS (url) {
var link = document.createElement('link')
link.rel = 'stylesheet'
link.type = 'text/css'
link.href = url
document.getElementsByTagName('head')[0].appendChild(link)
}
var cssURL =
'https://cdnjs.cloudflare.com/ajax/libs/github-markdown-css/5.5.0/github-markdown-light.min.css'
loadCSS(cssURL)
function injectCSS (css) {
// 检查页面中是否已经存在具有相同内容的style标签
@@ -112,7 +152,7 @@ async function getCustomnodeMappings (mode = 'url') {
nodes[node] = { url, title: n[1].title_aux }
}
}
// try {
// const response = await fetch(`${url}/customnode/getmappings?mode=${mode}`)
// const data = await response.json()
@@ -309,8 +349,7 @@ async function fetchReadmeContent (url) {
}
}
function createModal (url, markdown,title) {
function createModal (url, markdown, title) {
// Create modal element
var div =
document.querySelector('#mix-modal') || document.createElement('div')
@@ -329,7 +368,7 @@ function createModal (url, markdown,title) {
var modal = document.createElement('div')
div.appendChild(modal)
modal.classList.add("modal-body")
modal.classList.add('modal-body')
// Set modal styles
modal.style.cssText = `
background: white;
@@ -348,17 +387,17 @@ function createModal (url, markdown,title) {
var modalContent = document.createElement('div')
modalContent.classList.add('modal-content')
// Create modal header
const headerElement = document.createElement('div')
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=`
headTitleElement.style.cssText = `
color: var(--descrip-text);
font-size: 18px;
display: flex;
@@ -368,22 +407,20 @@ function createModal (url, markdown,title) {
text-decoration: none;
font-weight: bold;
`
headTitleElement.onmouseenter = function(){
headTitleElement.onmouseenter = function () {
headTitleElement.style.color = 'var(--comfy-menu-bg)'
}
headTitleElement.onmouseleave = function(){
headTitleElement.onmouseleave = function () {
headTitleElement.style.color = 'var(--descrip-text)'
}
headTitleElement.textContent= title ||'';
headTitleElement.textContent = title || ''
headTitleElement.href = url
headTitleElement.target='_blank'
headTitleElement.target = '_blank'
const linkIcon = document.createElement('small')
linkIcon.textContent = '🔗'
headTitleElement.appendChild(linkIcon);
headTitleElement.appendChild(linkIcon)
headerElement.appendChild(headTitleElement)
// Create close button
const closeButton = document.createElement('span')
closeButton.classList.add('close')
@@ -400,20 +437,19 @@ function createModal (url, markdown,title) {
user-select: none;
fill: var(--descrip-text);
`
closeButton.onmouseenter = function(){
closeButton.style.fill = 'var(--comfy-menu-bg)';
closeButton.onmouseenter = function () {
closeButton.style.fill = 'var(--comfy-menu-bg)'
}
closeButton.onmouseleave = function(){
closeButton.style.fill = 'var(--descrip-text)';
closeButton.onmouseleave = function () {
closeButton.style.fill = 'var(--descrip-text)'
}
headerElement.appendChild(closeButton)
// Click event to close the modal
function closeMixModal(){
function closeMixModal () {
div.style.display = 'none'
window.removeEventListener('keydown',MixModalEscKeyEvent)
window.removeEventListener('keydown', MixModalEscKeyEvent)
}
closeButton.onclick = function () {
closeMixModal()
@@ -433,10 +469,10 @@ function createModal (url, markdown,title) {
// Create element for displaying Markdown content
var markdownContent = document.createElement('div')
markdownContent.classList.add('markdown-content','markdown-body')
markdownContent.classList.add('markdown-content', 'markdown-body')
markdownContent.style.cssText = `max-width: 50vw;padding: 0px 24px 100px 24px;`
showdown.setFlavor('github');
showdown.setFlavor('github')
var converter = new showdown.Converter()
var html = converter.makeHtml(markdown)
@@ -468,7 +504,7 @@ function createModal (url, markdown,title) {
modal.appendChild(modalContent)
const footerElement = document.createElement('div')
footerElement.style.cssText=`
footerElement.style.cssText = `
position: absolute;
left: 0;
right: 0;
@@ -478,37 +514,35 @@ function createModal (url, markdown,title) {
`
const footerText = document.createElement('a')
footerText.href ="https://github.com/shadowcz007/comfyui-mixlab-nodes"
footerText.innerText = "Support by Mixlab"
footerText.style.cssText=`color:inherit`
footerText.target = "_blank"
footerText.onmouseenter=function(){
footerText.href = 'https://github.com/shadowcz007/comfyui-mixlab-nodes'
footerText.innerText = 'Support by Mixlab'
footerText.style.cssText = `color:inherit`
footerText.target = '_blank'
footerText.onmouseenter = function () {
footerText.style.color = 'var(--input-text)'
}
footerText.onmouseleave=function(){
footerText.onmouseleave = function () {
footerText.style.color = 'inherit'
}
footerText.onclick = function(e){
footerText.onclick = function (e) {
e.stopPropagation()
}
footerElement.appendChild(footerText)
div.appendChild(footerElement)
// Append modal element to the page
if (!document.querySelector('#mix-modal')) {
document.body.appendChild(div)
}
function MixModalEscKeyEvent(event){
if(event.key == "Escape"){
closeMixModal()
}
function MixModalEscKeyEvent (event) {
if (event.key == 'Escape') {
closeMixModal()
}
}
window.removeEventListener('keydown',MixModalEscKeyEvent)
window.addEventListener('keydown',MixModalEscKeyEvent)
window.removeEventListener('keydown', MixModalEscKeyEvent)
window.addEventListener('keydown', MixModalEscKeyEvent)
const bgElement = document.createElement('div')
bgElement.classList.add('mix-modal-bg')
@@ -517,13 +551,11 @@ function createModal (url, markdown,title) {
height:100%;
background-color: rgba(0,0,0,0.8);
`
bgElement.onclick = function( ){
bgElement.onclick = function () {
closeMixModal()
}
div.appendChild(bgElement)
}
app.registerExtension({
@@ -539,7 +571,7 @@ app.registerExtension({
let repo = nodesMap[node.type]
if (repo) {
let markdown = await fetchReadmeContent(repo.url)
createModal(repo.url,markdown,repo.title)
createModal(repo.url, markdown, repo.title)
}
}
@@ -548,17 +580,7 @@ app.registerExtension({
// replace it
const options = getNodeMenuOptions.apply(this, arguments) // start by calling the stored one
node.setDirtyCanvas(true, true) // force a redraw of (foreground, background)
// options.splice(
// options.length - 1,
// 0, // splice a new option in at the end
// {
// content: '♾️Mixlab', // with a name
// callback: () => {
// LGraphCanvas.prototype.helpAboutNode(node)
// } // and the callback
// },
// null // a divider
// )
return [
{
content: 'Help ♾️Mixlab', // with a name
@@ -570,168 +592,466 @@ app.registerExtension({
...options
] // and return the options
}
},
async setup () {
// Add canvas menu options
const orig = LGraphCanvas.prototype.getCanvasMenuOptions;
LGraphCanvas.prototype.getCanvasMenuOptions = function () {
const options = orig.apply(this, arguments);
const getGroupMenuOptions = LGraphCanvas.prototype.getGroupMenuOptions // store the existing method
LGraphCanvas.prototype.getGroupMenuOptions = function (node) {
// replace it
const options = getGroupMenuOptions.apply(this, arguments) // start by calling the stored one
node.setDirtyCanvas(true, true) // force a redraw of (foreground, background)
options.push(null, {
content: `Find ♾️Mixlab`,
disabled: false, // or a function determining whether to disable
callback: async () => {
nodesMap =
nodesMap && Object.keys(nodesMap).length > 0
? nodesMap
: await getCustomnodeMappings('url')
const nodesDiv = document.createDocumentFragment()
const nodes = (await app.graphToPrompt()).output
// console.log('[Mixlab]', 'loaded graph node: ', app)
let div =
document.querySelector('#mixlab_find_the_node') ||
document.createElement('div')
div.id = 'mixlab_find_the_node'
div.style = `
flex-direction: column;
align-items: end;
display:flex;position: absolute;
top: 50px; left: 50px; width: 200px;
color: var(--descrip-text);
background-color: var(--comfy-menu-bg);
padding: 10px;
border: 1px solid black;z-index: 999999999;padding-top: 0;`
div.innerHTML = ''
let btn = document.createElement('div')
btn.style=`display: flex;
width: calc(100% - 24px);
justify-content: space-between;
align-items: center;
padding: 0 12px;
height: 32px;`
let btnB = document.createElement('button')
let textB = document.createElement('p')
btn.appendChild(textB)
btn.appendChild(btnB)
textB.innerText = `Find The Node`
btnB.style = `float: right; border: none; color: var(--input-text);
background-color: var(--comfy-input-bg); border-color: var(--border-color);cursor: pointer;`
btnB.addEventListener('click', () => {
div.style.display = 'none'
})
btnB.innerText = 'X'
// 悬浮框拖动事件
div.addEventListener('mousedown', function (e) {
var startX = e.clientX
var startY = e.clientY
var offsetX = div.offsetLeft
var offsetY = div.offsetTop
function moveBox (e) {
var newX = e.clientX
var newY = e.clientY
var deltaX = newX - startX
var deltaY = newY - startY
div.style.left = offsetX + deltaX + 'px'
div.style.top = offsetY + deltaY + 'px'
}
function stopMoving () {
document.removeEventListener('mousemove', moveBox)
document.removeEventListener('mouseup', stopMoving)
}
document.addEventListener('mousemove', moveBox)
document.addEventListener('mouseup', stopMoving)
})
div.appendChild(btn)
const updateNodes = (ns, nd) => {
for (let nodeId in ns) {
let n = ns[nodeId].class_type
const { url, title } = nodesMap[n]
let d = document.createElement('button')
d.style = `text-align: left;margin:6px;color: var(--input-text);
background-color: var(--comfy-input-bg); border-color: var(--border-color);cursor: pointer;`
d.addEventListener('click', () => {
const node = app.graph.getNodeById(nodeId)
if (!node) return
app.canvas.centerOnNode(node)
app.canvas.setZoom(1)
})
d.addEventListener('mouseover', async () => {
// console.log('mouseover')
let n = (await app.graphToPrompt()).output
if (!deepEqual(n, ns)) {
nd.innerHTML = ''
updateNodes(n, nd)
}
})
d.innerHTML = `
<span>${'#' + nodeId} ${n}</span>
<a href="${url}" target="_blank" style="text-decoration: none;">🔗</a>
`
d.title = title
nd.appendChild(d)
}
// templete
const key = 'Comfy.NodeTemplates'
let templates = localStorage.getItem(key)
if (templates) {
templates = JSON.parse(templates)
} else {
templates = []
}
const store = () => localStorage.setItem(key, JSON.stringify(templates))
return [
{
content: 'Clone Group ♾️Mixlab', // with a name
callback: async (value, opts, e, menu, group) => {
const clipboardAction = async cb => {
// We use the clipboard functions but dont want to overwrite the current user clipboard
// Restore it after we've run our callback
const old = localStorage.getItem('litegrapheditor_clipboard')
await cb()
localStorage.setItem('litegrapheditor_clipboard', old)
}
let nodesDivv = document.createElement('div')
for (let nodeId in nodes) {
let n = nodes[nodeId].class_type
const { url, title } = nodesMap[n]
let d = document.createElement('button')
d.style = `text-align: left;margin:6px;color: var(--input-text);
background-color: var(--comfy-input-bg); border-color: var(--border-color);cursor: pointer;`
d.addEventListener('click', () => {
const node = app.graph.getNodeById(nodeId)
if (!node) return
app.canvas.centerOnNode(node)
app.canvas.setZoom(1)
clipboardAction(async () => {
let name = group.title
let nodes = group._nodes
app.canvas.copyToClipboard(nodes)
let data = localStorage.getItem('litegrapheditor_clipboard')
data = JSON.parse(data)
for (let i = 0; i < nodes.length; i++) {
const node = app.graph.getNodeById(nodes[i].id)
const nodeData = node.serialize()
let groupData = GroupNodeHandler.getGroupData(node)
if (groupData) {
groupData = groupData.nodeData
if (!data.groupNodes) {
data.groupNodes = {}
}
data.groupNodes[nodeData.name] = groupData
data.nodes[i].type = nodeData.name
}
}
await GroupNodeConfig.registerFromWorkflow(data.groupNodes, {})
localStorage.setItem(
'litegrapheditor_clipboard',
JSON.stringify(data)
)
app.canvas.pasteFromClipboard()
})
} // and the callback
},
{
content: 'Save Group as Template ♾️Mixlab', // with a name
callback: async (value, opts, e, menu, group) => {
// console.log(options)
const clipboardAction = async cb => {
// We use the clipboard functions but dont want to overwrite the current user clipboard
// Restore it after we've run our callback
const old = localStorage.getItem('litegrapheditor_clipboard')
await cb()
localStorage.setItem('litegrapheditor_clipboard', old)
}
clipboardAction(() => {
let name = group.title + ' ♾️Mixlab'
let nodes = group._nodes
app.canvas.copyToClipboard(nodes)
let data = localStorage.getItem('litegrapheditor_clipboard')
data = JSON.parse(data)
for (let i = 0; i < nodes.length; i++) {
const node = app.graph.getNodeById(nodes[i].id)
const nodeData = node.serialize()
let groupData = GroupNodeHandler.getGroupData(node)
if (groupData) {
groupData = groupData.nodeData
if (!data.groupNodes) {
data.groupNodes = {}
}
data.groupNodes[nodeData.name] = groupData
data.nodes[i].type = nodeData.name
}
}
templates.push({
name,
data: JSON.stringify(data)
})
d.addEventListener('mouseover', async () => {
console.log('mouseover')
let n = (await app.graphToPrompt()).output
if (!deepEqual(n, nodes)) {
nodesDivv.innerHTML = ''
updateNodes(n, nodesDivv)
store()
})
} // and the callback
},
null,
...options
] // and return the options
}
LGraphCanvas.prototype.centerOnNode = function (node) {
// console.log(node)
var dpr = window.devicePixelRatio || 1 // 获取设备像素比
this.ds.offset[0] =
-node.pos[0] -
node.size[0] * 0.5 +
(this.canvas.width * 0.5) / (this.ds.scale * dpr) // 考虑设备像素比
this.ds.offset[1] =
-node.pos[1] -
node.size[1] * 0.5 +
(this.canvas.height * 0.5) / (this.ds.scale * dpr) // 考虑设备像素比
this.setDirty(true, true)
}
},
setup () {
setTimeout(async () => {
// Add canvas menu options
const orig = LGraphCanvas.prototype.getCanvasMenuOptions
const apps = await get_my_app()
let apps_map = { '0': [] }
for (const app of apps) {
if (app.category) {
if (!apps_map[app.category]) apps_map[app.category] = []
apps_map[app.category].push(app)
} else {
apps_map['0'].push(app)
}
}
let apps_opts = []
for (const category in apps_map) {
console.log('category',typeof(category))
if (category === '0') {
apps_opts.push(
...Array.from(apps_map[category], a => {
return {
content: a.name,
has_submenu: false,
callback: async () => {
try {
let item = (await get_my_app(a.filename))[0]
if (item) {
// console.log(item.data)
app.loadGraphData(item.data)
setTimeout(() => {
const node = app.graph._nodes_in_order[0]
if (!node) return
app.canvas.centerOnNode(node)
app.canvas.setZoom(0.5)
}, 1000)
}
} catch (error) {}
}
}
})
)
} else {
// 二级
apps_opts.push({
content: '🚀 '+category,
has_submenu: true,
disabled: false,
submenu: {
options: Array.from(apps_map[category], a => {
return {
content: a.name,
callback: async () => {
try {
let item = (await get_my_app(a.filename, a.category))[0]
if (item) {
// console.log(item.data)
app.loadGraphData(item.data)
setTimeout(() => {
const node = app.graph._nodes_in_order[0]
if (!node) return
app.canvas.centerOnNode(node)
app.canvas.setZoom(0.5)
}, 1000)
}
} catch (error) {}
}
}
})
d.innerHTML = `
<span>${'#' + nodeId} ${n}</span>
<a href="${url}" target="_blank" style="text-decoration: none;">🔗</a>
`
d.title = title
nodesDiv.appendChild(d)
}
nodesDivv.appendChild(nodesDiv)
nodesDivv.style=`overflow: scroll;
height: 70vh;width: 100%;`
div.appendChild(nodesDivv)
if (!document.querySelector('#mixlab_find_the_node'))
document.body.appendChild(div)
})
}
}
// console.log('apps',apps_map, apps_opts,apps)
LGraphCanvas.prototype.getCanvasMenuOptions = function () {
const options = orig.apply(this, arguments)
options.push(
null,
{
content: `Nodes Map ♾️Mixlab`,
disabled: false,
callback: async () => {
nodesMap =
nodesMap && Object.keys(nodesMap).length > 0
? nodesMap
: await getCustomnodeMappings('url')
const nodesDiv = document.createDocumentFragment()
const nodes = (await app.graphToPrompt()).output
// console.log('[Mixlab]', 'loaded graph node: ', app)
let div =
document.querySelector('#mixlab_find_the_node') ||
document.createElement('div')
div.id = 'mixlab_find_the_node'
div.style = `
flex-direction: column;
align-items: end;
display:flex;position: absolute;
top: 50px; left: 50px; width: 200px;
color: var(--descrip-text);
background-color: var(--comfy-menu-bg);
padding: 10px;
border: 1px solid black;z-index: 999999999;padding-top: 0;`
div.innerHTML = ''
let btn = document.createElement('div')
btn.style = `display: flex;
width: calc(100% - 24px);
justify-content: space-between;
align-items: center;
padding: 0 12px;
height: 44px;`
let btnB = document.createElement('button')
let textB = document.createElement('p')
btn.appendChild(textB)
btn.appendChild(btnB)
textB.style.fontSize = '12px'
textB.innerText = `Locate and navigate nodes ♾️Mixlab`
btnB.style = `float: right; border: none; color: var(--input-text);
background-color: var(--comfy-input-bg); border-color: var(--border-color);cursor: pointer;`
btnB.addEventListener('click', () => {
div.style.display = 'none'
})
btnB.innerText = 'X'
// 悬浮框拖动事件
div.addEventListener('mousedown', function (e) {
var startX = e.clientX
var startY = e.clientY
var offsetX = div.offsetLeft
var offsetY = div.offsetTop
function moveBox (e) {
var newX = e.clientX
var newY = e.clientY
var deltaX = newX - startX
var deltaY = newY - startY
div.style.left = offsetX + deltaX + 'px'
div.style.top = offsetY + deltaY + 'px'
}
function stopMoving () {
document.removeEventListener('mousemove', moveBox)
document.removeEventListener('mouseup', stopMoving)
}
document.addEventListener('mousemove', moveBox)
document.addEventListener('mouseup', stopMoving)
})
div.appendChild(btn)
const updateNodes = (ns, nd) => {
let appInfoNodes = {}
try {
let appInfo = app.graph._nodes.filter(
n => n.type === 'AppInfo'
)[0]
if (appInfo) {
appInfoNodes[appInfo.id] = 2
for (const id of appInfo.widgets[1].value.split('\n')) {
if (id && id.trim() && parseInt(id)) {
appInfoNodes[id] = 0
}
}
for (const id of app.graph._nodes
.filter(n => n.type === 'AppInfo')[0]
.widgets[2].value.split('\n')) {
if (id && id.trim() && parseInt(id)) {
appInfoNodes[id] = 1
}
}
}
} catch (error) {
console.log(error)
}
for (let nodeId in ns) {
let n = ns[nodeId].title || ns[nodeId].class_type
if (nodesMap[n]) {
const { url, title } = nodesMap[n]
let d = document.createElement('button')
d.style = `text-align: left;
margin:6px;
color: var(--input-text);
background-color: var(--comfy-input-bg);
border-color: ${
appInfoNodes[nodeId] >= 0
? appInfoNodes[nodeId] === 1
? 'blue'
: 'red'
: 'var(--border-color)'
};
cursor: pointer;`
if (appInfoNodes[nodeId] === 2) {
// appinfo
d.style.backgroundColor = '#326328'
d.style.color = '#ffffff'
d.style.borderColor = 'transparent'
}
d.addEventListener('click', () => {
// console.log('node')
const node = app.graph.getNodeById(nodeId)
if (!node) return
app.canvas.centerOnNode(node)
app.canvas.setZoom(1)
})
d.addEventListener('mouseover', async () => {
// console.log('mouseover')
let n = (await app.graphToPrompt()).output
if (!deepEqual(n, ns)) {
nd.innerHTML = ''
updateNodes(n, nd)
}
})
d.innerHTML = `
<span>${'#' + nodeId} ${n}</span>
<a href="${url}" target="_blank" style="text-decoration: none;">🔗</a>
`
d.title = title
nd.appendChild(d)
}
}
}
let nodesDivv = document.createElement('div')
let appInfoNodes = {}
try {
let appInfo = app.graph._nodes.filter(
n => n.type === 'AppInfo'
)[0]
if (appInfo) {
appInfoNodes[appInfo.id] = 2
for (const id of appInfo.widgets[1].value.split('\n')) {
if (id && id.trim() && parseInt(id)) {
appInfoNodes[id] = 0
}
}
for (const id of app.graph._nodes
.filter(n => n.type === 'AppInfo')[0]
.widgets[2].value.split('\n')) {
if (id && id.trim() && parseInt(id)) {
appInfoNodes[id] = 1
}
}
}
} catch (error) {
console.log(error)
}
for (let nodeId in nodes) {
let n = nodes[nodeId].class_type
if (nodesMap[n]) {
const { url, title: _title } = nodesMap[n]
let title = app.graph.getNodeById(nodeId).title || _title
let d = document.createElement('button')
d.style = `text-align: left;
margin:6px;
color: var(--input-text);
background-color: var(--comfy-input-bg);
border-color: ${
appInfoNodes[nodeId] >= 0
? appInfoNodes[nodeId] === 1
? 'blue'
: 'red'
: 'var(--border-color)'
};
cursor: pointer;`
if (appInfoNodes[nodeId] === 2) {
// appinfo
d.style.backgroundColor = '#326328'
d.style.color = '#ffffff'
d.style.borderColor = 'transparent'
}
d.addEventListener('click', () => {
console.log('click')
const node = app.graph.getNodeById(nodeId)
if (!node) return
app.canvas.centerOnNode(node)
app.canvas.setZoom(1)
})
d.addEventListener('mouseover', async () => {
// console.log('mouseover')
let n = (await app.graphToPrompt()).output
if (!deepEqual(n, nodes)) {
nodesDivv.innerHTML = ''
updateNodes(n, nodesDivv)
}
})
d.innerHTML = `
<span>${'#' + nodeId} ${title}</span>
<a href="${url}" target="_blank" style="text-decoration: none;">🔗</a>
`
d.title = n
nodesDiv.appendChild(d)
}
}
nodesDivv.appendChild(nodesDiv)
nodesDivv.style = `overflow: scroll;
height: 70vh;width: 100%;`
div.appendChild(nodesDivv)
if (!document.querySelector('#mixlab_find_the_node'))
document.body.appendChild(div)
}
},
{
content: 'Workflow App ♾️Mixlab',
has_submenu: true,
disabled: false,
submenu: {
options:apps_opts
}
}
});
return options;
};
)
return options
}
}, 1000)
}
})
+225 -115
View File
@@ -1,10 +1,8 @@
import { app } from '../../../scripts/app.js'
import { api } from '../../../scripts/api.js'
import { ComfyWidgets } from '../../../scripts/widgets.js'
import { $el } from '../../../scripts/ui.js'
import { addValueControlWidget } from '../../../scripts/widgets.js'
const getLocalData = key => {
let data = {}
@@ -47,122 +45,234 @@ function get_position_style (ctx, widget_width, y, node_height) {
}
}
function hexToRGBA (hexColor) {
var hex = hexColor.replace('#', '')
var r = parseInt(hex.substring(0, 2), 16)
var g = parseInt(hex.substring(2, 4), 16)
var b = parseInt(hex.substring(4, 6), 16)
// 获取透明度的十六进制值
var alphaHex = hex.substring(6)
// 将透明度的十六进制值转换为十进制值
var alpha = parseInt(alphaHex, 16) / 255
return [r, g, b, alpha]
}
app.registerExtension({
name: 'Mixlab.utils.Color',
async getCustomWidgets (app) {
return {
TCOLOR (node, inputName, inputData, app) {
// console.log('##node', node)
const widget = {
type: inputData[0], // the type, CHEESE
name: inputName, // the name, slice
size: [128, 32], // a default size
draw (ctx, node, width, y) {},
computeSize (...args) {
return [128, 32] // a method to compute the current size of the widget
},
async serializeValue (nodeId, widgetIndex) {
let data = getLocalData('_mixlab_utils_color')
return data[node.id] || '#000000'
name: 'Mixlab.utils.Color',
init () {
$el('link', {
rel: 'stylesheet',
href: '/extensions/comfyui-mixlab-nodes/lib/classic.min.css',
parent: document.head
})
$el('style', {
textContent: `
.pickr{
display: flex;
justify-content: center;
align-items: center;
}
.pickr .pcr-button {
width: 56px;
height: 56px;
outline: 1px solid white;
}
`,
parent: document.body
})
},
async getCustomWidgets (app) {
return {
TCOLOR (node, inputName, inputData, app) {
// console.log('##node', node)
const widget = {
type: inputData[0], // the type, CHEESE
name: inputName, // the name, slice
size: [128, 32], // a default size
draw (ctx, node, width, y) {},
computeSize (...args) {
return [128, 32] // a method to compute the current size of the widget
},
async serializeValue (nodeId, widgetIndex) {
// let data = getLocalData('_mixlab_utils_color')
// let hex = data[node.id] || '#000000'
let hex = widget.value || '#000000'
let [r, g, b, a] = hexToRGBA(hex)
return {
hex,
r,
g,
b,
a
}
}
// widget.something = something; // maybe adds stuff to it
node.addCustomWidget(widget) // adds it to the node
return widget // and returns it.
}
}
},
async beforeRegisterNodeDef (nodeType, nodeData, app) {
if (nodeType.comfyClass == 'Color') {
const orig_nodeCreated = nodeType.prototype.onNodeCreated
nodeType.prototype.onNodeCreated = function () {
orig_nodeCreated?.apply(this, arguments)
console.log('Color nodeData', this.widgets)
const widget = {
type: 'div',
name: 'input_color',
draw (ctx, node, widget_width, y, widget_height) {
Object.assign(
this.div.style,
get_position_style(
ctx,
widget_width,
44,
node.size[1]
)
)
}
}
widget.div = $el('div', {})
document.body.appendChild(widget.div)
const inputDiv = (key, placeholder, value) => {
let div = document.createElement('div')
const ip = document.createElement('input')
ip.type = 'color'
ip.className = `${'comfy-multiline-input'} ${placeholder}`
div.style = `display: flex;
align-items: center;
margin: 6px 8px;
margin-top: 0;`
ip.placeholder = placeholder
ip.value = value
ip.style = `outline: none;
border: none;
padding: 4px;
width: 100%;cursor: pointer;
height: 32px;`
const label = document.createElement('label')
label.style = 'font-size: 10px;min-width:32px'
label.innerText = placeholder
div.appendChild(label)
div.appendChild(ip)
ip.addEventListener('change', () => {
let data = getLocalData(key)
data[this.id] = ip.value.trim()
localStorage.setItem(key, JSON.stringify(data))
// console.log(this.id, ip.value.trim())
})
return div
}
let inputColor = inputDiv('_mixlab_utils_color', 'Color', '#000000')
widget.div.appendChild(inputColor)
this.addCustomWidget(widget)
const onRemoved = this.onRemoved
this.onRemoved = () => {
inputColor.remove()
widget.div.remove()
return onRemoved?.()
}
this.serialize_widgets = true //需要保存参数
}
}
},
async loadedGraphNode (node, app) {
// Fires every time a node is constructed
// You can modify widgets/add handlers/etc here
if (node.type === 'Color') {
let widget = node.widgets.filter(w => w.div)[0]
let data = getLocalData('_mixlab_utils_color')
let id = node.id
widget.div.querySelector('.Color').value = data[id] || '#000000'
// widget.something = something; // maybe adds stuff to it
node.addCustomWidget(widget) // adds it to the node
return widget // and returns it.
}
}
})
},
async beforeRegisterNodeDef (nodeType, nodeData, app) {
if (nodeType.comfyClass == 'Color') {
const orig_nodeCreated = nodeType.prototype.onNodeCreated
nodeType.prototype.onNodeCreated = function () {
orig_nodeCreated?.apply(this, arguments)
// console.log('Color nodeData', this.widgets)
const widget = {
type: 'div',
name: 'input_color',
draw (ctx, node, widget_width, y, widget_height) {
Object.assign(
this.div.style,
get_position_style(ctx, widget_width, 44, node.size[1])
)
// console.log('draw',y,node.widgets[0].last_y)
}
}
widget.div = $el('div', {})
document.body.appendChild(widget.div)
const inputDiv = () => {
let div = document.createElement('div')
div.id = `color_picker_${this.id}`
return div
}
let inputColor = inputDiv('_mixlab_utils_color', 'Color', '#000000')
widget.div.appendChild(inputColor)
this.addCustomWidget(widget)
const pickr = Pickr.create({
el: `#${inputColor.id}`,
theme: 'classic', // or 'monolith', or 'nano'
// closeOnScroll: true,
default:'#000000',
swatches: [
'rgba(244, 67, 54, 1)',
'rgba(233, 30, 99, 0.95)',
'rgba(156, 39, 176, 0.9)',
'rgba(103, 58, 183, 0.85)',
'rgba(63, 81, 181, 0.8)',
'rgba(33, 150, 243, 0.75)',
'rgba(3, 169, 244, 0.7)',
'rgba(0, 188, 212, 0.7)',
'rgba(0, 150, 136, 0.75)',
'rgba(76, 175, 80, 0.8)',
'rgba(139, 195, 74, 0.85)',
'rgba(205, 220, 57, 0.9)',
'rgba(255, 235, 59, 0.95)',
'rgba(255, 193, 7, 1)'
],
components: {
// Main components
preview: true,
opacity: true,
hue: true,
// Input / output Options
interaction: {
hex: true,
rgba: true,
hsla: true,
hsva: true,
cmyk: true,
input: true,
// clear: true,
save: true,
cancel: true
}
}
})
pickr
.on('save', (color, instance) => {
// console.log('Event: "save"', color.toHEXA().toString())
// let data = getLocalData('_mixlab_utils_color')
// data[this.id] = color.toHEXA().toString()
// localStorage.setItem('_mixlab_utils_color', JSON.stringify(data))
try {
let tc = this.widgets.filter(w => w.type == 'TCOLOR')[0]
tc.value = color.toHEXA().toString()
} catch (error) {}
})
.on('cancel', instance => {
pickr && pickr.hide()
})
this.pickr = pickr
const handleMouseWheel = () => {
try {
this.pickr && this.pickr.hide()
} catch (error) {}
}
document.addEventListener('wheel', handleMouseWheel)
const onRemoved = this.onRemoved
this.onRemoved = () => {
inputColor.remove()
widget.div.remove()
try {
this.pickr.destroyAndRemove()
this.pickr = null
document.removeEventListener('wheel', handleMouseWheel)
} catch (error) {
console.log(error)
}
return onRemoved?.()
}
this.serialize_widgets = true //需要保存参数
}
}
},
async loadedGraphNode (node, app) {
// Fires every time a node is constructed
// You can modify widgets/add handlers/etc here
if (node.type === 'Color') {
try {
let TCOLOR = node.widgets.filter(w => w.type == 'TCOLOR')[0]
setTimeout(() => node.pickr.setColor(TCOLOR.value || '#000000'), 1000)
} catch (error) {}
}
}
})
app.registerExtension({
name: 'Mixlab.utils.TextToNumber',
async beforeRegisterNodeDef (nodeType, nodeData, app) {
if (nodeType.comfyClass == 'TextToNumber') {
const onExecuted = nodeType.prototype.onExecuted
nodeType.prototype.onExecuted = function (message) {
onExecuted?.apply(this, arguments)
const random_number = this.widgets.filter(
w => w.name === 'random_number'
)[0]
if (random_number.value === 'enable') {
const n = this.widgets.filter(w => w.name === 'number')[0]
n.value = message.num[0]
}
console.log('TextToNumber', random_number.value)
}
}
}
})
+2
View File
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+5
View File
File diff suppressed because one or more lines are too long
+1
View File
@@ -0,0 +1 @@
/*! PhotoSwipe main CSS by Dmytro Semenov | photoswipe.com */.pswp{--pswp-bg:#000;--pswp-placeholder-bg:#222;--pswp-root-z-index:100000;--pswp-preloader-color:rgba(79, 79, 79, 0.4);--pswp-preloader-color-secondary:rgba(255, 255, 255, 0.9);--pswp-icon-color:#fff;--pswp-icon-color-secondary:#4f4f4f;--pswp-icon-stroke-color:#4f4f4f;--pswp-icon-stroke-width:2px;--pswp-error-text-color:var(--pswp-icon-color)}.pswp{position:fixed;top:0;left:0;width:100%;height:100%;z-index:var(--pswp-root-z-index);display:none;touch-action:none;outline:0;opacity:.003;contain:layout style size;-webkit-tap-highlight-color:transparent}.pswp:focus{outline:0}.pswp *{box-sizing:border-box}.pswp img{max-width:none}.pswp--open{display:block}.pswp,.pswp__bg{transform:translateZ(0);will-change:opacity}.pswp__bg{opacity:.005;background:var(--pswp-bg)}.pswp,.pswp__scroll-wrap{overflow:hidden}.pswp__bg,.pswp__container,.pswp__content,.pswp__img,.pswp__item,.pswp__scroll-wrap,.pswp__zoom-wrap{position:absolute;top:0;left:0;width:100%;height:100%}.pswp__img,.pswp__zoom-wrap{width:auto;height:auto}.pswp--click-to-zoom.pswp--zoom-allowed .pswp__img{cursor:-webkit-zoom-in;cursor:-moz-zoom-in;cursor:zoom-in}.pswp--click-to-zoom.pswp--zoomed-in .pswp__img{cursor:move;cursor:-webkit-grab;cursor:-moz-grab;cursor:grab}.pswp--click-to-zoom.pswp--zoomed-in .pswp__img:active{cursor:-webkit-grabbing;cursor:-moz-grabbing;cursor:grabbing}.pswp--no-mouse-drag.pswp--zoomed-in .pswp__img,.pswp--no-mouse-drag.pswp--zoomed-in .pswp__img:active,.pswp__img{cursor:-webkit-zoom-out;cursor:-moz-zoom-out;cursor:zoom-out}.pswp__button,.pswp__container,.pswp__counter,.pswp__img{-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.pswp__item{z-index:1;overflow:hidden}.pswp__hidden{display:none!important}.pswp__content{pointer-events:none}.pswp__content>*{pointer-events:auto}.pswp__error-msg-container{display:grid}.pswp__error-msg{margin:auto;font-size:1em;line-height:1;color:var(--pswp-error-text-color)}.pswp .pswp__hide-on-close{opacity:.005;will-change:opacity;transition:opacity var(--pswp-transition-duration) cubic-bezier(.4,0,.22,1);z-index:10;pointer-events:none}.pswp--ui-visible .pswp__hide-on-close{opacity:1;pointer-events:auto}.pswp__button{position:relative;display:block;width:50px;height:60px;padding:0;margin:0;overflow:hidden;cursor:pointer;background:0 0;border:0;box-shadow:none;opacity:.85;-webkit-appearance:none;-webkit-touch-callout:none}.pswp__button:active,.pswp__button:focus,.pswp__button:hover{transition:none;padding:0;background:0 0;border:0;box-shadow:none;opacity:1}.pswp__button:disabled{opacity:.3;cursor:auto}.pswp__icn{fill:var(--pswp-icon-color);color:var(--pswp-icon-color-secondary)}.pswp__icn{position:absolute;top:14px;left:9px;width:32px;height:32px;overflow:hidden;pointer-events:none}.pswp__icn-shadow{stroke:var(--pswp-icon-stroke-color);stroke-width:var(--pswp-icon-stroke-width);fill:none}.pswp__icn:focus{outline:0}.pswp__img--with-bg,div.pswp__img--placeholder{background:var(--pswp-placeholder-bg)}.pswp__top-bar{position:absolute;left:0;top:0;width:100%;height:60px;display:flex;flex-direction:row;justify-content:flex-end;z-index:10;pointer-events:none!important}.pswp__top-bar>*{pointer-events:auto;will-change:opacity}.pswp__button--close{margin-right:6px}.pswp__button--arrow{position:absolute;top:0;width:75px;height:100px;top:50%;margin-top:-50px}.pswp__button--arrow:disabled{display:none;cursor:default}.pswp__button--arrow .pswp__icn{top:50%;margin-top:-30px;width:60px;height:60px;background:0 0;border-radius:0}.pswp--one-slide .pswp__button--arrow{display:none}.pswp--touch .pswp__button--arrow{visibility:hidden}.pswp--has_mouse .pswp__button--arrow{visibility:visible}.pswp__button--arrow--prev{right:auto;left:0}.pswp__button--arrow--next{right:0}.pswp__button--arrow--next .pswp__icn{left:auto;right:14px;transform:scale(-1,1)}.pswp__button--zoom{display:none}.pswp--zoom-allowed .pswp__button--zoom{display:block}.pswp--zoomed-in .pswp__zoom-icn-bar-v{display:none}.pswp__preloader{position:relative;overflow:hidden;width:50px;height:60px;margin-right:auto}.pswp__preloader .pswp__icn{opacity:0;transition:opacity .2s linear;animation:pswp-clockwise .6s linear infinite}.pswp__preloader--active .pswp__icn{opacity:.85}@keyframes pswp-clockwise{0%{transform:rotate(0)}100%{transform:rotate(360deg)}}.pswp__counter{height:30px;margin-top:15px;margin-inline-start:20px;font-size:14px;line-height:30px;color:var(--pswp-icon-color);text-shadow:1px 1px 3px var(--pswp-icon-color-secondary);opacity:.85}.pswp--one-slide .pswp__counter{display:none}
+3
View File
File diff suppressed because one or more lines are too long
+2423 -705
View File
File diff suppressed because it is too large Load Diff
+10 -9
View File
@@ -295,7 +295,7 @@
"Node name for S&R": "KSampler"
},
"widgets_values": [
1115769645491668,
482859286431021,
"randomize",
4,
1.6,
@@ -479,10 +479,10 @@
1928,
295
],
"size": {
"0": 315,
"1": 58
},
"size": [
315,
58
],
"flags": {},
"order": 12,
"mode": 0,
@@ -507,10 +507,10 @@
-65,
446
],
"size": {
"0": 315,
"1": 170
},
"size": [
312.78457519531213,
606.2132135620109
],
"flags": {},
"order": 3,
"mode": 0,
@@ -552,6 +552,7 @@
},
"widgets_values": [
null,
2018,
null,
null,
null,
+622 -747
View File
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large Load Diff
+723
View File
@@ -0,0 +1,723 @@
{
"last_node_id": 22,
"last_link_id": 23,
"nodes": [
{
"id": 9,
"type": "CLIPTextEncode",
"pos": [
2070,
830
],
"size": {
"0": 425.27801513671875,
"1": 180.6060791015625
},
"flags": {},
"order": 5,
"mode": 0,
"inputs": [
{
"name": "clip",
"type": "CLIP",
"link": 7
}
],
"outputs": [
{
"name": "CONDITIONING",
"type": "CONDITIONING",
"links": [
4
],
"slot_index": 0
}
],
"properties": {
"Node name for S&R": "CLIPTextEncode"
},
"widgets_values": [
"text, watermark"
]
},
{
"id": 7,
"type": "EmptyLatentImage",
"pos": [
2070,
1060
],
"size": {
"0": 315,
"1": 106
},
"flags": {},
"order": 0,
"mode": 0,
"outputs": [
{
"name": "LATENT",
"type": "LATENT",
"links": [
5
],
"slot_index": 0
}
],
"properties": {
"Node name for S&R": "EmptyLatentImage"
},
"widgets_values": [
512,
512,
1
]
},
{
"id": 6,
"type": "CheckpointLoaderSimple",
"pos": [
1640,
920
],
"size": {
"0": 315,
"1": 98
},
"flags": {},
"order": 1,
"mode": 0,
"outputs": [
{
"name": "MODEL",
"type": "MODEL",
"links": [
2
],
"slot_index": 0
},
{
"name": "CLIP",
"type": "CLIP",
"links": [
6,
7
],
"slot_index": 1
},
{
"name": "VAE",
"type": "VAE",
"links": [
9
],
"slot_index": 2
}
],
"properties": {
"Node name for S&R": "CheckpointLoaderSimple"
},
"widgets_values": [
"deliberate_v2.safetensors"
]
},
{
"id": 8,
"type": "CLIPTextEncode",
"pos": [
2080,
630
],
"size": {
"0": 422.84503173828125,
"1": 164.31304931640625
},
"flags": {},
"order": 8,
"mode": 0,
"inputs": [
{
"name": "clip",
"type": "CLIP",
"link": 6
},
{
"name": "text",
"type": "STRING",
"link": 16,
"widget": {
"name": "text"
}
}
],
"outputs": [
{
"name": "CONDITIONING",
"type": "CONDITIONING",
"links": [
3
],
"slot_index": 0
}
],
"properties": {
"Node name for S&R": "CLIPTextEncode"
},
"widgets_values": [
"beautiful scenery nature glass bottle landscape, , purple galaxy bottle,"
]
},
{
"id": 10,
"type": "VAEDecode",
"pos": [
2870,
630
],
"size": {
"0": 210,
"1": 46
},
"flags": {
"collapsed": false
},
"order": 11,
"mode": 0,
"inputs": [
{
"name": "samples",
"type": "LATENT",
"link": 8
},
{
"name": "vae",
"type": "VAE",
"link": 9
}
],
"outputs": [
{
"name": "IMAGE",
"type": "IMAGE",
"links": [
12
],
"slot_index": 0
}
],
"properties": {
"Node name for S&R": "VAEDecode"
}
},
{
"id": 16,
"type": "ShowTextForGPT",
"pos": [
3288,
-323
],
"size": {
"0": 449.1168212890625,
"1": 76
},
"flags": {},
"order": 9,
"mode": 0,
"inputs": [
{
"name": "text",
"type": "STRING",
"link": 20,
"widget": {
"name": "text"
}
}
],
"outputs": [
{
"name": "STRING",
"type": "STRING",
"links": null,
"shape": 6
}
],
"properties": {
"Node name for S&R": "ShowTextForGPT"
},
"widgets_values": [
"a girl face,super,(Pop Art:1.26),(Black and White:1.26)"
]
},
{
"id": 11,
"type": "PreviewImage",
"pos": [
3319,
-161
],
"size": {
"0": 435.8727111816406,
"1": 511.8609619140625
},
"flags": {},
"order": 13,
"mode": 0,
"inputs": [
{
"name": "images",
"type": "IMAGE",
"link": 13
}
],
"properties": {
"Node name for S&R": "PreviewImage"
}
},
{
"id": 5,
"type": "KSampler",
"pos": [
2520,
630
],
"size": {
"0": 315,
"1": 262
},
"flags": {},
"order": 10,
"mode": 0,
"inputs": [
{
"name": "model",
"type": "MODEL",
"link": 2
},
{
"name": "positive",
"type": "CONDITIONING",
"link": 3
},
{
"name": "negative",
"type": "CONDITIONING",
"link": 4
},
{
"name": "latent_image",
"type": "LATENT",
"link": 5
},
{
"name": "seed",
"type": "INT",
"link": 21,
"widget": {
"name": "seed"
}
}
],
"outputs": [
{
"name": "LATENT",
"type": "LATENT",
"links": [
8
],
"slot_index": 0
}
],
"properties": {
"Node name for S&R": "KSampler"
},
"widgets_values": [
1063141893699118,
"fixed",
20,
8,
"euler",
"normal",
1
]
},
{
"id": 18,
"type": "IntNumber",
"pos": [
2852,
275
],
"size": {
"0": 315,
"1": 58
},
"flags": {},
"order": 2,
"mode": 0,
"outputs": [
{
"name": "INT",
"type": "INT",
"links": [
21
],
"shape": 3,
"slot_index": 0
}
],
"title": "Seed",
"properties": {
"Node name for S&R": "IntNumber"
},
"widgets_values": [
-1
]
},
{
"id": 22,
"type": "PromptSlide",
"pos": [
2364,
-294
],
"size": {
"0": 315,
"1": 82
},
"flags": {},
"order": 3,
"mode": 0,
"outputs": [
{
"name": "prompt",
"type": "STRING",
"links": [
23
],
"shape": 3,
"slot_index": 0
}
],
"properties": {
"Node name for S&R": "PromptSlide"
},
"widgets_values": [
"Pop Art",
1.26
]
},
{
"id": 12,
"type": "AppInfo",
"pos": [
2924,
846
],
"size": {
"0": 400,
"1": 272
},
"flags": {},
"order": 12,
"mode": 0,
"inputs": [
{
"name": "image",
"type": "IMAGE",
"link": 12
}
],
"outputs": [
{
"name": "IMAGE",
"type": "IMAGE",
"links": [
13
],
"shape": 3,
"slot_index": 0
}
],
"properties": {
"Node name for S&R": "AppInfo"
},
"widgets_values": [
"Prompt-weight-test",
"21\n22\n18",
"11\n16",
"",
1,
"",
null
]
},
{
"id": 21,
"type": "PromptSlide",
"pos": [
2360,
-544
],
"size": {
"0": 315,
"1": 82
},
"flags": {},
"order": 4,
"mode": 0,
"outputs": [
{
"name": "prompt",
"type": "STRING",
"links": [
22
],
"shape": 3,
"slot_index": 0
}
],
"properties": {
"Node name for S&R": "PromptSlide"
},
"widgets_values": [
"Black and White",
1.26
]
},
{
"id": 13,
"type": "RandomPrompt",
"pos": [
2840,
-330
],
"size": {
"0": 400,
"1": 224
},
"flags": {},
"order": 6,
"mode": 0,
"inputs": [
{
"name": "mutable_prompt",
"type": "STRING",
"link": 22,
"widget": {
"name": "mutable_prompt"
}
},
{
"name": "immutable_prompt",
"type": "STRING",
"link": 23,
"widget": {
"name": "immutable_prompt"
}
}
],
"outputs": [
{
"name": "STRING",
"type": "STRING",
"links": [
19
],
"shape": 6,
"slot_index": 0
}
],
"properties": {
"Node name for S&R": "RandomPrompt"
},
"widgets_values": [
1,
"",
" ``",
"disable",
""
]
},
{
"id": 15,
"type": "RandomPrompt",
"pos": [
2840,
-30
],
"size": {
"0": 400,
"1": 224
},
"flags": {},
"order": 7,
"mode": 0,
"inputs": [
{
"name": "mutable_prompt",
"type": "STRING",
"link": 19,
"widget": {
"name": "mutable_prompt"
}
}
],
"outputs": [
{
"name": "STRING",
"type": "STRING",
"links": [
16,
20
],
"shape": 6,
"slot_index": 0
}
],
"properties": {
"Node name for S&R": "RandomPrompt"
},
"widgets_values": [
1,
"",
" a girl face,super,``",
"disable",
""
]
}
],
"links": [
[
2,
6,
0,
5,
0,
"MODEL"
],
[
3,
8,
0,
5,
1,
"CONDITIONING"
],
[
4,
9,
0,
5,
2,
"CONDITIONING"
],
[
5,
7,
0,
5,
3,
"LATENT"
],
[
6,
6,
1,
8,
0,
"CLIP"
],
[
7,
6,
1,
9,
0,
"CLIP"
],
[
8,
5,
0,
10,
0,
"LATENT"
],
[
9,
6,
2,
10,
1,
"VAE"
],
[
12,
10,
0,
12,
0,
"IMAGE"
],
[
13,
12,
0,
11,
0,
"IMAGE"
],
[
16,
15,
0,
8,
1,
"STRING"
],
[
19,
13,
0,
15,
0,
"STRING"
],
[
20,
15,
0,
16,
0,
"STRING"
],
[
21,
18,
0,
5,
4,
"INT"
],
[
22,
21,
0,
13,
0,
"STRING"
],
[
23,
22,
0,
13,
1,
"STRING"
]
],
"groups": [],
"config": {},
"extra": {},
"version": 0.4
}
+717
View File
@@ -0,0 +1,717 @@
{
"last_node_id": 25,
"last_link_id": 32,
"nodes": [
{
"id": 5,
"type": "CLIPTextEncode",
"pos": [
1029,
-2149
],
"size": {
"0": 425.27801513671875,
"1": 180.6060791015625
},
"flags": {},
"order": 7,
"mode": 0,
"inputs": [
{
"name": "clip",
"type": "CLIP",
"link": 6
}
],
"outputs": [
{
"name": "CONDITIONING",
"type": "CONDITIONING",
"links": [
3
],
"slot_index": 0
}
],
"title": "负向prompt",
"properties": {
"Node name for S&R": "CLIPTextEncode"
},
"widgets_values": [
"text, watermark"
]
},
{
"id": 6,
"type": "VAEDecode",
"pos": [
1867,
-2378
],
"size": {
"0": 210,
"1": 46
},
"flags": {
"collapsed": false
},
"order": 10,
"mode": 0,
"inputs": [
{
"name": "samples",
"type": "LATENT",
"link": 7
},
{
"name": "vae",
"type": "VAE",
"link": 8
}
],
"outputs": [
{
"name": "IMAGE",
"type": "IMAGE",
"links": [
9
],
"slot_index": 0
}
],
"properties": {
"Node name for S&R": "VAEDecode"
}
},
{
"id": 15,
"type": "EnhanceImage",
"pos": [
2591,
-2531
],
"size": {
"0": 315,
"1": 58
},
"flags": {},
"order": 12,
"mode": 0,
"inputs": [
{
"name": "image",
"type": "IMAGE",
"link": 25
}
],
"outputs": [
{
"name": "IMAGE",
"type": "IMAGE",
"links": [
18
],
"shape": 3,
"slot_index": 0
}
],
"properties": {
"Node name for S&R": "EnhanceImage"
},
"widgets_values": [
1.1
]
},
{
"id": 21,
"type": "EmptyLatentImage",
"pos": [
998,
-2674
],
"size": [
315,
106
],
"flags": {},
"order": 8,
"mode": 0,
"inputs": [
{
"name": "width",
"type": "INT",
"link": 30,
"widget": {
"name": "width"
}
},
{
"name": "height",
"type": "INT",
"link": 32,
"widget": {
"name": "height"
}
}
],
"outputs": [
{
"name": "LATENT",
"type": "LATENT",
"links": [
26
],
"shape": 3
}
],
"properties": {
"Node name for S&R": "EmptyLatentImage"
},
"widgets_values": [
512,
512,
1
]
},
{
"id": 24,
"type": "LimitNumber",
"pos": [
665,
-2958
],
"size": {
"0": 315,
"1": 82
},
"flags": {},
"order": 4,
"mode": 0,
"inputs": [
{
"name": "number",
"type": "*",
"link": 29
}
],
"outputs": [
{
"name": "number",
"type": "*",
"links": [
30
],
"shape": 3,
"slot_index": 0
}
],
"properties": {
"Node name for S&R": "LimitNumber"
},
"widgets_values": [
512,
4089
]
},
{
"id": 25,
"type": "LimitNumber",
"pos": [
648,
-2659
],
"size": {
"0": 315,
"1": 82
},
"flags": {},
"order": 5,
"mode": 0,
"inputs": [
{
"name": "number",
"type": "*",
"link": 31
}
],
"outputs": [
{
"name": "number",
"type": "*",
"links": [
32
],
"shape": 3,
"slot_index": 0
}
],
"properties": {
"Node name for S&R": "LimitNumber"
},
"widgets_values": [
512,
4089
]
},
{
"id": 22,
"type": "IntNumber",
"pos": [
302,
-2758
],
"size": {
"0": 315,
"1": 58
},
"flags": {},
"order": 0,
"mode": 0,
"outputs": [
{
"name": "INT",
"type": "INT",
"links": [
29
],
"shape": 3,
"slot_index": 0
}
],
"title": "Width",
"properties": {
"Node name for S&R": "IntNumber"
},
"widgets_values": [
512
]
},
{
"id": 23,
"type": "IntNumber",
"pos": [
299,
-2601
],
"size": {
"0": 315,
"1": 58
},
"flags": {},
"order": 1,
"mode": 0,
"outputs": [
{
"name": "INT",
"type": "INT",
"links": [
31
],
"shape": 3,
"slot_index": 0
}
],
"title": "Height",
"properties": {
"Node name for S&R": "IntNumber"
},
"widgets_values": [
512
]
},
{
"id": 2,
"type": "CheckpointLoaderSimple",
"pos": [
567,
-2422
],
"size": {
"0": 315,
"1": 98
},
"flags": {},
"order": 2,
"mode": 0,
"outputs": [
{
"name": "MODEL",
"type": "MODEL",
"links": [
1
],
"slot_index": 0
},
{
"name": "CLIP",
"type": "CLIP",
"links": [
5,
6
],
"slot_index": 1
},
{
"name": "VAE",
"type": "VAE",
"links": [
8
],
"slot_index": 2
}
],
"title": "Model",
"properties": {
"Node name for S&R": "CheckpointLoaderSimple"
},
"widgets_values": [
"deliberate_v2.safetensors"
]
},
{
"id": 1,
"type": "KSampler",
"pos": [
1509,
-2394
],
"size": {
"0": 315,
"1": 262
},
"flags": {},
"order": 9,
"mode": 0,
"inputs": [
{
"name": "model",
"type": "MODEL",
"link": 1
},
{
"name": "positive",
"type": "CONDITIONING",
"link": 2
},
{
"name": "negative",
"type": "CONDITIONING",
"link": 3
},
{
"name": "latent_image",
"type": "LATENT",
"link": 26,
"slot_index": 3
},
{
"name": "denoise",
"type": "FLOAT",
"link": 16,
"widget": {
"name": "denoise"
},
"slot_index": 4
}
],
"outputs": [
{
"name": "LATENT",
"type": "LATENT",
"links": [
7
],
"slot_index": 0
}
],
"properties": {
"Node name for S&R": "KSampler"
},
"widgets_values": [
730250870715434,
"randomize",
15,
6.9,
"euler",
"karras",
0.59
]
},
{
"id": 14,
"type": "FloatSlider",
"pos": [
1007,
-2819
],
"size": {
"0": 315,
"1": 58
},
"flags": {},
"order": 3,
"mode": 0,
"outputs": [
{
"name": "FLOAT",
"type": "FLOAT",
"links": [
16
],
"shape": 3,
"slot_index": 0
}
],
"title": "denoise",
"properties": {
"Node name for S&R": "FloatSlider"
},
"widgets_values": [
1
]
},
{
"id": 16,
"type": "PreviewImage",
"pos": [
2949,
-2615
],
"size": {
"0": 210,
"1": 246
},
"flags": {},
"order": 13,
"mode": 0,
"inputs": [
{
"name": "images",
"type": "IMAGE",
"link": 18
}
],
"properties": {
"Node name for S&R": "PreviewImage"
}
},
{
"id": 4,
"type": "CLIPTextEncode",
"pos": [
1022,
-2375
],
"size": {
"0": 422.84503173828125,
"1": 164.31304931640625
},
"flags": {},
"order": 6,
"mode": 0,
"inputs": [
{
"name": "clip",
"type": "CLIP",
"link": 5
}
],
"outputs": [
{
"name": "CONDITIONING",
"type": "CONDITIONING",
"links": [
2
],
"slot_index": 0
}
],
"title": "prompt",
"properties": {
"Node name for S&R": "CLIPTextEncode"
},
"widgets_values": [
"superman,fat cat"
]
},
{
"id": 7,
"type": "AppInfo",
"pos": [
2134,
-2528
],
"size": {
"0": 408.4201965332031,
"1": 406.9195861816406
},
"flags": {},
"order": 11,
"mode": 0,
"inputs": [
{
"name": "image",
"type": "IMAGE",
"link": 9
}
],
"outputs": [
{
"name": "IMAGE",
"type": "IMAGE",
"links": [
25
],
"shape": 3,
"slot_index": 0
}
],
"properties": {
"Node name for S&R": "AppInfo"
},
"widgets_values": [
"Text-to-Image",
"4\n22\n23\n2\n\n\n",
"16",
"演示基本的文生图流程",
1,
"#comfyui-mixlab-nodes# ",
null
]
}
],
"links": [
[
1,
2,
0,
1,
0,
"MODEL"
],
[
2,
4,
0,
1,
1,
"CONDITIONING"
],
[
3,
5,
0,
1,
2,
"CONDITIONING"
],
[
5,
2,
1,
4,
0,
"CLIP"
],
[
6,
2,
1,
5,
0,
"CLIP"
],
[
7,
1,
0,
6,
0,
"LATENT"
],
[
8,
2,
2,
6,
1,
"VAE"
],
[
9,
6,
0,
7,
0,
"IMAGE"
],
[
16,
14,
0,
1,
4,
"FLOAT"
],
[
18,
15,
0,
16,
0,
"IMAGE"
],
[
25,
7,
0,
15,
0,
"IMAGE"
],
[
26,
21,
0,
1,
3,
"LATENT"
],
[
29,
22,
0,
24,
0,
"*"
],
[
30,
24,
0,
21,
0,
"INT"
],
[
31,
23,
0,
25,
0,
"*"
],
[
32,
25,
0,
21,
1,
"INT"
]
],
"groups": [],
"config": {},
"extra": {},
"version": 0.4
}
File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 2.5 MiB

+314
View File
@@ -0,0 +1,314 @@
{
"last_node_id": 10,
"last_link_id": 16,
"nodes": [
{
"id": 9,
"type": "SpeechSynthesis",
"pos": [
40,
356
],
"size": {
"0": 352.8227844238281,
"1": 95.3553237915039
},
"flags": {},
"order": 1,
"mode": 0,
"inputs": [
{
"name": "text",
"type": "STRING",
"link": 12,
"widget": {
"name": "text"
}
}
],
"outputs": [
{
"name": "STRING",
"type": "STRING",
"links": [
13,
16
],
"shape": 6,
"slot_index": 0
}
],
"properties": {
"Node name for S&R": "SpeechSynthesis"
},
"widgets_values": [
"作为一个人工智能助手,我没有性别,也无法进行社交互动。我的主要任务是为用户提供帮助和信息。请问有什么问题我可以为您解答吗?"
]
},
{
"id": 8,
"type": "SpeechRecognition",
"pos": [
537,
321
],
"size": {
"0": 228.5580291748047,
"1": 239.85951232910156
},
"flags": {},
"order": 3,
"mode": 0,
"inputs": [
{
"name": "start_by",
"type": "INT",
"link": 14,
"widget": {
"name": "start_by"
}
}
],
"outputs": [
{
"name": "prompt",
"type": "STRING",
"links": [
15
],
"shape": 3,
"slot_index": 0
}
],
"properties": {
"Node name for S&R": "SpeechRecognition"
},
"widgets_values": [
null,
1,
4,
null
]
},
{
"id": 4,
"type": "ChatGPTOpenAI",
"pos": [
-403,
14
],
"size": {
"0": 400,
"1": 358
},
"flags": {},
"order": 0,
"mode": 0,
"outputs": [
{
"name": "text",
"type": "STRING",
"links": [
12
],
"shape": 3,
"slot_index": 0
},
{
"name": "messages",
"type": "STRING",
"links": null,
"shape": 3
},
{
"name": "session_history",
"type": "STRING",
"links": null,
"shape": 3
}
],
"properties": {
"Node name for S&R": "ChatGPTOpenAI"
},
"widgets_values": [
null,
null,
"跟你一样泡妞",
"You are ChatGPT, a large language model trained by OpenAI. Answer as concisely as possible.",
"gpt-3.5-turbo",
354,
"randomize",
1,
null
]
},
{
"id": 10,
"type": "ShowTextForGPT",
"pos": [
867,
349
],
"size": {
"0": 315,
"1": 76.00000762939453
},
"flags": {},
"order": 4,
"mode": 0,
"inputs": [
{
"name": "text",
"type": "STRING",
"link": 15,
"widget": {
"name": "text"
}
}
],
"outputs": [
{
"name": "STRING",
"type": "STRING",
"links": null,
"shape": 6
}
],
"properties": {
"Node name for S&R": "ShowTextForGPT"
},
"widgets_values": [
"纽斯"
]
},
{
"id": 5,
"type": "DynamicDelayProcessor",
"pos": [
496,
4
],
"size": {
"0": 400,
"1": 200
},
"flags": {},
"order": 2,
"mode": 0,
"inputs": [
{
"name": "any_input",
"type": "*",
"link": 13
},
{
"name": "delay_by_text",
"type": "STRING",
"link": 16,
"widget": {
"name": "delay_by_text"
}
}
],
"outputs": [
{
"name": "output",
"type": "*",
"links": [
14
],
"shape": 3,
"slot_index": 0
}
],
"properties": {
"Node name for S&R": "DynamicDelayProcessor"
},
"widgets_values": [
1,
"",
2.5,
"enable",
1
]
}
],
"links": [
[
4,
2,
0,
5,
0,
"*"
],
[
7,
4,
0,
6,
0,
"STRING"
],
[
8,
6,
0,
5,
1,
"STRING"
],
[
9,
5,
0,
7,
0,
"INT"
],
[
12,
4,
0,
9,
0,
"STRING"
],
[
13,
9,
0,
5,
0,
"*"
],
[
14,
5,
0,
8,
0,
"INT"
],
[
15,
8,
0,
10,
0,
"STRING"
],
[
16,
9,
0,
5,
1,
"STRING"
]
],
"groups": [],
"config": {},
"extra": {},
"version": 0.4
}
File diff suppressed because it is too large Load Diff
+828
View File
@@ -0,0 +1,828 @@
{
"last_node_id": 23,
"last_link_id": 0,
"nodes": [
{
"id": 7,
"type": "ResizeImageMixlab",
"pos": [
1096.229866976564,
731.8207909156248
],
"size": {
"0": 315,
"1": 106
},
"flags": {},
"order": 0,
"mode": 0,
"inputs": [
{
"name": "image",
"type": "IMAGE",
"link": null
}
],
"outputs": [
{
"name": "IMAGE",
"type": "IMAGE",
"links": null,
"shape": 3
}
],
"properties": {
"Node name for S&R": "ResizeImageMixlab"
},
"widgets_values": [
512,
512,
"width"
]
},
{
"id": 8,
"type": "LoadImagesFromPath",
"pos": [
1101.229866976564,
900.8207909156248
],
"size": {
"0": 315,
"1": 238
},
"flags": {},
"order": 1,
"mode": 0,
"outputs": [
{
"name": "IMAGE",
"type": "IMAGE",
"links": null,
"shape": 6
},
{
"name": "MASK",
"type": "MASK",
"links": null,
"shape": 6
},
{
"name": "STRING",
"type": "STRING",
"links": null,
"shape": 3
}
],
"properties": {
"Node name for S&R": "LoadImagesFromPath"
},
"widgets_values": [
"C:\\Users\\38957\\Documents\\GitHub\\extract-anything\\outputs",
"disable",
"enable",
0,
"disable",
null,
null
]
},
{
"id": 3,
"type": "ShowTextForGPT",
"pos": [
1786,
252
],
"size": {
"0": 315,
"1": 58
},
"flags": {},
"order": 2,
"mode": 0,
"inputs": [
{
"name": "text",
"type": "STRING",
"link": null,
"widget": {
"name": "text"
}
}
],
"outputs": [
{
"name": "STRING",
"type": "STRING",
"links": null,
"shape": 6
}
],
"properties": {
"Node name for S&R": "ShowTextForGPT"
},
"widgets_values": [
""
]
},
{
"id": 9,
"type": "TextImage",
"pos": [
1448.4608623625004,
735.0514200906249
],
"size": {
"0": 315,
"1": 198
},
"flags": {},
"order": 3,
"mode": 0,
"outputs": [
{
"name": "IMAGE",
"type": "IMAGE",
"links": null,
"shape": 3
},
{
"name": "MASK",
"type": "MASK",
"links": null,
"shape": 3
}
],
"properties": {
"Node name for S&R": "TextImage"
},
"widgets_values": [
"龍馬精神迎新歲",
"C:\\Users\\38957\\Documents\\ai-lab\\ComfyUI_windows_portable\\ComfyUI\\custom_nodes\\comfyui-mixlab-nodes\\assets\\王汉宗颜楷体繁.ttf",
100,
12,
"#000000",
true
]
},
{
"id": 2,
"type": "ChatGPTOpenAI",
"pos": [
1086,
235
],
"size": {
"0": 400,
"1": 358
},
"flags": {},
"order": 4,
"mode": 0,
"outputs": [
{
"name": "text",
"type": "STRING",
"links": null,
"shape": 3
},
{
"name": "messages",
"type": "STRING",
"links": null,
"shape": 3
},
{
"name": "session_history",
"type": "STRING",
"links": null,
"shape": 3
}
],
"properties": {
"Node name for S&R": "ChatGPTOpenAI"
},
"widgets_values": [
null,
null,
"",
"You are ChatGPT, a large language model trained by OpenAI. Answer as concisely as possible.",
"gpt-3.5-turbo",
0,
"randomize",
1,
null
]
},
{
"id": 16,
"type": "SpeechRecognition",
"pos": [
2892.502226325001,
724.176680771093
],
"size": {
"0": 322.593505859375,
"1": 135.22850036621094
},
"flags": {},
"order": 5,
"mode": 0,
"outputs": [
{
"name": "prompt",
"type": "STRING",
"links": null,
"shape": 3
}
],
"properties": {
"Node name for S&R": "SpeechRecognition"
},
"widgets_values": [
null,
null
]
},
{
"id": 17,
"type": "SpeechSynthesis",
"pos": [
2901.502226325001,
924.1766807710948
],
"size": {
"0": 315,
"1": 58
},
"flags": {},
"order": 6,
"mode": 0,
"inputs": [
{
"name": "text",
"type": "STRING",
"link": null,
"widget": {
"name": "text"
}
}
],
"outputs": [
{
"name": "STRING",
"type": "STRING",
"links": null,
"shape": 6
}
],
"properties": {
"Node name for S&R": "SpeechSynthesis"
},
"widgets_values": [
""
]
},
{
"id": 18,
"type": "ShowLayer",
"pos": [
2157.913220668749,
232.7887044208986
],
"size": {
"0": 315,
"1": 226
},
"flags": {},
"order": 7,
"mode": 0,
"inputs": [
{
"name": "layers",
"type": "LAYER",
"link": null
}
],
"properties": {
"Node name for S&R": "ShowLayer"
}
},
{
"id": 19,
"type": "NewLayer",
"pos": [
2494.913220668749,
232.7887044208986
],
"size": {
"0": 315,
"1": 218
},
"flags": {},
"order": 8,
"mode": 0,
"inputs": [
{
"name": "image",
"type": "IMAGE",
"link": null
},
{
"name": "mask",
"type": "MASK",
"link": null
},
{
"name": "layers",
"type": "LAYER",
"link": null
}
],
"outputs": [
{
"name": "layers",
"type": "LAYER",
"links": null,
"shape": 6
}
],
"properties": {
"Node name for S&R": "NewLayer"
},
"widgets_values": [
0,
0,
512,
512,
0,
"width"
]
},
{
"id": 20,
"type": "MergeLayers",
"pos": [
2163.913220668749,
515.7887044208991
],
"size": {
"0": 315.47509765625,
"1": 69.53152465820312
},
"flags": {},
"order": 9,
"mode": 0,
"inputs": [
{
"name": "layers",
"type": "LAYER",
"link": null
},
{
"name": "image",
"type": "IMAGE",
"link": null
}
],
"outputs": [
{
"name": "IMAGE",
"type": "IMAGE",
"links": null,
"shape": 3
}
],
"properties": {
"Node name for S&R": "MergeLayers"
}
},
{
"id": 4,
"type": "CharacterInText",
"pos": [
1778,
386
],
"size": {
"0": 319.45068359375,
"1": 194.45606994628906
},
"flags": {},
"order": 10,
"mode": 0,
"outputs": [
{
"name": "INT",
"type": "INT",
"links": null,
"shape": 3
}
],
"properties": {
"Node name for S&R": "CharacterInText"
},
"widgets_values": [
"",
"",
1
]
},
{
"id": 21,
"type": "Note",
"pos": [
2507.333181606249,
517.1286262958992
],
"size": {
"0": 210,
"1": 58
},
"flags": {},
"order": 11,
"mode": 0,
"properties": {
"text": ""
},
"widgets_values": [
"TODO:可视化操作"
],
"color": "#323",
"bgcolor": "#535"
},
{
"id": 22,
"type": "Note",
"pos": [
1533,
347
],
"size": {
"0": 210,
"1": 58
},
"flags": {},
"order": 12,
"mode": 0,
"properties": {
"text": ""
},
"widgets_values": [
"funtion call"
],
"color": "#323",
"bgcolor": "#535"
},
{
"id": 11,
"type": "3DImage",
"pos": [
1794.6918193937495,
724.2820719460937
],
"size": {
"0": 309.2974548339844,
"1": 433.12554931640625
},
"flags": {},
"order": 13,
"mode": 0,
"inputs": [
{
"name": "material",
"type": "IMAGE",
"link": null
}
],
"outputs": [
{
"name": "IMAGE",
"type": "IMAGE",
"links": null,
"shape": 3
},
{
"name": "MASK",
"type": "MASK",
"links": null,
"shape": 3
},
{
"name": "BG_IMAGE",
"type": "IMAGE",
"links": null,
"shape": 3
},
{
"name": "MATERIAL",
"type": "IMAGE",
"links": null,
"shape": 3
}
],
"properties": {
"Node name for S&R": "3DImage"
}
},
{
"id": 13,
"type": "ScreenShare",
"pos": [
2138.691819393749,
728.2820719460937
],
"size": {
"0": 322.9458923339844,
"1": 594.0604858398438
},
"flags": {},
"order": 14,
"mode": 0,
"outputs": [
{
"name": "IMAGE",
"type": "IMAGE",
"links": null,
"shape": 3
},
{
"name": "PROMPT",
"type": "STRING",
"links": null,
"shape": 3
},
{
"name": "FLOAT",
"type": "FLOAT",
"links": null,
"shape": 3
},
{
"name": "INT",
"type": "INT",
"links": null,
"shape": 3
}
],
"properties": {
"Node name for S&R": "ScreenShare"
},
"widgets_values": [
null,
null,
null,
null,
null
]
},
{
"id": 14,
"type": "FloatingVideo",
"pos": [
2486.691819393749,
726.2820719460937
],
"size": {
"0": 302.3232421875,
"1": 303.61279296875
},
"flags": {},
"order": 15,
"mode": 0,
"inputs": [
{
"name": "images",
"type": "IMAGE",
"link": null
}
],
"properties": {
"Node name for S&R": "FloatingVideo"
},
"widgets_values": [
null
]
},
{
"id": 6,
"type": "TransparentImage",
"pos": [
2496.691819393749,
1157.2820719460938
],
"size": {
"0": 315,
"1": 146
},
"flags": {},
"order": 16,
"mode": 0,
"inputs": [
{
"name": "images",
"type": "IMAGE",
"link": null
},
{
"name": "masks",
"type": "MASK",
"link": null
}
],
"outputs": [
{
"name": "file_path",
"type": "STRING",
"links": null,
"shape": 6
},
{
"name": "IMAGE",
"type": "IMAGE",
"links": null,
"shape": 6
},
{
"name": "RGBA",
"type": "RGBA",
"links": null,
"shape": 6
}
],
"properties": {
"Node name for S&R": "TransparentImage"
},
"widgets_values": [
"yes",
"yes",
"Mixlab_save"
]
},
{
"id": 12,
"type": "ImageCropByAlpha",
"pos": [
1806.6918193937495,
1240.2820719460938
],
"size": {
"0": 289.27215576171875,
"1": 46
},
"flags": {},
"order": 17,
"mode": 0,
"inputs": [
{
"name": "image",
"type": "IMAGE",
"link": null
},
{
"name": "RGBA",
"type": "RGBA",
"link": null
}
],
"outputs": [
{
"name": "IMAGE",
"type": "IMAGE",
"links": null,
"shape": 3
}
],
"properties": {
"Node name for S&R": "ImageCropByAlpha"
}
},
{
"id": 5,
"type": "EnhanceImage",
"pos": [
1098.6918193937504,
1217.2820719460938
],
"size": {
"0": 315,
"1": 58
},
"flags": {},
"order": 18,
"mode": 0,
"inputs": [
{
"name": "image",
"type": "IMAGE",
"link": null
}
],
"outputs": [
{
"name": "IMAGE",
"type": "IMAGE",
"links": null,
"shape": 3
}
],
"properties": {
"Node name for S&R": "EnhanceImage"
},
"widgets_values": [
0.5
]
},
{
"id": 10,
"type": "SvgImage",
"pos": [
1454.6918193937504,
1074.2820719460938
],
"size": {
"0": 315,
"1": 170
},
"flags": {},
"order": 19,
"mode": 0,
"outputs": [
{
"name": "IMAGE",
"type": "IMAGE",
"links": null,
"shape": 3
},
{
"name": "layers",
"type": "LAYER",
"links": null,
"shape": 6
}
],
"properties": {
"Node name for S&R": "SvgImage"
},
"widgets_values": [
null,
null
]
}
],
"links": [],
"groups": [
{
"title": "GPT",
"bounding": [
1083,
157,
1039,
450
],
"color": "#b06634",
"font_size": 24,
"locked": false
},
{
"title": "Image",
"bounding": [
1086,
650,
1735,
682
],
"color": "#A88",
"font_size": 24,
"locked": false
},
{
"title": "Audio",
"bounding": [
2882,
650,
344,
342
],
"color": "#8A8",
"font_size": 24,
"locked": false
},
{
"title": "Layer",
"bounding": [
2148,
158,
672,
437
],
"color": "#a1309b",
"font_size": 24,
"locked": false
}
],
"config": {},
"extra": {},
"version": 0.4
}
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 1.2 MiB

-2
View File
@@ -1,2 +0,0 @@
![workflow](./assets/City_Snapshot_00005_.png)
![workflow](./assets/挖掘机_00076_1.png)