Compare commits

..
353 Commits
Author SHA1 Message Date
shadowcz007 686ebcfd8b v0.17.1 2024-03-09 13:57:29 +08:00
shadowcz007 51aaba39cf Merge branch 'main' of https://github.com/shadowcz007/comfyui-mixlab-nodes 2024-03-09 00:16:10 +08:00
shadowcz007 d7c6632499 Update index.html 2024-03-09 00:16:07 +08:00
shadow 1b0ea06876 Merge pull request #188 from cd0304/main
add chatglm4 model
2024-03-09 00:07:52 +08:00
cd0304 8ebe88629b Update ChatGPT.py 2024-03-07 23:22:22 +08:00
cd0304 e226992703 Update requirements.txt 2024-03-07 23:20:39 +08:00
shadowcz007 11a8394d69 Update index.html 2024-03-05 00:04:27 +08:00
shadowcz007 9f54a1b91a Update index.html 2024-03-04 20:47:17 +08:00
shadow 35d11061e9 Merge pull request #185 from shadowcz007/0.18-story
update  web/index.html
2024-03-04 20:32:13 +08:00
shadowcz007 65d8b490ca ing 2024-03-04 20:31:45 +08:00
shadowcz007 0fef12c3b1 Update index.html 2024-03-03 11:58:47 +08:00
shadowcz007 8516bff224 Update index.html 2024-03-03 11:11:39 +08:00
shadowcz007 1bcc501352 Update index.html 2024-03-02 23:55:46 +08:00
shadowcz007 f492b17fbe Update index.html 2024-03-02 23:16:08 +08:00
shadowcz007 48ae90f80e 增加说明 2024-02-28 20:07:31 +08:00
shadowcz007 9321ccbc48 test 2024-02-27 21:53:10 +08:00
shadowcz007 402cd01e1a Update README.md 2024-02-27 15:31:43 +08:00
shadowcz007 7b2d0e29c6 Update PromptNode.py 2024-02-27 15:18:58 +08:00
shadowcz007 52d38c401a Update PromptNode.py 2024-02-27 14:51:52 +08:00
shadowcz007 a34dd61076 fixbug 2024-02-24 14:54:40 +08:00
shadowcz007 a53a3e772a Update README.md 2024-02-24 10:40:11 +08:00
shadowcz007 8f24c294a7 update 2024-02-23 18:47:47 +08:00
shadowcz007 acc3f76654 ing 2024-02-18 20:45:58 +08:00
shadowcz007 8c977fb442 ing 2024-02-18 17:33:11 +08:00
shadowcz007 aa20a2de67 fixbug 2024-02-13 15:55:50 +08:00
shadowcz007 5fcb154d89 Update ui_mixlab.js 2024-02-13 15:48:22 +08:00
shadowcz007 0980129f4e Update ui_mixlab.js 2024-02-13 15:45:56 +08:00
shadowcz007 1258746886 v0.17.0
- app模式支持VHS_LoadVideo节点作为输入
- 动态提示,鼠标悬浮可显示结果
2024-02-13 15:18:48 +08:00
shadowcz007 ed128b0ad6 app 支持VHS_LoadVideo 节点作为输入 2024-02-13 15:09:34 +08:00
shadowcz007 f0db08acd6 add TESTNODE_TOKEN
显示text-to-token的过程,方便对prompt进行精修
2024-02-12 21:32:37 +08:00
shadowcz007 7c655e3080 Update ui_mixlab.js 2024-02-12 19:40:09 +08:00
shadowcz007 1b9871c3df Update ui_mixlab.js 2024-02-12 19:20:34 +08:00
shadowcz007 7568aaf243 Update ui_mixlab.js 2024-02-12 17:42:11 +08:00
shadowcz007 a76be8450d mouseover show dynamic_prompt's result 2024-02-12 17:11:03 +08:00
shadowcz007 5564ee1246 rembgNode update
"briarmbg","u2net","u2netp","u2net_human_seg","u2net_cloth_seg","silueta","isnet-general-use","isnet-anime"
2024-02-08 14:01:36 +08:00
shadowcz007 a6e9251521 add briarmbg to rembgNode 2024-02-08 13:57:01 +08:00
shadowcz007 0bee093916 Update README.md 2024-02-08 11:57:10 +08:00
shadowcz007 13a9878823 Update README.md 2024-02-08 11:56:41 +08:00
shadowcz007 acd35d50f8 Update ChatGPT.py 2024-02-08 11:53:03 +08:00
shadowcz007 5c0d99e72d comfyui-CLIPSeg 2024-02-08 10:16:49 +08:00
shadowcz007 e37af93be3 0.15.1 2024-02-06 23:06:33 +08:00
shadowcz007 244c1700e1 Update ImageNode.py 2024-02-06 22:22:56 +08:00
shadowcz007 a3a15473ba LoadImage 的mask保存到appinfo 2024-02-06 19:01:41 +08:00
shadowcz007 d734b5077c GetImageSize_ 增加最小尺寸 2024-02-05 22:44:04 +08:00
shadowcz007 5430072b19 Update app_mixlab.js 2024-02-05 22:25:25 +08:00
shadowcz007 465aebaed4 Update app_mixlab.js 2024-02-05 22:24:34 +08:00
shadowcz007 ee0b16c2ea 修复appinfo配置的bug 2024-02-05 17:56:25 +08:00
shadowcz007 21d5eacb41 Update index.html 2024-02-04 23:46:25 +08:00
shadowcz007 c06688eb0b comfyui-consistency-decoder 2024-02-02 09:47:51 +08:00
shadowcz007 b74bbcd279 fixbug :SaveImageToLocal 2024-02-01 23:52:23 +08:00
shadowcz007 64d8d9b05d Delete echarts.min.js 2024-02-01 00:48:25 +08:00
shadowcz007 6ab60f281b Update ImageNode.py 2024-01-31 00:24:23 +08:00
shadowcz007 e35be3b2fa Update README.md 2024-01-30 22:57:13 +08:00
shadowcz007 0a0c27ac96 fixbug:Save Group as Template 2024-01-30 22:54:39 +08:00
shadowcz007 fb249e84eb SplitImage增加mask输出 2024-01-30 18:07:21 +08:00
shadowcz007 76ad86fcae Update __init__.py 2024-01-29 23:18:16 +08:00
shadowcz007 037614d227 showText 可以保存txt到本地目录 2024-01-29 14:37:51 +08:00
shadowcz007 4a50e445fd 从本地读取文件-输出文件名 2024-01-29 13:46:02 +08:00
shadowcz007 6f3c1c4393 update 2024-01-29 11:55:10 +08:00
shadowcz007 c6a9b4b592 Update gpt_mixlab.js 2024-01-29 11:45:53 +08:00
shadowcz007 e915ac4eca Update ChatGPT.py 2024-01-29 11:37:49 +08:00
shadowcz007 a857793f63 fixbug 2024-01-29 11:34:06 +08:00
shadowcz007 31914f7510 Update ChatGPT.py 2024-01-29 11:12:02 +08:00
shadowcz007 0be859f0ee fixbug 2024-01-28 21:38:13 +08:00
shadowcz007 14b9c3697b Update ImageNode.py 2024-01-28 20:38:22 +08:00
shadowcz007 96b66a57bb showText can save to local 2024-01-28 18:13:50 +08:00
shadowcz007 f13701c489 v0.15.0 2024-01-28 16:40:31 +08:00
shadow 31515b810e Merge pull request #159 from wfjsw/debloat-init-1
publish routes without having to replicate add_routes
2024-01-28 16:01:17 +08:00
shadowcz007 29e84e08a4 add CenterImage 2024-01-28 15:59:54 +08:00
shadowcz007 0ac9ad9757 修复批量保存本地图片的bug 2024-01-27 22:44:47 +08:00
shadowcz007 9a432e0608 Update Utils.py 2024-01-27 00:58:08 +08:00
shadowcz007 c83ba5fe7f Update PromptNode.py 2024-01-27 00:57:40 +08:00
shadowcz007 1b55c743ea 增加一些seed来控制节点 2024-01-27 00:33:49 +08:00
shadowcz007 a93579376c 不覆盖文件 2024-01-26 22:47:34 +08:00
shadowcz007 eba49f3c68 add SaveImageToLocal 2024-01-26 12:15:01 +08:00
shadowcz007 3a3da49c69 Update ImageNode.py 2024-01-25 20:10:29 +08:00
shadowcz007 3d68e48219 Update ImageNode.py 2024-01-25 20:07:31 +08:00
shadowcz007 4351fa6a0e 增加mask 的resize 2024-01-25 17:54:51 +08:00
shadowcz007 77222d2808 修复ImageCropByAlpha的bug 2024-01-25 15:40:00 +08:00
Jabasukuriputo Wang 36db7e5a9a publish routes without having to replicate add_routes 2024-01-25 00:44:51 -06:00
shadowcz007 3572368f16 fixbug 2024-01-25 10:38:23 +08:00
shadowcz007 0755dc1462 CreateLoraNames 2024-01-24 22:59:49 +08:00
shadowcz007 94f81b7102 fixbug 2024-01-24 17:45:15 +08:00
shadowcz007 08f8fe3d7e Update README.md 2024-01-23 23:44:47 +08:00
shadowcz007 a6cd383d67 add Sampler_names 2024-01-23 14:23:04 +08:00
shadowcz007 10face6ab0 CkptNames 2024-01-23 14:04:26 +08:00
shadowcz007 a6259ff600 add CkptNames 2024-01-23 13:58:59 +08:00
shadowcz007 d7d9e6cbfe add smart_connect_v1 2024-01-23 12:18:38 +08:00
shadowcz007 8263609470 优化LoadImageURL,增加seed,保证图片加载失败后可以继续 2024-01-23 10:03:55 +08:00
shadowcz007 fc2367de76 centerOnNode & fix node (widgets) 2024-01-21 20:31:57 +08:00
shadowcz007 9a4f2ebc70 Update ui_mixlab.js 2024-01-20 22:39:56 +08:00
shadowcz007 812879610a v0.14.0
发布新节点splitImage & gridoutput,用于分割图片和随机摆放元素
修复若干bug
2024-01-20 21:43:18 +08:00
shadowcz007 c5e521ccc1 add splitImage&gridoutput 2024-01-20 17:39:53 +08:00
shadowcz007 bc1c8fa351 Update index.html 2024-01-19 09:45:03 +08:00
shadowcz007 7271fcf9c1 api 2024-01-18 22:49:13 +08:00
shadowcz007 ace3b7707b Update README.md 2024-01-18 12:45:29 +08:00
shadowcz007 9eb65cc4ee Update ClipInterrogator.py 2024-01-18 11:12:03 +08:00
shadowcz007 c5c2bc779c add JoinWithDelimiter 2024-01-18 11:00:37 +08:00
shadowcz007 ae1751d9c0 Update Utils.py 2024-01-18 00:32:12 +08:00
shadowcz007 d17583ef7d fixbug 2024-01-17 17:56:23 +08:00
shadowcz007 a363713ae0 v0.13.0
EmbeddingPrompt & 修复若干bug
2024-01-16 23:17:56 +08:00
shadowcz007 1566165bd4 Create space.txt 2024-01-16 17:50:51 +08:00
shadowcz007 fe065fa318 OutlineMask for inpaint 2024-01-16 10:54:26 +08:00
shadowcz007 202d5cf071 支持富文本定义跳转按钮 2024-01-15 14:21:39 +08:00
shadowcz007 b785a9dc5b add EmbeddingPrompt 2024-01-15 13:02:26 +08:00
shadowcz007 73bc658b2f 修复 sentencepiece 未安装的bug 2024-01-15 08:40:07 +08:00
shadowcz007 0b94216138 Update ui_mixlab.js 2024-01-14 20:35:27 +08:00
shadowcz007 86eec2b4cc Update ui_mixlab.js 2024-01-14 20:34:07 +08:00
shadowcz007 c99b531d28 Update ui_mixlab.js 2024-01-14 20:33:53 +08:00
shadowcz007 74a4338cb5 Merge branch 'main' of https://github.com/shadowcz007/comfyui-mixlab-nodes 2024-01-14 13:14:42 +08:00
shadowcz007 d691c52e49 Update TextGenerateNode.py 2024-01-14 13:12:10 +08:00
gold3bear bd3e9e4b3c 去掉调试数据 2024-01-14 12:31:05 +08:00
shadow 3c3ca5fb9c Merge pull request #138 from shadowcz007/fix-chinese-prompt
Fix chinese prompt
2024-01-14 11:58:08 +08:00
shadowcz007 e4ff4fce1c update 2024-01-14 11:57:18 +08:00
gold3bear 2918d4b07d correct Chinese text to prompt syntax 2024-01-14 11:50:14 +08:00
gold3bear c40e49be46 fix chinese prompt 2024-01-14 11:34:58 +08:00
shadowcz007 4ccda20975 add rembg 2024-01-14 11:06:46 +08:00
shadowcz007 09957617d3 修复SwitchByIndex的bug 2024-01-13 20:59:29 +08:00
shadowcz007 c703aa7058 中文prompt增加选项,可控制是否添加更多 2024-01-13 20:59:07 +08:00
shadowcz007 64d366d323 Update prompt_mixlab.js 2024-01-13 17:12:03 +08:00
shadowcz007 333e0a2faa Update ImageNode.py 2024-01-13 16:32:51 +08:00
shadowcz007 a677d95bc8 Update README.md 2024-01-13 16:02:24 +08:00
shadowcz007 aafd87e84b v0.12.0 ChinesePrompt && PromptGenerate
> ChinesePrompt && PromptGenerate,中文prompt节点,直接用中文书写你的prompt

![](./assets/ChinesePrompt_workflow.svg)

> Web App增加图片编辑器
2024-01-13 15:59:52 +08:00
shadowcz007 efa3bae54b Create profession.txt 2024-01-13 12:25:05 +08:00
shadowcz007 8e4362689d Lama、ClipInterrogator安装移到节点内 2024-01-13 12:11:46 +08:00
shadowcz007 b86634284e appinfo运行bug 2024-01-13 11:34:17 +08:00
shadowcz007 b3293ddccd 优化promptImage的预览 2024-01-12 17:16:47 +08:00
shadowcz007 4b40831b83 prompt keywords 2024-01-11 23:05:25 +08:00
shadowcz007 e426b0521c 添加图片编辑功能 2024-01-11 23:01:58 +08:00
shadowcz007 1777bf6e06 修复切换workflow,数据未清空的情况 2024-01-11 23:01:42 +08:00
shadowcz007 dafc892f0f add image edit for web app 2024-01-11 15:41:05 +08:00
shadowcz007 fea0cfd5dd 更新workflow示例 2024-01-11 15:40:31 +08:00
shadowcz007 a7e158db6d update workflow example 2024-01-11 15:32:20 +08:00
shadowcz007 455ac4abd3 Update promptslide-appinfo-workflow.svg 2024-01-11 15:27:11 +08:00
shadowcz007 778dfa2cf5 update workflow example 2024-01-11 15:24:31 +08:00
shadowcz007 329f2e6f81 系统字体的获取 2024-01-10 16:19:49 +08:00
shadowcz007 63ad6d97d7 Update ImageNode.py 2024-01-09 22:59:34 +08:00
shadowcz007 8db56db7cf Update ImageNode.py 2024-01-09 22:34:13 +08:00
shadowcz007 bd542f1e0b Update app_mixlab.js 2024-01-09 18:42:36 +08:00
shadowcz007 a162e53dea Update ImageNode.py 2024-01-09 18:05:18 +08:00
shadowcz007 a8a4c848ed Update __init__.py 2024-01-09 16:15:15 +08:00
shadowcz007 bddd38996a Update ImageNode.py 2024-01-09 15:16:38 +08:00
shadowcz007 9f084eae94 修复LoadImagesFromPath的bug 2024-01-09 14:33:13 +08:00
shadowcz007 c54c635161 v0.11.4 2024-01-09 12:59:36 +08:00
shadowcz007 fa8b42e05e 增强ImageCrop功能 2024-01-09 12:48:20 +08:00
shadowcz007 9c3c323884 fixbug: user_manager 2024-01-09 12:41:29 +08:00
shadowcz007 c8a46439be v0.11.3 - 修复appinfo的logo输入 2024-01-09 09:17:00 +08:00
shadowcz007 b7ec701259 Update Utils.py 2024-01-09 09:16:15 +08:00
shadowcz007 a2cd0e0a38 v0.11.2
- 优化appinfo - 自动保存
- 优化mergeLayer,可以输出合成的mask
- 添加一个实验性的节点 ImageColorTransfer
- random prompt ,增加上传关键词功能
- 修复LoadImageFromPath的bug
2024-01-08 23:03:37 +08:00
shadowcz007 7bccc0e236 优化appinfo-不需要输出,每次运行都会自动更新数据 2024-01-08 23:01:31 +08:00
shadowcz007 a3a649a79f 修复 LoadImagesFromPath 不更新的bug 2024-01-08 22:11:10 +08:00
shadowcz007 2a14d30552 添加一个实验性的节点 ImageColorTransfer 2024-01-08 16:40:00 +08:00
shadowcz007 313bef0609 ClipInterrogator优化 2024-01-07 22:55:24 +08:00
shadowcz007 960a80aeca Merge branch 'main' of https://github.com/shadowcz007/comfyui-mixlab-nodes 2024-01-07 19:59:24 +08:00
shadowcz007 20e6d50a98 修复bug 2024-01-07 19:59:22 +08:00
shadow 507c3417d6 Merge pull request #114 from shadowcz007/fix_ssl_prot_occupied
fix:ssl prot occupied
2024-01-07 17:08:08 +08:00
gold3bear d1adb8d4ed fix:ssl prot occupied 2024-01-07 15:56:23 +08:00
shadowcz007 915ff12747 支持图片的batch 2024-01-06 23:20:43 +08:00
shadowcz007 fbade79137 Update utils_mixlab.js 2024-01-06 20:15:17 +08:00
shadowcz007 a240a677d0 Update utils_mixlab.js 2024-01-06 20:14:17 +08:00
shadowcz007 ab64cf31f6 FloatSlider 优化 2024-01-06 20:13:00 +08:00
shadowcz007 67f2e32dae floatSlider 优化 2024-01-06 20:00:58 +08:00
shadowcz007 0dc40fe052 修复bug 2024-01-06 17:55:26 +08:00
shadowcz007 b7225be552 Update ImageNode.py 2024-01-06 16:25:35 +08:00
shadowcz007 629e00ec94 fixbug 2024-01-06 12:14:02 +08:00
shadowcz007 6d033c9314 random prompt ,增加上传关键词功能 2024-01-06 08:40:27 +08:00
shadowcz007 4f8926ed00 promptslide 上传txt后写入workflow保留列表数据 2024-01-06 08:12:59 +08:00
shadow 1daa1a4603 Merge pull request #111 from shadowcz007/v.11.0-PromptImage-node-图片和prompt匹配
V0.11.0 PromptImage & PromptSimplification
2024-01-06 00:17:26 +08:00
shadowcz007 062773d929 v0.11.0
> PromptImage & PromptSimplification,Assist in simplifying prompt words, comparing images and prompt word nodes.
2024-01-06 00:16:33 +08:00
shadowcz007 57decadaef Update index.html 2024-01-06 00:14:04 +08:00
shadowcz007 ec804ab7c9 优化 2024-01-05 23:09:08 +08:00
shadowcz007 3ee7533098 Update prompt_mixlab.js 2024-01-05 16:40:30 +08:00
shadowcz007 0d383ccc1f add PromptImage 2024-01-05 15:27:13 +08:00
shadowcz007 e2f2257c34 Update index.html 2024-01-05 12:22:23 +08:00
shadowcz007 d62b9fc4c6 ClipInterrogator可以作为输出 2024-01-05 11:52:04 +08:00
shadowcz007 be7ad0c7fb Update index.html 2024-01-04 23:35:56 +08:00
shadowcz007 156864cc8b PromptSimplification 2024-01-04 23:33:24 +08:00
shadowcz007 7240e496cc Update PromptNode.py 2024-01-04 23:02:48 +08:00
shadowcz007 8acdf4018d test PromptSimplification 2024-01-04 20:32:43 +08:00
shadowcz007 1ea7c3e203 修复floatSlide最大值问题 2024-01-04 19:43:33 +08:00
shadowcz007 e54aeb6125 Update index.html 2024-01-04 18:35:54 +08:00
shadowcz007 d59f51fbcf Update README.md 2024-01-04 18:20:52 +08:00
shadowcz007 a667eb6982 修复seed 为fixed 的运行按钮bug & 支持sd-xl 的SamplerCustom 2024-01-04 18:20:00 +08:00
shadowcz007 8d72732247 Update index.html 2024-01-04 17:19:46 +08:00
shadowcz007 f0f3b30a62 Update index.html 2024-01-04 17:03:36 +08:00
shadowcz007 d99fe24542 fixbug 2024-01-04 16:05:27 +08:00
shadowcz007 ea4c7381bd Update index.html 2024-01-04 14:07:38 +08:00
shadow e900d20641 Merge pull request #107 from shadowcz007/v0.10-add-clip-interrogator
Update index.html
2024-01-04 14:02:42 +08:00
shadowcz007 8a46647d8c Update index.html 2024-01-04 14:02:19 +08:00
shadow 968178bf57 Merge pull request #106 from shadowcz007/v0.10-add-clip-interrogator
V0.10 add clip interrogator
2024-01-04 13:37:31 +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
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
80 changed files with 28497 additions and 4721 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/*
+133 -38
View File
@@ -1,18 +1,61 @@
##
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)
> [Mixlab nodes discord](https://discord.gg/cXs9vZSqeK)
####
[comfyui-ultralytics-yolo](https://github.com/shadowcz007/comfyui-ultralytics-yolo)
[comfyui-moondream](https://github.com/shadowcz007/comfyui-moondream)
[comfyui-CLIPSeg](https://github.com/shadowcz007/comfyui-CLIPSeg)
### 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! 💻🌐
## 🚀🚗🚚🏃 Workflow-to-APP
- 新增AppInfo节点,可以通过简单的配置,把workflow转变为一个Web APP。
- 支持多个web app 切换
- 发布为app的workflow,可以在右键里再次编辑了
- web app可以设置分类,在comfyui右键菜单可以编辑更新web app
- 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
> 暂时支持 9 种节点作为界面上的输入节点:Load Image、VHS_LoadVideo、CLIPTextEncode、PromptSlide、TextInput_、Color、FloatSlider、IntNumber、CheckpointLoaderSimple、LoraLoader
> 输出节点:PreviewImage 、SaveImage、ShowTextForGPT、VHS_VideoCombine、PromptImage
> seed统一输入控件,支持:SamplerCustom、KSampler
> 配套[ps插件](https://github.com/shadowcz007/comfyui-ps-plugin)
> 如果遇到上传图片不成功,请检查下:局域网或者是云服务,请使用https,端口8189这个服务( 感谢 @Damien 反馈问题)
> If you encounter difficulties in uploading images, please check the following: for local network or cloud services, please use HTTPS and the service on port 8189. (Thanks to @Damien for reporting the issue.)
## 🏃🚗🚚🚀 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,25 +66,39 @@ 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)
[Voice + Real-time Face Swap Workflow](./workflow/语音+实时换脸workflow.json)
### 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
> Support for calling multiple GPTs.ChatGPT、ChatGLM3 、ChatGLM4 , Some code provided by rui. If you are using OpenAI's service, fill in https://api.openai.com/v1 . If you are using a local LLM service, fill in http://127.0.0.1:xxxx/v1 . Azure OpenAI:https://xxxx.openai.azure.com
![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)
> PromptImage & PromptSimplification,Assist in simplifying prompt words, comparing images and prompt word nodes.
> ChinesePrompt && PromptGenerate,中文prompt节点,直接用中文书写你的prompt
![](./assets/ChinesePrompt_workflow.svg)
### Layers
@@ -51,6 +108,36 @@ 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)
- [使用CkptNames 对比不同的模型效果](./workflow/ckpts-image-workflow.json)
- [CkptNames compare the effects of different models.](./workflow/ckpts-image-workflow.json)
## Other Nodes
![main](./assets/all-workflow.svg)
@@ -58,24 +145,13 @@ https://github.com/shadowcz007/comfyui-mixlab-nodes/assets/12645064/e7e77f90-e43
[workflow-1](./workflow/1-workflow.json)
> randomPrompt
![randomPrompt](./assets/randomPrompt.png)
> TransparentImage
![TransparentImage](./assets/TransparentImage.png)
> Consistency Decoder
[openai Consistency Decoder]( https://github.com/openai/consistencydecoder)
![Consistency](./assets/consistency.png)
After downloading the OpenAI VAE model, place it in the "model/vae" directory for use.
https://openaipublic.azureedge.net/diff-vae/c9cebd3132dd9c42936d803e33424145a748843c8f716c0814838bdc8a2fe7cb/decoder.pt
> FeatheredMask、SmoothMask
Add edges to an image.
@@ -83,11 +159,22 @@ Add edges to an image.
![FeatheredMask](./assets/FlVou_Y6kaGWYoEj1Tn0aTd4AjMI.jpg)
> LaMaInpainting
from [simple-lama-inpainting](https://github.com/enesmsahin/simple-lama-inpainting)
> rembgNode
"briarmbg","u2net","u2netp","u2net_human_seg","u2net_cloth_seg","silueta","isnet-general-use","isnet-anime"
*** briarmbg *** model was developed by BRlA Al and can be used as an open-source model for non-commercial purposes
### 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.
@@ -97,12 +184,16 @@ An improvement has been made to directly redirect to GitHub to search for missin
### Models
[Download CLIPSeg](https://huggingface.co/CIDAS/clipseg-rd64-refined/tree/main), move to : model/clipseg
<!-- ### Workflow
[Workflow](./workflow.md) -->
[Download rembg Models](https://github.com/danielgatis/rembg/tree/main#Models),move to:models/rembg
[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
[Download succinctly/text2image-prompt-generator](https://huggingface.co/succinctly/text2image-prompt-generator/tree/main),move to:prompt_generator/text2image-prompt-generator
[Download Helsinki-NLP/opus-mt-zh-en](https://huggingface.co/Helsinki-NLP/opus-mt-zh-en/tree/main),move to:prompt_generator/opus-mt-zh-en
## Installation
@@ -133,21 +224,25 @@ If you are using a venv, make sure you have it activated before installation and
pip3 install -r requirements.txt
```
#### Chinese community
访问 [www.mixcomfy.com](https://www.mixcomfy.com),获得更多内测功能,关注微信公众号:Mixlab无界社区
#### Thanks:
[ComfyUI-CLIPSeg](https://github.com/biegert/ComfyUI-CLIPSeg/tree/main)
####
File / LoadImagesFromPath SaveImageToLocal LoadImagesFromURL
#### discussions:
[discussions](https://github.com/shadowcz007/comfyui-mixlab-nodes/discussions)
### TODO:
- 音频播放节点:带可视化、支持多音轨、可配置音轨音量
- vector https://github.com/GeorgLegato/stable-diffusion-webui-vectorstudio
<picture>
<source
+338 -44
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'):
@@ -155,19 +192,149 @@ def read_workflow_json_files(folder_path):
def get_workflows():
# print("#####path::", current_path)
workflow_path=os.path.join(current_path, "workflow")
print('workflow_path: ',workflow_path)
# print('workflow_path: ',workflow_path)
if not os.path.exists(workflow_path):
# 使用mkdir()方法创建新目录
os.mkdir(workflow_path)
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 or is_all:
apps.append({
"filename":item["filename"],
# "category":item['category'],
"data":x,
"date":item["date"],
})
else:
category=''
input=None
output=None
if 'category' in x['app']:
category=x['app']['category']
if 'input' in x['app']:
input=x['app']['input']
if 'output' in x['app']:
output=x['app']['output']
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'],
"input":input,
"output":output
}
},
"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=''
input=None
output=None
if 'category' in x['app']:
category=x['app']['category']
if 'input' in x['app']:
input=x['app']['input']
if 'output' in x['app']:
output=x['app']['output']
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'],
"input":input,
"output":output
}
},
"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)
@@ -201,33 +368,59 @@ async def new_request(self, method, url, *args, **kwargs):
# 应用 Monkey Patch
aiohttp.ClientSession._request = new_request
import socket
async def check_port_available(address, port):
#检查端口是否可用
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
try:
sock.bind((address, port))
return True
except socket.error:
return False
# https
async def new_start(self, address, port, verbose=True, call_on_start=None):
try:
runner = web.AppRunner(self.app, access_log=None)
await runner.setup()
if not await check_port_available(address, port):
raise RuntimeError(f"Port {port} is already in use.")
site = web.TCPSite(runner, address, port)
await site.start()
import ssl
crt,key=create_for_https()
crt, key = create_for_https()
ssl_context = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH)
ssl_context.load_cert_chain(crt,key)
site2 = web.TCPSite(runner, address, port+1,ssl_context=ssl_context)
await site2.start()
ssl_context.load_cert_chain(crt, key)
success = False
for i in range(10): # 尝试最多10次
if await check_port_available(address, port + 1 + i):
https_port = port + 1 + i
site2 = web.TCPSite(runner, address, https_port, ssl_context=ssl_context)
await site2.start()
success = True
break
if not success:
raise RuntimeError(f"Ports {port + 1} to {port + 10} are all in use.")
if address == '':
address = '0.0.0.0'
if verbose:
# print('\033[91mMixlab Nodes: \033[93mLoaded\033[0m')
print("\033[93mStarting server\n")
print("\033[93mTo see the GUI go to: http://{}:{}".format(address, port))
print("\033[93mTo see the GUI go to: https://{}:{}\033[0m".format(address, port+1))
print("\033[93mTo see the GUI go to: https://{}:{}\033[0m".format(address, https_port))
if call_on_start is not None:
call_on_start(address, port)
except Exception as e:
print(f"Error starting the server: {e}")
# import webbrowser
# if os.name == 'nt' and address == '0.0.0.0':
# address = '127.0.0.1'
@@ -238,7 +431,7 @@ async def new_start(self, address, port, verbose=True, call_on_start=None):
PromptServer.start=new_start
# 创建路由表
routes = web.RouteTableDef()
routes = PromptServer.instance.routes
@routes.post('/mixlab')
async def mixlab_hander(request):
@@ -253,6 +446,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 +470,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,21 +517,6 @@ async def nodes_map_hander(request):
return web.json_response(result)
def new_add_routes(self):
import nodes
self.app.add_routes(routes)
self.app.add_routes(self.routes)
for name, dir in nodes.EXTENSION_WEB_DIRS.items():
self.app.add_routes([
web.static('/extensions/' + urllib.parse.quote(name), dir, follow_symlinks=True),
])
self.app.add_routes([
web.static('/', self.web_root, follow_symlinks=True),
])
PromptServer.add_routes=new_add_routes
# 扩展api接口
# from server import PromptServer
@@ -317,29 +530,46 @@ 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.Vae import VAELoader,VAEDecode
from .nodes.PromptNode import EmbeddingPrompt,RandomPrompt,PromptSlide,PromptSimplification,PromptImage,JoinWithDelimiter
from .nodes.ImageNode import SaveImageToLocal,SplitImage,GridOutput,GetImageSize_,MirroredImage,ImageColorTransfer,NoiseImage,TransparentImage,GradientImage,LoadImagesFromPath,LoadImagesFromURL,ResizeImage,TextImage,SvgImage,Image3D,ShowLayer,NewLayer,MergeLayers,CenterImage,AreaToMask,SmoothMask,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.ChatGPT import ChatGPTNode,ShowTextForGPT,CharacterInText,TextSplitByDelimiter
from .nodes.Audio import GamePal,SpeechRecognition,SpeechSynthesis
from .nodes.Utils import CreateLoraNames,CreateSampler_names,CreateCkptNames,CreateSeedNode,TESTNODE_,TESTNODE_TOKEN,AppInfo,IntNumber,FloatSlider,TextInput,ColorInput,FontInput,TextToNumber,DynamicDelayProcessor,LimitNumber,SwitchByIndex,MultiplicationNode
from .nodes.Mask import OutlineMask,FeatheredMask
# 要导出的所有节点及其名称的字典
# 注意:名称应全局唯一
NODE_CLASS_MAPPINGS = {
"AppInfo":AppInfo,
"TESTNODE_":TESTNODE_,
"TESTNODE_TOKEN":TESTNODE_TOKEN,
"RandomPrompt":RandomPrompt,
# "LoraPrompt":LoraPrompt,
"EmbeddingPrompt":EmbeddingPrompt,
"PromptSlide":PromptSlide,
"PromptSimplification":PromptSimplification,
"PromptImage":PromptImage,
"MirroredImage":MirroredImage,
"NoiseImage":NoiseImage,
"GradientImage":GradientImage,
"TransparentImage":TransparentImage,
"ResizeImageMixlab":ResizeImage,
"LoadImagesFromPath":LoadImagesFromPath,
"LoadImagesFromURL":LoadImagesFromURL,
"TextImage":TextImage,
"EnhanceImage":EnhanceImage,
"SvgImage":SvgImage,
"3DImage":Image3D,
"EmptyLayer":EmptyLayer,
"3DImage":Image3D,
"ImageColorTransfer":ImageColorTransfer,
"ShowLayer":ShowLayer,
"NewLayer":NewLayer,
"SplitImage":SplitImage,
"CenterImage":CenterImage,
"GridOutput":GridOutput,
"MergeLayers":MergeLayers,
"SplitLongMask":SplitLongMask,
"FeatheredMask":FeatheredMask,
@@ -347,23 +577,42 @@ NODE_CLASS_MAPPINGS = {
"FaceToMask":FaceToMask,
"AreaToMask":AreaToMask,
"ImageCropByAlpha":ImageCropByAlpha,
"VAELoaderConsistencyDecoder":VAELoader,
"VAEDecodeConsistencyDecoder":VAEDecode,
# "VAELoaderConsistencyDecoder":VAELoader,
"SaveImageToLocal":SaveImageToLocal,
# "VAEDecodeConsistencyDecoder":VAEDecode,
"ScreenShare":ScreenShareNode,
"FloatingVideo":FloatingVideo,
"CLIPSeg_":CLIPSeg,
"CombineMasks_":CombineMasks,
"ChatGPTOpenAI":ChatGPTNode,
"ShowTextForGPT":ShowTextForGPT,
"CharacterInText":CharacterInText,
"TextSplitByDelimiter":TextSplitByDelimiter,
"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,
"OutlineMask":OutlineMask,
"JoinWithDelimiter":JoinWithDelimiter,
"Seed_":CreateSeedNode,
"CkptNames_":CreateCkptNames,
"SamplerNames_":CreateSampler_names,
"LoraNames_":CreateLoraNames
# "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 +623,57 @@ 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",
"PromptGenerate_Mix":"PromptGenerate ♾️Mixlab",
"ChinesePrompt_Mix":"ChinesePrompt ♾️Mixlab",
"GamePal":"GamePal ♾️Mixlab",
"RembgNode_Mix":"Removebg",
"LoraNames_":"LoraName_TriggerWords.safetensors"
}
# web ui的节点功能
WEB_DIRECTORY = "./web"
print('--------------')
print('\033[91mMixlab Nodes: \033[93mLoaded\033[0m')
print('--------------')
print('\033[91m ### Mixlab Nodes: \033[93mLoaded')
try:
from .nodes.Lama import LaMaInpainting
print('LaMaInpainting.available',LaMaInpainting.available)
if LaMaInpainting.available:
NODE_CLASS_MAPPINGS['LaMaInpainting']=LaMaInpainting
except Exception as e:
print('LaMaInpainting.available',False,e)
try:
from .nodes.ClipInterrogator import ClipInterrogator
print('ClipInterrogator.available',ClipInterrogator.available)
if ClipInterrogator.available:
NODE_CLASS_MAPPINGS['ClipInterrogator']=ClipInterrogator
except Exception as e:
print('ClipInterrogator.available',False,e)
try:
from .nodes.TextGenerateNode import PromptGenerate,ChinesePrompt
print('PromptGenerate.available',PromptGenerate.available)
if PromptGenerate.available:
NODE_CLASS_MAPPINGS['PromptGenerate_Mix']=PromptGenerate
print('ChinesePrompt.available',ChinesePrompt.available)
if ChinesePrompt.available:
NODE_CLASS_MAPPINGS['ChinesePrompt_Mix']=ChinesePrompt
except Exception as e:
print('TextGenerateNode.available',False,e)
try:
from .nodes.RembgNode import RembgNode_
print('RembgNode_.available',RembgNode_.available)
if RembgNode_.available:
NODE_CLASS_MAPPINGS['RembgNode_Mix']=RembgNode_
except Exception as e:
print('RembgNode_.available',False,e)
print('\033[93m -------------- \033[0m')
Binary file not shown.

After

Width:  |  Height:  |  Size: 101 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 2.4 MiB

Binary file not shown.
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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 784 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

+30
View File
@@ -0,0 +1,30 @@
Jony Ive
Dieter Rams
Philippe Starck
Karim Rashid
Yves Béhar
Marc Newson
Naoto Fukasawa
Jonathan Adler
Patricia Urquiola
Ross Lovegrove
Tom Dixon
Jasper Morrison
Charles Eames
Ray Eames
Achille Castiglioni
Ron Arad
Konstantin Grcic
Marcel Wanders
Maarten Baas
Stefan Sagmeister
Ingo Maurer
Hella Jongerius
Sam Hecht
Kim Colin
Jaime Hayon
Michael Anastassiades
Nendo
Oki Sato
Matali Crasset
Tokujin Yoshioka
+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
+30
View File
@@ -0,0 +1,30 @@
Elegant evening gown
Casual jeans and t-shirt
Formal black suit
Stylish leather jacket
Flowy bohemian dress
Sporty tracksuit
Chic little black dress
Trendy ripped jeans
Classic white button-down shirt
Cozy oversized sweater
Sophisticated tailored blazer
Quirky patterned leggings
Striped sailor top
Polished knee-length skirt
Vintage-inspired floral dress
Edgy motorcycle jacket
Preppy polo shirt
Boho maxi skirt
Professional pinstripe suit
Relaxed denim shorts
Glamorous sequined dress
Athletic running shoes
Formal bow tie
Casual baseball cap
Stylish fedora hat
Warm woolen scarf
Comfortable cotton socks
Trendy ankle boots
Cute summer sandals
Cozy pajama set
+30
View File
@@ -0,0 +1,30 @@
Happy
Sad
Angry
Surprised
Excited
Worried
Confused
Disgusted
Amused
Bored
Curious
Embarrassed
Frustrated
Nervous
Pleased
Relieved
Shy
Tired
Serious
Silly
Proud
Grumpy
Smug
Sarcastic
Flirty
Skeptical
Shocked
Blissful
Envious
Mischievous
+29 -8
View File
@@ -4761,26 +4761,43 @@
],
"https://github.com/shadowcz007/comfyui-mixlab-nodes": [
[
"GridOutput",
"SplitImage",
"PromptGenerate_Mix",
"JoinWithDelimiter",
"ChinesePrompt_Mix",
"3DImage",
"AppInfo",
"IntNumber",
"FloatSlider",
"ResizeImage",
"NoiseImage",
"PromptImage",
"SaveImageToLocal",
"AreaToMask",
"CLIPSeg",
"CLIPSeg_",
"CharacterInText",
"ChatGPTOpenAI",
"Color",
"CombineMasks_",
"CombineSegMasks",
"EmptyLayer",
"Seed_",
"CkptNames_",
"SamplerNames_",
"LoraNames_",
"EnhanceImage",
"GradientImage",
"FaceToMask",
"FeatheredMask",
"FloatingVideo",
"Font",
"ImageCropByAlpha",
"LoadImagesFromPath",
"LoadImagesFromURL",
"MergeLayers",
"NewLayer",
"CenterImage",
"RandomPrompt",
"PromptSlide",
"PromptSimplification",
"ClipInterrogator",
"ScreenShare",
"ShowLayer",
"ShowTextForGPT",
@@ -4790,12 +4807,16 @@
"SplitLongMask",
"SvgImage",
"TextImage",
"ResizeImageMixlab",
"TransparentImage",
"VAEDecodeConsistencyDecoder",
"VAELoaderConsistencyDecoder"
"TextToNumber",
"TextInput_",
"DynamicDelayProcessor",
"LaMaInpainting",
"Moondream"
],
{
"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
+1
View File
@@ -0,0 +1 @@
{}
+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
+101
View File
@@ -0,0 +1,101 @@
Doctor
Teacher
Engineer
Lawyer
Accountant
Nurse
Architect
Chef
Pilot
Scientist
Artist
Writer
Musician
Actor
Photographer
Police officer
Firefighter
Dentist
Pharmacist
Veterinarian
Electrician
Plumber
Carpenter
Mechanic
Farmer
Astronaut
Athlete
Journalist
Politician
Economist
Psychologist
Social worker
Librarian
Translator
Salesperson
Entrepreneur
Financial advisor
Graphic designer
Web developer
Marketing manager
Human resources manager
Project manager
Event planner
Fashion designer
Interior decorator
Real estate agent
Archaeologist
Biologist
Chemist
Geologist
Physicist
Mathematician
Historian
Geographer
Economist
Sociologist
Anthropologist
Archaeologist
Linguist
Philosopher
Economist
Sociologist
Anthropologist
Archaeologist
Linguist
Philosopher
Geographer
Historian
Economist
Sociologist
Anthropologist
Archaeologist
Linguist
Philosopher
Geographer
Historian
Economist
Sociologist
Anthropologist
Archaeologist
Linguist
Philosopher
Geographer
Historian
Economist
Sociologist
Anthropologist
Archaeologist
Linguist
Philosopher
Geographer
Historian
Economist
Sociologist
Anthropologist
Archaeologist
Linguist
Philosopher
Geographer
Historian
#MixCopilot
+58
View File
@@ -0,0 +1,58 @@
Residential space
Apartment building
Villa
Bungalow
Condominium
Commercial space
Shopping mall
Supermarket
Restaurant
Store
Market
Office space
Office building
Office
Meeting room
Co-working space
Educational space
School
University
Training institution
Library
Laboratory
Medical space
Hospital
Clinic
Pharmacy
Nursing home
Rehabilitation center
Cultural space
Museum
Library
Theater
Concert hall
Gallery
Sports space
Sports stadium
Gym
Swimming pool
Basketball court
Football field
Transportation space
Airport
Train station
Subway station
Bus stop
Parking lot
Public space
Park
Square
Street
Pedestrian street
Community center
Industrial space
Factory
Warehouse
Production workshop
Mine
Power plant
+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
+58 -4
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",)
@@ -14,13 +24,13 @@ class SpeechRecognition:
FUNCTION = "run"
CATEGORY = "♾️Mixlab/audio"
CATEGORY = "♾️Mixlab/Audio"
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:
@@ -38,9 +48,53 @@ class SpeechSynthesis:
OUTPUT_NODE = True
OUTPUT_IS_LIST = (True,)
CATEGORY = "♾️Mixlab/audio"
CATEGORY = "♾️Mixlab/Audio"
def run(self, text):
# 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,)}
+151 -16
View File
@@ -1,7 +1,26 @@
import openai
import time
import urllib.error
import re,json
import re,json,os,string,random
import folder_paths
import hashlib
from zhipuai import ZhipuAI
def get_unique_hash(string):
hash_object = hashlib.sha1(string.encode())
unique_hash = hash_object.hexdigest()
return unique_hash
def generate_random_string(length):
letters = string.ascii_letters + string.digits
return ''.join(random.choice(letters) for _ in range(length))
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("*")
# 判断是否是azure服务
def is_azure_url(url):
@@ -27,6 +46,11 @@ def openai_client(key,url):
base_url=url
)
return client
def ZhipuAI_client(key):
client = ZhipuAI(
api_key=key, # 填写您的 APIKey
)
return client
@@ -73,17 +97,17 @@ class ChatGPTNode:
def INPUT_TYPES(cls):
return {
"required": {
"api_key":("KEY", {"default": "", "multiline": True}),
"api_url":("URL", {"default": "", "multiline": True}),
"prompt": ("STRING", {"multiline": True}),
"api_key":("KEY", {"default": "", "multiline": True,"dynamicPrompts": False}),
"api_url":("URL", {"default": "", "multiline": True,"dynamicPrompts": False}),
"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"],
"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","glm-4"],
{"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": {
@@ -124,8 +148,13 @@ class ChatGPTNode:
if is_azure_url(api_url):
client=azure_client(api_key,api_url)
else:
client=openai_client(api_key,api_url)
print('openai url')
# 根据用户选择的模型,设置相应的接口和模型名称
if model == "glm-4" :
client = ZhipuAI_client(api_key) # 使用 Zhipuai 的接口
print('using Zhipuai interface')
else :
client = openai_client(api_key,api_url) # 使用 ChatGPT 的接口
print('using ChatGPT interface')
# 把用户的提示添加到会话历史中
# 调用API时传递整个会话历史
@@ -167,8 +196,11 @@ class ShowTextForGPT:
def INPUT_TYPES(s):
return {
"required": {
"text": ("STRING", {"forceInput": True}),
}
"text": ("STRING", {"forceInput": True,"dynamicPrompts": False}),
},
"optional":{
"output_dir": ("STRING",{"forceInput": True,"default": "","multiline": True,"dynamicPrompts": False}),
}
}
INPUT_IS_LIST = True
@@ -179,9 +211,63 @@ class ShowTextForGPT:
CATEGORY = "♾️Mixlab/GPT"
def run(self, text):
# print(session_history)
def run(self, text,output_dir=[""]):
# 类型纠正
texts=[]
for t in text:
if not isinstance(t, str):
t = str(t)
texts.append(t)
text=texts
if len(output_dir)==1 and (output_dir[0]=='' or os.path.dirname(output_dir[0])==''):
t='\n'.join(text)
output_dir=[
os.path.join(folder_paths.get_temp_directory(),
get_unique_hash(t)+'.txt'
)
]
elif len(output_dir)==1:
base=os.path.basename(output_dir[0])
t='\n'.join(text)
if base=='' or os.path.splitext(base)[1]=='':
base=get_unique_hash(t)+'.txt'
output_dir=[
os.path.join(output_dir[0],
base
)
]
# elif len(output_dir)>1:
if len(output_dir)==1 and len(text)>1:
output_dir=[output_dir[0] for _ in range(len(text))]
for i in range(len(text)):
o_fp=output_dir[i]
dirp=os.path.dirname(o_fp)
if dirp=='':
dirp=folder_paths.get_temp_directory()
o_fp=os.path.join(folder_paths.get_temp_directory(),o_fp
)
if not os.path.exists(dirp):
os.mkdir(dirp)
if not os.path.splitext(o_fp)[1].lower()=='.txt':
o_fp=o_fp+'.txt'
t=text[i]
with open(o_fp, 'w') as file:
file.write(t)
# print(text)
return {"ui": {"text": text}, "result": (text,)}
class CharacterInText:
@@ -189,8 +275,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
@@ -211,7 +297,56 @@ class CharacterInText:
def run(self, text,character,start_index):
# print(text,character,start_index)
b=1 if character in text else 0
b=1 if character.lower() in text.lower() else 0
return (b+start_index,)
class TextSplitByDelimiter:
@classmethod
def INPUT_TYPES(s):
return {
"required": {
"text": ("STRING", {"multiline": True,"dynamicPrompts": False}),
"delimiter":(["newline","comma"],),
"start_index": ("INT", {
"default": 0,
"min": 0, #Minimum value
"max": 1000, #Maximum value
"step": 1, #Slider's step
"display": "number" # Cosmetic only: display as "number" or "slider"
}),
"skip_every": ("INT", {
"default": 0,
"min": 0, #Minimum value
"max": 10, #Maximum value
"step": 1, #Slider's step
"display": "number" # Cosmetic only: display as "number" or "slider"
}),
"max_count": ("INT", {
"default": 10,
"min": 1, #Minimum value
"max": 1000, #Maximum value
"step": 1, #Slider's step
"display": "number" # Cosmetic only: display as "number" or "slider"
}),
}
}
INPUT_IS_LIST = False
RETURN_TYPES = ("STRING",)
FUNCTION = "run"
# OUTPUT_NODE = True
OUTPUT_IS_LIST = (True,)
CATEGORY = "♾️Mixlab/GPT"
def run(self, text,delimiter,start_index,skip_every,max_count):
arr=[]
if delimiter=='newline':
arr = [line for line in text.split('\n') if line.strip()]
elif delimiter=='comma':
arr = [line for line in text.split(',') if line.strip()]
arr= arr[start_index:start_index + max_count * (skip_every+1):(skip_every+1)]
return (arr,)
+277
View File
@@ -0,0 +1,277 @@
import os,sys
import folder_paths
from PIL import Image
import importlib.util
import comfy.utils
import numpy as np
import json
import torch
import random
# from clip_interrogator import Config, Interrogator
global _available
_available=False
def is_installed(package):
try:
spec = importlib.util.find_spec(package)
except ModuleNotFoundError:
return False
return spec is not None
try:
if is_installed('clip_interrogator')==False:
import subprocess
# 安装
print('#pip install clip-interrogator==0.6.0')
result = subprocess.run([sys.executable, '-s', '-m', 'pip', 'install', 'clip-interrogator==0.6.0'], capture_output=True, text=True)
#检查命令执行结果
if result.returncode == 0:
print("#install success")
from clip_interrogator import Config, Interrogator
_available=True
else:
print("#install error")
else:
from clip_interrogator import Config, Interrogator
_available=True
except:
_available=False
try:
from transformers import AutoProcessor, BlipForConditionalGeneration
except:
_available=False
print('pls check transformers.__version__>=4.36.0:: AutoProcessor, BlipForConditionalGeneration')
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")
caption_model_path='Salesforce/blip-image-captioning-base'
cache_path=os.path.join(folder_paths.models_dir, "clip_interrogator")
# Tensor to PIL
def tensor2pil(image):
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_fn(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":medium_ranks,
"artist_ranks":artist_ranks,
"movement_ranks":movement_ranks,
"trending_ranks":trending_ranks,
"flavor_ranks":flavor_ranks
}
def generate_sentences(data):
sentences = []
# Get the length of data
data_length = len(data)
# Use a recursive function to handle variable-length data
def generate_recursive(index, current_sentence, current_score):
# Check if recursion is complete
if index == data_length:
sentences.append({"sentence": current_sentence, "score": current_score})
return
# Get the current level data
current_data = data[index]
# Iterate through the current level data
for phrase in current_data:
sentence = current_sentence + ("," if current_sentence.strip() else "") + phrase
score = current_score + current_data[phrase]
generate_recursive(index + 1, sentence, score)
# Start recursive generation of sentences
generate_recursive(0, "", 0)
# Sort the generated sentences by score in descending order
sentences.sort(key=lambda x: x["score"], reverse=True)
def get_random_elements(elements, num):
return random.sample(elements, num)
ps = get_random_elements(sentences, 5)
ps = [s["sentence"] for s in sorted(ps, key=lambda x: x["score"], reverse=True)]
return ps
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:
global _available
available=_available
@classmethod
def INPUT_TYPES(s):
return {"required": {
"image": ("IMAGE",),
"prompt_mode": (['fast','classic','best','negative'],),
"image_analysis": (["off","on"],),
},
# "optional":{
# "output":("CLIPINTERROGATOR", {"multiline": True,"default": "", "dynamicPrompts": False})
# },
}
RETURN_TYPES = ("STRING","STRING",)
RETURN_NAMES = ("prompt","random_samples",)
FUNCTION = "run"
CATEGORY = "♾️Mixlab/Prompt"
OUTPUT_NODE = True
INPUT_IS_LIST = True
OUTPUT_IS_LIST = (True,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_fn(ci,im)
analysis_result.append( 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
# analysis_result=[]
# items = app.graph.getNodeById(31).widgets[2].value["items"]
random_samples=[]
for r in analysis_result:
random_sample = generate_sentences([r['medium_ranks'], r['artist_ranks'],r['movement_ranks'],r['trending_ranks'],r['flavor_ranks']])
for s in random_sample:
random_samples.append(s)
# print(len(random_samples))
# print('-----')
# print( random_samples)
return {
"ui":{
"prompt": prompt_result,
"analysis":analysis_result,
"random_samples":random_samples
},
"result": (prompt_result,random_samples,)}
-261
View File
@@ -1,261 +0,0 @@
#### Thanks:
# [ComfyUI-CLIPSeg](https://github.com/biegert/ComfyUI-CLIPSeg/tree/main)
from transformers import CLIPSegProcessor, CLIPSegForImageSegmentation
from PIL import Image
import torch
import torchvision.transforms as T
import numpy as np
from torchvision.transforms.functional import to_pil_image
import matplotlib.pyplot as plt
import matplotlib.cm as cm
import cv2
from scipy.ndimage import gaussian_filter
from typing import Optional, Tuple
import warnings,os
warnings.filterwarnings("ignore", category=UserWarning, module="torch")
warnings.filterwarnings("ignore", category=UserWarning, module="safetensors")
import folder_paths
import logging
logger = logging.getLogger('CLIPSeg nodes')
clipseg_model_dir = os.path.join(folder_paths.models_dir, "clipseg")
if not os.path.exists(clipseg_model_dir):
clipseg_model_dir='CIDAS/clipseg-rd64-refined'
"""Helper methods for CLIPSeg nodes"""
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()
return (array * 255).astype(np.uint8)
def numpy_to_tensor(array: np.ndarray) -> torch.Tensor:
"""Convert a numpy array to a tensor and scale its values from 0-255 to 0-1."""
array = array.astype(np.float32) / 255.0
return torch.from_numpy(array)[None,]
def apply_colormap(mask: torch.Tensor, colormap) -> np.ndarray:
"""Apply a colormap to a tensor and convert it to a numpy array."""
colored_mask = colormap(mask.numpy())[:, :, :3]
return (colored_mask * 255).astype(np.uint8)
def resize_image(image: np.ndarray, dimensions: Tuple[int, int]) -> np.ndarray:
"""Resize an image to the given dimensions using linear interpolation."""
return cv2.resize(image, dimensions, interpolation=cv2.INTER_LINEAR)
def overlay_image(background: np.ndarray, foreground: np.ndarray, alpha: float) -> np.ndarray:
"""Overlay the foreground image onto the background with a given opacity (alpha)."""
return cv2.addWeighted(background, 1 - alpha, foreground, alpha, 0)
def dilate_mask(mask: torch.Tensor, dilation_factor: float) -> torch.Tensor:
"""Dilate a mask using a square kernel with a given dilation factor."""
kernel_size = int(dilation_factor * 2) + 1
kernel = np.ones((kernel_size, kernel_size), np.uint8)
mask_dilated = cv2.dilate(mask.numpy(), kernel, iterations=1)
return torch.from_numpy(mask_dilated)
class CLIPSeg:
def __init__(self):
pass
@classmethod
def INPUT_TYPES(s):
"""
Return a dictionary which contains config for all input fields.
Some types (string): "MODEL", "VAE", "CLIP", "CONDITIONING", "LATENT", "IMAGE", "INT", "STRING", "FLOAT".
Input types "INT", "STRING" or "FLOAT" are special values for fields on the node.
The type can be a list for selection.
Returns: `dict`:
- Key input_fields_group (`string`): Can be either required, hidden or optional. A node class must have property `required`
- Value input_fields (`dict`): Contains input fields config:
* Key field_name (`string`): Name of a entry-point method's argument
* Value field_config (`tuple`):
+ First value is a string indicate the type of field or a list for selection.
+ Secound value is a config for type "INT", "STRING" or "FLOAT".
"""
return {"required":
{
"image": ("IMAGE",),
"text": ("STRING", {"multiline": False}),
},
"optional":
{
"blur": ("FLOAT", {"min": 0, "max": 15, "step": 0.1, "default": 7}),
"threshold": ("FLOAT", {"min": 0, "max": 1, "step": 0.05, "default": 0.4}),
"dilation_factor": ("INT", {"min": 0, "max": 10, "step": 1, "default": 4}),
}
}
CATEGORY = "♾️Mixlab/mask"
RETURN_TYPES = ("MASK", "IMAGE", "IMAGE",)
RETURN_NAMES = ("Mask","Heatmap Mask", "BW Mask")
# INPUT_IS_LIST = True
# OUTPUT_IS_LIST = (True,)
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]:
"""Create a segmentation mask from an image and a text prompt using CLIPSeg.
Args:
image (torch.Tensor): The image to segment.
text (str): The text prompt to use for segmentation.
blur (float): How much to blur the segmentation mask.
threshold (float): The threshold to use for binarizing the segmentation mask.
dilation_factor (int): How much to dilate the segmentation mask.
Returns:
Tuple[torch.Tensor, torch.Tensor, torch.Tensor]: The segmentation mask, the heatmap mask, and the binarized mask.
"""
# Convert the Tensor to a PIL image
image_np = image.numpy().squeeze() # Remove the first dimension (batch size of 1)
# Convert the numpy array back to the original range (0-255) and data type (uint8)
image_np = (image_np * 255).astype(np.uint8)
# Create a PIL image from the numpy array
i = Image.fromarray(image_np, mode="RGB")
processor = CLIPSegProcessor.from_pretrained(clipseg_model_dir)
model = CLIPSegForImageSegmentation.from_pretrained(clipseg_model_dir)
prompt = text
input_prc = processor(text=prompt, images=i, padding="max_length", return_tensors="pt")
# Predict the segemntation mask
with torch.no_grad():
outputs = model(**input_prc)
tensor = torch.sigmoid(outputs[0]) # get the mask
# Apply a threshold to the original tensor to cut off low values
thresh = threshold
tensor_thresholded = torch.where(tensor > thresh, tensor, torch.tensor(0, dtype=torch.float))
# Apply Gaussian blur to the thresholded tensor
sigma = blur
tensor_smoothed = gaussian_filter(tensor_thresholded.numpy(), sigma=sigma)
tensor_smoothed = torch.from_numpy(tensor_smoothed)
# Normalize the smoothed tensor to [0, 1]
mask_normalized = (tensor_smoothed - tensor_smoothed.min()) / (tensor_smoothed.max() - tensor_smoothed.min())
# Dilate the normalized mask
mask_dilated = dilate_mask(mask_normalized, dilation_factor)
# Convert the mask to a heatmap and a binary mask
heatmap = apply_colormap(mask_dilated, cm.viridis)
binary_mask = apply_colormap(mask_dilated, cm.Greys_r)
# Overlay the heatmap and binary mask on the original image
dimensions = (image_np.shape[1], image_np.shape[0])
heatmap_resized = resize_image(heatmap, dimensions)
binary_mask_resized = resize_image(binary_mask, dimensions)
alpha_heatmap, alpha_binary = 0.5, 1
overlay_heatmap = overlay_image(image_np, heatmap_resized, alpha_heatmap)
overlay_binary = overlay_image(image_np, binary_mask_resized, alpha_binary)
# Convert the numpy arrays to tensors
image_out_heatmap = numpy_to_tensor(overlay_heatmap)
image_out_binary = numpy_to_tensor(overlay_binary)
# Save or display the resulting binary mask
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]
return tensor_bw, image_out_heatmap, image_out_binary
#OUTPUT_NODE = False
class CombineMasks:
def __init__(self):
pass
@classmethod
def INPUT_TYPES(s):
return {"required":
{
"input_image": ("IMAGE", ),
"mask_1": ("MASK", ),
"mask_2": ("MASK", ),
},
"optional":
{
"mask_3": ("MASK",),
},
}
CATEGORY = "♾️Mixlab/mask"
RETURN_TYPES = ("MASK", "IMAGE", "IMAGE",)
RETURN_NAMES = ("Combined Mask","Heatmap Mask", "BW Mask")
FUNCTION = "combine_masks"
def combine_masks(self, input_image: torch.Tensor, mask_1: torch.Tensor, mask_2: torch.Tensor, mask_3: Optional[torch.Tensor] = None) -> Tuple[torch.Tensor, torch.Tensor, torch.Tensor]:
"""A method that combines two or three masks into one mask. Takes in tensors and returns the mask as a tensor, as well as the heatmap and binary mask as tensors."""
# Combine masks
if mask_1 is not None:
mask_1 = mask_1.squeeze()
if mask_2 is not None:
mask_2 = mask_2.squeeze()
if mask_3 is not None:
mask_3 = mask_3.squeeze()
print(mask_1.shape,mask_2.shape , mask_3.shape)
combined_mask = mask_1 + mask_2 + mask_3 if mask_3 is not None else mask_1 + mask_2
# print(combined_mask)
# Convert image and masks to numpy arrays
image_np = tensor_to_numpy(input_image)
heatmap = apply_colormap(combined_mask, cm.viridis)
binary_mask = apply_colormap(combined_mask, cm.Greys_r)
# Resize heatmap and binary mask to match the original image dimensions
dimensions = (image_np.shape[1], image_np.shape[0])
print('heatmap',heatmap)
if dimensions is None or dimensions[0] == 0 or dimensions[1] == 0:
raise ValueError("Invalid dimensions")
heatmap_resized = resize_image(heatmap, dimensions)
binary_mask_resized = resize_image(binary_mask, dimensions)
# Overlay the heatmap and binary mask onto the original image
alpha_heatmap, alpha_binary = 0.5, 1
overlay_heatmap = overlay_image(image_np, heatmap_resized, alpha_heatmap)
overlay_binary = overlay_image(image_np, binary_mask_resized, alpha_binary)
# Convert overlays to tensors
image_out_heatmap = numpy_to_tensor(overlay_heatmap)
image_out_binary = numpy_to_tensor(overlay_binary)
return combined_mask, image_out_heatmap, image_out_binary
# A dictionary that contains all nodes you want to export with their names
# NOTE: names should be globally unique
# NODE_CLASS_MAPPINGS = {
# "CLIPSeg": CLIPSeg,
# "CombineSegMasks": CombineMasks,
# }
+1405 -229
View File
File diff suppressed because it is too large Load Diff
+120
View File
@@ -0,0 +1,120 @@
import os,sys
import folder_paths
from PIL import Image
import importlib.util
import numpy as np
import torch
global _available
_available=False
def is_installed(package):
try:
spec = importlib.util.find_spec(package)
except ModuleNotFoundError:
return False
return spec is not None
if is_installed('simple_lama_inpainting')==False:
import subprocess
from packaging import version
if version.parse(torch.__version__)>=version.parse('2.1'):
# 安装
print('#pip install simple_lama_inpainting')
result = subprocess.run([sys.executable, '-s', '-m', 'pip', 'install', 'simple_lama_inpainting'], capture_output=True, text=True)
#检查命令执行结果
if result.returncode == 0:
print("#install success")
from simple_lama_inpainting import SimpleLama
_available=True
else:
print("#install error")
else:
print('#pls check your torch version >= 2.1')
else:
from simple_lama_inpainting import SimpleLama
_available=True
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:
global _available
available=_available
@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,)
+171
View File
@@ -0,0 +1,171 @@
import scipy.ndimage
import torch
from nodes import MAX_RESOLUTION
import numpy as np
# from PIL import Image, ImageDraw
from PIL import Image, ImageOps
from comfy.cli_args import args
import cv2
# 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 grow(mask, expand, tapered_corners):
c = 0 if tapered_corners else 1
kernel = np.array([[c, 1, c],
[1, 1, 1],
[c, 1, c]])
mask = mask.reshape((-1, mask.shape[-2], mask.shape[-1]))
out = []
for m in mask:
output = m.numpy()
for _ in range(abs(expand)):
if expand < 0:
output = scipy.ndimage.grey_erosion(output, footprint=kernel)
else:
output = scipy.ndimage.grey_dilation(output, footprint=kernel)
output = torch.from_numpy(output)
out.append(output)
return torch.stack(out, dim=0)
def combine(destination, source, x, y):
output = destination.reshape((-1, destination.shape[-2], destination.shape[-1])).clone()
source = source.reshape((-1, source.shape[-2], source.shape[-1]))
left, top = (x, y,)
right, bottom = (min(left + source.shape[-1], destination.shape[-1]), min(top + source.shape[-2], destination.shape[-2]))
visible_width, visible_height = (right - left, bottom - top,)
source_portion = source[:, :visible_height, :visible_width]
destination_portion = destination[:, top:bottom, left:right]
#operation == "subtract":
output[:, top:bottom, left:right] = destination_portion - source_portion
output = torch.clamp(output, 0.0, 1.0)
return output
class OutlineMask:
@classmethod
def INPUT_TYPES(s):
return {
"required": {
"mask": ("MASK",),
"outline_width":("INT", {"default": 10,"min": 1, "max": MAX_RESOLUTION, "step": 1}),
"tapered_corners": ("BOOLEAN", {"default": True}),
}
}
RETURN_TYPES = ('MASK',)
FUNCTION = "run"
CATEGORY = "♾️Mixlab/Mask"
# 运行的函数
def run(self, mask, outline_width, tapered_corners):
m1=grow(mask,outline_width,tapered_corners)
m2=grow(mask,-outline_width,tapered_corners)
m3=combine(m1,m2,0,0)
return (m3,)
class FeatheredMask:
@classmethod
def INPUT_TYPES(s):
return {
"required": {
"mask": ("MASK",),
"start_offset":("INT", {"default": 1,
"min": -150,
"max": 150,
"step": 1,
"display": "slider"}),
"feathering_weight":("FLOAT", {"default": 0.1,
"min": 0.0,
"max": 1,
"step": 0.1,
"display": "slider"})
}
}
RETURN_TYPES = ('MASK',)
FUNCTION = "run"
CATEGORY = "♾️Mixlab/Mask"
OUTPUT_IS_LIST = (True,)
# 运行的函数
def run(self,mask,start_offset, feathering_weight):
# print(mask.shape,mask.size())
num,_,_=mask.size()
masks=[]
for i in range(num):
mm=mask[i]
image=tensor2pil(mm)
# Open the image using PIL
image = image.convert("L")
if start_offset>0:
image=ImageOps.invert(image)
# Convert the image to a numpy array
image_np = np.array(image)
# Use Canny edge detection to get black contours
edges = cv2.Canny(image_np, 30, 150)
for i in range(0,abs(start_offset)):
# int(100*feathering_weight)
a=int(abs(start_offset)*0.1*i)
# Dilate the black contours to make them wider
kernel = np.ones((a, a), np.uint8)
dilated_edges = cv2.dilate(edges, kernel, iterations=1)
# dilated_edges = cv2.erode(edges, kernel, iterations=1)
# Smooth the dilated edges using Gaussian blur
smoothed_edges = cv2.GaussianBlur(dilated_edges, (5, 5), 0)
# Adjust the feathering weight
feathering_weight = max(0, min(feathering_weight, 1))
# Blend the smoothed edges with the original image to achieve feathering effect
image_np = cv2.addWeighted(image_np, 1, smoothed_edges, feathering_weight, feathering_weight)
# Convert the result back to PIL image
result_image = Image.fromarray(np.uint8(image_np))
result_image=result_image.convert("L")
if start_offset>0:
result_image=ImageOps.invert(result_image)
result_image=result_image.convert("L")
mt=pil2tensor(result_image)
masks.append(mt)
# print( mt.size())
return (masks,)
+445 -52
View File
@@ -1,15 +1,90 @@
import random
import comfy.utils
import json
import os
import numpy as np
from urllib import request, parse
import folder_paths
from PIL import Image, ImageOps,ImageFilter,ImageEnhance,ImageDraw,ImageSequence, ImageFont
from PIL.PngImagePlugin import PngInfo
import hashlib
import requests
import json
# 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)
embeddings_path=os.path.join(folder_paths.models_dir, "embeddings")
def get_files_with_extension(directory, extension):
file_list = []
for root, dirs, files in os.walk(directory):
for file in files:
if file.endswith(extension):
file_name = os.path.splitext(file)[0]
file_list.append(file_name)
return file_list
def join_with_(text_list,delimiter):
joined_text = delimiter.join(text_list)
return joined_text
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 load_json(file_path):
try:
with open(file_path, 'r') as json_file:
data = json.load(json_file)
return data
except FileNotFoundError:
print(f"File not found: {file_path}")
return None
except json.JSONDecodeError:
print(f"Error decoding JSON in file: {file_path}")
return None
def save_json(data_dict, file_path):
try:
with open(file_path, 'w') as json_file:
json.dump(data_dict, json_file, indent=4)
print(f"Data saved to {file_path}")
except Exception as e:
print(f"Error saving JSON to file: {e}")
# pysss的lora加载器
# def get_model_version_info(hash_value):
# # http://127.0.0.1:1082
# proxies = {'http': 'http://127.0.0.1:1082', 'https': 'https://127.0.0.1:1082'}
# api_url = f"https://civitai.com/api/v1/model-versions/by-hash/{hash_value}"
# print(api_url)
# response = requests.get(api_url,proxies=proxies, verify=False)
# if response.status_code == 200:
# return response.json()
# else:
# return None
# def calculate_sha256(file_path):
# sha256_hash = hashlib.sha256()
# with open(file_path, "rb") as f:
# for chunk in iter(lambda: f.read(4096), b""):
# sha256_hash.update(chunk)
# return sha256_hash.hexdigest()
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("*")
default_prompt1='''Swing
@@ -45,6 +120,232 @@ default_prompt1='''Swing
'''
default_prompt1="\n".join([p.strip() for p in default_prompt1.split('\n') if p.strip()!=''])
def tensor2pil(image):
return Image.fromarray(np.clip(255. * image.cpu().numpy().squeeze(), 0, 255).astype(np.uint8))
def addWeight(text, weight=1):
if weight == 1:
return text
else:
return f"({text}:{round(weight,3)})"
def prompt_delete_words(sentence, new_words_length):
# 使用逗号分割句子,并去除空格
words = [word.strip() for word in sentence.split(",")]
# 计算需要删除的单词数量
num_to_delete = len(words) - new_words_length
words_to=[w for w in words]
# 逐个删除单词并存储在新列表中
new_words = []
for i in range(len(words)):
if num_to_delete > 0:
num_to_delete -= 1
else:
words_to.pop()
if len(words_to)>0:
new_words.append(", ".join(words_to))
return new_words
# # 测试方法
# sentence = "a computer, a glass tablet with a keyboard on a dark background, 3d illustration, reflection, cgi 8k, clear glass, archaic, cut-away, white outline"
# new_words_length = 5
# result = prompt_delete_words(sentence, new_words_length)
# print(result)
class PromptImage:
def __init__(self):
self.output_dir = folder_paths.get_output_directory()
self.type = "output"
self.prefix_append = "PromptImage"
self.compress_level = 4
@classmethod
def INPUT_TYPES(s):
return {
"required": {
"prompts": ("STRING",
{
"multiline": True,
"default": '',
"dynamicPrompts": False
}),
"images": ("IMAGE",{"default": None}),
"save_to_image": (["enable", "disable"],),
}
}
RETURN_TYPES = ()
OUTPUT_NODE = True
INPUT_IS_LIST = True
FUNCTION = "run"
CATEGORY = "♾️Mixlab/Prompt"
# 运行的函数
def run(self,prompts,images,save_to_image):
filename_prefix="mixlab_"
filename_prefix += self.prefix_append
full_output_folder, filename, counter, subfolder, filename_prefix = folder_paths.get_save_image_path(
filename_prefix, self.output_dir, images[0].shape[1], images[0].shape[0])
results = list()
save_to_image=save_to_image[0]=='enable'
for index in range(len(images)):
res=[]
imgs=images[index]
for image in imgs:
img=tensor2pil(image)
metadata = None
if save_to_image:
metadata = PngInfo()
prompt_text=prompts[index]
if prompt_text is not None:
metadata.add_text("prompt_text", prompt_text)
file = f"{filename}_{index}_{counter:05}_.png"
img.save(os.path.join(full_output_folder, file), pnginfo=metadata, compress_level=self.compress_level)
res.append({
"filename": file,
"subfolder": subfolder,
"type": self.type
})
counter += 1
results.append(res)
return { "ui": { "_images": results,"prompts":prompts } }
class PromptSimplification:
@classmethod
def INPUT_TYPES(s):
return {
"required": {
"prompt": ("STRING",
{
"multiline": True,
"default": '',
"dynamicPrompts": False
}),
"length":("INT", {"default": 5, "min": 1,"max":100, "step": 1, "display": "number"}),
# "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 = ("prompts",)
FUNCTION = "run"
CATEGORY = "♾️Mixlab/Prompt"
INPUT_IS_LIST = True
OUTPUT_IS_LIST = (True,)
OUTPUT_NODE = True
# 运行的函数
def run(self,prompt,length):
length=length[0]
result=[]
for p in prompt:
nps=prompt_delete_words(p,length)
for n in nps:
result.append(n)
result= [elem.strip() for elem in result if elem.strip()]
return {"ui": {"prompts": result}, "result": (result,)}
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:
'''
@@ -70,6 +371,10 @@ class RandomPrompt:
"default": 'sticker, Cartoon, ``'
}),
"random_sample": (["enable", "disable"],),
# "seed": ("INT", {"default": 0, "min": 0, "max": 0xffffffffffffffff, "step": 1}),
},
"optional":{
"seed": (any_type, {"default": 0, "min": 0, "max": 0xffffffffffffffff}),
}
}
@@ -79,15 +384,15 @@ class RandomPrompt:
FUNCTION = "run"
CATEGORY = "♾️Mixlab/prompt"
CATEGORY = "♾️Mixlab/Prompt"
OUTPUT_IS_LIST = (True,)
OUTPUT_NODE = True
# 运行的函数
def run(self,max_count,mutable_prompt,immutable_prompt,random_sample):
print('#运行的函数',mutable_prompt,immutable_prompt,max_count,random_sample)
def run(self,max_count,mutable_prompt,immutable_prompt,random_sample,seed=0):
# print('#运行的函数',mutable_prompt,immutable_prompt,max_count,random_sample)
# Split the text into an array of words
words1 = mutable_prompt.split("\n")
@@ -106,6 +411,11 @@ class RandomPrompt:
w1=w1.strip()
for w2 in words2:
w2=w2.strip()
if '``' not in w2:
if w2=="":
w2='``'
else:
w2=w2+',``'
if w1!='' and w2!='':
prompts.append(w2.replace('``', w1))
pbar.update(1)
@@ -119,69 +429,152 @@ class RandomPrompt:
else:
prompts = prompts[:min(max_count,len(prompts))]
prompts= [elem.strip() for elem in prompts if elem.strip()]
# return (new_prompt)
return {"ui": {"prompts": prompts}, "result": (prompts,)}
# class LoraPrompt:
# @classmethod
# def INPUT_TYPES(s):
# return {
# "required": {
# "lora_name":(sorted(folder_paths.get_filename_list("loras"), key=str.lower),),
# "weight": ("FLOAT", {"default": 1, "min": -2, "max": 2,"step":0.01 ,"display": "slider"}),
# "force_update": ("BOOLEAN", {"default": False}),
# },
# }
# RETURN_TYPES = ("STRING","STRING",any_type)
# RETURN_NAMES = ("lora_name","prompt","tags",)
# FUNCTION = "run"
# CATEGORY = "♾️Mixlab/Prompt"
# OUTPUT_IS_LIST = (False,False,True,)
# # OUTPUT_NODE = True
# # 运行的函数
# def run(self,lora_name,weight,force_update=False):
# # print('##LoraPrompt',__file__)
# # 从本地数据库读取
# json_tags_path = os.path.join(os.path.dirname(os.path.dirname(__file__)),r'data/loras_tags.json')
# if not os.path.exists(json_tags_path):
# save_json({},json_tags_path)
# lora_tags = load_json(json_tags_path)
# output_tags = lora_tags.get(lora_name, None) if lora_tags is not None else None
# if output_tags is not None:
# output_tags = ",".join(output_tags)
# print("trainedWords:",output_tags)
# else:
# output_tags = ""
# lora_path = folder_paths.get_full_path("loras", lora_name)
# if output_tags == "" or force_update:
# print("calculating lora hash")
# LORAsha256 = calculate_sha256(lora_path)
# print("requesting infos")
# model_info = get_model_version_info(LORAsha256)
# if model_info is not None:
# if "trainedWords" in model_info:
# print("tags found!")
# if lora_tags is None:
# lora_tags = {}
# lora_tags[lora_name] = model_info["trainedWords"]
# save_json(lora_tags,json_tags_path)
# output_tags = ",".join(model_info["trainedWords"])
# print("trainedWords:",output_tags)
# else:
# print("No informations found.")
# if lora_tags is None:
# lora_tags = {}
# lora_tags[lora_name] = []
# save_json(lora_tags,json_tags_path)
# weight = round(weight, 3)
# prompt=[]
# for p in output_tags.split(','):
# if weight!=1:
# prompt.append('('+p+':'+str(weight)+')')
# else:
# prompt.append(p)
# prompt=",".join(prompt)
# return (lora_name,prompt,output_tags.split(','),)
class RunWorkflow:
class EmbeddingPrompt:
@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": ''
}),
"embedding":(get_files_with_extension(embeddings_path,'.pt'),),
"weight": ("FLOAT", {"default": 1, "min": -2, "max": 2,"step":0.01 ,"display": "slider"}),
},
}
RETURN_TYPES = ("IMAGE","STRING",)
RETURN_TYPES = ("STRING",)
FUNCTION = "run"
CATEGORY = "♾️Mixlab/workflow"
OUTPUT_IS_LIST = (True,)
OUTPUT_NODE = True
CATEGORY = "♾️Mixlab/Prompt"
OUTPUT_IS_LIST = (False,)
# 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
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]])
def run(self,embedding,weight):
weight = round(weight, 3)
prompt='embedding:'+embedding
if weight!=1:
prompt='('+prompt+':'+str(weight)+')'
prompt=" "+prompt+' '
# return (new_prompt)
return {"ui":{"images": []},"result": ([image],['text'],)}
return (prompt,)
RETURN_TYPES = (any_type,)
class JoinWithDelimiter:
@classmethod
def INPUT_TYPES(s):
return {"required": {
"text_list": (any_type,),
"delimiter":(["newline","comma","backslash","space"],),
},
}
RETURN_TYPES = ("STRING",)
FUNCTION = "run"
CATEGORY = "♾️Mixlab/Prompt"
INPUT_IS_LIST = True # 当true的时候,输入时list,当false的时候,如果输入是list,则会自动包一层for循环调用
OUTPUT_IS_LIST = (False,)
def run(self,text_list,delimiter):
delimiter=delimiter[0]
if delimiter =='newline':
delimiter='\n'
elif delimiter=='comma':
delimiter=','
elif delimiter=='backslash':
delimiter='\\'
elif delimiter=='space':
delimiter=' '
t=''
if isinstance(text_list, list):
t=join_with_(text_list,delimiter)
return (t,)
+694
View File
@@ -0,0 +1,694 @@
import os,sys
import folder_paths
from PIL import Image
import importlib.util
import comfy.utils
import numpy as np
import torch
from huggingface_hub import hf_hub_download
import torch.nn as nn
import torch.nn.functional as F
from torchvision.transforms.functional import normalize
# BRIA-RMBG-1.4 / briarmbg.py
class REBNCONV(nn.Module):
def __init__(self,in_ch=3,out_ch=3,dirate=1,stride=1):
super(REBNCONV,self).__init__()
self.conv_s1 = nn.Conv2d(in_ch,out_ch,3,padding=1*dirate,dilation=1*dirate,stride=stride)
self.bn_s1 = nn.BatchNorm2d(out_ch)
self.relu_s1 = nn.ReLU(inplace=True)
def forward(self,x):
hx = x
xout = self.relu_s1(self.bn_s1(self.conv_s1(hx)))
return xout
## upsample tensor 'src' to have the same spatial size with tensor 'tar'
def _upsample_like(src,tar):
src = F.interpolate(src,size=tar.shape[2:],mode='bilinear')
return src
### RSU-7 ###
class RSU7(nn.Module):
def __init__(self, in_ch=3, mid_ch=12, out_ch=3, img_size=512):
super(RSU7,self).__init__()
self.in_ch = in_ch
self.mid_ch = mid_ch
self.out_ch = out_ch
self.rebnconvin = REBNCONV(in_ch,out_ch,dirate=1) ## 1 -> 1/2
self.rebnconv1 = REBNCONV(out_ch,mid_ch,dirate=1)
self.pool1 = nn.MaxPool2d(2,stride=2,ceil_mode=True)
self.rebnconv2 = REBNCONV(mid_ch,mid_ch,dirate=1)
self.pool2 = nn.MaxPool2d(2,stride=2,ceil_mode=True)
self.rebnconv3 = REBNCONV(mid_ch,mid_ch,dirate=1)
self.pool3 = nn.MaxPool2d(2,stride=2,ceil_mode=True)
self.rebnconv4 = REBNCONV(mid_ch,mid_ch,dirate=1)
self.pool4 = nn.MaxPool2d(2,stride=2,ceil_mode=True)
self.rebnconv5 = REBNCONV(mid_ch,mid_ch,dirate=1)
self.pool5 = nn.MaxPool2d(2,stride=2,ceil_mode=True)
self.rebnconv6 = REBNCONV(mid_ch,mid_ch,dirate=1)
self.rebnconv7 = REBNCONV(mid_ch,mid_ch,dirate=2)
self.rebnconv6d = REBNCONV(mid_ch*2,mid_ch,dirate=1)
self.rebnconv5d = REBNCONV(mid_ch*2,mid_ch,dirate=1)
self.rebnconv4d = REBNCONV(mid_ch*2,mid_ch,dirate=1)
self.rebnconv3d = REBNCONV(mid_ch*2,mid_ch,dirate=1)
self.rebnconv2d = REBNCONV(mid_ch*2,mid_ch,dirate=1)
self.rebnconv1d = REBNCONV(mid_ch*2,out_ch,dirate=1)
def forward(self,x):
b, c, h, w = x.shape
hx = x
hxin = self.rebnconvin(hx)
hx1 = self.rebnconv1(hxin)
hx = self.pool1(hx1)
hx2 = self.rebnconv2(hx)
hx = self.pool2(hx2)
hx3 = self.rebnconv3(hx)
hx = self.pool3(hx3)
hx4 = self.rebnconv4(hx)
hx = self.pool4(hx4)
hx5 = self.rebnconv5(hx)
hx = self.pool5(hx5)
hx6 = self.rebnconv6(hx)
hx7 = self.rebnconv7(hx6)
hx6d = self.rebnconv6d(torch.cat((hx7,hx6),1))
hx6dup = _upsample_like(hx6d,hx5)
hx5d = self.rebnconv5d(torch.cat((hx6dup,hx5),1))
hx5dup = _upsample_like(hx5d,hx4)
hx4d = self.rebnconv4d(torch.cat((hx5dup,hx4),1))
hx4dup = _upsample_like(hx4d,hx3)
hx3d = self.rebnconv3d(torch.cat((hx4dup,hx3),1))
hx3dup = _upsample_like(hx3d,hx2)
hx2d = self.rebnconv2d(torch.cat((hx3dup,hx2),1))
hx2dup = _upsample_like(hx2d,hx1)
hx1d = self.rebnconv1d(torch.cat((hx2dup,hx1),1))
return hx1d + hxin
### RSU-6 ###
class RSU6(nn.Module):
def __init__(self, in_ch=3, mid_ch=12, out_ch=3):
super(RSU6,self).__init__()
self.rebnconvin = REBNCONV(in_ch,out_ch,dirate=1)
self.rebnconv1 = REBNCONV(out_ch,mid_ch,dirate=1)
self.pool1 = nn.MaxPool2d(2,stride=2,ceil_mode=True)
self.rebnconv2 = REBNCONV(mid_ch,mid_ch,dirate=1)
self.pool2 = nn.MaxPool2d(2,stride=2,ceil_mode=True)
self.rebnconv3 = REBNCONV(mid_ch,mid_ch,dirate=1)
self.pool3 = nn.MaxPool2d(2,stride=2,ceil_mode=True)
self.rebnconv4 = REBNCONV(mid_ch,mid_ch,dirate=1)
self.pool4 = nn.MaxPool2d(2,stride=2,ceil_mode=True)
self.rebnconv5 = REBNCONV(mid_ch,mid_ch,dirate=1)
self.rebnconv6 = REBNCONV(mid_ch,mid_ch,dirate=2)
self.rebnconv5d = REBNCONV(mid_ch*2,mid_ch,dirate=1)
self.rebnconv4d = REBNCONV(mid_ch*2,mid_ch,dirate=1)
self.rebnconv3d = REBNCONV(mid_ch*2,mid_ch,dirate=1)
self.rebnconv2d = REBNCONV(mid_ch*2,mid_ch,dirate=1)
self.rebnconv1d = REBNCONV(mid_ch*2,out_ch,dirate=1)
def forward(self,x):
hx = x
hxin = self.rebnconvin(hx)
hx1 = self.rebnconv1(hxin)
hx = self.pool1(hx1)
hx2 = self.rebnconv2(hx)
hx = self.pool2(hx2)
hx3 = self.rebnconv3(hx)
hx = self.pool3(hx3)
hx4 = self.rebnconv4(hx)
hx = self.pool4(hx4)
hx5 = self.rebnconv5(hx)
hx6 = self.rebnconv6(hx5)
hx5d = self.rebnconv5d(torch.cat((hx6,hx5),1))
hx5dup = _upsample_like(hx5d,hx4)
hx4d = self.rebnconv4d(torch.cat((hx5dup,hx4),1))
hx4dup = _upsample_like(hx4d,hx3)
hx3d = self.rebnconv3d(torch.cat((hx4dup,hx3),1))
hx3dup = _upsample_like(hx3d,hx2)
hx2d = self.rebnconv2d(torch.cat((hx3dup,hx2),1))
hx2dup = _upsample_like(hx2d,hx1)
hx1d = self.rebnconv1d(torch.cat((hx2dup,hx1),1))
return hx1d + hxin
### RSU-5 ###
class RSU5(nn.Module):
def __init__(self, in_ch=3, mid_ch=12, out_ch=3):
super(RSU5,self).__init__()
self.rebnconvin = REBNCONV(in_ch,out_ch,dirate=1)
self.rebnconv1 = REBNCONV(out_ch,mid_ch,dirate=1)
self.pool1 = nn.MaxPool2d(2,stride=2,ceil_mode=True)
self.rebnconv2 = REBNCONV(mid_ch,mid_ch,dirate=1)
self.pool2 = nn.MaxPool2d(2,stride=2,ceil_mode=True)
self.rebnconv3 = REBNCONV(mid_ch,mid_ch,dirate=1)
self.pool3 = nn.MaxPool2d(2,stride=2,ceil_mode=True)
self.rebnconv4 = REBNCONV(mid_ch,mid_ch,dirate=1)
self.rebnconv5 = REBNCONV(mid_ch,mid_ch,dirate=2)
self.rebnconv4d = REBNCONV(mid_ch*2,mid_ch,dirate=1)
self.rebnconv3d = REBNCONV(mid_ch*2,mid_ch,dirate=1)
self.rebnconv2d = REBNCONV(mid_ch*2,mid_ch,dirate=1)
self.rebnconv1d = REBNCONV(mid_ch*2,out_ch,dirate=1)
def forward(self,x):
hx = x
hxin = self.rebnconvin(hx)
hx1 = self.rebnconv1(hxin)
hx = self.pool1(hx1)
hx2 = self.rebnconv2(hx)
hx = self.pool2(hx2)
hx3 = self.rebnconv3(hx)
hx = self.pool3(hx3)
hx4 = self.rebnconv4(hx)
hx5 = self.rebnconv5(hx4)
hx4d = self.rebnconv4d(torch.cat((hx5,hx4),1))
hx4dup = _upsample_like(hx4d,hx3)
hx3d = self.rebnconv3d(torch.cat((hx4dup,hx3),1))
hx3dup = _upsample_like(hx3d,hx2)
hx2d = self.rebnconv2d(torch.cat((hx3dup,hx2),1))
hx2dup = _upsample_like(hx2d,hx1)
hx1d = self.rebnconv1d(torch.cat((hx2dup,hx1),1))
return hx1d + hxin
### RSU-4 ###
class RSU4(nn.Module):
def __init__(self, in_ch=3, mid_ch=12, out_ch=3):
super(RSU4,self).__init__()
self.rebnconvin = REBNCONV(in_ch,out_ch,dirate=1)
self.rebnconv1 = REBNCONV(out_ch,mid_ch,dirate=1)
self.pool1 = nn.MaxPool2d(2,stride=2,ceil_mode=True)
self.rebnconv2 = REBNCONV(mid_ch,mid_ch,dirate=1)
self.pool2 = nn.MaxPool2d(2,stride=2,ceil_mode=True)
self.rebnconv3 = REBNCONV(mid_ch,mid_ch,dirate=1)
self.rebnconv4 = REBNCONV(mid_ch,mid_ch,dirate=2)
self.rebnconv3d = REBNCONV(mid_ch*2,mid_ch,dirate=1)
self.rebnconv2d = REBNCONV(mid_ch*2,mid_ch,dirate=1)
self.rebnconv1d = REBNCONV(mid_ch*2,out_ch,dirate=1)
def forward(self,x):
hx = x
hxin = self.rebnconvin(hx)
hx1 = self.rebnconv1(hxin)
hx = self.pool1(hx1)
hx2 = self.rebnconv2(hx)
hx = self.pool2(hx2)
hx3 = self.rebnconv3(hx)
hx4 = self.rebnconv4(hx3)
hx3d = self.rebnconv3d(torch.cat((hx4,hx3),1))
hx3dup = _upsample_like(hx3d,hx2)
hx2d = self.rebnconv2d(torch.cat((hx3dup,hx2),1))
hx2dup = _upsample_like(hx2d,hx1)
hx1d = self.rebnconv1d(torch.cat((hx2dup,hx1),1))
return hx1d + hxin
### RSU-4F ###
class RSU4F(nn.Module):
def __init__(self, in_ch=3, mid_ch=12, out_ch=3):
super(RSU4F,self).__init__()
self.rebnconvin = REBNCONV(in_ch,out_ch,dirate=1)
self.rebnconv1 = REBNCONV(out_ch,mid_ch,dirate=1)
self.rebnconv2 = REBNCONV(mid_ch,mid_ch,dirate=2)
self.rebnconv3 = REBNCONV(mid_ch,mid_ch,dirate=4)
self.rebnconv4 = REBNCONV(mid_ch,mid_ch,dirate=8)
self.rebnconv3d = REBNCONV(mid_ch*2,mid_ch,dirate=4)
self.rebnconv2d = REBNCONV(mid_ch*2,mid_ch,dirate=2)
self.rebnconv1d = REBNCONV(mid_ch*2,out_ch,dirate=1)
def forward(self,x):
hx = x
hxin = self.rebnconvin(hx)
hx1 = self.rebnconv1(hxin)
hx2 = self.rebnconv2(hx1)
hx3 = self.rebnconv3(hx2)
hx4 = self.rebnconv4(hx3)
hx3d = self.rebnconv3d(torch.cat((hx4,hx3),1))
hx2d = self.rebnconv2d(torch.cat((hx3d,hx2),1))
hx1d = self.rebnconv1d(torch.cat((hx2d,hx1),1))
return hx1d + hxin
class myrebnconv(nn.Module):
def __init__(self, in_ch=3,
out_ch=1,
kernel_size=3,
stride=1,
padding=1,
dilation=1,
groups=1):
super(myrebnconv,self).__init__()
self.conv = nn.Conv2d(in_ch,
out_ch,
kernel_size=kernel_size,
stride=stride,
padding=padding,
dilation=dilation,
groups=groups)
self.bn = nn.BatchNorm2d(out_ch)
self.rl = nn.ReLU(inplace=True)
def forward(self,x):
return self.rl(self.bn(self.conv(x)))
class BriaRMBG(nn.Module):
def __init__(self,in_ch=3,out_ch=1):
super(BriaRMBG,self).__init__()
self.conv_in = nn.Conv2d(in_ch,64,3,stride=2,padding=1)
self.pool_in = nn.MaxPool2d(2,stride=2,ceil_mode=True)
self.stage1 = RSU7(64,32,64)
self.pool12 = nn.MaxPool2d(2,stride=2,ceil_mode=True)
self.stage2 = RSU6(64,32,128)
self.pool23 = nn.MaxPool2d(2,stride=2,ceil_mode=True)
self.stage3 = RSU5(128,64,256)
self.pool34 = nn.MaxPool2d(2,stride=2,ceil_mode=True)
self.stage4 = RSU4(256,128,512)
self.pool45 = nn.MaxPool2d(2,stride=2,ceil_mode=True)
self.stage5 = RSU4F(512,256,512)
self.pool56 = nn.MaxPool2d(2,stride=2,ceil_mode=True)
self.stage6 = RSU4F(512,256,512)
# decoder
self.stage5d = RSU4F(1024,256,512)
self.stage4d = RSU4(1024,128,256)
self.stage3d = RSU5(512,64,128)
self.stage2d = RSU6(256,32,64)
self.stage1d = RSU7(128,16,64)
self.side1 = nn.Conv2d(64,out_ch,3,padding=1)
self.side2 = nn.Conv2d(64,out_ch,3,padding=1)
self.side3 = nn.Conv2d(128,out_ch,3,padding=1)
self.side4 = nn.Conv2d(256,out_ch,3,padding=1)
self.side5 = nn.Conv2d(512,out_ch,3,padding=1)
self.side6 = nn.Conv2d(512,out_ch,3,padding=1)
# self.outconv = nn.Conv2d(6*out_ch,out_ch,1)
def forward(self,x):
hx = x
hxin = self.conv_in(hx)
#hx = self.pool_in(hxin)
#stage 1
hx1 = self.stage1(hxin)
hx = self.pool12(hx1)
#stage 2
hx2 = self.stage2(hx)
hx = self.pool23(hx2)
#stage 3
hx3 = self.stage3(hx)
hx = self.pool34(hx3)
#stage 4
hx4 = self.stage4(hx)
hx = self.pool45(hx4)
#stage 5
hx5 = self.stage5(hx)
hx = self.pool56(hx5)
#stage 6
hx6 = self.stage6(hx)
hx6up = _upsample_like(hx6,hx5)
#-------------------- decoder --------------------
hx5d = self.stage5d(torch.cat((hx6up,hx5),1))
hx5dup = _upsample_like(hx5d,hx4)
hx4d = self.stage4d(torch.cat((hx5dup,hx4),1))
hx4dup = _upsample_like(hx4d,hx3)
hx3d = self.stage3d(torch.cat((hx4dup,hx3),1))
hx3dup = _upsample_like(hx3d,hx2)
hx2d = self.stage2d(torch.cat((hx3dup,hx2),1))
hx2dup = _upsample_like(hx2d,hx1)
hx1d = self.stage1d(torch.cat((hx2dup,hx1),1))
#side output
d1 = self.side1(hx1d)
d1 = _upsample_like(d1,x)
d2 = self.side2(hx2d)
d2 = _upsample_like(d2,x)
d3 = self.side3(hx3d)
d3 = _upsample_like(d3,x)
d4 = self.side4(hx4d)
d4 = _upsample_like(d4,x)
d5 = self.side5(hx5d)
d5 = _upsample_like(d5,x)
d6 = self.side6(hx6)
d6 = _upsample_like(d6,x)
return [F.sigmoid(d1), F.sigmoid(d2), F.sigmoid(d3), F.sigmoid(d4), F.sigmoid(d5), F.sigmoid(d6)],[hx1d,hx2d,hx3d,hx4d,hx5d,hx6]
U2NET_HOME=os.path.join(folder_paths.models_dir, "rembg")
os.environ["U2NET_HOME"] = U2NET_HOME
global _available
_available=False
def is_installed(package):
try:
spec = importlib.util.find_spec(package)
except ModuleNotFoundError:
return False
return spec is not None
try:
if is_installed('rembg')==False:
import subprocess
# 安装
print('#pip install rembg[gpu]')
result = subprocess.run([sys.executable, '-s', '-m', 'pip', 'install', 'rembg[gpu]'], capture_output=True, text=True)
#检查命令执行结果
if result.returncode == 0:
print("#install success")
from rembg import new_session, remove
_available=True
else:
print("#install error")
else:
from rembg import new_session, remove
_available=True
except:
_available=False
def briarmbg_run(images=[]):
mroot=os.path.join(folder_paths.models_dir, "rembg")
m=os.path.join(mroot,'briarmbg.pth')
if os.path.exists(m)==False:
# 下载
m1=hf_hub_download("briaai/RMBG-1.4",
local_dir=mroot,
filename='model.pth',
local_dir_use_symlinks=False,
endpoint='https://hf-mirror.com')
os.rename(m1, m)
net=BriaRMBG()
if torch.cuda.is_available():
net.load_state_dict(torch.load(m))
net=net.cuda()
else:
net.load_state_dict(torch.load(m,map_location="cpu"))
net.eval()
masks=[]
rgba_images=[]
rgb_images=[]
for orig_image in images:
w,h = orig_im_size = orig_image.size
image = orig_image.convert('RGB')
model_input_size = (1024, 1024)
image = image.resize(model_input_size, Image.BILINEAR)
im_np = np.array(image)
im_tensor = torch.tensor(im_np, dtype=torch.float32).permute(2,0,1)
im_tensor = torch.unsqueeze(im_tensor,0)
im_tensor = torch.divide(im_tensor,255.0)
im_tensor = normalize(im_tensor,[0.5,0.5,0.5],[1.0,1.0,1.0])
if torch.cuda.is_available():
im_tensor=im_tensor.cuda()
result=net(im_tensor)
result = torch.squeeze(F.interpolate(result[0][0], size=(h,w), mode='bilinear') ,0)
ma = torch.max(result)
mi = torch.min(result)
result = (result-mi)/(ma-mi)
im_array = (result*255).cpu().data.numpy().astype(np.uint8)
mask = Image.fromarray(np.squeeze(im_array))
# mask.save('test.png')
# mask=tensor2pil(result)
mask=mask.convert('L')
masks.append(mask)
# rgba图
image_rgba =orig_image.convert("RGBA")
image_rgba.putalpha(mask)
rgba_images.append(image_rgba)
#rgb
rgb_image = Image.new("RGB", image_rgba.size, (0, 0, 0))
rgb_image.paste(image_rgba, mask=image_rgba.split()[3])
rgb_images.append(rgb_image)
return (masks,rgba_images,rgb_images)
def run_bg(model_name= "unet",images=[]):
# model_name = "unet" # "isnet-general-use"
rembg_session = new_session(model_name)
masks=[]
rgba_images=[]
rgb_images=[]
# 进度条
pbar = comfy.utils.ProgressBar(len(images) )
for img in images:
# use the post_process_mask argument to post process the mask to get better results.
mask = remove(img, session=rembg_session,only_mask=True,post_process_mask=True)
# mask=mask.convert('L')
# masks.append(mask)
if model_name=="u2net_cloth_seg":
width, original_height = mask.size
num_slices = original_height // img.height
for i in range(num_slices):
top = i * img.height
bottom = (i + 1) * img.height
slice_image = mask.crop((0, top, width, bottom))
slice_mask=slice_image.convert('L')
masks.append(slice_mask)
# rgba图
image_rgba = img.convert("RGBA")
image_rgba.putalpha(slice_mask)
rgba_images.append(image_rgba)
#rgb
rgb_image = Image.new("RGB", image_rgba.size, (0, 0, 0))
rgb_image.paste(image_rgba, mask=image_rgba.split()[3])
rgb_images.append(rgb_image)
else:
mask=mask.convert('L')
# mask.save(output_path)
masks.append(mask)
# rgba图
image_rgba = img.convert("RGBA")
image_rgba.putalpha(mask)
rgba_images.append(image_rgba)
#rgb
rgb_image = Image.new("RGB", image_rgba.size, (0, 0, 0))
rgb_image.paste(image_rgba, mask=image_rgba.split()[3])
rgb_images.append(rgb_image)
pbar.update(1)
return (masks,rgba_images,rgb_images)
# 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)
class RembgNode_:
global _available
available=_available
@classmethod
def INPUT_TYPES(s):
return {"required": {
"image": ("IMAGE",),
"model_name": ([
"briarmbg",
"u2net",
"u2netp",
"u2net_human_seg",
"u2net_cloth_seg",
"silueta",
"isnet-general-use",
"isnet-anime",
],),
},
}
RETURN_TYPES = ("MASK","IMAGE","RGBA",)
RETURN_NAMES = ("masks","images","RGBAs")
FUNCTION = "run"
CATEGORY = "♾️Mixlab/Mask"
OUTPUT_NODE = True
INPUT_IS_LIST = True
OUTPUT_IS_LIST = (True,True,True,)
def run(self,image,model_name):
# 兼容list输入和batch输入
model_name=model_name[0]
images=[]
for ims in image:
for im in ims:
im=tensor2pil(im)
images.append(im)
if model_name=='briarmbg':
masks,rgba_images,rgb_images=briarmbg_run(images)
else:
masks,rgba_images,rgb_images=run_bg(model_name,images)
masks=[pil2tensor(m) for m in masks]
rgba_images=[pil2tensor(m) for m in rgba_images]
rgb_images=[pil2tensor(m) for m in rgb_images]
return (masks,rgb_images,rgba_images,)
+7 -5
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}),
} }
@@ -91,17 +93,17 @@ class ScreenShareNode:
RETURN_NAMES = ("IMAGE","PROMPT","FLOAT","INT")
FUNCTION = "run"
CATEGORY = "♾️Mixlab/image"
CATEGORY = "♾️Mixlab/Image"
# INPUT_IS_LIST = True
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
@@ -116,7 +118,7 @@ class FloatingVideo:
OUTPUT_NODE = True
FUNCTION = "run"
CATEGORY = "♾️Mixlab/image"
CATEGORY = "♾️Mixlab/Image"
# INPUT_IS_LIST = True
# OUTPUT_IS_LIST = (False,False,)
+307
View File
@@ -0,0 +1,307 @@
from transformers import pipeline, set_seed,AutoTokenizer, AutoModelForSeq2SeqLM
import random
import re
import os,sys
import folder_paths
# from PIL import Image
import importlib.util
import comfy.utils
# import numpy as np
import torch
import random
global _available
_available=True
text_generator_model_path=os.path.join(folder_paths.models_dir, "prompt_generator/text2image-prompt-generator")
if not os.path.exists(text_generator_model_path):
print(f"## text_generator_model not found: {text_generator_model_path}, pls download from https://huggingface.co/succinctly/text2image-prompt-generator/tree/main")
text_generator_model_path='succinctly/text2image-prompt-generator'
zh_en_model_path=os.path.join(folder_paths.models_dir, "prompt_generator/opus-mt-zh-en")
if not os.path.exists(zh_en_model_path):
print(f"## zh_en_model not found: {zh_en_model_path}, pls download from https://huggingface.co/Helsinki-NLP/opus-mt-zh-en/tree/main")
zh_en_model_path='Helsinki-NLP/opus-mt-zh-en'
def is_installed(package):
try:
spec = importlib.util.find_spec(package)
except ModuleNotFoundError:
return False
return spec is not None
try:
if is_installed('sentencepiece')==False:
import subprocess
# 安装
print('#pip install sentencepiece')
result = subprocess.run([sys.executable, '-s', '-m', 'pip', 'install', 'sentencepiece'], capture_output=True, text=True)
#检查命令执行结果
if result.returncode == 0 and is_installed('sentencepiece'):
print("#install success")
_available=True
else:
print("#install error")
_available=False
else:
_available=True
except:
_available=False
def translate(zh_en_tokenizer,zh_en_model,text):
with torch.no_grad():
encoded = zh_en_tokenizer([text], return_tensors="pt")
encoded.to(zh_en_model.device)
sequences = zh_en_model.generate(**encoded)
return zh_en_tokenizer.batch_decode(sequences, skip_special_tokens=True)[0]
# input = "青春不能回头,所以青春没有终点。 ——《火影忍者》"
# print(input, translate(input))
def text_generate(text_pipe,input,seed=None):
if seed==None:
seed = random.randint(100, 1000000)
set_seed(seed)
for count in range(6):
sequences = text_pipe(input, max_length=random.randint(60, 90), num_return_sequences=8)
list = []
for sequence in sequences:
line = sequence['generated_text'].strip()
if line != input and len(line) > (len(input) + 4) and line.endswith((":", "-", "—")) is False:
list.append(line)
result = "\n".join(list)
result = re.sub('[^ ]+\.[^ ]+','', result)
result = result.replace("<", "").replace(">", "")
if result != "":
return result
if count == 5:
return result
# input = "Youth can't turn back, so there's no end to youth."
# print(input, text_generate(input))
import re
def correct_prompt_syntax(prompt):
print("input prompt",prompt)
corrected_elements = []
# 处理成统一的英文标点
prompt = prompt.replace('(', '(').replace(')', ')').replace(',', ',').replace(';', ',').replace('。', '.').replace(':',':')
# 删除多余的空格
prompt = re.sub(r'\s+', ' ', prompt).strip()
# 分词
prompt_elements = prompt.split(',')
for element in prompt_elements:
element = element.strip()
# 处理空元素
if not element:
continue
# 检查并处理圆括号、方括号、尖括号
if element[0] in '([':
corrected_element = balance_brackets(element, '(', ')') if element[0] == '(' else balance_brackets(element, '[', ']')
elif element[0] == '<':
corrected_element = balance_brackets(element, '<', '>')
else:
# 删除开头的右括号或右方括号
corrected_element = element.lstrip(')]')
corrected_elements.append(corrected_element)
# 重组修正后的prompt
corrected_prompt = ', '.join(corrected_elements)
print("output prompt",corrected_prompt)
return corrected_prompt
def balance_brackets(element, open_bracket, close_bracket):
open_brackets_count = element.count(open_bracket)
close_brackets_count = element.count(close_bracket)
return element + close_bracket * (open_brackets_count - close_brackets_count)
# # 示例使用
# test_prompt = "((middle-century castles)), [forsaken: 0.8], (mystery dragons: 1.3, mist forests, sunsets, quiet; (((dummy)), [fisting city: 0.5] background, radiant, soft and flavoured,] promising mountains, ((starry: 1.6), [[crowds], [middle-century castle: urban landscapes of the future: 0.5], [yellow: bright sun: 0.7], overlooking"
# corrected_prompt = correct_prompt_syntax(test_prompt)
# print(corrected_prompt)
class ChinesePrompt:
global _available
available=_available
@classmethod
def INPUT_TYPES(s):
return {"required": {
"text": ("STRING",{"multiline": True,"default": "", "dynamicPrompts": False}),
"generation": (["on","off"],{"default": "off"}),
},
"optional":{
"seed":("INT", {"default": 100, "min": 100, "max": 1000000}),
},
}
RETURN_TYPES = ("STRING",)
RETURN_NAMES = ("prompt",)
FUNCTION = "run"
CATEGORY = "♾️Mixlab/Prompt"
OUTPUT_NODE = True
INPUT_IS_LIST = True
OUTPUT_IS_LIST = (True,)
global text_pipe,zh_en_model,zh_en_tokenizer
text_pipe= None
zh_en_model=None
zh_en_tokenizer=None
def run(self,text,seed,generation):
global text_pipe,zh_en_model,zh_en_tokenizer
seed=seed[0]
generation=generation[0]
# 进度条
pbar = comfy.utils.ProgressBar(len(text)+1)
texts = [correct_prompt_syntax(t) for t in text]
print('correct_prompt_syntax::',texts)
if zh_en_model==None:
zh_en_model = AutoModelForSeq2SeqLM.from_pretrained(zh_en_model_path).eval()
zh_en_tokenizer = AutoTokenizer.from_pretrained(zh_en_model_path,padding=True, truncation=True)
zh_en_model.to("cuda" if torch.cuda.is_available() else "cpu")
# zh_en_tokenizer.to("cuda" if torch.cuda.is_available() else "cpu")
text_pipe=pipeline('text-generation', model=text_generator_model_path,device="cuda" if torch.cuda.is_available() else "cpu")
# text_pipe.model.to("cuda" if torch.cuda.is_available() else "cpu")
prompt_result=[]
# print('zh_en_model device',zh_en_model.device,text_pipe.model.device,torch.cuda.current_device() )
en_texts=[]
for t in texts:
en_text=translate(zh_en_tokenizer,zh_en_model,t)
en_texts.append(en_text)
zh_en_model.to('cpu')
print("test en_text",en_texts)
# en_text.to("cuda" if torch.cuda.is_available() else "cpu")
pbar.update(1)
for t in en_texts:
if generation=='on':
prompt =text_generate(text_pipe,t,seed)
# 多条,还是单条
lines = prompt.split("\n")
longest_line = max(lines, key=len)
# print(longest_line)
prompt_result.append(longest_line)
else:
prompt_result.append(t)
pbar.update(1)
text_pipe.model.to('cpu')
prompt_result = [correct_prompt_syntax(p) for p in prompt_result]
return {
"ui":{
"prompt": prompt_result
},
"result": (prompt_result,)}
class PromptGenerate:
global _available
available=_available
@classmethod
def INPUT_TYPES(s):
return {"required": {
"text": ("STRING",{"multiline": True,"default": "", "dynamicPrompts": False}),
},
"optional":{
"multiple": (["off","on"],),
"seed":("INT", {"default": 100, "min": 100, "max": 1000000}),
},
}
RETURN_TYPES = ("STRING",)
RETURN_NAMES = ("prompt",)
FUNCTION = "run"
CATEGORY = "♾️Mixlab/Prompt"
OUTPUT_NODE = True
INPUT_IS_LIST = True
OUTPUT_IS_LIST = (True,)
global text_pipe
text_pipe= None
#
def run(self,text,multiple,seed):
global text_pipe
seed=seed[0]
multiple=multiple[0]
# 进度条
pbar = comfy.utils.ProgressBar(len(text))
text_pipe=pipeline('text-generation', model=text_generator_model_path,device="cuda" if torch.cuda.is_available() else "cpu")
prompt_result=[]
for t in text:
prompt =text_generate(text_pipe,t,seed)
prompt = prompt.split("\n")
if multiple=='off':
prompt = [max(prompt, key=len)]
for p in prompt:
prompt_result.append(p)
pbar.update(1)
text_pipe.model.to('cpu')
return {
"ui":{
"prompt": prompt_result
},
"result": (prompt_result,)}
+817 -17
View File
@@ -1,8 +1,100 @@
import os
import os,platform
import re,random,json
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 torch
import importlib.util
def recursive_search(directory, excluded_dir_names=None):
if not os.path.isdir(directory):
return [], {}
if excluded_dir_names is None:
excluded_dir_names = []
result = []
dirs = {directory: os.path.getmtime(directory)}
for dirpath, subdirs, filenames in os.walk(directory, followlinks=True, topdown=True):
subdirs[:] = [d for d in subdirs if d not in excluded_dir_names]
for file_name in filenames:
relative_path = os.path.relpath(os.path.join(dirpath, file_name), directory)
result.append(relative_path)
for d in subdirs:
path = os.path.join(dirpath, d)
dirs[path] = os.path.getmtime(path)
return result, dirs
def filter_files_extensions(files, extensions):
return sorted(list(filter(lambda a: os.path.splitext(a)[-1].lower() in extensions or len(extensions) == 0, files)))
def get_system_font_path():
ps=[]
system = platform.system()
if system == "Windows":
ps.append(os.path.join(os.environ["WINDIR"], "Fonts"))
elif system == "Darwin":
ps.append(os.path.join("/Library", "Fonts"))
elif system == "Linux":
ps.append(os.path.join("/usr", "share", "fonts"))
ps.append(os.path.join("/usr", "local", "share", "fonts"))
ps=[p for p in ps if os.path.exists(p)]
file_paths=[]
for f in ps:
result, dirs=recursive_search(f)
for r in result:
file_paths.append(r)
file_paths=filter_files_extensions(file_paths,[".otf", ".ttf"])
return file_paths
# 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 = {}
@@ -16,14 +108,14 @@ def get_font_files(directory):
# 尝试获取系统字体
try:
font_paths = fm.findSystemFonts()
for path in font_paths:
font_paths = get_system_font_path()
for file in font_paths:
try:
font_prop = fm.FontProperties(fname=path)
font_name = font_prop.get_name()
font_files[font_name] = path
font_name = os.path.splitext(file)[0]
font_path = file
font_files[font_name] = os.path.abspath(font_path)
except Exception as e:
print(f"Error processing font {path}: {e}")
print(f"Error processing font {file}: {e}")
except Exception as e:
print(f"Error finding system fonts: {e}")
@@ -35,6 +127,21 @@ 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:
if torch.is_tensor(item):
print('item.shape',item.shape)
for i in range(item.shape[0]):
flat_list.append(item[i:i + 1, ...])
else:
flat_list.append(item)
return flat_list
class ColorInput:
@classmethod
def INPUT_TYPES(s):
@@ -44,18 +151,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"
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,)
@@ -73,11 +185,699 @@ class FontInput:
FUNCTION = "run"
CATEGORY = "♾️Mixlab/utils"
CATEGORY = "♾️Mixlab/Utils"
INPUT_IS_LIST = 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"],),
"max_num":("INT", {
"default": 10,
"min":2, #Minimum value
"max": 10000000000, #Maximum value
"step": 1, #Slider's step
"display": "number" # Cosmetic only: display as "number" or "slider"
}),
},
"optional":{
"seed": (any_type, {"default": 0, "min": 0, "max": 0xffffffffffffffff}),
}
}
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,max_num,seed=0):
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, max_num)
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": 0xffffffffffffffff, #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
scaled_number = (number - min_value) / (max_value - min_value)
return (scaled_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,),
"multiply_by":("FLOAT", {
"default": 1,
"min": -2, #Minimum value
"max": 0xffffffffffffffff,
"step": 0.01, #Slider's step
"display": "number" # Cosmetic only: display as "number" or "slider"
}),
"add_by":("FLOAT", {
"default": 0,
"min": -2000, #Minimum value
"max": 0xffffffffffffffff,
"step": 0.01, #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,font):
def run(self,numberA,multiply_by,add_by):
b=int(numberA*multiply_by+add_by)
a=float(numberA*multiply_by+add_by)
return (a,b,)
return (font_files[font],)
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,"dynamicPrompts": False,}),
"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}),
"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":{
"IMAGE": ("IMAGE",),
"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}),
"auto_save": (["enable","disable"],),
}
}
RETURN_TYPES = ()
# RETURN_NAMES = ("IMAGE",)
FUNCTION = "run"
CATEGORY = "♾️Mixlab"
OUTPUT_NODE = True
INPUT_IS_LIST = True
# OUTPUT_IS_LIST = (True,)
def run(self,name,input_ids,output_ids,IMAGE,description,version,share_prefix,link,category,auto_save):
name=name[0]
im=None
if IMAGE:
im=IMAGE[0][0]
#TODO batch 的方式需要处理
im=create_temp_file(im)
# 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]
# 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": ()}
class SwitchByIndex:
@classmethod
def INPUT_TYPES(cls):
return {
"optional":{
"A":(any_type,),
"B":(any_type,),
},
"required": {
"index":("INT", {
"default": -1,
"min": -1,
"max": 1000,
"step": 1,
"display": "number"
}),
"flat": (['off',"on"],),
}
}
RETURN_TYPES = (any_type,"INT",)
RETURN_NAMES = ("C","count",)
FUNCTION = "run"
CATEGORY = "♾️Mixlab/Utils"
INPUT_IS_LIST = True
OUTPUT_IS_LIST = (True,False,)
def run(self, A=[],B=[],index=-1,flat='on'):
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,len(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,)
class ListStatistics:
@staticmethod
def count_types(lst):
type_count = {}
for item in lst:
item_type = type(item).__name__
if item_type not in type_count:
type_count[item_type] = []
if item_type in ['dict', 'str', 'int', 'float']:
type_count[item_type].append(item)
return type_count
# # 示例列表
# my_list = [1, 'hello', {'name': 'John'}, 3.14, {'age': 25}, 'world', 10]
# # 创建ListStatistics对象
# list_stats = ListStatistics()
# # 调用count_types方法进行统计
# result = list_stats.count_types(my_list)
# # 输出结果
# for item_type, values in result.items():
# print(item_type + ':')
# for value in values:
# print(value)
# print('---')
class TESTNODE_:
@classmethod
def INPUT_TYPES(s):
return {"required": {
"ANY":(any_type,),
},
}
RETURN_TYPES = (any_type,)
FUNCTION = "run"
CATEGORY = "♾️Mixlab/__TEST"
OUTPUT_NODE = True
INPUT_IS_LIST = True
OUTPUT_IS_LIST = (True,)
def run(self,ANY):
# print(ANY)
# data=ANY
list_stats = ListStatistics()
# 调用count_types方法进行统计
result = list_stats.count_types(ANY)
# 假设我们有一个模块文件名为 my_module.py,它位于 'importables' 目录下
module_path = os.path.join(os.path.dirname(__file__),'test.py')
# 使用 spec_from_file_location 获取模块的元数据(名称、定义等)
spec = importlib.util.spec_from_file_location('test', module_path)
module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(module)
functions = getattr(module, 'run') # 获取函数
functions(ANY)
return {"ui": {"data": result,"type":[str(type(ANY[0]))]}, "result": (ANY,)}
class TESTNODE_TOKEN:
@classmethod
def INPUT_TYPES(s):
return {"required": {
"text":("STRING", {"forceInput": True,}),
"clip": ("CLIP", )
},
}
RETURN_TYPES = ("STRING",)
FUNCTION = "run"
CATEGORY = "♾️Mixlab/__TEST"
OUTPUT_NODE = True
INPUT_IS_LIST = False
OUTPUT_IS_LIST = (False,)
def run(self,text,clip=None):
# print(text)
tokens = clip.tokenize(text)
tokens=[v for v in tokens.values()][0][0]
tokens=json.dumps(tokens)
return (tokens,)
class CreateSeedNode:
def __init__(self):
pass
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"seed": ("INT", {"default": 0, "min": 0, "max": 0xffffffffffffffff}),
}
}
RETURN_TYPES = ("INT",)
RETURN_NAMES = ("seed",)
OUTPUT_NODE = True
FUNCTION = "run"
CATEGORY = "♾️Mixlab/Utils"
def run(self, seed):
return (seed,)
class CreateCkptNames:
def __init__(self):
pass
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"ckpt_names": ("STRING",{"multiline": True,"default": "\n".join(folder_paths.get_filename_list("checkpoints")),"dynamicPrompts": False}),
}
}
RETURN_TYPES = (any_type,)
RETURN_NAMES = ("ckpt_names",)
INPUT_IS_LIST = False
OUTPUT_IS_LIST = (True,)
# OUTPUT_NODE = True
FUNCTION = "run"
CATEGORY = "♾️Mixlab/Utils"
def run(self, ckpt_names):
ckpt_names=ckpt_names.split('\n')
ckpt_names = [name for name in ckpt_names if name.strip()]
return (ckpt_names,)
class CreateLoraNames:
def __init__(self):
pass
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"lora_names": ("STRING",{"multiline": True,"default": "\n".join(folder_paths.get_filename_list("loras")),"dynamicPrompts": False}),
}
}
RETURN_TYPES = (any_type,"STRING",)
RETURN_NAMES = ("lora_names","prompt",)
INPUT_IS_LIST = False
OUTPUT_IS_LIST = (True,True,)
# OUTPUT_NODE = True
FUNCTION = "run"
CATEGORY = "♾️Mixlab/Utils"
def run(self, lora_names):
lora_names=lora_names.split('\n')
lora_names = [name for name in lora_names if name.strip()]
prompts=[os.path.splitext(n)[0] for n in lora_names]
return (lora_names,prompts,)
class CreateSampler_names:
def __init__(self):
pass
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"sampler_names": ("STRING",{"multiline": True,"default": "\n".join(comfy.samplers.KSampler.SAMPLERS),"dynamicPrompts": False}),
}
}
RETURN_TYPES = (any_type,)
RETURN_NAMES = ("sampler_names",)
INPUT_IS_LIST = False
OUTPUT_IS_LIST = (True,)
# OUTPUT_NODE = True
FUNCTION = "run"
CATEGORY = "♾️Mixlab/Utils"
def run(self, sampler_names):
sampler_names=sampler_names.split('\n')
sampler_names = [name for name in sampler_names if name.strip()]
return (sampler_names,)
-179
View File
@@ -1,179 +0,0 @@
# https://github.com/openai/consistencydecoder/blob/main/consistencydecoder/__init__.py
import folder_paths
from comfy import model_management
import math
import torch
import numpy as np
from PIL import Image
class ConsistencyDecoderWrapper:
def __init__(self, decoder):
self.decoder = decoder
def decode(self, x):
return self.decoder(x)
def _extract_into_tensor(arr, timesteps, broadcast_shape):
# from: https://github.com/openai/guided-diffusion/blob/22e0df8183507e13a7813f8d38d51b072ca1e67c/guided_diffusion/gaussian_diffusion.py#L895 """
res = arr[timesteps].float()
dims_to_append = len(broadcast_shape) - len(res.shape)
return res[(...,) + (None,) * dims_to_append]
def betas_for_alpha_bar(num_diffusion_timesteps, alpha_bar, max_beta=0.999):
# from: https://github.com/openai/guided-diffusion/blob/22e0df8183507e13a7813f8d38d51b072ca1e67c/guided_diffusion/gaussian_diffusion.py#L45
betas = []
for i in range(num_diffusion_timesteps):
t1 = i / num_diffusion_timesteps
t2 = (i + 1) / num_diffusion_timesteps
betas.append(min(1 - alpha_bar(t2) / alpha_bar(t1), max_beta))
return torch.tensor(betas)
class ConsistencyDecoder:
def __init__(self, device="cuda:0", download_target=""):
self.n_distilled_steps = 64
# download_target = _download("https://openaipublic.azureedge.net/diff-vae/c9cebd3132dd9c42936d803e33424145a748843c8f716c0814838bdc8a2fe7cb/decoder.pt", download_root)
self.ckpt = torch.jit.load(download_target).to(device)
self.device = device
sigma_data = 0.5
betas = betas_for_alpha_bar(
1024, lambda t: math.cos((t + 0.008) / 1.008 * math.pi / 2) ** 2
).to(device)
alphas = 1.0 - betas
alphas_cumprod = torch.cumprod(alphas, dim=0)
self.sqrt_alphas_cumprod = torch.sqrt(alphas_cumprod)
self.sqrt_one_minus_alphas_cumprod = torch.sqrt(1.0 - alphas_cumprod)
sqrt_recip_alphas_cumprod = torch.sqrt(1.0 / alphas_cumprod)
sigmas = torch.sqrt(1.0 / alphas_cumprod - 1)
self.c_skip = (
sqrt_recip_alphas_cumprod
* sigma_data**2
/ (sigmas**2 + sigma_data**2)
)
self.c_out = sigmas * sigma_data / (sigmas**2 + sigma_data**2) ** 0.5
self.c_in = sqrt_recip_alphas_cumprod / (sigmas**2 + sigma_data**2) ** 0.5
@staticmethod
def round_timesteps(
timesteps, total_timesteps, n_distilled_steps, truncate_start=True
):
with torch.no_grad():
space = torch.div(total_timesteps, n_distilled_steps, rounding_mode="floor")
rounded_timesteps = (
torch.div(timesteps, space, rounding_mode="floor") + 1
) * space
if truncate_start:
rounded_timesteps[rounded_timesteps == total_timesteps] -= space
else:
rounded_timesteps[rounded_timesteps == total_timesteps] -= space
rounded_timesteps[rounded_timesteps == 0] += space
return rounded_timesteps
@staticmethod
def ldm_transform_latent(z, extra_scale_factor=1):
channel_means = [0.38862467, 0.02253063, 0.07381133, -0.0171294]
channel_stds = [0.9654121, 1.0440036, 0.76147926, 0.77022034]
if len(z.shape) != 4:
raise ValueError()
z = z * 0.18215
channels = [z[:, i] for i in range(z.shape[1])]
channels = [
extra_scale_factor * (c - channel_means[i]) / channel_stds[i]
for i, c in enumerate(channels)
]
return torch.stack(channels, dim=1)
@torch.no_grad()
def __call__(
self,
features: torch.Tensor,
schedule=[1.0, 0.5],
):
features = self.ldm_transform_latent(features)
ts = self.round_timesteps(
torch.arange(0, 1024),
1024,
self.n_distilled_steps,
truncate_start=False,
)
shape = (
features.size(0),
3,
8 * features.size(2),
8 * features.size(3),
)
x_start = torch.zeros(shape, device=features.device, dtype=features.dtype)
schedule_timesteps = [int((1024 - 1) * s) for s in schedule]
for i in schedule_timesteps:
t = ts[i].item()
t_ = torch.tensor([t] * features.shape[0]).to(self.device)
noise = torch.randn_like(x_start)
x_start = (
_extract_into_tensor(self.sqrt_alphas_cumprod, t_, x_start.shape)
* x_start
+ _extract_into_tensor(
self.sqrt_one_minus_alphas_cumprod, t_, x_start.shape
)
* noise
)
c_in = _extract_into_tensor(self.c_in, t_, x_start.shape)
model_output = self.ckpt(c_in * x_start, t_, features=features)
B, C = x_start.shape[:2]
model_output, _ = torch.split(model_output, C, dim=1)
pred_xstart = (
_extract_into_tensor(self.c_out, t_, x_start.shape) * model_output
+ _extract_into_tensor(self.c_skip, t_, x_start.shape) * x_start
).clamp(-1, 1)
x_start = pred_xstart
return x_start
class VAELoader:
@classmethod
def INPUT_TYPES(s):
return {"required": { "vae_name": (folder_paths.get_filename_list("vae"), )}}
RETURN_TYPES = ("VAE",)
FUNCTION = "load_vae"
CATEGORY = "♾️Mixlab/ConsistencyDecoder"
#TODO: scale factor?
def load_vae(self, vae_name):
vae_path = folder_paths.get_full_path("vae", vae_name)
device = 'cuda:0'
# print('device',device)
consistencyDecoder = ConsistencyDecoder(device=device,
download_target=vae_path) # Model size: 2.49 GB
vae = ConsistencyDecoderWrapper(consistencyDecoder)
return (vae,)
class VAEDecode:
@classmethod
def INPUT_TYPES(s):
return {"required": { "samples": ("LATENT", ), "vae": ("VAE", )}}
RETURN_TYPES = ("IMAGE",)
FUNCTION = "decode"
CATEGORY = "♾️Mixlab/ConsistencyDecoder"
def decode(self, vae, samples):
image = vae.decode(samples["samples"].to("cuda:0"))
image = image[0].cpu().numpy()
image = (image + 1.0) * 127.5
image = image.clip(0, 255).astype(np.uint8)
image = Image.fromarray(image.transpose(1, 2, 0))
image = image.convert("RGB")
image = np.array(image).astype(np.float32) / 255.0
image = torch.from_numpy(image)[None,]
return (image, )
+8
View File
@@ -0,0 +1,8 @@
import folder_paths
# 外挂一个文件,用来编写新的节点
def run(v):
output_dir = folder_paths.get_temp_directory()
print('1323',v,output_dir)
+5 -1
View File
@@ -3,4 +3,8 @@ pyOpenSSL
watchdog
opencv-python-headless
matplotlib
openai
openai
simple-lama-inpainting
clip-interrogator==0.6.0
transformers>=4.36.0
zhipuai
+2427
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()
}
}
})
+455
View File
@@ -0,0 +1,455 @@
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 = []) {
// workflow
// const workflow=jsonData.workflow;
// const nodes=workflow.nodes;
const data = jsonData.output
let input = []
let 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清单
try {
let keywords = node.widgets.filter(w => w.name === 'upload')[0]
.value
keywords = JSON.parse(keywords)
options.keywords = keywords
} catch (error) {
console.log(error)
}
}
if (node.type == 'Color') {
}
// loadImage的mask支持
if (node.type === 'LoadImage') {
let output = node.outputs.filter(ot => ot.type == 'MASK')[0]
if (output.links) {
// 有输出
options.hasMask = true
}
}
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' || node.type == 'SamplerCustom') {
// seed 的类型收集
try {
seed[id] = node.widgets.filter(
w => w.name === 'seed' || w.name == 'noise_seed'
)[0].linkedWidgets[0].value
} catch (error) {}
}
}
}
// 修复bug,当节点不存在时
input = input.filter(i => i)
output = output.filter(i => i)
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, showInfo = true) {
console.log('####SAVE', json[0])
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()
let { input, output, seed } = extractInputAndOutputData(
data,
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)
}
if (showInfo) {
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)
}
}
function getInputsAndOutputs () {
const inputs =
`LoadImage VHS_LoadVideo CLIPTextEncode PromptSlide TextInput_ Color FloatSlider IntNumber CheckpointLoaderSimple LoraLoader`.split(
' '
),
outputs = `PreviewImage SaveImage ShowTextForGPT VHS_VideoCombine`.split(
' '
)
let inputsId = [],
outputsId = []
for (let node of app.graph._nodes) {
if (inputs.includes(node.type)) {
inputsId.push(node.id)
}
if (outputs.includes(node.type)) {
outputsId.push(node.id)
}
}
return {
input: inputsId,
output: outputsId
}
}
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)
// 自动计算workflow里哪些节点支持
let input_ids = this.widgets.filter(w => w.name == 'input_ids')[0],
output_ids = this.widgets.filter(w => w.name == 'output_ids')[0]
const { input, output } = getInputsAndOutputs()
input_ids.value = input.join('\n')
output_ids.value = output.join('\n')
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 //需要保存参数
window._mixlab_app_json = null
}
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 {
let a = this.widgets.filter(w => w.name === 'AppInfoRun')[0]
if (a) {
if (!a.value) a.value = 0
a.value += 1
}
const div = this.widgets.filter(w => w.div)[0].div
Array.from(
div.querySelectorAll('button'),
b => (b.style.background = 'yellow')
)
} catch (error) {}
}
}
},
async loadedGraphNode (node, app) {
// console.log('#loadedGraphNode1111')
window._mixlab_app_json = null //切换workflow需要清空
if (node.type === 'AppInfo') {
let auto_save = node.widgets.filter(w => w.name == 'auto_save')[0]
if (auto_save) {
if (!['enable', 'disable'].includes(auto_save.value)) {
auto_save.value = 'enable'
}
}
// app.canvas.centerOnNode(node)
// app.canvas.setZoom(0.45)
}
}
})
api.addEventListener('execution_start', async ({ detail }) => {
console.log('#execution_start', detail)
window._mixlab_app_json = null
})
api.addEventListener('executed', async ({ detail }) => {
console.log('#executed', detail)
// window._mixlab_app_json=null;
const { output } = getInputsAndOutputs()
if (output.includes(parseInt(detail.node))) {
let appinfo = app.graph.findNodesByType('AppInfo')[0]
if (appinfo) {
let auto_save = appinfo.widgets.filter(w => w.name == 'auto_save')[0]
if (auto_save?.value === 'enable') {
// 自动保存
console.log('auto_save')
if (window._mixlab_app_json) save(window._mixlab_app_json, false, false)
}
}
}
})
+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.1'
const version = 'v0.17.1'
fetch(`https://api.github.com/repos/${repoOwner}/${repoName}/releases/latest`)
.then(response => response.json())
+119
View File
@@ -0,0 +1,119 @@
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 getRandomElements (arr, num) {
var result = []
var len = arr.length
for (var i = 0; i < num; i++) {
var randomIndex = Math.floor(Math.random() * len)
result.push(arr[randomIndex])
}
return result
}
const createPrompt = (node, prompts, items, sample) => {
const w = ComfyWidgets['STRING'](
node,
'text',
['STRING', { multiline: true }],
app
).widget
w.inputEl.readOnly = true
w.inputEl.style.opacity = 0.6
w.value = typeof prompts === 'string' ? prompts : prompts.join('\n\n')
const w2 = ComfyWidgets['STRING'](
node,
'text',
['STRING', { multiline: true }],
app
).widget
w2.inputEl.readOnly = true
w2.inputEl.style.opacity = 0.6
w2.value = typeof items === 'string' ? items : JSON.stringify(items, null, 2)
const w3 = ComfyWidgets['STRING'](
node,
'text',
['STRING', { multiline: true }],
app
).widget
w3.inputEl.readOnly = true
w3.inputEl.style.opacity = 0.6
w3.value = typeof sample === 'string' ? sample : sample.join('\n\n')
}
app.registerExtension({
name: 'Mixlab.prompt.ClipInterrogator',
async beforeRegisterNodeDef (nodeType, nodeData, app) {
if (nodeData.name === 'ClipInterrogator') {
function populate (prompts, items, random_samples) {
if (this.widgets) {
for (let i = 0; i < this.widgets.length; i++) {
if (this.widgets[i].type !== 'combo') this.widgets[i].onRemove?.()
}
this.widgets.length = 2
}
createPrompt(this, prompts, items, random_samples)
// console.log('ClipInterrogator', w, w2)
requestAnimationFrame(() => {
const sz = this.computeSize()
if (sz[0] < this.size[0]) {
sz[0] = this.size[0]
}
if (sz[1] < this.size[1]) {
sz[1] = this.size[1]
}
this.onResize?.(sz)
app.graph.setDirtyCanvas(true, false)
})
}
// When the node is executed we will be sent the input text, display this in the widget
const onExecuted = nodeType.prototype.onExecuted
nodeType.prototype.onExecuted = function (message) {
onExecuted?.apply(this, arguments)
// console.log('##', message)
populate.call(
this,
message.prompt,
message.analysis,
message.random_samples
)
}
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 === 'ClipInterrogator') {
try {
let widgets_values = node.widgets_values
console.log(widgets_values )
try {
if (widgets_values[2] && widgets_values[3] && widgets_values[4])
createPrompt(
node,
widgets_values[2],
widgets_values[3],
widgets_values[4]
)
} catch (error) {
console.log(error)
}
} catch (error) {}
}
}
})
+72 -65
View File
@@ -61,14 +61,14 @@ 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
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
return [128, 32] // a method to compute the current size of the widget
},
async serializeValue (nodeId, widgetIndex) {
let data = getLocalData('_mixlab_api_key')
@@ -203,75 +203,82 @@ app.registerExtension({
app.registerExtension({
name: 'Mixlab.GPT.ShowTextForGPT',
async beforeRegisterNodeDef(nodeType, nodeData, app) {
if (nodeData.name === "ShowTextForGPT") {
function populate(text) {
if (this.widgets) {
const pos = this.widgets.findIndex((w) => w.name === "text");
if (pos !== -1) {
for (let i = pos; i < this.widgets.length; i++) {
this.widgets[i].onRemove?.();
}
this.widgets.length = pos;
}
}
// console.log('ShowTextForGPT',text)
for (let list of text) {
const w = ComfyWidgets["STRING"](this, "text", ["STRING", { multiline: true }], app).widget;
w.inputEl.readOnly = true;
w.inputEl.style.opacity = 0.6;
async beforeRegisterNodeDef (nodeType, nodeData, app) {
if (nodeData.name === 'ShowTextForGPT') {
function populate (text) {
text = text.filter(t => t && t?.trim())
try {
let data=JSON.parse(list);
data=Array.from(data,d=>{
return {
...d,
content:decodeURIComponent(d.content)
}
})
list=JSON.stringify(data,null,2)
} catch (error) {
// console.log(error)
if (this.widgets) {
// const pos = this.widgets.findIndex(w => w.name === 'text')
for (let i = 0; i < this.widgets.length; i++) {
if (this.widgets[i].name == 'show_text') this.widgets[i].onRemove?.()
}
this.widgets.length = 1
}
// console.log('ShowTextForGPT',text)
for (let list of text) {
if (list) {
// console.log('#####', list)
const w = ComfyWidgets['STRING'](
this,
'show_text',
['STRING', { multiline: true }],
app
).widget
w.inputEl.readOnly = true
w.inputEl.style.opacity = 0.6
w.value =list;
}
try {
if (typeof list != 'string') {
let data = JSON.parse(list)
data = Array.from(data, d => {
return {
...d,
content: decodeURIComponent(d.content)
}
})
list = JSON.stringify(data, null, 2)
}
} catch (error) {
console.log(error)
}
w.value = list
}
}
// console.log('ShowTextForGPT',this.widgets.length)
requestAnimationFrame(() => {
const sz = this.computeSize();
if (sz[0] < this.size[0]) {
sz[0] = this.size[0];
}
if (sz[1] < this.size[1]) {
sz[1] = this.size[1];
}
this.onResize?.(sz);
app.graph.setDirtyCanvas(true, false);
});
}
requestAnimationFrame(() => {
if (this) {
const sz = this.computeSize()
if (sz[0] < this.size[0]) {
sz[0] = this.size[0]
}
if (sz[1] < this.size[1]) {
sz[1] = this.size[1]
}
this.onResize?.(sz)
app.graph.setDirtyCanvas(true, false)
}
})
}
// When the node is executed we will be sent the input text, display this in the widget
const onExecuted = nodeType.prototype.onExecuted;
nodeType.prototype.onExecuted = function (message) {
onExecuted?.apply(this, arguments);
populate.call(this, message.text);
};
// When the node is executed we will be sent the input text, display this in the widget
const onExecuted = nodeType.prototype.onExecuted
nodeType.prototype.onExecuted = function (message) {
onExecuted?.apply(this, arguments)
// console.log('##onExecuted', this, message)
if (message.text) populate.call(this, message.text)
}
const onConfigure = nodeType.prototype.onConfigure;
nodeType.prototype.onConfigure = function () {
onConfigure?.apply(this, arguments);
if (this.widgets_values?.length) {
populate.call(this, this.widgets_values);
}
};
const onConfigure = nodeType.prototype.onConfigure
nodeType.prototype.onConfigure = function () {
onConfigure?.apply(this, arguments)
if (this.widgets_values?.length) {
populate.call(this, this.widgets_values)
}
}
this.serialize_widgets = true //需要保存参数
}
},
}
}
})
+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)
}
}
})
+311 -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,212 @@ 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 }
}
function findImages(nodeId) {
// 检查当前节点是否有 imgs 字段
const n = app.graph.getNodeById(nodeId)
if (n.imgs) {
return n.imgs;
}
// 检查当前节点的 inputs 是否有 image 字段
if (n.inputs) {
for (let i = 0; i < n.inputs.length; i++) {
if (n.inputs[i].name==='image'||n.inputs[i].name==='images') {
// 获取新的 nodeId,并递归调用 findImages 函数
var linkId = n.inputs[i]?.link;
var origin_id = app.graph.links[linkId].origin_id
return findImages(origin_id);
}
}
}
// 如果没有找到 imgs 字段或者 image 字段,则返回 null
return null;
}
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 +396,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 +407,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 +425,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 +548,96 @@ 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.id)
let imgs=findImages(this.id)
// let topLinkId = this.inputs[0].link
// let topNodeId = app.graph.links[topLinkId].origin_id
let topIm = imgs[0]
let linkId = this.inputs[3].link
let nodeId = app.graph.links[linkId].origin_id
// console.log(linkId,this.inputs)
let imgs2=findImages(nodeId)
let im = imgs2[0]
console.log(topIm,im)
// let src = im.src
setArea(
im.naturalWidth,
im.naturalHeight,
topIm.src,
im.src,
data,
updateValue
)
} catch (error) {
console.log(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 //需要保存参数
}
}
})
+67 -65
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,7 +478,7 @@ 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`,
@@ -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)
@@ -603,7 +600,8 @@ app.registerExtension({
ctx,
widget_width,
widget_height * 5,
node.size[1]
node.size[1],
40
)
)
}
@@ -693,22 +691,22 @@ app.registerExtension({
}
})
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.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: {
@@ -734,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) => {
@@ -894,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) {
@@ -939,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
)
}
}
}
})
@@ -1036,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;
@@ -1067,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 &&
@@ -1086,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'
@@ -1153,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
@@ -1165,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()
@@ -1189,7 +1173,6 @@ async function setArea (src) {
window._mixlab_screen_webcamVideo,
!window._mixlab_screen_live
)
remove()
}
}
@@ -1238,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)
)
}
}
@@ -1348,7 +1331,13 @@ app.registerExtension({
let w = 360,
s = widget.preview.videoWidth / widget.preview.videoHeight,
h = w / s || w
console.log(h)
// console.log(h)
if (!window.documentPictureInPicture) {
window.alert(
'This feature is available only in secure contexts (HTTPS), in some or all supporting browsers. https://developer.mozilla.org/en-US/docs/Web/API/Document_Picture-in-Picture_API'
)
}
const pipWindow = await documentPictureInPicture.requestWindow({
width: w,
@@ -1817,6 +1806,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',
@@ -2102,13 +2099,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]
@@ -2144,10 +2141,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') {
@@ -2240,7 +2242,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]
@@ -2251,7 +2253,7 @@ const node = {
} else {
// 动态添加
const w = ComfyWidgets.STRING(
node,
this,
'prompts',
['STRING', { multiline: true }],
app
+565
View File
@@ -0,0 +1,565 @@
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 PhotoSwipeLightbox from '/extensions/comfyui-mixlab-nodes/lib/photoswipe-lightbox.esm.min.js'
function loadCSS (url) {
var link = document.createElement('link')
link.rel = 'stylesheet'
link.type = 'text/css'
link.href = url
document.getElementsByTagName('head')[0].appendChild(link)
// Create a style element
const style = document.createElement('style')
// Define the CSS rule for scrollbar width
const cssRule = `.pswp__custom-caption {
background: rgb(20 27 70);
font-size: 16px;
color: #fff;
width: calc(100% - 32px);
max-width: 980px;
padding: 2px 8px;
border-radius: 4px;
position: absolute;
left: 50%;
bottom: 16px;
transform: translateX(-50%);
}
.pswp__custom-caption a {
color: #fff;
text-decoration: underline;
}
.hidden-caption-content {
display: none;
}`
// Add the CSS rule to the style element
style.appendChild(document.createTextNode(cssRule))
// Append the style element to the document head
document.head.appendChild(style)
}
loadCSS('/extensions/comfyui-mixlab-nodes/lib/photoswipe.min.css')
function initLightBox () {
const lightbox = new PhotoSwipeLightbox({
gallery: '.prompt_image_output',
children: 'a',
pswpModule: () =>
import('/extensions/comfyui-mixlab-nodes/lib/photoswipe.esm.min.js')
})
lightbox.on('uiRegister', function () {
lightbox.pswp.ui.registerElement({
name: 'custom-caption',
order: 9,
isButton: false,
appendTo: 'root',
html: 'Caption text',
onInit: (el, pswp) => {
lightbox.pswp.on('change', () => {
const currSlideElement = lightbox.pswp.currSlide.data.element
let captionHTML = ''
if (currSlideElement) {
const hiddenCaption = currSlideElement.querySelector(
'.hidden-caption-content'
)
if (hiddenCaption) {
// get caption from element with class hidden-caption-content
captionHTML = hiddenCaption.innerHTML
} else {
// get caption from alt attribute
captionHTML = currSlideElement
.querySelector('img')
.getAttribute('alt')
}
}
el.innerHTML = captionHTML || ''
})
}
})
})
lightbox.init()
}
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'
}
}
function createImage (url) {
let im = new Image()
return new Promise((res, rej) => {
im.onload = () => res(im)
im.src = url
})
}
async function fetchImage (url) {
try {
const response = await fetch(url)
const blob = await response.blob()
return blob
} catch (error) {
console.error('出现错误:', error)
}
}
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.RandomPrompt',
async beforeRegisterNodeDef (nodeType, nodeData, app) {
if (nodeType.comfyClass == 'RandomPrompt') {
const orig_nodeCreated = nodeType.prototype.onNodeCreated
nodeType.prototype.onNodeCreated = async function () {
orig_nodeCreated?.apply(this, arguments)
const mutable_prompt = this.widgets.filter(
w => w.name == 'mutable_prompt'
)[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 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)
mutable_prompt.value = keywords.join('\n')
inp.remove()
}
// 以文本方式读取文件
reader.readAsText(file)
})
})
widget.div.appendChild(btn)
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 === 'RandomPrompt') {
}
}
})
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 = JSON.stringify(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 uploadWidget = node.widgets.filter(w => w.name == 'upload')[0]
// console.log('##widget', uploadWidget.value)
let keywords = JSON.parse(uploadWidget.value)
// console.log('keywords',keywords)
let widget = node.widgets.filter(w => w.select)[0]
if (keywords && keywords[0]) {
widget.select.style.display = 'block'
createSelect(widget.select, keywords, prompt)
}
} catch (error) {}
}
}
})
const _createResult = async (node, widget, message) => {
widget.div.innerHTML = ``
const width = node.size[0] * 0.5 - 12
let height_add = 0
for (let index = 0; index < message._images.length; index++) {
const imgs = message._images[index]
for (const img of imgs) {
let url = api.apiURL(
`/view?filename=${encodeURIComponent(img.filename)}&type=${
img.type
}&subfolder=${
img.subfolder
}${app.getPreviewFormatParam()}${app.getRandParam()}`
)
let image = await createImage(url)
// 创建card
let div = document.createElement('div')
div.className = 'card'
div.draggable = true
div.ondragend = async event => {
console.log('拖动停止')
let url = div.querySelector('img').src
let blob = await fetchImage(url)
let imageNode = null
// No image node selected: add a new one
if (!imageNode) {
const newNode = LiteGraph.createNode('LoadImage')
newNode.pos = [...app.canvas.graph_mouse]
imageNode = app.graph.add(newNode)
app.graph.change()
}
// const blob = item.getAsFile();
imageNode.pasteFile(blob)
}
div.setAttribute('data-scale', image.naturalHeight / image.naturalWidth)
let h = (image.naturalHeight * width) / image.naturalWidth
if (index % 2 === 0) height_add += h
div.style = `width: ${width}px;height:${h}px;position: relative;margin: 4px;`
div.innerHTML = `<a href="${url}"
data-pswp-width="${image.naturalWidth}"
data-pswp-height="${image.naturalHeight}"
target="_blank">
<img src="${url}" style='width: 100%' alt="${message.prompts[index]}"/>
</a>
<p style="position: absolute;
bottom: 0;
left: 0;
opacity: 0.6;
background-color: var(--comfy-input-bg);
color: var(--descrip-text);
margin: 0;
font-size: 12px;
padding: 5px;
text-align: left;">${message.prompts[index]}</p>`
widget.div.appendChild(div)
}
}
node.size[1] = 98 + height_add
}
app.registerExtension({
name: 'Mixlab.prompt.PromptImage',
async beforeRegisterNodeDef (nodeType, nodeData, app) {
if (nodeType.comfyClass == 'PromptImage') {
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: 'result',
draw (ctx, node, widget_width, y, widget_height) {
Object.assign(this.div.style, {
...get_position_style(ctx, widget_width, y, node.size[1]),
flexWrap: 'wrap',
justifyContent: 'space-between',
// outline: '1px solid red',
paddingLeft: '0px',
width: widget_width + 'px'
})
}
}
widget.div = $el('div', {})
widget.div.className = 'prompt_image_output'
document.body.appendChild(widget.div)
this.addCustomWidget(widget)
initLightBox()
const onRemoved = this.onRemoved
this.onRemoved = () => {
widget.div.remove()
return onRemoved?.()
}
const onResize = this.onResize
this.onResize = function () {
// 缩放发生
// console.log('##缩放发生', this.size)
let w = this.size[0] * 0.5 - 12
Array.from(widget.div.querySelectorAll('.card'), card => {
card.style.width = `${w}px`
card.style.height = `${
w * parseFloat(card.getAttribute('data-scale'))
}px`
})
return onResize?.apply(this, arguments)
}
// this.serialize_widgets = true //需要保存参数
}
const onExecuted = nodeType.prototype.onExecuted
nodeType.prototype.onExecuted = async function (message) {
onExecuted?.apply(this, arguments)
console.log('#PromptImage', message.prompts, message._images)
// window._mixlab_app_json = message.json
try {
let widget = this.widgets.filter(w => w.name === 'result')[0]
widget.value = message
_createResult(this, widget, { ...message })
} catch (error) {
console.log(error)
}
}
this.serialize_widgets = true //需要保存参数
}
},
async loadedGraphNode (node, app) {
if (node.type === 'PromptImage') {
// await sleep(0)
let widget = node.widgets.filter(w => w.name === 'result')[0]
console.log('widget.value', widget.value)
initLightBox()
let cards = widget.div.querySelectorAll('.card')
if (cards.length == 0) node.size = [280, 120]
if(widget.value) _createResult(node, widget, widget.value)
}
}
})
+302
View File
@@ -0,0 +1,302 @@
const smart_connect_config_input = [
{
node_type: 'CLIPTextEncode',
node_widget_name: 'text',
inputNodeName: 'RandomPrompt',
inputNode_output_name: 'STRING'
},
{
node_type: 'CLIPTextEncode',
node_widget_name: 'text',
inputNodeName: 'EmbeddingPrompt',
inputNode_output_name: 'STRING'
},
{
node_type: 'CLIPTextEncode',
node_widget_name: 'text',
inputNodeName: 'ChinesePrompt_Mix',
inputNode_output_name: 'prompt'
},
{
node_type: 'CheckpointLoaderSimple',
node_widget_name: 'ckpt_name',
inputNodeName: 'CkptNames_',
inputNode_output_name: 'ckpt_names'
},
{
node_type: 'KSampler',
node_widget_name: 'sampler_name',
inputNodeName: 'SamplerNames_',
inputNode_output_name: 'sampler_names'
},
{
node_type: 'LoraLoaderModelOnly',
node_widget_name: 'lora_name',
inputNodeName: 'LoraNames_',
inputNode_output_name: 'lora_names'
},
{
node_type: 'LoadLoRA',
node_widget_name: 'lora_name',
inputNodeName: 'LoraNames_',
inputNode_output_name: 'lora_names'
},
{
node_type: 'Moondream',
node_widget_name: 'image',
inputNodeName: 'LoadImage',
inputNode_output_name: 'IMAGE'
}
]
const smart_connect_config_output = [
{
node_type: 'LoadImage',
node_output_name: 'IMAGE',
outputNodeName: 'ClipInterrogator',
outputNode_input_name: 'image'
},
{
node_type: 'VAEDecode',
node_output_name: 'IMAGE',
outputNodeName: 'PromptImage',
outputNode_input_name: 'images'
},
{
node_type: 'VAEDecode',
node_output_name: 'IMAGE',
outputNodeName: 'PreviewImage',
outputNode_input_name: 'images'
},
{
node_type: 'VAEDecode',
node_output_name: 'IMAGE',
outputNodeName: 'SaveImage',
outputNode_input_name: 'images'
},
{
node_type: 'Moondream',
node_output_name: 'STRING',
outputNodeName: 'ShowTextForGPT',
outputNode_input_name: 'text'
}
]
// import {
// convertToInput,
// getConfig,
// isConvertableWidget
// } from '../../../extensions/core/widgetInputs.js'
const CONVERTED_TYPE = 'converted-widget'
const GET_CONFIG = Symbol()
function getConfig (widgetName) {
const { nodeData } = this.constructor
return (
nodeData?.input?.required[widgetName] ??
nodeData?.input?.optional?.[widgetName]
)
}
function hideWidget (node, widget, suffix = '') {
widget.origType = widget.type
widget.origComputeSize = widget.computeSize
widget.origSerializeValue = widget.serializeValue
widget.computeSize = () => [0, -4] // -4 is due to the gap litegraph adds between widgets automatically
widget.type = CONVERTED_TYPE + suffix
widget.serializeValue = () => {
// Prevent serializing the widget if we have no input linked
if (!node.inputs) {
return undefined
}
let node_input = node.inputs.find(i => i.widget?.name === widget.name)
if (!node_input || !node_input.link) {
return undefined
}
return widget.origSerializeValue
? widget.origSerializeValue()
: widget.value
}
// Hide any linked widgets, e.g. seed+seedControl
if (widget.linkedWidgets) {
for (const w of widget.linkedWidgets) {
hideWidget(node, w, ':' + widget.name)
}
}
}
function convertToInput (node, widget, config) {
hideWidget(node, widget)
const type = config[0]
// Add input and store widget config for creating on primitive node
const sz = node.size
node.addInput(widget.name, type, {
widget: { name: widget.name, [GET_CONFIG]: () => config }
})
for (const widget of node.widgets) {
widget.last_y += LiteGraph.NODE_SLOT_HEIGHT
}
// Restore original size but grow if needed
node.setSize([Math.max(sz[0], node.size[0]), Math.max(sz[1], node.size[1])])
}
export function smart_init () {
LGraphCanvas.prototype._createNodeForInput = function (
node,
widget,
inputNodeName,
inputNode_slot
) {
// console.log(node.pos)
// var widget = node.widgets.filter(w => w.name === node_widget_name)[0]
if (widget) {
// 如果有存在的,没有连线输出的,自动连,不新建
let input_node = null
Array.from(app.graph.findNodesByType(inputNodeName), n => {
var links = n.outputs.filter(o => o.name === inputNode_slot)[0].links
// console.log(links)
if (!links || links?.length === 0) input_node = n
})
// 新建
if (!input_node) {
input_node = LiteGraph.createNode(inputNodeName)
input_node.pos = [node.pos[0] - node.size[0] - 24, node.pos[1] - 48]
app.canvas.graph.add(input_node, false)
} else {
input_node.pos = [node.pos[0] - node.size[0] - 24, node.pos[1] - 48]
}
const config = getConfig.call(node, widget.name) ?? [
widget.type,
widget.options || {}
]
let node_slotType = config[0]
// 如果input没有,则创建
if (!node.inputs?.filter(inp => inp.name === widget.name)[0]||!node.inputs)
convertToInput(node, widget, config)
input_node.connectByType(inputNode_slot, node, node_slotType)
}
}
LGraphCanvas.prototype._createNodeForOutput = function (
node,
widget,
outputNodeName,
outputNode_slot
) {
if (widget) {
let output_node
Array.from(app.graph.findNodesByType(outputNodeName), n => {
var links = n.inputs.filter(o => o.name === outputNode_slot)[0].links
// console.log(links)
if (!links || links?.length === 0) output_node = n
})
console.log('output_node', output_node, widget.name)
if (!output_node) {
// 新建
output_node = LiteGraph.createNode(outputNodeName)
output_node.pos = [node.pos[0] + node.size[0] + 24, node.pos[1] - 48]
app.canvas.graph.add(output_node, false)
} else {
output_node.pos = [node.pos[0] + node.size[0] + 24, node.pos[1] - 48]
}
const config = getConfig.call(node, widget.name) ?? [
widget.type,
widget.options || {}
]
let node_slotType = config[0]
console.log(node_slotType, output_node, outputNode_slot)
let type = output_node.inputs.filter(
inp => inp.name == outputNode_slot
)[0].type
node.connectByType(node_slotType, output_node, type)
}
}
}
export function addSmartMenu (options, node) {
let sopts = []
for (const sc of smart_connect_config_input) {
// 有智能推荐,则出现
if (node.type === sc.node_type) {
// console.log('smart',node)
// 则出现 randomPrompt
// CLIPTextEncode 的widget ,name== 'text'
let node_widget_name = sc.node_widget_name
let widget = node.widgets.filter(w => w.name === node_widget_name)[0]
if (!widget) {
// 控件没有,则查找inputs
widget = node.inputs.filter(w => w.name === node_widget_name)[0]
}
let isLinkNull = true
// 如果input里已经有,但是link为空
if (node.inputs?.filter(inp => inp.name === node_widget_name)[0]) {
isLinkNull =
node.inputs.filter(inp => inp.name === node_widget_name)[0].link ===
null
}
if (widget && isLinkNull) {
sopts.push({
content: sc.inputNodeName.split('_')[0] + '➡️',
callback: () => {
LGraphCanvas.prototype._createNodeForInput(
node, //当前node
widget, //当前node里需要自动连线的widget
sc.inputNodeName, //作为input的node type
sc.inputNode_output_name // 作为input的node的outputs的name. the input slot type of the target node
)
}
})
}
}
}
for (const sc of smart_connect_config_output) {
if (node.type === sc.node_type) {
let node_output_name = sc.node_output_name
const widget = node.outputs.filter(w => w.name === node_output_name)[0]
let isLinkNull = true
// 如果output里 link为空
if (node.outputs?.filter(inp => inp.name === node_output_name)[0]) {
isLinkNull =
node.outputs.filter(inp => inp.name === node_output_name)[0].links
?.length === 0
if (!node.outputs.filter(inp => inp.name === node_output_name)[0].links)
isLinkNull = true
}
if (widget && isLinkNull) {
sopts.push({
content: '➡️' + sc.outputNodeName.split('_')[0],
callback: () => {
LGraphCanvas.prototype._createNodeForOutput(
node, //当前node
widget, //当前node里需要自动连线的widget
sc.outputNodeName, //作为input的node type
sc.outputNode_input_name // 作为input的node的outputs的name. the input slot type of the target node
)
}
})
}
}
}
if (sopts.length > 0) options = [...sopts, null, ...options]
return options
}
+1006 -218
View File
File diff suppressed because it is too large Load Diff
+311 -118
View File
@@ -1,10 +1,5 @@
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 { $el } from '../../../scripts/ui.js'
const getLocalData = key => {
let data = {}
@@ -47,122 +42,320 @@ 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)
}
}
}
})
const min_max = node => {
if(node.widgets){
const min_value = node.widgets.filter(w => w.name === 'min_value')[0]
const max_value = node.widgets.filter(w => w.name === 'max_value')[0]
const number = node.widgets.filter(w => w.name === 'number')[0]
if (number) {
number.options.min = min_value.value
number.options.max = max_value.value
number.value = Math.min(number.options.max, number.value)
number.value = Math.max(number.options.min, number.value)
}
if (min_value)
min_value.callback = e => {
number.options.min = e
number.value = e
}
if (max_value)
max_value.callback = e => {
number.options.max = e
number.value = e
}
}
}
app.registerExtension({
name: 'Mixlab.utils.FloatSlider',
async beforeRegisterNodeDef (nodeType, nodeData, app) {
if (nodeType.comfyClass == 'FloatSlider') {
const orig_nodeCreated = nodeType.prototype.onNodeCreated;
nodeType.prototype.onNodeCreated = function () {
orig_nodeCreated?.apply(this, arguments)
min_max(this)
}
}
},
async loadedGraphNode (node, app) {
if (node.type === 'FloatSlider') {
min_max(node)
}
}
})
app.registerExtension({
name: 'Mixlab.utils.IntNumber',
async beforeRegisterNodeDef (nodeType, nodeData, app) {
if (nodeType.comfyClass == 'IntNumber') {
const orig_nodeCreated = nodeType.prototype.onNodeCreated
nodeType.prototype.onNodeCreated = function () {
orig_nodeCreated?.apply(this, arguments)
min_max(this)
}
}
},
async loadedGraphNode (node, app) {
if (node.type === 'IntNumber') {
min_max(node)
}
}
})
app.registerExtension({
name: 'Mixlab.utils.TESTNODE_',
async beforeRegisterNodeDef (nodeType, nodeData, app) {
if (nodeType.comfyClass == 'TESTNODE_') {
const onExecuted = nodeType.prototype.onExecuted;
nodeType.prototype.onExecuted = function (message) {
onExecuted?.apply(this, arguments);
console.log('##',message)
};
}
},
})
+59 -30
View File
@@ -60,30 +60,7 @@ app.registerExtension({
type: inputData[0], // the type, CHEESE
name: inputName, // the name, slice
size: [128, 24], // a default size
draw (ctx, node, width, y) {
// // 绘制文件图标的函数
// function drawFileIcon () {
// // 清空画布
// // ctx.clearRect(0, 0, canvas.width, canvas.height)
// // 绘制文件外框
// ctx.fillStyle = '#000'
// ctx.fillRect(5, 5, 40, 40)
// // 绘制文件夹图标
// ctx.fillStyle = '#f00'
// ctx.fillRect(10, 15, 30, 20)
// // 绘制监听符号
// ctx.beginPath()
// ctx.arc(30, 35, 5, 0, 2 * Math.PI)
// ctx.fillStyle = '#00f'
// ctx.fill()
// }
// // 调用绘制函数
// drawFileIcon()
},
draw (ctx, node, width, y) {},
computeSize (...args) {
return [128, 24] // a method to compute the current size of the widget
},
@@ -124,11 +101,19 @@ app.registerExtension({
nodeType.prototype.onNodeCreated = function () {
orig_nodeCreated?.apply(this, arguments)
console.log('watch widtget', this.widgets)
// 虚拟的widget,用于更新节点,让其每次都运行
const widget = {
type: 'div',
name: 'seed',
draw (ctx, node, widget_width, y, widget_height) {}
}
this.addCustomWidget(widget)
const watcher = this.widgets.filter(w => w.name == 'watcher')[0]
watcher.callback = () => {
console.log('watcher', watcher.value)
if (watcher.value === 'enable') {
if (window._mixlab_watcher_t)
clearInterval(window._mixlab_watcher_t)
@@ -140,7 +125,7 @@ app.registerExtension({
window._mixlab_file_path_watcher = json.event_type
// widget.card.innerText = window._mixlab_file_path_watcher || ''
//运行
document.querySelector('#queue-button').click()
// document.querySelector('#queue-button').click()
}
})
}, 1000)
@@ -162,15 +147,59 @@ app.registerExtension({
window._mixlab_file_path_watcher = json.event_type
})
/*
Add the widget, make sure we clean up nicely, and we do not want to be serialized!
*/
// this.addCustomWidget(widget)
this.onRemoved = function () {
// widget.card.remove()
}
this.serialize_widgets = true
}
const onExecuted = nodeType.prototype.onExecuted
nodeType.prototype.onExecuted = function (message) {
onExecuted?.apply(this, arguments)
console.log(message)
try {
let seed = this.widgets.filter(w => w.name === 'seed')[0]
if (seed) {
if (!seed.value) seed.value = 0
seed.value += 1
}
} catch (error) {}
}
}
},
async loadedGraphNode (node, app) {
if (node.type === 'LoadImagesFromPath') {
const watcher = node.widgets.filter(w => w.name == 'watcher')[0]
if (watcher) {
if (watcher.value === 'enable') {
if (window._mixlab_watcher_t) clearInterval(window._mixlab_watcher_t)
window._mixlab_watcher_t = setInterval(() => {
// 上次路径填充
getConfig().then(json => {
console.log(json.event_type)
if (json.event_type != window._mixlab_file_path_watcher) {
window._mixlab_file_path_watcher = json.event_type
// widget.card.innerText = window._mixlab_file_path_watcher || ''
//运行
document.querySelector('#queue-button').click()
}
})
}, 1000)
} else {
if (window._mixlab_watcher_t) {
clearInterval(window._mixlab_watcher_t)
}
window._mixlab_watcher_t = null
}
}
try {
let seed = node.widgets.filter(w => w.name === 'seed')[0]
if (seed) {
if (!seed.value) seed.value = 0
seed.value += 1
}
} catch (error) {}
}
}
})
+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
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
+736
View File
@@ -0,0 +1,736 @@
{
"last_node_id": 29,
"last_link_id": 32,
"nodes": [
{
"id": 7,
"type": "CLIPTextEncode",
"pos": [
108,
316
],
"size": {
"0": 425.27801513671875,
"1": 180.6060791015625
},
"flags": {},
"order": 8,
"mode": 0,
"inputs": [
{
"name": "clip",
"type": "CLIP",
"link": 16
}
],
"outputs": [
{
"name": "CONDITIONING",
"type": "CONDITIONING",
"links": [
6
],
"slot_index": 0
}
],
"properties": {
"Node name for S&R": "CLIPTextEncode"
},
"widgets_values": [
"text, watermark"
]
},
{
"id": 18,
"type": "ControlNetApply",
"pos": [
485,
792
],
"size": {
"0": 317.4000244140625,
"1": 98
},
"flags": {},
"order": 10,
"mode": 0,
"inputs": [
{
"name": "conditioning",
"type": "CONDITIONING",
"link": 22,
"slot_index": 0
},
{
"name": "control_net",
"type": "CONTROL_NET",
"link": 18,
"slot_index": 1
},
{
"name": "image",
"type": "IMAGE",
"link": 26
}
],
"outputs": [
{
"name": "CONDITIONING",
"type": "CONDITIONING",
"links": [
21
],
"shape": 3,
"slot_index": 0
}
],
"properties": {
"Node name for S&R": "ControlNetApply"
},
"widgets_values": [
1
]
},
{
"id": 6,
"type": "CLIPTextEncode",
"pos": [
117,
95
],
"size": {
"0": 422.84503173828125,
"1": 164.31304931640625
},
"flags": {},
"order": 7,
"mode": 0,
"inputs": [
{
"name": "clip",
"type": "CLIP",
"link": 15
}
],
"outputs": [
{
"name": "CONDITIONING",
"type": "CONDITIONING",
"links": [
22
],
"slot_index": 0
}
],
"properties": {
"Node name for S&R": "CLIPTextEncode"
},
"widgets_values": [
"a future city,buiding,future,magic,under water"
]
},
{
"id": 15,
"type": "LoraLoader",
"pos": [
116,
-113
],
"size": {
"0": 315,
"1": 126
},
"flags": {},
"order": 3,
"mode": 0,
"inputs": [
{
"name": "model",
"type": "MODEL",
"link": 13
},
{
"name": "clip",
"type": "CLIP",
"link": 14
}
],
"outputs": [
{
"name": "MODEL",
"type": "MODEL",
"links": [
12,
23
],
"shape": 3,
"slot_index": 0
},
{
"name": "CLIP",
"type": "CLIP",
"links": [
15,
16
],
"shape": 3,
"slot_index": 1
}
],
"properties": {
"Node name for S&R": "LoraLoader"
},
"widgets_values": [
"lcm-lora-sdv1-5.safetensors",
1,
1
]
},
{
"id": 4,
"type": "CheckpointLoaderSimple",
"pos": [
-380,
185
],
"size": {
"0": 315,
"1": 98
},
"flags": {},
"order": 0,
"mode": 0,
"outputs": [
{
"name": "MODEL",
"type": "MODEL",
"links": [
13
],
"slot_index": 0
},
{
"name": "CLIP",
"type": "CLIP",
"links": [
14
],
"slot_index": 1
},
{
"name": "VAE",
"type": "VAE",
"links": [
8
],
"slot_index": 2
}
],
"properties": {
"Node name for S&R": "CheckpointLoaderSimple"
},
"widgets_values": [
"deliberate_v2.safetensors"
]
},
{
"id": 19,
"type": "DiffControlNetLoader",
"pos": [
40,
792
],
"size": {
"0": 367.8165283203125,
"1": 58.083831787109375
},
"flags": {},
"order": 6,
"mode": 0,
"inputs": [
{
"name": "model",
"type": "MODEL",
"link": 23,
"slot_index": 0
}
],
"outputs": [
{
"name": "CONTROL_NET",
"type": "CONTROL_NET",
"links": [
18
],
"shape": 3
}
],
"properties": {
"Node name for S&R": "DiffControlNetLoader"
},
"widgets_values": [
"control_v11f1p_sd15_depth.pth"
]
},
{
"id": 5,
"type": "EmptyLatentImage",
"pos": [
65,
573
],
"size": {
"0": 315,
"1": 106
},
"flags": {},
"order": 1,
"mode": 0,
"outputs": [
{
"name": "LATENT",
"type": "LATENT",
"links": [
2
],
"slot_index": 0
}
],
"properties": {
"Node name for S&R": "EmptyLatentImage"
},
"widgets_values": [
512,
512,
1
]
},
{
"id": 25,
"type": "LeReS-DepthMapPreprocessor",
"pos": [
47,
904
],
"size": {
"0": 369.6000061035156,
"1": 130
},
"flags": {},
"order": 4,
"mode": 0,
"inputs": [
{
"name": "image",
"type": "IMAGE",
"link": 31
}
],
"outputs": [
{
"name": "IMAGE",
"type": "IMAGE",
"links": [
26,
27
],
"shape": 3,
"slot_index": 0
}
],
"properties": {
"Node name for S&R": "LeReS-DepthMapPreprocessor"
},
"widgets_values": [
0.1,
0,
"disable",
512
]
},
{
"id": 3,
"type": "KSampler",
"pos": [
1100,
-97
],
"size": {
"0": 315,
"1": 262
},
"flags": {},
"order": 11,
"mode": 0,
"inputs": [
{
"name": "model",
"type": "MODEL",
"link": 12,
"slot_index": 0
},
{
"name": "positive",
"type": "CONDITIONING",
"link": 21
},
{
"name": "negative",
"type": "CONDITIONING",
"link": 6
},
{
"name": "latent_image",
"type": "LATENT",
"link": 2
}
],
"outputs": [
{
"name": "LATENT",
"type": "LATENT",
"links": [
7
],
"slot_index": 0
}
],
"properties": {
"Node name for S&R": "KSampler"
},
"widgets_values": [
170633013599955,
"fixed",
4,
1.6,
"lcm",
"simple",
1
]
},
{
"id": 8,
"type": "VAEDecode",
"pos": [
1100,
-241
],
"size": {
"0": 210,
"1": 46
},
"flags": {},
"order": 12,
"mode": 0,
"inputs": [
{
"name": "samples",
"type": "LATENT",
"link": 7
},
{
"name": "vae",
"type": "VAE",
"link": 8
}
],
"outputs": [
{
"name": "IMAGE",
"type": "IMAGE",
"links": [
28
],
"slot_index": 0
}
],
"properties": {
"Node name for S&R": "VAEDecode"
}
},
{
"id": 20,
"type": "PreviewImage",
"pos": [
492,
955
],
"size": {
"0": 526.9624633789062,
"1": 367.3833923339844
},
"flags": {},
"order": 9,
"mode": 0,
"inputs": [
{
"name": "images",
"type": "IMAGE",
"link": 27
}
],
"properties": {
"Node name for S&R": "PreviewImage"
}
},
{
"id": 26,
"type": "FloatingVideo",
"pos": [
1565,
-181
],
"size": [
315,
58
],
"flags": {},
"order": 13,
"mode": 0,
"inputs": [
{
"name": "images",
"type": "IMAGE",
"link": 28
}
],
"properties": {
"Node name for S&R": "FloatingVideo"
},
"widgets_values": [
null
]
},
{
"id": 28,
"type": "PreviewImage",
"pos": [
-241,
953
],
"size": {
"0": 210,
"1": 246
},
"flags": {},
"order": 5,
"mode": 0,
"inputs": [
{
"name": "images",
"type": "IMAGE",
"link": 32
}
],
"properties": {
"Node name for S&R": "PreviewImage"
}
},
{
"id": 29,
"type": "ScreenShare",
"pos": [
-639,
366
],
"size": [
315,
644
],
"flags": {},
"order": 2,
"mode": 0,
"outputs": [
{
"name": "IMAGE",
"type": "IMAGE",
"links": [
31,
32
],
"shape": 3,
"slot_index": 0
},
{
"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,
500,
null,
null,
null,
null
]
}
],
"links": [
[
2,
5,
0,
3,
3,
"LATENT"
],
[
6,
7,
0,
3,
2,
"CONDITIONING"
],
[
7,
3,
0,
8,
0,
"LATENT"
],
[
8,
4,
2,
8,
1,
"VAE"
],
[
12,
15,
0,
3,
0,
"MODEL"
],
[
13,
4,
0,
15,
0,
"MODEL"
],
[
14,
4,
1,
15,
1,
"CLIP"
],
[
15,
15,
1,
6,
0,
"CLIP"
],
[
16,
15,
1,
7,
0,
"CLIP"
],
[
18,
19,
0,
18,
1,
"CONTROL_NET"
],
[
21,
18,
0,
3,
1,
"CONDITIONING"
],
[
22,
6,
0,
18,
0,
"CONDITIONING"
],
[
23,
15,
0,
19,
0,
"MODEL"
],
[
26,
25,
0,
18,
2,
"IMAGE"
],
[
27,
25,
0,
20,
0,
"IMAGE"
],
[
28,
8,
0,
26,
0,
"IMAGE"
],
[
31,
29,
0,
25,
0,
"IMAGE"
],
[
32,
29,
0,
28,
0,
"IMAGE"
]
],
"groups": [],
"config": {},
"extra": {},
"version": 0.4
}
+59 -58
View File
@@ -1,6 +1,6 @@
{
"last_node_id": 23,
"last_link_id": 51,
"last_node_id": 22,
"last_link_id": 48,
"nodes": [
{
"id": 7,
@@ -105,7 +105,7 @@
{
"name": "text",
"type": "STRING",
"link": 51,
"link": 48,
"widget": {
"name": "text"
}
@@ -295,7 +295,7 @@
"Node name for S&R": "KSampler"
},
"widgets_values": [
1115769645491668,
644769503212755,
"randomize",
4,
1.6,
@@ -316,19 +316,47 @@
"1": 246
},
"flags": {},
"order": 7,
"order": 6,
"mode": 0,
"inputs": [
{
"name": "images",
"type": "IMAGE",
"link": 50
"link": 46
}
],
"properties": {
"Node name for S&R": "PreviewImage"
}
},
{
"id": 20,
"type": "FloatingVideo",
"pos": [
2041,
277
],
"size": [
315,
58
],
"flags": {},
"order": 12,
"mode": 0,
"inputs": [
{
"name": "images",
"type": "IMAGE",
"link": 41
}
],
"properties": {
"Node name for S&R": "FloatingVideo"
},
"widgets_values": [
null
]
},
{
"id": 6,
"type": "LoraLoader",
@@ -445,13 +473,13 @@
"1": 58
},
"flags": {},
"order": 6,
"order": 7,
"mode": 0,
"inputs": [
{
"name": "image",
"type": "IMAGE",
"link": 49
"link": 47
}
],
"outputs": [
@@ -473,44 +501,16 @@
]
},
{
"id": 20,
"type": "FloatingVideo",
"pos": [
1928,
295
],
"size": {
"0": 315,
"1": 58
},
"flags": {},
"order": 12,
"mode": 0,
"inputs": [
{
"name": "images",
"type": "IMAGE",
"link": 41
}
],
"properties": {
"Node name for S&R": "FloatingVideo"
},
"widgets_values": [
null
]
},
{
"id": 23,
"id": 22,
"type": "ScreenShare",
"pos": [
-65,
446
-111,
427
],
"size": [
315,
644
],
"size": {
"0": 315,
"1": 170
},
"flags": {},
"order": 3,
"mode": 0,
@@ -519,8 +519,8 @@
"name": "IMAGE",
"type": "IMAGE",
"links": [
49,
50
46,
47
],
"shape": 3,
"slot_index": 0
@@ -529,7 +529,7 @@
"name": "PROMPT",
"type": "STRING",
"links": [
51
48
],
"shape": 3,
"slot_index": 1
@@ -552,6 +552,7 @@
},
"widgets_values": [
null,
500,
null,
null,
null,
@@ -809,24 +810,24 @@
"CLIP"
],
[
49,
23,
0,
18,
0,
"IMAGE"
],
[
50,
23,
46,
22,
0,
2,
0,
"IMAGE"
],
[
51,
23,
47,
22,
0,
18,
0,
"IMAGE"
],
[
48,
22,
1,
8,
1,
+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
+722
View File
@@ -0,0 +1,722 @@
{
"last_node_id": 24,
"last_link_id": 26,
"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": 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)",
"a girl face,super,(Pop Art:1.26),(Black and White:1.26)"
]
},
{
"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": 130
},
"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": [
0,
0,
1,
1
]
},
{
"id": 22,
"type": "PromptSlide",
"pos": [
2364,
-294
],
"size": {
"0": 315,
"1": 106
},
"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,
null
]
},
{
"id": 21,
"type": "PromptSlide",
"pos": [
2360,
-544
],
"size": {
"0": 315,
"1": 106
},
"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,
null
]
},
{
"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",
""
]
},
{
"id": 11,
"type": "PreviewImage",
"pos": [
3449,
-102
],
"size": {
"0": 435.8727111816406,
"1": 511.8609619140625
},
"flags": {},
"order": 12,
"mode": 0,
"inputs": [
{
"name": "images",
"type": "IMAGE",
"link": 24
}
],
"properties": {
"Node name for S&R": "PreviewImage"
}
},
{
"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": [
24,
26
],
"slot_index": 0
}
],
"properties": {
"Node name for S&R": "VAEDecode"
}
},
{
"id": 24,
"type": "AppInfo",
"pos": [
3363.0014990624995,
551.2261418945309
],
"size": {
"0": 400,
"1": 344
},
"flags": {},
"order": 13,
"mode": 0,
"inputs": [
{
"name": "IMAGE",
"type": "IMAGE",
"link": 26
}
],
"properties": {
"Node name for S&R": "AppInfo"
},
"widgets_values": [
"Prompt-weight-test",
"21\n22\n18",
"11\n16",
"",
1,
"",
"https://",
"",
"enable",
2
]
}
],
"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"
],
[
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"
],
[
24,
10,
0,
11,
0,
"IMAGE"
],
[
26,
10,
0,
24,
0,
"IMAGE"
]
],
"groups": [],
"config": {},
"extra": {},
"version": 0.4
}
+719
View File
@@ -0,0 +1,719 @@
{
"last_node_id": 26,
"last_link_id": 34,
"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": 15,
"type": "EnhanceImage",
"pos": [
2591,
-2531
],
"size": {
"0": 315,
"1": 58
},
"flags": {},
"order": 12,
"mode": 0,
"inputs": [
{
"name": "image",
"type": "IMAGE",
"link": 34
}
],
"outputs": [
{
"name": "IMAGE",
"type": "IMAGE",
"links": [
18
],
"shape": 3,
"slot_index": 0
}
],
"properties": {
"Node name for S&R": "EnhanceImage"
},
"widgets_values": [
1.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": 130
},
"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": [
1,
0,
1,
1
]
},
{
"id": 23,
"type": "IntNumber",
"pos": [
299,
-2601
],
"size": {
"0": 315,
"1": 130
},
"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": [
1,
0,
1,
1
]
},
{
"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": [
52336089190516,
"randomize",
15,
6.9,
"euler",
"karras",
0.59
]
},
{
"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": 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": [
33,
34
],
"slot_index": 0
}
],
"properties": {
"Node name for S&R": "VAEDecode"
}
},
{
"id": 26,
"type": "AppInfo",
"pos": [
2134,
-2264
],
"size": {
"0": 400,
"1": 344.00006103515625
},
"flags": {},
"order": 11,
"mode": 0,
"inputs": [
{
"name": "LOGO",
"type": "IMAGE",
"link": 33
}
],
"properties": {
"Node name for S&R": "AppInfo"
},
"widgets_values": [
"Text-to-Image",
"4\n22\n23\n2\n\n\n",
"16",
"演示基本的文生图流程",
1,
"#comfyui-mixlab-nodes# ",
"https://",
"",
"enable",
1
]
},
{
"id": 14,
"type": "FloatSlider",
"pos": [
1007,
-2860
],
"size": {
"0": 315,
"1": 130
},
"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,
0,
1,
0.001
]
},
{
"id": 21,
"type": "EmptyLatentImage",
"pos": [
1004,
-2633
],
"size": {
"0": 315,
"1": 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
]
}
],
"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"
],
[
16,
14,
0,
1,
4,
"FLOAT"
],
[
18,
15,
0,
16,
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"
],
[
33,
6,
0,
26,
0,
"IMAGE"
],
[
34,
6,
0,
15,
0,
"IMAGE"
]
],
"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
}
+558
View File
@@ -0,0 +1,558 @@
{
"last_node_id": 69,
"last_link_id": 72,
"nodes": [
{
"id": 37,
"type": "CLIPTextEncode",
"pos": [
6705,
-216
],
"size": {
"0": 400,
"1": 200
},
"flags": {},
"order": 5,
"mode": 0,
"inputs": [
{
"name": "clip",
"type": "CLIP",
"link": 33
}
],
"outputs": [
{
"name": "CONDITIONING",
"type": "CONDITIONING",
"links": [
34
],
"shape": 3
}
],
"properties": {
"Node name for S&R": "CLIPTextEncode"
},
"widgets_values": [
"beautiful scenery nature glass bottle landscape, , purple galaxy bottle,"
]
},
{
"id": 5,
"type": "CLIPTextEncode",
"pos": [
6693,
61
],
"size": {
"0": 425.27801513671875,
"1": 180.6060791015625
},
"flags": {},
"order": 4,
"mode": 0,
"inputs": [
{
"name": "clip",
"type": "CLIP",
"link": 6
},
{
"name": "text",
"type": "STRING",
"link": 25,
"widget": {
"name": "text"
}
}
],
"outputs": [
{
"name": "CONDITIONING",
"type": "CONDITIONING",
"links": [
3
],
"slot_index": 0
}
],
"properties": {
"Node name for S&R": "CLIPTextEncode"
},
"widgets_values": [
"text, watermark"
]
},
{
"id": 3,
"type": "EmptyLatentImage",
"pos": [
6689,
306
],
"size": {
"0": 315,
"1": 106
},
"flags": {},
"order": 0,
"mode": 0,
"outputs": [
{
"name": "LATENT",
"type": "LATENT",
"links": [
4
],
"slot_index": 0
}
],
"properties": {
"Node name for S&R": "EmptyLatentImage"
},
"widgets_values": [
512,
512,
1
]
},
{
"id": 27,
"type": "EmbeddingPrompt",
"pos": [
6104,
23
],
"size": {
"0": 399.6408996582031,
"1": 82
},
"flags": {},
"order": 1,
"mode": 0,
"outputs": [
{
"name": "STRING",
"type": "STRING",
"links": [
25
],
"shape": 3
}
],
"properties": {
"Node name for S&R": "EmbeddingPrompt"
},
"widgets_values": [
"negative-embed-verybadimagenegative_v1.3",
1
]
},
{
"id": 67,
"type": "VAEDecode",
"pos": [
7551,
-184
],
"size": {
"0": 210,
"1": 46
},
"flags": {},
"order": 7,
"mode": 0,
"inputs": [
{
"name": "samples",
"type": "LATENT",
"link": 70
},
{
"name": "vae",
"type": "VAE",
"link": 69
}
],
"outputs": [
{
"name": "IMAGE",
"type": "IMAGE",
"links": [
71
],
"shape": 3
}
],
"properties": {
"Node name for S&R": "VAEDecode"
}
},
{
"id": 1,
"type": "KSampler",
"pos": [
7187,
-145
],
"size": {
"0": 315,
"1": 262
},
"flags": {},
"order": 6,
"mode": 0,
"inputs": [
{
"name": "model",
"type": "MODEL",
"link": 1
},
{
"name": "positive",
"type": "CONDITIONING",
"link": 34
},
{
"name": "negative",
"type": "CONDITIONING",
"link": 3
},
{
"name": "latent_image",
"type": "LATENT",
"link": 4
}
],
"outputs": [
{
"name": "LATENT",
"type": "LATENT",
"links": [
70
],
"slot_index": 0
}
],
"properties": {
"Node name for S&R": "KSampler"
},
"widgets_values": [
408562451564429,
"fixed",
20,
8,
"euler",
"normal",
1
]
},
{
"id": 61,
"type": "PromptImage",
"pos": [
7853,
-238
],
"size": [
465.6378949342379,
760.4568424013569
],
"flags": {},
"order": 8,
"mode": 0,
"inputs": [
{
"name": "images",
"type": "IMAGE",
"link": 71
},
{
"name": "prompts",
"type": "STRING",
"link": 72,
"widget": {
"name": "prompts"
}
}
],
"properties": {
"Node name for S&R": "PromptImage"
},
"widgets_values": [
"",
"disable",
{
"_images": [
[
{
"filename": "mixlab_PromptImage_0_00027_.png",
"subfolder": "",
"type": "output"
}
],
[
{
"filename": "mixlab_PromptImage_1_00028_.png",
"subfolder": "",
"type": "output"
}
],
[
{
"filename": "mixlab_PromptImage_2_00029_.png",
"subfolder": "",
"type": "output"
}
],
[
{
"filename": "mixlab_PromptImage_3_00030_.png",
"subfolder": "",
"type": "output"
}
],
[
{
"filename": "mixlab_PromptImage_4_00031_.png",
"subfolder": "",
"type": "output"
}
],
[
{
"filename": "mixlab_PromptImage_5_00032_.png",
"subfolder": "",
"type": "output"
}
]
],
"prompts": [
"512-inpainting-ema.safetensors",
"SSD-1B.safetensors",
"awportrait_v12.safetensors",
"cardosAnime_v20.safetensors",
"deliberate_v2.safetensors",
"gameIconInstitute_v40.safetensors"
]
}
]
},
{
"id": 2,
"type": "CheckpointLoaderSimple",
"pos": [
6105,
-139
],
"size": {
"0": 315,
"1": 98
},
"flags": {},
"order": 3,
"mode": 0,
"inputs": [
{
"name": "ckpt_name",
"type": [
"512-inpainting-ema.safetensors",
"SSD-1B.safetensors",
"awportrait_v12.safetensors",
"cardosAnime_v20.safetensors",
"deliberate_v2.safetensors",
"gameIconInstitute_v40.safetensors",
"illuminatiDiffusionV1_v11-unclip-h-fp16.safetensors",
"sd_xl_turbo_1.0_fp16.safetensors",
"svd.safetensors"
],
"link": 64,
"widget": {
"name": "ckpt_name"
}
}
],
"outputs": [
{
"name": "MODEL",
"type": "MODEL",
"links": [
1
],
"slot_index": 0
},
{
"name": "CLIP",
"type": "CLIP",
"links": [
6,
33
],
"slot_index": 1
},
{
"name": "VAE",
"type": "VAE",
"links": [
69
],
"slot_index": 2
}
],
"properties": {
"Node name for S&R": "CheckpointLoaderSimple"
},
"widgets_values": [
"deliberate_v2.safetensors"
]
},
{
"id": 56,
"type": "CkptNames_",
"pos": [
7860,
-494
],
"size": {
"0": 400,
"1": 200
},
"flags": {},
"order": 2,
"mode": 0,
"outputs": [
{
"name": "ckpt_names",
"type": "*",
"links": [
64,
72
],
"shape": 6,
"slot_index": 0
}
],
"properties": {
"Node name for S&R": "CkptNames_"
},
"widgets_values": [
"512-inpainting-ema.safetensors\nSSD-1B.safetensors\nawportrait_v12.safetensors\ncardosAnime_v20.safetensors\ndeliberate_v2.safetensors\ngameIconInstitute_v40.safetensors"
]
}
],
"links": [
[
1,
2,
0,
1,
0,
"MODEL"
],
[
3,
5,
0,
1,
2,
"CONDITIONING"
],
[
4,
3,
0,
1,
3,
"LATENT"
],
[
6,
2,
1,
5,
0,
"CLIP"
],
[
25,
27,
0,
5,
1,
"STRING"
],
[
33,
2,
1,
37,
0,
"CLIP"
],
[
34,
37,
0,
1,
1,
"CONDITIONING"
],
[
64,
56,
0,
2,
0,
[
"512-inpainting-ema.safetensors",
"SSD-1B.safetensors",
"awportrait_v12.safetensors",
"cardosAnime_v20.safetensors",
"deliberate_v2.safetensors",
"gameIconInstitute_v40.safetensors",
"illuminatiDiffusionV1_v11-unclip-h-fp16.safetensors",
"sd_xl_turbo_1.0_fp16.safetensors",
"svd.safetensors"
]
],
[
69,
2,
2,
67,
1,
"VAE"
],
[
70,
1,
0,
67,
0,
"LATENT"
],
[
71,
67,
0,
61,
0,
"IMAGE"
],
[
72,
56,
0,
61,
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
@@ -0,0 +1 @@
<?xml version="1.0" ?><svg id="Layer_1" style="enable-background:new 0 0 50 50;" version="1.1" viewBox="0 0 50 50" xml:space="preserve" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><g id="Layer_1_1_"><path d="M18.293,31.707h6.414l24-24l-6.414-6.414l-24,24V31.707z M45.879,7.707l-3.586,3.586l-3.586-3.586l3.586-3.586 L45.879,7.707z M20.293,26.121l17-17l3.586,3.586l-17,17h-3.586V26.121z"/><polygon points="43.293,19.707 41.293,19.707 41.293,46.707 3.293,46.707 3.293,8.707 31.293,8.707 31.293,6.707 1.293,6.707 1.293,48.707 43.293,48.707 "/></g></svg>

After

Width:  |  Height:  |  Size: 589 B

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