Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b289513c9d | ||
|
|
e56eee9dc6 | ||
|
|
a9165125b5 | ||
|
|
a9d77be4e7 | ||
|
|
b2e96ef256 | ||
|
|
55d5c7a58e | ||
|
|
fef673ade4 | ||
|
|
c0bd700407 | ||
|
|
8e6423dc30 | ||
|
|
e46945427d | ||
|
|
57bb837d35 | ||
|
|
99c3d53d8b | ||
|
|
4ab38a1f06 | ||
|
|
cea6d8029e | ||
|
|
e632401bc5 | ||
|
|
b521933ca3 | ||
|
|
748e335add | ||
|
|
8d74ce9fca | ||
|
|
7fc06c38c9 | ||
|
|
c2f99d072d | ||
|
|
e5132a2d7f | ||
|
|
ff0dee00a4 | ||
|
|
ca9a72f2a7 | ||
|
|
1ff6280992 | ||
|
|
a45a4413dd | ||
|
|
050911912f | ||
|
|
a025d1236c | ||
|
|
2ecd73d1f0 | ||
|
|
7b6b0115e8 | ||
|
|
3a30e79cfa | ||
|
|
5354c63777 | ||
|
|
110139fb28 | ||
|
|
c24dee9a5e | ||
|
|
fcc30e1894 | ||
|
|
528f997fc4 | ||
|
|
83a9fc6536 | ||
|
|
bfee9e4b07 | ||
|
|
dafd40026c | ||
|
|
ece384a3d0 | ||
|
|
c43cab93ba | ||
|
|
91654b973a | ||
|
|
8c9652bb78 | ||
|
|
eb5935be5f | ||
|
|
79f0bf36a1 | ||
|
|
5cf6ce2508 | ||
|
|
fdb4b5aab8 | ||
|
|
4f0ddab1b8 | ||
|
|
ee7da07a4e | ||
|
|
4526acbfa6 | ||
|
|
ee087ae50e | ||
|
|
e6873292a6 | ||
|
|
54b02fac6c | ||
|
|
cd55589499 | ||
|
|
d816711e01 |
@@ -0,0 +1,8 @@
|
||||
@echo off
|
||||
..\python_embeded\python.exe .\update.py ..\HanzoStudio\
|
||||
if exist update_new.py (
|
||||
move /y update_new.py update.py
|
||||
echo Running updater again since it got updated.
|
||||
..\python_embeded\python.exe .\update.py ..\HanzoStudio\ --skip_self_update
|
||||
)
|
||||
if "%~1"=="" pause
|
||||
@@ -47,7 +47,7 @@ def pull(repo, remote_name='origin', branch='master'):
|
||||
pygit2.option(pygit2.GIT_OPT_SET_OWNER_VALIDATION, 0)
|
||||
repo_path = str(sys.argv[1])
|
||||
repo = pygit2.Repository(repo_path)
|
||||
ident = pygit2.Signature('comfyui', 'comfy@ui')
|
||||
ident = pygit2.Signature('hanzo-studio', 'studio@hanzo.ai')
|
||||
try:
|
||||
print("stashing current changes") # noqa: T201
|
||||
repo.stash(ident)
|
||||
@@ -153,8 +153,8 @@ if not os.path.exists(req_path) or not files_equal(repo_req_path, req_path):
|
||||
pass
|
||||
|
||||
|
||||
stable_update_script = os.path.join(repo_path, ".ci/update_windows/update_comfyui_stable.bat")
|
||||
stable_update_script_to = os.path.join(cur_path, "update_comfyui_stable.bat")
|
||||
stable_update_script = os.path.join(repo_path, ".ci/update_windows/update_studio_stable.bat")
|
||||
stable_update_script_to = os.path.join(cur_path, "update_studio_stable.bat")
|
||||
|
||||
try:
|
||||
if not file_size(stable_update_script_to) > 10:
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
@echo off
|
||||
..\python_embeded\python.exe .\update.py ..\ComfyUI\
|
||||
if exist update_new.py (
|
||||
move /y update_new.py update.py
|
||||
echo Running updater again since it got updated.
|
||||
..\python_embeded\python.exe .\update.py ..\ComfyUI\ --skip_self_update
|
||||
)
|
||||
if "%~1"=="" pause
|
||||
@@ -1,8 +0,0 @@
|
||||
@echo off
|
||||
..\python_embeded\python.exe .\update.py ..\ComfyUI\ --stable
|
||||
if exist update_new.py (
|
||||
move /y update_new.py update.py
|
||||
echo Running updater again since it got updated.
|
||||
..\python_embeded\python.exe .\update.py ..\ComfyUI\ --skip_self_update --stable
|
||||
)
|
||||
if "%~1"=="" pause
|
||||
@@ -0,0 +1,8 @@
|
||||
@echo off
|
||||
..\python_embeded\python.exe .\update.py ..\HanzoStudio\ --stable
|
||||
if exist update_new.py (
|
||||
move /y update_new.py update.py
|
||||
echo Running updater again since it got updated.
|
||||
..\python_embeded\python.exe .\update.py ..\HanzoStudio\ --skip_self_update --stable
|
||||
)
|
||||
if "%~1"=="" pause
|
||||
@@ -7,22 +7,19 @@ If you have a AMD gpu:
|
||||
|
||||
run_amd_gpu.bat
|
||||
|
||||
If you have memory issues you can try disabling the smart memory management by running comfyui with:
|
||||
If you have memory issues you can try disabling the smart memory management by running Hanzo Studio with:
|
||||
|
||||
run_amd_gpu_disable_smart_memory.bat
|
||||
|
||||
IF YOU GET A RED ERROR IN THE UI MAKE SURE YOU HAVE A MODEL/CHECKPOINT IN: ComfyUI\models\checkpoints
|
||||
IF YOU GET A RED ERROR IN THE UI MAKE SURE YOU HAVE A MODEL/CHECKPOINT IN: HanzoStudio\models\checkpoints
|
||||
|
||||
You can download the stable diffusion XL one from: https://huggingface.co/stabilityai/stable-diffusion-xl-base-1.0/blob/main/sd_xl_base_1.0_0.9vae.safetensors
|
||||
|
||||
|
||||
RECOMMENDED WAY TO UPDATE:
|
||||
To update the ComfyUI code: update\update_comfyui.bat
|
||||
To update the Hanzo Studio code: update\update_studio.bat
|
||||
|
||||
|
||||
TO SHARE MODELS BETWEEN COMFYUI AND ANOTHER UI:
|
||||
In the ComfyUI directory you will find a file: extra_model_paths.yaml.example
|
||||
TO SHARE MODELS BETWEEN HANZO STUDIO AND ANOTHER UI:
|
||||
In the HanzoStudio directory you will find a file: extra_model_paths.yaml.example
|
||||
Rename this file to: extra_model_paths.yaml and edit it with your favorite text editor.
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
.\python_embeded\python.exe -s ComfyUI\main.py --windows-standalone-build
|
||||
.\python_embeded\python.exe -s HanzoStudio\main.py --windows-standalone-build
|
||||
pause
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
.\python_embeded\python.exe -s ComfyUI\main.py --windows-standalone-build --disable-smart-memory
|
||||
.\python_embeded\python.exe -s HanzoStudio\main.py --windows-standalone-build --disable-smart-memory
|
||||
pause
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
.\python_embeded\python.exe -s ComfyUI\main.py --windows-standalone-build --fast
|
||||
.\python_embeded\python.exe -s HanzoStudio\main.py --windows-standalone-build --fast
|
||||
pause
|
||||
|
||||
@@ -15,20 +15,20 @@ run_cpu.bat
|
||||
|
||||
|
||||
|
||||
IF YOU GET A RED ERROR IN THE UI MAKE SURE YOU HAVE A MODEL/CHECKPOINT IN: ComfyUI\models\checkpoints
|
||||
IF YOU GET A RED ERROR IN THE UI MAKE SURE YOU HAVE A MODEL/CHECKPOINT IN: HanzoStudio\models\checkpoints
|
||||
|
||||
You can download the stable diffusion 1.5 one from: https://huggingface.co/Comfy-Org/stable-diffusion-v1-5-archive/blob/main/v1-5-pruned-emaonly-fp16.safetensors
|
||||
You can download the stable diffusion 1.5 one from: https://huggingface.co/hanzoai/stable-diffusion-v1-5-archive/blob/main/v1-5-pruned-emaonly-fp16.safetensors
|
||||
|
||||
|
||||
RECOMMENDED WAY TO UPDATE:
|
||||
To update the ComfyUI code: update\update_comfyui.bat
|
||||
To update the Hanzo Studio code: update\update_studio.bat
|
||||
|
||||
|
||||
|
||||
To update ComfyUI with the python dependencies, note that you should ONLY run this if you have issues with python dependencies.
|
||||
update\update_comfyui_and_python_dependencies.bat
|
||||
To update Hanzo Studio with the python dependencies, note that you should ONLY run this if you have issues with python dependencies.
|
||||
update\update_studio_and_python_dependencies.bat
|
||||
|
||||
|
||||
TO SHARE MODELS BETWEEN COMFYUI AND ANOTHER UI:
|
||||
In the ComfyUI directory you will find a file: extra_model_paths.yaml.example
|
||||
TO SHARE MODELS BETWEEN HANZO STUDIO AND ANOTHER UI:
|
||||
In the HanzoStudio directory you will find a file: extra_model_paths.yaml.example
|
||||
Rename this file to: extra_model_paths.yaml and edit it with your favorite text editor.
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
..\python_embeded\python.exe -s ..\ComfyUI\main.py --windows-standalone-build --disable-api-nodes
|
||||
echo If you see this and ComfyUI did not start try updating your Nvidia Drivers to the latest. If you get a c10.dll error you need to install vc redist that you can find: https://aka.ms/vc14/vc_redist.x64.exe
|
||||
..\python_embeded\python.exe -s ..\HanzoStudio\main.py --windows-standalone-build --disable-api-nodes
|
||||
echo If you see this and Hanzo Studio did not start try updating your Nvidia Drivers to the latest. If you get a c10.dll error you need to install vc redist that you can find: https://aka.ms/vc14/vc_redist.x64.exe
|
||||
pause
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
.\python_embeded\python.exe -s ComfyUI\main.py --cpu --windows-standalone-build
|
||||
.\python_embeded\python.exe -s HanzoStudio\main.py --cpu --windows-standalone-build
|
||||
pause
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
.\python_embeded\python.exe -s ComfyUI\main.py --windows-standalone-build
|
||||
echo If you see this and ComfyUI did not start try updating your Nvidia Drivers to the latest. If you get a c10.dll error you need to install vc redist that you can find: https://aka.ms/vc14/vc_redist.x64.exe
|
||||
.\python_embeded\python.exe -s HanzoStudio\main.py --windows-standalone-build
|
||||
echo If you see this and Hanzo Studio did not start try updating your Nvidia Drivers to the latest. If you get a c10.dll error you need to install vc redist that you can find: https://aka.ms/vc14/vc_redist.x64.exe
|
||||
pause
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
.\python_embeded\python.exe -s ComfyUI\main.py --windows-standalone-build --fast fp16_accumulation
|
||||
echo If you see this and ComfyUI did not start try updating your Nvidia Drivers to the latest. If you get a c10.dll error you need to install vc redist that you can find: https://aka.ms/vc14/vc_redist.x64.exe
|
||||
.\python_embeded\python.exe -s HanzoStudio\main.py --windows-standalone-build --fast fp16_accumulation
|
||||
echo If you see this and Hanzo Studio did not start try updating your Nvidia Drivers to the latest. If you get a c10.dll error you need to install vc redist that you can find: https://aka.ms/vc14/vc_redist.x64.exe
|
||||
pause
|
||||
|
||||
@@ -26,7 +26,7 @@ reviews:
|
||||
enable_prompt_for_ai_agents: true
|
||||
|
||||
path_filters:
|
||||
- "!comfy_api_nodes/apis/**"
|
||||
- "!studio_api_nodes/apis/**"
|
||||
- "!**/generated/*.pyi"
|
||||
- "!.ci/**"
|
||||
- "!script_examples/**"
|
||||
@@ -44,26 +44,26 @@ reviews:
|
||||
only due to whitespace or structural reformatting (e.g., removing a `with:` block),
|
||||
treat it as unchanged. Contributors should not feel obligated to address
|
||||
pre-existing issues outside the scope of their contribution.
|
||||
- path: "comfy/**"
|
||||
- path: "studio/**"
|
||||
instructions: |
|
||||
Core ML/diffusion engine. Focus on:
|
||||
- Backward compatibility (breaking changes affect all custom nodes)
|
||||
- Memory management and GPU resource handling
|
||||
- Performance implications in hot paths
|
||||
- Thread safety for concurrent execution
|
||||
- path: "comfy_api_nodes/**"
|
||||
- path: "studio_api_nodes/**"
|
||||
instructions: |
|
||||
Third-party API integration nodes. Focus on:
|
||||
- No hardcoded API keys or secrets
|
||||
- Proper error handling for API failures (timeouts, rate limits, auth errors)
|
||||
- Correct Pydantic model usage
|
||||
- Security of user data passed to external APIs
|
||||
- path: "comfy_extras/**"
|
||||
- path: "studio_extras/**"
|
||||
instructions: |
|
||||
Community-contributed extra nodes. Focus on:
|
||||
- Consistency with node patterns (INPUT_TYPES, RETURN_TYPES, FUNCTION, CATEGORY)
|
||||
- No breaking changes to existing node interfaces
|
||||
- path: "comfy_execution/**"
|
||||
- path: "studio_execution/**"
|
||||
instructions: |
|
||||
Execution engine (graph execution, caching, jobs). Focus on:
|
||||
- Caching correctness
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
models/
|
||||
output/
|
||||
input/
|
||||
custom_nodes/*
|
||||
# ...but the pack manifest must reach the build context so the image can vendor
|
||||
# the packs reproducibly (scripts/install_custom_nodes.sh clones them at build).
|
||||
# Local clones stay excluded so the image builds them fresh from the manifest.
|
||||
!custom_nodes/hanzo-packs.txt
|
||||
user/
|
||||
temp/
|
||||
orgs/
|
||||
.git/
|
||||
.github/
|
||||
__pycache__/
|
||||
*.pyc
|
||||
*.pyo
|
||||
*.log
|
||||
*.egg-info/
|
||||
.venv/
|
||||
venv/
|
||||
.mypy_cache/
|
||||
.pytest_cache/
|
||||
.ruff_cache/
|
||||
tests/
|
||||
tests-unit/
|
||||
scripts/*
|
||||
# ...except the pack installer, which the image build invokes.
|
||||
!scripts/install_custom_nodes.sh
|
||||
notebooks/
|
||||
*.swp
|
||||
.env
|
||||
.env.*
|
||||
@@ -1,5 +1,5 @@
|
||||
name: Bug Report
|
||||
description: "Something is broken inside of ComfyUI. (Do not use this if you're just having issues and need help, or if the issue relates to a custom node)"
|
||||
description: "Something is broken inside of Hanzo Studio. (Do not use this if you're just having issues and need help, or if the issue relates to a custom node)"
|
||||
labels: ["Potential Bug"]
|
||||
body:
|
||||
- type: markdown
|
||||
@@ -7,23 +7,23 @@ body:
|
||||
value: |
|
||||
Before submitting a **Bug Report**, please ensure the following:
|
||||
|
||||
- **1:** You are running the latest version of ComfyUI.
|
||||
- **2:** You have your ComfyUI logs and relevant workflow on hand and will post them in this bug report.
|
||||
- **1:** You are running the latest version of Hanzo Studio.
|
||||
- **2:** You have your Hanzo Studio logs and relevant workflow on hand and will post them in this bug report.
|
||||
- **3:** You confirmed that the bug is not caused by a custom node. You can disable all custom nodes by passing
|
||||
`--disable-all-custom-nodes` command line argument. If you have custom node try updating them to the latest version.
|
||||
- **4:** This is an actual bug in ComfyUI, not just a support question. A bug is when you can specify exact
|
||||
- **4:** This is an actual bug in Hanzo Studio, not just a support question. A bug is when you can specify exact
|
||||
steps to replicate what went wrong and others will be able to repeat your steps and see the same issue happen.
|
||||
|
||||
## Very Important
|
||||
|
||||
Please make sure that you post ALL your ComfyUI logs in the bug report. A bug report without logs will likely be ignored.
|
||||
Please make sure that you post ALL your Hanzo Studio logs in the bug report. A bug report without logs will likely be ignored.
|
||||
- type: checkboxes
|
||||
id: custom-nodes-test
|
||||
attributes:
|
||||
label: Custom Node Testing
|
||||
description: Please confirm you have tried to reproduce the issue with all custom nodes disabled.
|
||||
options:
|
||||
- label: I have tried disabling custom nodes and the issue persists (see [how to disable custom nodes](https://docs.comfy.org/troubleshooting/custom-node-issues#step-1%3A-test-with-all-custom-nodes-disabled) if you need help)
|
||||
- label: I have tried disabling custom nodes and the issue persists (see [how to disable custom nodes](https://docs.hanzo.ai/troubleshooting/custom-node-issues#step-1%3A-test-with-all-custom-nodes-disabled) if you need help)
|
||||
required: false
|
||||
- type: textarea
|
||||
attributes:
|
||||
@@ -40,7 +40,7 @@ body:
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Steps to Reproduce
|
||||
description: "Describe how to reproduce the issue. Please be sure to attach a workflow JSON or PNG, ideally one that doesn't require custom nodes to test. If the bug open happens when certain custom nodes are used, most likely that custom node is what has the bug rather than ComfyUI, in which case it should be reported to the node's author."
|
||||
description: "Describe how to reproduce the issue. Please be sure to attach a workflow JSON or PNG, ideally one that doesn't require custom nodes to test. If the bug open happens when certain custom nodes are used, most likely that custom node is what has the bug rather than Hanzo Studio, in which case it should be reported to the node's author."
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
|
||||
@@ -1,11 +1,8 @@
|
||||
blank_issues_enabled: true
|
||||
contact_links:
|
||||
- name: ComfyUI Frontend Issues
|
||||
url: https://github.com/Comfy-Org/ComfyUI_frontend/issues
|
||||
about: Issues related to the ComfyUI frontend (display issues, user interaction bugs), please go to the frontend repo to file the issue
|
||||
- name: ComfyUI Matrix Space
|
||||
url: https://app.element.io/#/room/%23comfyui_space%3Amatrix.org
|
||||
about: The ComfyUI Matrix Space is available for support and general discussion related to ComfyUI (Matrix is like Discord but open source).
|
||||
- name: Comfy Org Discord
|
||||
url: https://discord.gg/comfyorg
|
||||
about: The Comfy Org Discord is available for support and general discussion related to ComfyUI.
|
||||
- name: Hanzo Studio Frontend Issues
|
||||
url: https://github.com/hanzoai/studio/issues
|
||||
about: Issues related to the Hanzo Studio frontend (display issues, user interaction bugs).
|
||||
- name: Hanzo AI Discord
|
||||
url: https://discord.gg/hanzoai
|
||||
about: The Hanzo AI Discord is available for support and general discussion related to Hanzo Studio.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
name: Feature Request
|
||||
description: "You have an idea for something new you would like to see added to ComfyUI's core."
|
||||
description: "You have an idea for something new you would like to see added to Hanzo Studio's core."
|
||||
labels: [ "Feature" ]
|
||||
body:
|
||||
- type: markdown
|
||||
@@ -7,11 +7,11 @@ body:
|
||||
value: |
|
||||
Before submitting a **Feature Request**, please ensure the following:
|
||||
|
||||
**1:** You are running the latest version of ComfyUI.
|
||||
**1:** You are running the latest version of Hanzo Studio.
|
||||
**2:** You have looked to make sure there is not already a feature that does what you need, and there is not already a Feature Request listed for the same idea.
|
||||
**3:** This is something that makes sense to add to ComfyUI Core, and wouldn't make more sense as a custom node.
|
||||
**3:** This is something that makes sense to add to Hanzo Studio Core, and wouldn't make more sense as a custom node.
|
||||
|
||||
If unsure, ask on the [ComfyUI Matrix Space](https://app.element.io/#/room/%23comfyui_space%3Amatrix.org) or the [Comfy Org Discord](https://discord.gg/comfyorg) first.
|
||||
If unsure, ask on the [Hanzo AI Discord](https://discord.gg/hanzoai) first.
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Feature Idea
|
||||
|
||||
@@ -7,17 +7,17 @@ body:
|
||||
value: |
|
||||
Before submitting a **User Report** issue, please ensure the following:
|
||||
|
||||
**1:** You are running the latest version of ComfyUI.
|
||||
**1:** You are running the latest version of Hanzo Studio.
|
||||
**2:** You have made an effort to find public answers to your question before asking here. In other words, you googled it first, and scrolled through recent help topics.
|
||||
|
||||
If unsure, ask on the [ComfyUI Matrix Space](https://app.element.io/#/room/%23comfyui_space%3Amatrix.org) or the [Comfy Org Discord](https://discord.gg/comfyorg) first.
|
||||
If unsure, ask on the [Hanzo AI Discord](https://discord.gg/hanzoai) first.
|
||||
- type: checkboxes
|
||||
id: custom-nodes-test
|
||||
attributes:
|
||||
label: Custom Node Testing
|
||||
description: Please confirm you have tried to reproduce the issue with all custom nodes disabled.
|
||||
options:
|
||||
- label: I have tried disabling custom nodes and the issue persists (see [how to disable custom nodes](https://docs.comfy.org/troubleshooting/custom-node-issues#step-1%3A-test-with-all-custom-nodes-disabled) if you need help)
|
||||
- label: I have tried disabling custom nodes and the issue persists (see [how to disable custom nodes](https://docs.hanzo.ai/troubleshooting/custom-node-issues#step-1%3A-test-with-all-custom-nodes-disabled) if you need help)
|
||||
required: false
|
||||
- type: textarea
|
||||
attributes:
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="1280" height="640" viewBox="0 0 1280 640" role="img" aria-label="studio">
|
||||
<rect width="1280" height="640" fill="#0A0A0A"/>
|
||||
<svg x="96" y="215" width="210" height="210" viewBox="0 0 67 67"><path d="M22.21 67V44.6369H0V67H22.21Z" fill="#fff"/><path d="M66.7038 22.3184H22.2534L0.0878906 44.6367H44.4634L66.7038 22.3184Z" fill="#fff"/><path d="M22.21 0H0V22.3184H22.21V0Z" fill="#fff"/><path d="M66.7198 0H44.5098V22.3184H66.7198V0Z" fill="#fff"/><path d="M66.7198 67V44.6369H44.5098V67H66.7198Z" fill="#fff"/></svg>
|
||||
<text x="378" y="276" font-family="Inter,system-ui,-apple-system,sans-serif" font-size="78" font-weight="800" letter-spacing="-2" fill="#ffffff">studio</text>
|
||||
<text x="378" y="322" font-family="Inter,system-ui,sans-serif" font-size="30" fill="#ffffff" opacity=".66">Hanzo Studio — Visual AI Engine</text>
|
||||
<rect x="378" y="338" width="806" height="3" rx="1.5" fill="#ffffff" opacity=".9"/>
|
||||
<text x="378" y="390" font-family="Inter,system-ui,sans-serif" font-size="24" font-weight="600" fill="#ffffff" opacity=".5">github.com/hanzoai</text>
|
||||
<text x="1184" y="390" text-anchor="end" font-family="Inter,system-ui,sans-serif" font-size="24" font-weight="600" fill="#ffffff" opacity=".5">hanzo.ai</text>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.2 KiB |
@@ -4,7 +4,7 @@ on:
|
||||
pull_request_target:
|
||||
types: [opened, reopened, synchronize, ready_for_review]
|
||||
paths:
|
||||
- 'comfy_api_nodes/**' # only run if these files changed
|
||||
- 'studio_api_nodes/**' # only run if these files changed
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
# Canonical CI/CD — imports the shared hanzoai/ci reusable workflow, which
|
||||
# reads hanzo.yml (images + test + deploy + kms). No per-repo build logic,
|
||||
# no GitHub-hosted runners (defaults to the Hanzo cloud arc pool).
|
||||
name: CI/CD
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
tags: ['v*']
|
||||
pull_request:
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
cicd:
|
||||
uses: hanzoai/ci/.github/workflows/build.yml@v1
|
||||
# Target the Hanzo arc scale set by its installation name. ARC scale sets
|
||||
# are matched by name, not by the [self-hosted,linux,amd64] label triple
|
||||
# that ci@v1 defaults to — without this override the job never gets a
|
||||
# runner and sits queued.
|
||||
with:
|
||||
runner: '["hanzo-build-linux-amd64"]'
|
||||
secrets: inherit
|
||||
@@ -1,5 +1,5 @@
|
||||
# This is the GitHub Workflow that drives full-GPU-enabled tests of pull requests to ComfyUI, when the 'Run-CI-Test' label is added
|
||||
# Results are reported as checkmarks on the commits, as well as onto https://ci.comfy.org/
|
||||
# This is the GitHub Workflow that drives full-GPU-enabled tests of pull requests to Hanzo Studio, when the 'Run-CI-Test' label is added
|
||||
# Results are reported as checkmarks on the commits, as well as onto https://ci.hanzo.ai/
|
||||
name: Pull Request CI Workflow Runs
|
||||
on:
|
||||
pull_request_target:
|
||||
@@ -34,7 +34,7 @@ jobs:
|
||||
python_version: ${{ matrix.python_version }}
|
||||
torch_version: ${{ matrix.torch_version }}
|
||||
google_credentials: ${{ secrets.GCS_SERVICE_ACCOUNT_JSON }}
|
||||
comfyui_flags: ${{ matrix.flags }}
|
||||
studio_flags: ${{ matrix.flags }}
|
||||
use_prior_commit: 'true'
|
||||
comment:
|
||||
if: ${{ github.event.label.name == 'Run-CI-Test' }}
|
||||
@@ -49,5 +49,5 @@ jobs:
|
||||
issue_number: context.issue.number,
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
body: '(Automated Bot Message) CI Tests are running, you can view the results at https://ci.comfy.org/?branch=${{ github.event.pull_request.number }}%2Fmerge'
|
||||
body: '(Automated Bot Message) CI Tests are running, you can view the results at https://ci.hanzo.ai/?branch=${{ github.event.pull_request.number }}%2Fmerge'
|
||||
})
|
||||
|
||||
@@ -126,7 +126,7 @@ jobs:
|
||||
--arg release_tag "$RELEASE_TAG" \
|
||||
--arg release_url "$RELEASE_URL" \
|
||||
'{
|
||||
event_type: "comfyui_release_published",
|
||||
event_type: "studio_release_published",
|
||||
client_payload: {
|
||||
release_tag: $release_tag,
|
||||
release_url: $release_url
|
||||
@@ -138,7 +138,7 @@ jobs:
|
||||
-H "Accept: application/vnd.github+json" \
|
||||
-H "Content-Type: application/json" \
|
||||
-H "Authorization: Bearer ${DISPATCH_TOKEN}" \
|
||||
https://api.github.com/repos/Comfy-Org/desktop/dispatches \
|
||||
https://api.github.com/repos/hanzoai/studio-desktop/dispatches \
|
||||
-d "$PAYLOAD"
|
||||
|
||||
echo "✅ Dispatched ComfyUI release ${RELEASE_TAG} to Comfy-Org/desktop"
|
||||
echo "✅ Dispatched Hanzo Studio release ${RELEASE_TAG} to hanzoai/studio-desktop"
|
||||
|
||||
@@ -16,8 +16,11 @@ jobs:
|
||||
with:
|
||||
python-version: 3.x
|
||||
|
||||
- name: Install uv
|
||||
run: curl -LsSf https://astral.sh/uv/install.sh | sh
|
||||
|
||||
- name: Install Ruff
|
||||
run: pip install ruff
|
||||
run: uv pip install --system ruff
|
||||
|
||||
- name: Run Ruff
|
||||
run: ruff check .
|
||||
@@ -35,14 +38,16 @@ jobs:
|
||||
with:
|
||||
python-version: '3.12'
|
||||
|
||||
- name: Install uv
|
||||
run: curl -LsSf https://astral.sh/uv/install.sh | sh
|
||||
|
||||
- name: Install requirements
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cpu
|
||||
pip install -r requirements.txt
|
||||
uv pip install --system torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cpu
|
||||
uv pip install --system -r requirements.txt
|
||||
|
||||
- name: Install Pylint
|
||||
run: pip install pylint
|
||||
run: uv pip install --system pylint
|
||||
|
||||
- name: Run Pylint
|
||||
run: pylint comfy_api_nodes
|
||||
run: pylint studio_api_nodes
|
||||
|
||||
@@ -76,7 +76,7 @@ on:
|
||||
default: true
|
||||
|
||||
jobs:
|
||||
package_comfy_windows:
|
||||
package_studio_windows:
|
||||
permissions:
|
||||
contents: "write"
|
||||
packages: "write"
|
||||
@@ -93,12 +93,12 @@ jobs:
|
||||
with:
|
||||
path: |
|
||||
${{ inputs.cache_tag }}_python_deps.tar
|
||||
update_comfyui_and_python_dependencies.bat
|
||||
update_studio_and_python_dependencies.bat
|
||||
key: ${{ runner.os }}-build-${{ inputs.cache_tag }}-${{ inputs.python_minor }}
|
||||
- shell: bash
|
||||
run: |
|
||||
mv ${{ inputs.cache_tag }}_python_deps.tar ../
|
||||
mv update_comfyui_and_python_dependencies.bat ../
|
||||
mv update_studio_and_python_dependencies.bat ../
|
||||
cd ..
|
||||
tar xf ${{ inputs.cache_tag }}_python_deps.tar
|
||||
pwd
|
||||
@@ -107,7 +107,7 @@ jobs:
|
||||
- shell: bash
|
||||
run: |
|
||||
cd ..
|
||||
cp -r ComfyUI ComfyUI_copy
|
||||
cp -r HanzoStudio HanzoStudio_copy
|
||||
curl https://www.python.org/ftp/python/3.${{ inputs.python_minor }}.${{ inputs.python_patch }}/python-3.${{ inputs.python_minor }}.${{ inputs.python_patch }}-embed-amd64.zip -o python_embeded.zip
|
||||
unzip python_embeded.zip -d python_embeded
|
||||
cd python_embeded
|
||||
@@ -117,11 +117,11 @@ jobs:
|
||||
./python.exe get-pip.py
|
||||
./python.exe -s -m pip install ../${{ inputs.cache_tag }}_python_deps/*
|
||||
|
||||
grep comfy ../ComfyUI/requirements.txt > ./requirements_comfyui.txt
|
||||
./python.exe -s -m pip install -r requirements_comfyui.txt
|
||||
rm requirements_comfyui.txt
|
||||
grep -E "comfyui-frontend-package|comfyui-workflow-templates|comfyui-embedded-docs|comfy-kitchen|comfy-aimdo" ../HanzoStudio/requirements.txt > ./requirements_studio.txt
|
||||
./python.exe -s -m pip install -r requirements_studio.txt
|
||||
rm requirements_studio.txt
|
||||
|
||||
sed -i '1i../ComfyUI' ./python3${{ inputs.python_minor }}._pth
|
||||
sed -i '1i../HanzoStudio' ./python3${{ inputs.python_minor }}._pth
|
||||
|
||||
if test -f ./Lib/site-packages/torch/lib/dnnl.lib; then
|
||||
rm ./Lib/site-packages/torch/lib/dnnl.lib #I don't think this is actually used and I need the space
|
||||
@@ -131,40 +131,40 @@ jobs:
|
||||
|
||||
cd ..
|
||||
|
||||
git clone --depth 1 https://github.com/comfyanonymous/taesd
|
||||
cp taesd/*.safetensors ./ComfyUI_copy/models/vae_approx/
|
||||
git clone --depth 1 https://github.com/hanzoai/taesd
|
||||
cp taesd/*.safetensors ./HanzoStudio_copy/models/vae_approx/
|
||||
|
||||
mkdir ComfyUI_windows_portable
|
||||
mv python_embeded ComfyUI_windows_portable
|
||||
mv ComfyUI_copy ComfyUI_windows_portable/ComfyUI
|
||||
mkdir HanzoStudio_windows_portable
|
||||
mv python_embeded HanzoStudio_windows_portable
|
||||
mv HanzoStudio_copy HanzoStudio_windows_portable/HanzoStudio
|
||||
|
||||
cd ComfyUI_windows_portable
|
||||
cd HanzoStudio_windows_portable
|
||||
|
||||
mkdir update
|
||||
cp -r ComfyUI/.ci/update_windows/* ./update/
|
||||
cp -r ComfyUI/.ci/windows_${{ inputs.rel_name }}_base_files/* ./
|
||||
cp ../update_comfyui_and_python_dependencies.bat ./update/
|
||||
cp -r HanzoStudio/.ci/update_windows/* ./update/
|
||||
cp -r HanzoStudio/.ci/windows_${{ inputs.rel_name }}_base_files/* ./
|
||||
cp ../update_studio_and_python_dependencies.bat ./update/
|
||||
|
||||
cd ..
|
||||
|
||||
"C:\Program Files\7-Zip\7z.exe" a -t7z -m0=lzma2 -mx=9 -mfb=128 -md=768m -ms=on -mf=BCJ2 ComfyUI_windows_portable.7z ComfyUI_windows_portable
|
||||
mv ComfyUI_windows_portable.7z ComfyUI/ComfyUI_windows_portable_${{ inputs.rel_name }}${{ inputs.rel_extra_name }}.7z
|
||||
"C:\Program Files\7-Zip\7z.exe" a -t7z -m0=lzma2 -mx=9 -mfb=128 -md=768m -ms=on -mf=BCJ2 HanzoStudio_windows_portable.7z HanzoStudio_windows_portable
|
||||
mv HanzoStudio_windows_portable.7z HanzoStudio/HanzoStudio_windows_portable_${{ inputs.rel_name }}${{ inputs.rel_extra_name }}.7z
|
||||
|
||||
- shell: bash
|
||||
if: ${{ inputs.test_release }}
|
||||
run: |
|
||||
cd ..
|
||||
cd ComfyUI_windows_portable
|
||||
python_embeded/python.exe -s ComfyUI/main.py --quick-test-for-ci --cpu
|
||||
cd HanzoStudio_windows_portable
|
||||
python_embeded/python.exe -s HanzoStudio/main.py --quick-test-for-ci --cpu
|
||||
|
||||
python_embeded/python.exe -s ./update/update.py ComfyUI/
|
||||
python_embeded/python.exe -s ./update/update.py HanzoStudio/
|
||||
|
||||
ls
|
||||
|
||||
- name: Upload binaries to release
|
||||
uses: softprops/action-gh-release@v2
|
||||
with:
|
||||
files: ComfyUI_windows_portable_${{ inputs.rel_name }}${{ inputs.rel_extra_name }}.7z
|
||||
files: HanzoStudio_windows_portable_${{ inputs.rel_name }}${{ inputs.rel_extra_name }}.7z
|
||||
tag_name: ${{ inputs.git_tag }}
|
||||
draft: true
|
||||
overwrite_files: true
|
||||
|
||||
@@ -25,7 +25,8 @@ jobs:
|
||||
uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: ${{ matrix.python-version }}
|
||||
- name: Install uv
|
||||
run: curl -LsSf https://astral.sh/uv/install.sh | sh
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
pip install -r requirements.txt
|
||||
uv pip install --system -r requirements.txt
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# This is the GitHub Workflow that drives automatic full-GPU-enabled tests of all new commits to the master branch of ComfyUI
|
||||
# Results are reported as checkmarks on the commits, as well as onto https://ci.comfy.org/
|
||||
name: Full Comfy CI Workflow Runs
|
||||
# This is the GitHub Workflow that drives automatic full-GPU-enabled tests of all new commits to the main branch of Hanzo Studio
|
||||
# Results are reported as checkmarks on the commits, as well as onto https://ci.hanzo.ai/
|
||||
name: Full Hanzo Studio CI Workflow Runs
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
@@ -46,7 +46,7 @@ jobs:
|
||||
python_version: ${{ matrix.python_version }}
|
||||
torch_version: ${{ matrix.torch_version }}
|
||||
google_credentials: ${{ secrets.GCS_SERVICE_ACCOUNT_JSON }}
|
||||
comfyui_flags: ${{ matrix.flags }}
|
||||
studio_flags: ${{ matrix.flags }}
|
||||
|
||||
# test-win-nightly:
|
||||
# strategy:
|
||||
@@ -69,7 +69,7 @@ jobs:
|
||||
# python_version: ${{ matrix.python_version }}
|
||||
# torch_version: ${{ matrix.torch_version }}
|
||||
# google_credentials: ${{ secrets.GCS_SERVICE_ACCOUNT_JSON }}
|
||||
# comfyui_flags: ${{ matrix.flags }}
|
||||
# studio_flags: ${{ matrix.flags }}
|
||||
|
||||
test-unix-nightly:
|
||||
strategy:
|
||||
@@ -96,4 +96,4 @@ jobs:
|
||||
python_version: ${{ matrix.python_version }}
|
||||
torch_version: ${{ matrix.torch_version }}
|
||||
google_credentials: ${{ secrets.GCS_SERVICE_ACCOUNT_JSON }}
|
||||
comfyui_flags: ${{ matrix.flags }}
|
||||
studio_flags: ${{ matrix.flags }}
|
||||
|
||||
@@ -19,12 +19,13 @@ jobs:
|
||||
uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: '3.12'
|
||||
- name: Install uv
|
||||
run: curl -LsSf https://astral.sh/uv/install.sh | sh
|
||||
- name: Install requirements
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cpu
|
||||
pip install -r requirements.txt
|
||||
pip install -r tests-unit/requirements.txt
|
||||
uv pip install --system torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cpu
|
||||
uv pip install --system -r requirements.txt
|
||||
uv pip install --system -r tests-unit/requirements.txt
|
||||
- name: Run Execution Tests
|
||||
run: |
|
||||
python -m pytest tests/execution -v --skip-timing-checks
|
||||
|
||||
@@ -10,26 +10,27 @@ jobs:
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout ComfyUI
|
||||
- name: Checkout Hanzo Studio
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
repository: "Comfy-Org/ComfyUI"
|
||||
path: "ComfyUI"
|
||||
repository: "hanzoai/studio"
|
||||
path: "studio"
|
||||
- uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: '3.10'
|
||||
- name: Install uv
|
||||
run: curl -LsSf https://astral.sh/uv/install.sh | sh
|
||||
- name: Install requirements
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cpu
|
||||
pip install -r requirements.txt
|
||||
pip install wait-for-it
|
||||
working-directory: ComfyUI
|
||||
- name: Start ComfyUI server
|
||||
uv pip install --system torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cpu
|
||||
uv pip install --system -r requirements.txt
|
||||
uv pip install --system wait-for-it
|
||||
working-directory: studio
|
||||
- name: Start Hanzo Studio server
|
||||
run: |
|
||||
python main.py --cpu 2>&1 | tee console_output.log &
|
||||
wait-for-it --service 127.0.0.1:8188 -t 30
|
||||
working-directory: ComfyUI
|
||||
working-directory: studio
|
||||
- name: Check for unhandled exceptions in server log
|
||||
run: |
|
||||
grep -v "Found comfy_kitchen backend triton: {'available': False, 'disabled': True, 'unavailable_reason': \"ImportError: No module named 'triton'\", 'capabilities': \[\]}" console_output.log | grep -v "Found comfy_kitchen backend triton: {'available': False, 'disabled': False, 'unavailable_reason': \"ImportError: No module named 'triton'\", 'capabilities': \[\]}" > console_output_filtered.log
|
||||
@@ -38,10 +39,10 @@ jobs:
|
||||
echo "Unhandled exception/error found in server log."
|
||||
exit 1
|
||||
fi
|
||||
working-directory: ComfyUI
|
||||
working-directory: studio
|
||||
- uses: actions/upload-artifact@v4
|
||||
if: always()
|
||||
with:
|
||||
name: console-output
|
||||
path: ComfyUI/console_output.log
|
||||
path: studio/console_output.log
|
||||
retention-days: 30
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
name: Generate Pydantic Stubs from api.comfy.org
|
||||
name: Generate Pydantic Stubs from api.hanzo.ai
|
||||
|
||||
on:
|
||||
schedule:
|
||||
@@ -8,49 +8,49 @@ on:
|
||||
jobs:
|
||||
generate-models:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: '3.10'
|
||||
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
pip install 'datamodel-code-generator[http]'
|
||||
npm install @redocly/cli
|
||||
|
||||
|
||||
- name: Download OpenAPI spec
|
||||
run: |
|
||||
curl -o openapi.yaml https://api.comfy.org/openapi
|
||||
|
||||
curl -o openapi.yaml https://api.hanzo.ai/openapi
|
||||
|
||||
- name: Filter OpenAPI spec with Redocly
|
||||
run: |
|
||||
npx @redocly/cli bundle openapi.yaml --output filtered-openapi.yaml --config comfy_api_nodes/redocly.yaml --remove-unused-components
|
||||
|
||||
npx @redocly/cli bundle openapi.yaml --output filtered-openapi.yaml --config studio_api_nodes/redocly.yaml --remove-unused-components
|
||||
|
||||
- name: Generate API models
|
||||
run: |
|
||||
datamodel-codegen --use-subclass-enum --input filtered-openapi.yaml --output comfy_api_nodes/apis --output-model-type pydantic_v2.BaseModel
|
||||
|
||||
datamodel-codegen --use-subclass-enum --input filtered-openapi.yaml --output studio_api_nodes/apis --output-model-type pydantic_v2.BaseModel
|
||||
|
||||
- name: Check for changes
|
||||
id: git-check
|
||||
run: |
|
||||
git diff --exit-code comfy_api_nodes/apis || echo "changes=true" >> $GITHUB_OUTPUT
|
||||
|
||||
git diff --exit-code studio_api_nodes/apis || echo "changes=true" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Create Pull Request
|
||||
if: steps.git-check.outputs.changes == 'true'
|
||||
uses: peter-evans/create-pull-request@v5
|
||||
with:
|
||||
commit-message: 'chore: update API models from OpenAPI spec'
|
||||
title: 'Update API models from api.comfy.org'
|
||||
title: 'Update API models from api.hanzo.ai'
|
||||
body: |
|
||||
This PR updates the API models based on the latest api.comfy.org OpenAPI specification.
|
||||
|
||||
Generated automatically by the a Github workflow.
|
||||
This PR updates the API models based on the latest api.hanzo.ai OpenAPI specification.
|
||||
|
||||
Generated automatically by a Github workflow.
|
||||
branch: update-api-stubs
|
||||
delete-branch: true
|
||||
base: master
|
||||
base: main
|
||||
|
||||
@@ -6,7 +6,7 @@ on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
version:
|
||||
description: 'ComfyUI version (e.g., v0.7.0)'
|
||||
description: 'Hanzo Studio version (e.g., v0.7.0)'
|
||||
required: true
|
||||
type: string
|
||||
|
||||
@@ -26,34 +26,34 @@ jobs:
|
||||
fi
|
||||
echo "version=$VERSION" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Checkout comfyui-ci-container
|
||||
- name: Checkout studio-ci-container
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
repository: comfy-org/comfyui-ci-container
|
||||
repository: hanzoai/studio-ci-container
|
||||
token: ${{ secrets.CI_CONTAINER_PAT }}
|
||||
|
||||
- name: Check current version
|
||||
id: current
|
||||
run: |
|
||||
CURRENT=$(grep -oP 'ARG COMFYUI_VERSION=\K.*' Dockerfile || echo "unknown")
|
||||
CURRENT=$(grep -oP 'ARG STUDIO_VERSION=\K.*' Dockerfile || echo "unknown")
|
||||
echo "current_version=$CURRENT" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Update Dockerfile
|
||||
run: |
|
||||
VERSION="${{ steps.version.outputs.version }}"
|
||||
sed -i "s/^ARG COMFYUI_VERSION=.*/ARG COMFYUI_VERSION=${VERSION}/" Dockerfile
|
||||
sed -i "s/^ARG STUDIO_VERSION=.*/ARG STUDIO_VERSION=${VERSION}/" Dockerfile
|
||||
|
||||
- name: Create Pull Request
|
||||
id: create-pr
|
||||
uses: peter-evans/create-pull-request@v7
|
||||
with:
|
||||
token: ${{ secrets.CI_CONTAINER_PAT }}
|
||||
branch: automation/comfyui-${{ steps.version.outputs.version }}
|
||||
title: "chore: bump ComfyUI to ${{ steps.version.outputs.version }}"
|
||||
branch: automation/studio-${{ steps.version.outputs.version }}
|
||||
title: "chore: bump Hanzo Studio to ${{ steps.version.outputs.version }}"
|
||||
body: |
|
||||
Updates ComfyUI version from `${{ steps.current.outputs.current_version }}` to `${{ steps.version.outputs.version }}`
|
||||
Updates Hanzo Studio version from `${{ steps.current.outputs.current_version }}` to `${{ steps.version.outputs.version }}`
|
||||
|
||||
**Triggered by:** ${{ github.event_name == 'release' && format('[Release {0}]({1})', github.event.release.tag_name, github.event.release.html_url) || 'Manual workflow dispatch' }}
|
||||
|
||||
labels: automation
|
||||
commit-message: "chore: bump ComfyUI to ${{ steps.version.outputs.version }}"
|
||||
commit-message: "chore: bump Hanzo Studio to ${{ steps.version.outputs.version }}"
|
||||
|
||||
@@ -30,9 +30,9 @@ jobs:
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
|
||||
- name: Update comfyui_version.py
|
||||
- name: Update studio_version.py
|
||||
run: |
|
||||
# Read version from pyproject.toml and update comfyui_version.py
|
||||
# Read version from pyproject.toml and update studio_version.py
|
||||
python -c '
|
||||
import tomllib
|
||||
|
||||
@@ -41,8 +41,8 @@ jobs:
|
||||
config = tomllib.load(f)
|
||||
version = config["project"]["version"]
|
||||
|
||||
# Write version to comfyui_version.py
|
||||
with open("comfyui_version.py", "w") as f:
|
||||
# Write version to studio_version.py
|
||||
with open("studio_version.py", "w") as f:
|
||||
f.write("# This file is automatically generated by the build process when version is\n")
|
||||
f.write("# updated in pyproject.toml.\n")
|
||||
f.write(f"__version__ = \"{version}\"\n")
|
||||
@@ -54,6 +54,6 @@ jobs:
|
||||
git config --local user.email "github-actions@github.com"
|
||||
git fetch origin ${{ github.head_ref }}
|
||||
git checkout -B ${{ github.head_ref }} origin/${{ github.head_ref }}
|
||||
git add comfyui_version.py
|
||||
git diff --quiet && git diff --staged --quiet || git commit -m "chore: Update comfyui_version.py to match pyproject.toml"
|
||||
git add studio_version.py
|
||||
git diff --quiet && git diff --staged --quiet || git commit -m "chore: Update studio_version.py to match pyproject.toml"
|
||||
git push origin HEAD:${{ github.head_ref }}
|
||||
|
||||
@@ -46,18 +46,18 @@ jobs:
|
||||
- shell: bash
|
||||
run: |
|
||||
echo "@echo off
|
||||
call update_comfyui.bat nopause
|
||||
call update_studio.bat nopause
|
||||
echo -
|
||||
echo This will try to update pytorch and all python dependencies.
|
||||
echo -
|
||||
echo If you just want to update normally, close this and run update_comfyui.bat instead.
|
||||
echo If you just want to update normally, close this and run update_studio.bat instead.
|
||||
echo -
|
||||
pause
|
||||
..\python_embeded\python.exe -s -m pip install --upgrade torch torchvision torchaudio ${{ inputs.xformers }} --extra-index-url https://download.pytorch.org/whl/cu${{ inputs.cu }} -r ../ComfyUI/requirements.txt pygit2
|
||||
pause" > update_comfyui_and_python_dependencies.bat
|
||||
..\python_embeded\python.exe -s -m pip install --upgrade torch torchvision torchaudio ${{ inputs.xformers }} --extra-index-url https://download.pytorch.org/whl/cu${{ inputs.cu }} -r ../HanzoStudio/requirements.txt pygit2
|
||||
pause" > update_studio_and_python_dependencies.bat
|
||||
|
||||
grep -v comfyui requirements.txt > requirements_nocomfyui.txt
|
||||
python -m pip wheel --no-cache-dir torch torchvision torchaudio ${{ inputs.xformers }} ${{ inputs.extra_dependencies }} --extra-index-url https://download.pytorch.org/whl/cu${{ inputs.cu }} -r requirements_nocomfyui.txt pygit2 -w ./temp_wheel_dir
|
||||
grep -v studio requirements.txt > requirements_nostudio.txt
|
||||
python -m pip wheel --no-cache-dir torch torchvision torchaudio ${{ inputs.xformers }} ${{ inputs.extra_dependencies }} --extra-index-url https://download.pytorch.org/whl/cu${{ inputs.cu }} -r requirements_nostudio.txt pygit2 -w ./temp_wheel_dir
|
||||
python -m pip install --no-cache-dir ./temp_wheel_dir/*
|
||||
echo installed basic
|
||||
ls -lah temp_wheel_dir
|
||||
@@ -68,5 +68,5 @@ jobs:
|
||||
with:
|
||||
path: |
|
||||
cu${{ inputs.cu }}_python_deps.tar
|
||||
update_comfyui_and_python_dependencies.bat
|
||||
update_studio_and_python_dependencies.bat
|
||||
key: ${{ runner.os }}-build-cu${{ inputs.cu }}-${{ inputs.python_minor }}
|
||||
|
||||
@@ -38,18 +38,18 @@ jobs:
|
||||
- shell: bash
|
||||
run: |
|
||||
echo "@echo off
|
||||
call update_comfyui.bat nopause
|
||||
call update_studio.bat nopause
|
||||
echo -
|
||||
echo This will try to update pytorch and all python dependencies.
|
||||
echo -
|
||||
echo If you just want to update normally, close this and run update_comfyui.bat instead.
|
||||
echo If you just want to update normally, close this and run update_studio.bat instead.
|
||||
echo -
|
||||
pause
|
||||
..\python_embeded\python.exe -s -m pip install --upgrade ${{ inputs.torch_dependencies }} -r ../ComfyUI/requirements.txt pygit2
|
||||
pause" > update_comfyui_and_python_dependencies.bat
|
||||
..\python_embeded\python.exe -s -m pip install --upgrade ${{ inputs.torch_dependencies }} -r ../HanzoStudio/requirements.txt pygit2
|
||||
pause" > update_studio_and_python_dependencies.bat
|
||||
|
||||
grep -v comfyui requirements.txt > requirements_nocomfyui.txt
|
||||
python -m pip wheel --no-cache-dir ${{ inputs.torch_dependencies }} -r requirements_nocomfyui.txt pygit2 -w ./temp_wheel_dir
|
||||
grep -v studio requirements.txt > requirements_nostudio.txt
|
||||
python -m pip wheel --no-cache-dir ${{ inputs.torch_dependencies }} -r requirements_nostudio.txt pygit2 -w ./temp_wheel_dir
|
||||
python -m pip install --no-cache-dir ./temp_wheel_dir/*
|
||||
echo installed basic
|
||||
ls -lah temp_wheel_dir
|
||||
@@ -60,5 +60,5 @@ jobs:
|
||||
with:
|
||||
path: |
|
||||
${{ inputs.cache_tag }}_python_deps.tar
|
||||
update_comfyui_and_python_dependencies.bat
|
||||
update_studio_and_python_dependencies.bat
|
||||
key: ${{ runner.os }}-build-${{ inputs.cache_tag }}-${{ inputs.python_minor }}
|
||||
|
||||
@@ -42,45 +42,45 @@ jobs:
|
||||
- shell: bash
|
||||
run: |
|
||||
cd ..
|
||||
cp -r ComfyUI ComfyUI_copy
|
||||
cp -r HanzoStudio HanzoStudio_copy
|
||||
curl https://www.python.org/ftp/python/3.${{ inputs.python_minor }}.${{ inputs.python_patch }}/python-3.${{ inputs.python_minor }}.${{ inputs.python_patch }}-embed-amd64.zip -o python_embeded.zip
|
||||
unzip python_embeded.zip -d python_embeded
|
||||
cd python_embeded
|
||||
echo 'import site' >> ./python3${{ inputs.python_minor }}._pth
|
||||
curl https://bootstrap.pypa.io/get-pip.py -o get-pip.py
|
||||
./python.exe get-pip.py
|
||||
python -m pip wheel torch torchvision torchaudio --pre --extra-index-url https://download.pytorch.org/whl/nightly/cu${{ inputs.cu }} -r ../ComfyUI/requirements.txt pygit2 -w ../temp_wheel_dir
|
||||
python -m pip wheel torch torchvision torchaudio --pre --extra-index-url https://download.pytorch.org/whl/nightly/cu${{ inputs.cu }} -r ../HanzoStudio/requirements.txt pygit2 -w ../temp_wheel_dir
|
||||
ls ../temp_wheel_dir
|
||||
./python.exe -s -m pip install --pre ../temp_wheel_dir/*
|
||||
sed -i '1i../ComfyUI' ./python3${{ inputs.python_minor }}._pth
|
||||
sed -i '1i../HanzoStudio' ./python3${{ inputs.python_minor }}._pth
|
||||
|
||||
rm ./Lib/site-packages/torch/lib/dnnl.lib #I don't think this is actually used and I need the space
|
||||
cd ..
|
||||
|
||||
git clone --depth 1 https://github.com/comfyanonymous/taesd
|
||||
cp taesd/*.safetensors ./ComfyUI_copy/models/vae_approx/
|
||||
git clone --depth 1 https://github.com/hanzoai/taesd
|
||||
cp taesd/*.safetensors ./HanzoStudio_copy/models/vae_approx/
|
||||
|
||||
mkdir ComfyUI_windows_portable_nightly_pytorch
|
||||
mv python_embeded ComfyUI_windows_portable_nightly_pytorch
|
||||
mv ComfyUI_copy ComfyUI_windows_portable_nightly_pytorch/ComfyUI
|
||||
mkdir HanzoStudio_windows_portable_nightly_pytorch
|
||||
mv python_embeded HanzoStudio_windows_portable_nightly_pytorch
|
||||
mv HanzoStudio_copy HanzoStudio_windows_portable_nightly_pytorch/HanzoStudio
|
||||
|
||||
cd ComfyUI_windows_portable_nightly_pytorch
|
||||
cd HanzoStudio_windows_portable_nightly_pytorch
|
||||
|
||||
mkdir update
|
||||
cp -r ComfyUI/.ci/update_windows/* ./update/
|
||||
cp -r ComfyUI/.ci/windows_nvidia_base_files/* ./
|
||||
cp -r ComfyUI/.ci/windows_nightly_base_files/* ./
|
||||
cp -r HanzoStudio/.ci/update_windows/* ./update/
|
||||
cp -r HanzoStudio/.ci/windows_nvidia_base_files/* ./
|
||||
cp -r HanzoStudio/.ci/windows_nightly_base_files/* ./
|
||||
|
||||
echo "call update_comfyui.bat nopause
|
||||
..\python_embeded\python.exe -s -m pip install --upgrade --pre torch torchvision torchaudio --extra-index-url https://download.pytorch.org/whl/nightly/cu${{ inputs.cu }} -r ../ComfyUI/requirements.txt pygit2
|
||||
pause" > ./update/update_comfyui_and_python_dependencies.bat
|
||||
echo "call update_studio.bat nopause
|
||||
..\python_embeded\python.exe -s -m pip install --upgrade --pre torch torchvision torchaudio --extra-index-url https://download.pytorch.org/whl/nightly/cu${{ inputs.cu }} -r ../HanzoStudio/requirements.txt pygit2
|
||||
pause" > ./update/update_studio_and_python_dependencies.bat
|
||||
cd ..
|
||||
|
||||
"C:\Program Files\7-Zip\7z.exe" a -t7z -m0=lzma2 -mx=9 -mfb=128 -md=512m -ms=on -mf=BCJ2 ComfyUI_windows_portable_nightly_pytorch.7z ComfyUI_windows_portable_nightly_pytorch
|
||||
mv ComfyUI_windows_portable_nightly_pytorch.7z ComfyUI/ComfyUI_windows_portable_nvidia_or_cpu_nightly_pytorch.7z
|
||||
"C:\Program Files\7-Zip\7z.exe" a -t7z -m0=lzma2 -mx=9 -mfb=128 -md=512m -ms=on -mf=BCJ2 HanzoStudio_windows_portable_nightly_pytorch.7z HanzoStudio_windows_portable_nightly_pytorch
|
||||
mv HanzoStudio_windows_portable_nightly_pytorch.7z HanzoStudio/HanzoStudio_windows_portable_nvidia_or_cpu_nightly_pytorch.7z
|
||||
|
||||
cd ComfyUI_windows_portable_nightly_pytorch
|
||||
python_embeded/python.exe -s ComfyUI/main.py --quick-test-for-ci --cpu
|
||||
cd HanzoStudio_windows_portable_nightly_pytorch
|
||||
python_embeded/python.exe -s HanzoStudio/main.py --quick-test-for-ci --cpu
|
||||
|
||||
ls
|
||||
|
||||
@@ -88,6 +88,6 @@ jobs:
|
||||
uses: svenstaro/upload-release-action@v2
|
||||
with:
|
||||
repo_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
file: ComfyUI_windows_portable_nvidia_or_cpu_nightly_pytorch.7z
|
||||
file: HanzoStudio_windows_portable_nvidia_or_cpu_nightly_pytorch.7z
|
||||
tag: "latest"
|
||||
overwrite: true
|
||||
|
||||
@@ -25,7 +25,7 @@ on:
|
||||
# - master
|
||||
|
||||
jobs:
|
||||
package_comfyui:
|
||||
package_studio:
|
||||
permissions:
|
||||
contents: "write"
|
||||
packages: "write"
|
||||
@@ -37,12 +37,12 @@ jobs:
|
||||
with:
|
||||
path: |
|
||||
cu${{ inputs.cu }}_python_deps.tar
|
||||
update_comfyui_and_python_dependencies.bat
|
||||
update_studio_and_python_dependencies.bat
|
||||
key: ${{ runner.os }}-build-cu${{ inputs.cu }}-${{ inputs.python_minor }}
|
||||
- shell: bash
|
||||
run: |
|
||||
mv cu${{ inputs.cu }}_python_deps.tar ../
|
||||
mv update_comfyui_and_python_dependencies.bat ../
|
||||
mv update_studio_and_python_dependencies.bat ../
|
||||
cd ..
|
||||
tar xf cu${{ inputs.cu }}_python_deps.tar
|
||||
pwd
|
||||
@@ -55,7 +55,7 @@ jobs:
|
||||
- shell: bash
|
||||
run: |
|
||||
cd ..
|
||||
cp -r ComfyUI ComfyUI_copy
|
||||
cp -r HanzoStudio HanzoStudio_copy
|
||||
curl https://www.python.org/ftp/python/3.${{ inputs.python_minor }}.${{ inputs.python_patch }}/python-3.${{ inputs.python_minor }}.${{ inputs.python_patch }}-embed-amd64.zip -o python_embeded.zip
|
||||
unzip python_embeded.zip -d python_embeded
|
||||
cd python_embeded
|
||||
@@ -63,36 +63,36 @@ jobs:
|
||||
curl https://bootstrap.pypa.io/get-pip.py -o get-pip.py
|
||||
./python.exe get-pip.py
|
||||
./python.exe -s -m pip install ../cu${{ inputs.cu }}_python_deps/*
|
||||
sed -i '1i../ComfyUI' ./python3${{ inputs.python_minor }}._pth
|
||||
sed -i '1i../HanzoStudio' ./python3${{ inputs.python_minor }}._pth
|
||||
|
||||
rm ./Lib/site-packages/torch/lib/dnnl.lib #I don't think this is actually used and I need the space
|
||||
rm ./Lib/site-packages/torch/lib/libprotoc.lib
|
||||
rm ./Lib/site-packages/torch/lib/libprotobuf.lib
|
||||
cd ..
|
||||
|
||||
git clone --depth 1 https://github.com/comfyanonymous/taesd
|
||||
cp taesd/*.safetensors ./ComfyUI_copy/models/vae_approx/
|
||||
git clone --depth 1 https://github.com/hanzoai/taesd
|
||||
cp taesd/*.safetensors ./HanzoStudio_copy/models/vae_approx/
|
||||
|
||||
mkdir ComfyUI_windows_portable
|
||||
mv python_embeded ComfyUI_windows_portable
|
||||
mv ComfyUI_copy ComfyUI_windows_portable/ComfyUI
|
||||
mkdir HanzoStudio_windows_portable
|
||||
mv python_embeded HanzoStudio_windows_portable
|
||||
mv HanzoStudio_copy HanzoStudio_windows_portable/HanzoStudio
|
||||
|
||||
cd ComfyUI_windows_portable
|
||||
cd HanzoStudio_windows_portable
|
||||
|
||||
mkdir update
|
||||
cp -r ComfyUI/.ci/update_windows/* ./update/
|
||||
cp -r ComfyUI/.ci/windows_nvidia_base_files/* ./
|
||||
cp ../update_comfyui_and_python_dependencies.bat ./update/
|
||||
cp -r HanzoStudio/.ci/update_windows/* ./update/
|
||||
cp -r HanzoStudio/.ci/windows_nvidia_base_files/* ./
|
||||
cp ../update_studio_and_python_dependencies.bat ./update/
|
||||
|
||||
cd ..
|
||||
|
||||
"C:\Program Files\7-Zip\7z.exe" a -t7z -m0=lzma2 -mx=9 -mfb=128 -md=768m -ms=on -mf=BCJ2 ComfyUI_windows_portable.7z ComfyUI_windows_portable
|
||||
mv ComfyUI_windows_portable.7z ComfyUI/new_ComfyUI_windows_portable_nvidia_cu${{ inputs.cu }}_or_cpu.7z
|
||||
"C:\Program Files\7-Zip\7z.exe" a -t7z -m0=lzma2 -mx=9 -mfb=128 -md=768m -ms=on -mf=BCJ2 HanzoStudio_windows_portable.7z HanzoStudio_windows_portable
|
||||
mv HanzoStudio_windows_portable.7z HanzoStudio/new_HanzoStudio_windows_portable_nvidia_cu${{ inputs.cu }}_or_cpu.7z
|
||||
|
||||
cd ComfyUI_windows_portable
|
||||
python_embeded/python.exe -s ComfyUI/main.py --quick-test-for-ci --cpu
|
||||
cd HanzoStudio_windows_portable
|
||||
python_embeded/python.exe -s HanzoStudio/main.py --quick-test-for-ci --cpu
|
||||
|
||||
python_embeded/python.exe -s ./update/update.py ComfyUI/
|
||||
python_embeded/python.exe -s ./update/update.py HanzoStudio/
|
||||
|
||||
ls
|
||||
|
||||
@@ -100,7 +100,7 @@ jobs:
|
||||
uses: svenstaro/upload-release-action@v2
|
||||
with:
|
||||
repo_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
file: new_ComfyUI_windows_portable_nvidia_cu${{ inputs.cu }}_or_cpu.7z
|
||||
file: new_HanzoStudio_windows_portable_nvidia_cu${{ inputs.cu }}_or_cpu.7z
|
||||
tag: "latest"
|
||||
overwrite: true
|
||||
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
name: Workflow Sanity
|
||||
on:
|
||||
pull_request:
|
||||
paths: ['.github/workflows/**']
|
||||
jobs:
|
||||
sanity:
|
||||
uses: hanzoai/.github/.github/workflows/workflow-sanity.yml@main
|
||||
@@ -5,8 +5,12 @@ __pycache__/
|
||||
!/input/example.png
|
||||
/models/
|
||||
/temp/
|
||||
/custom_nodes/
|
||||
!custom_nodes/example_node.py.example
|
||||
# Ignore vendored pack contents but keep the directory listable so the tracked
|
||||
# files below can be re-included (a file under a fully-excluded dir cannot be).
|
||||
/custom_nodes/*
|
||||
!/custom_nodes/example_node.py.example
|
||||
# Pinned pack manifest is tracked; the pack sources it clones are not (vendored at build).
|
||||
!/custom_nodes/hanzo-packs.txt
|
||||
extra_model_paths.yaml
|
||||
/.vs
|
||||
.vscode/
|
||||
@@ -24,3 +28,4 @@ web_custom_versions/
|
||||
openapi.yaml
|
||||
filtered-openapi.yaml
|
||||
uv.lock
|
||||
/custom_nodes_quarantine/
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
# Admins
|
||||
* @comfyanonymous @kosinkadink @guill
|
||||
* @hanzoai/studio
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
# Contributing to ComfyUI
|
||||
# Contributing to Hanzo Studio
|
||||
|
||||
Welcome, and thank you for your interest in contributing to ComfyUI!
|
||||
Welcome, and thank you for your interest in contributing to Hanzo Studio!
|
||||
|
||||
There are several ways in which you can contribute, beyond writing code. The goal of this document is to provide a high-level overview of how you can get involved.
|
||||
|
||||
## Asking Questions
|
||||
|
||||
Have a question? Instead of opening an issue, please ask on [Discord](https://comfy.org/discord) or [Matrix](https://app.element.io/#/room/%23comfyui_space%3Amatrix.org) channels. Our team and the community will help you.
|
||||
Have a question? Instead of opening an issue, please ask on [Discord](https://discord.gg/hanzoai) first. Our team and the community will help you.
|
||||
|
||||
## Providing Feedback
|
||||
|
||||
@@ -16,24 +16,24 @@ See the `#bug-report`, `#feature-request` and `#feedback` channels on Discord.
|
||||
|
||||
## Reporting Issues
|
||||
|
||||
Have you identified a reproducible problem in ComfyUI? Do you have a feature request? We want to hear about it! Here's how you can report your issue as effectively as possible.
|
||||
Have you identified a reproducible problem in Hanzo Studio? Do you have a feature request? We want to hear about it! Here's how you can report your issue as effectively as possible.
|
||||
|
||||
|
||||
### Look For an Existing Issue
|
||||
|
||||
Before you create a new issue, please do a search in [open issues](https://github.com/comfyanonymous/ComfyUI/issues) to see if the issue or feature request has already been filed.
|
||||
Before you create a new issue, please do a search in [open issues](https://github.com/hanzoai/studio/issues) to see if the issue or feature request has already been filed.
|
||||
|
||||
If you find your issue already exists, make relevant comments and add your [reaction](https://github.com/blog/2119-add-reactions-to-pull-requests-issues-and-comments). Use a reaction in place of a "+1" comment:
|
||||
|
||||
* 👍 - upvote
|
||||
* 👎 - downvote
|
||||
* upvote
|
||||
* downvote
|
||||
|
||||
If you cannot find an existing issue that describes your bug or feature, create a new issue. We have an issue template in place to organize new issues.
|
||||
|
||||
|
||||
### Creating Pull Requests
|
||||
|
||||
* Please refer to the article on [creating pull requests](https://github.com/comfyanonymous/ComfyUI/wiki/How-to-Contribute-Code) and contributing to this project.
|
||||
* Please refer to the article on [creating pull requests](https://github.com/hanzoai/studio/wiki/How-to-Contribute-Code) and contributing to this project.
|
||||
|
||||
|
||||
## Thank You
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
FROM python:3.11-slim AS base
|
||||
|
||||
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||
git \
|
||||
build-essential \
|
||||
libgl1 \
|
||||
libglib2.0-0 \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
# Install PyTorch CPU first (large layer, cached separately)
|
||||
RUN pip install --no-cache-dir \
|
||||
torch torchvision torchaudio \
|
||||
--index-url https://download.pytorch.org/whl/cpu
|
||||
|
||||
# Install remaining dependencies
|
||||
COPY requirements.txt .
|
||||
RUN pip install --no-cache-dir -r requirements.txt
|
||||
|
||||
# Apply Hanzo Studio branding to frontend package
|
||||
COPY branding/ /tmp/branding/
|
||||
RUN chmod +x /tmp/branding/apply-branding.sh && /tmp/branding/apply-branding.sh && rm -rf /tmp/branding
|
||||
|
||||
# Copy application code
|
||||
COPY . .
|
||||
|
||||
# Vendor the Hanzo Studio custom-node packs (segmentation, controlnet, ipadapter,
|
||||
# upscaling, video, utils) at their pinned commits and install their deps with the
|
||||
# torch/numpy/transformers stack locked. See custom_nodes/hanzo-packs.txt.
|
||||
RUN chmod +x scripts/install_custom_nodes.sh && scripts/install_custom_nodes.sh
|
||||
|
||||
# Create directories for runtime volumes
|
||||
RUN mkdir -p models output input custom_nodes user
|
||||
|
||||
EXPOSE 8188
|
||||
|
||||
# Default: listen on all interfaces, CPU mode
|
||||
# Environment variables:
|
||||
# STUDIO_ENABLE_IAM_AUTH=1 - Enable IAM auth via hanzo.id
|
||||
# STUDIO_IAM_URL=https://hanzo.id
|
||||
# STUDIO_NO_LOCALHOST_BYPASS=1 - Require auth even for localhost
|
||||
# STUDIO_MULTI_TENANT=1 - Per-org storage isolation
|
||||
# STUDIO_ORG_ID=default-org - Default org ID
|
||||
# STUDIO_ENABLE_BILLING=1 - Commerce billing integration
|
||||
# STUDIO_BILLING_CHECK_BALANCE=1 - Check balance before prompts
|
||||
# STUDIO_COMMERCE_URL=http://commerce.hanzo.svc:8001
|
||||
# STUDIO_COMMERCE_TOKEN=... - Commerce API token
|
||||
# STUDIO_ENABLE_METRICS=1 - /metrics endpoint for VictoriaMetrics
|
||||
# STUDIO_RATE_LIMIT_RPM=60 - Prompts per minute per org
|
||||
CMD ["python", "main.py", "--listen", "0.0.0.0", "--port", "8188", "--cpu"]
|
||||
@@ -0,0 +1,191 @@
|
||||
# Hanzo Studio
|
||||
|
||||
## Overview
|
||||
**The most powerful and modular visual AI engine and application.**
|
||||
|
||||
## Tech Stack
|
||||
- **Language**: Python
|
||||
|
||||
## Build & Run
|
||||
```bash
|
||||
uv sync
|
||||
uv run pytest
|
||||
```
|
||||
|
||||
## Structure
|
||||
```
|
||||
studio/
|
||||
CODEOWNERS
|
||||
CONTRIBUTING.md
|
||||
Dockerfile
|
||||
LICENSE
|
||||
QUANTIZATION.md
|
||||
README.md
|
||||
alembic.ini
|
||||
alembic_db/
|
||||
api_server/
|
||||
app/
|
||||
blueprints/
|
||||
branding/
|
||||
comfyui.log
|
||||
comfyui.prev.log
|
||||
comfyui.prev2.log
|
||||
```
|
||||
|
||||
## Key Files
|
||||
- `README.md` -- Project documentation
|
||||
- `pyproject.toml` -- Python project config
|
||||
- `Dockerfile` -- Container build (applies `branding/` then runs `main.py`)
|
||||
- `hanzo.yml` + `.github/workflows/cicd.yml` -- canonical CI/CD (hanzoai/ci); builds `ghcr.io/hanzoai/studio`, rolls the `studio` operator Service CR at `studio.hanzo.ai`.
|
||||
|
||||
## Custom-node packs ("hanzo packages")
|
||||
The popular ComfyUI packs are vendored as first-class hanzo packages, pinned and
|
||||
reproducible. One and only one way to add/bump them:
|
||||
|
||||
- **Manifest**: `custom_nodes/hanzo-packs.txt` — `<dir> <git_url> <commit_sha>
|
||||
<spdx>` lines (immutable SHAs). This is the tracked source of truth; the cloned
|
||||
pack sources are gitignored (vendored at build).
|
||||
- **Deps**: `custom_nodes-requirements.txt` — hand-resolved, only what's not
|
||||
already in `requirements.txt`. Installed with the studio torch(cu130)/NumPy 2.x/
|
||||
Transformers 5.x stack **locked** so packs can never downgrade it.
|
||||
- **Installer**: `scripts/install_custom_nodes.sh` clones each pack at its pin and
|
||||
installs deps; the lock is derived from the live env (correct for both the CPU
|
||||
image and the GB10 cu130 venv). `ultralytics` is installed `--no-deps` (else it
|
||||
pulls non-headless opencv). Local (GB10): `STUDIO_PY=.venv/bin/python
|
||||
STUDIO_PIP="uv pip install --python .venv/bin/python" scripts/install_custom_nodes.sh`.
|
||||
- **Image**: `Dockerfile` runs the installer after `COPY . .`; `.dockerignore`
|
||||
re-includes only the manifest + installer (`custom_nodes/*` / `scripts/*` form).
|
||||
So `git push origin main` → CI builds `ghcr.io/hanzoai/studio` with the packs →
|
||||
studio.hanzo.ai gets them on the next image build.
|
||||
|
||||
**Compat shim — why packs work here at all.** This fork renamed ComfyUI's core
|
||||
packages (`comfy`→`studio`, `comfy_extras`→`studio_extras`, `comfy/comfy_types`→
|
||||
`studio/node_types`, and the `Comfy*`→`Studio*` API classes; see
|
||||
`scripts/rename_modules.py`). `studio_compat/` is one meta-path finder that lazily
|
||||
aliases every `comfy*` import to its `studio*` counterpart and mirrors
|
||||
`ComfyExtension`/`io.ComfyNode`→`Studio*` — so upstream packs run **unmodified**,
|
||||
no forks/renames. It is the phase-4 redirect `rename_modules.py` deferred, done
|
||||
with the 3.12 `find_spec` API. Activated by `import studio_compat` at the top of
|
||||
`main.py`. `nodes.py` also honors the upstream `comfy_entrypoint` V3 name.
|
||||
|
||||
Restart the local worker only when idle: `scripts/studio-service-swap.sh` polls
|
||||
`/queue` and restarts on the first empty window. Bad packs are isolated by the
|
||||
loader (studio still boots); genuinely-incompatible ones go to
|
||||
`custom_nodes_quarantine/` (e.g. was-node-suite: hard `numba` dep vs NumPy 2.5).
|
||||
|
||||
## Multi-tenant + IAM (cloud: studio.hanzo.ai)
|
||||
Standalone app; `console.hanzo.ai` link-outs land here already authenticated via
|
||||
shared Hanzo IAM (standard OIDC code flow). No embedding.
|
||||
|
||||
- **Auth** (`middleware/iam_auth_middleware.py`): local **JWKS** JWT validation
|
||||
against `${STUDIO_IAM_URL}/v1/iam/.well-known/jwks` — signature + `exp` +
|
||||
`iss` + `aud` (client_id `hanzo-studio`). Org from the `owner` claim
|
||||
(`organization` fallback) → `request["iam_user"]["org_id"]`. Anonymous mode:
|
||||
off by default locally (loopback bypass); enable in cloud with
|
||||
`STUDIO_ENABLE_IAM_AUTH=true` (the one auth switch — this is the
|
||||
"STUDIO_AUTH=off default" behavior). Browser flow: unauth HTML → IAM
|
||||
`authorize` (code+PKCE) → `/callback` exchanges the code, sets an httpOnly
|
||||
session cookie (no frontend changes). `verify_jwt(...)` is a pure, unit-tested
|
||||
function.
|
||||
- **Tenancy** (`folder_paths.py`, `app/user_manager.py`): with
|
||||
`STUDIO_MULTI_TENANT=true`, userdata is `user/orgs/{org}/user/{user}/…` and
|
||||
outputs are `output/orgs/{org}/…`. `get_public_user_directory(user, org)` is
|
||||
org-rooted (fixed the bug where org userdata always resolved to `None`).
|
||||
Execution outputs are scoped via `folder_paths.set_execution_org()`, bound by
|
||||
the prompt worker from the queue item's `org_id`.
|
||||
- **GPU federation** (`middleware/{worker_client,prompt_router,compute_config,
|
||||
visor_client}.py`): `/v1/workers/register` (heartbeat), `/v1/workers` (list),
|
||||
`/v1/worker/execute` (in-cluster push fast path). Worker↔coordinator trust via
|
||||
`X-Worker-Token` (`STUDIO_WORKER_TOKEN`, KMS-sourced).
|
||||
- **Durable render queue** (`middleware/tasks_queue.py`, `docs/federation.md`):
|
||||
`SqlitePromptQueue` — crash-durable render queue in a single SQLite file
|
||||
(stdlib `sqlite3`, WAL, **zero external processes**). `STUDIO_QUEUE_DB=<path>`
|
||||
→ `server.py` swaps `PromptQueue` for it (precedence **STUDIO_QUEUE_DB >
|
||||
STUDIO_PERSIST_QUEUE > memory**; default local behavior unchanged). The
|
||||
`PromptQueue` seam maps to a durable job state machine: `put→INSERT (pending,
|
||||
idempotent on prompt_id)`, `get→claim next pending under BEGIN IMMEDIATE
|
||||
(exactly-once, leased)`, `task_done→done | retry/failed`. Crash-recovery: a
|
||||
lease + reap returns an abandoned claim to `pending` (on every get(), a
|
||||
heartbeat thread, and on boot) so another process re-runs it — idempotent
|
||||
(SaveImage suffixes increment). Multiple Studio processes on the same DB file
|
||||
compete safely. Tested: `middleware_test/tasks_queue_test.py` (incl.
|
||||
crash-recovery across instances). Future (federation.md §6): a remote queue
|
||||
backend served by Hanzo Tasks + a Go/unified-binary (HIP-0106) migration.
|
||||
- **Engine selector** (`middleware/engine_selector.py`): `/v1/engines` lists
|
||||
execution targets for the org (`local` + registered compute_config workers) and
|
||||
`PUT /v1/engines/default` sets a per-org default (stored on
|
||||
`ComputeProfile.default_engine`). `prompt_router.route_prompt` routes to the
|
||||
chosen worker; `local` = unchanged. Leased cloud machines (Visor
|
||||
`GET /v1/machines`) are a future engine class (interface stubbed, not built).
|
||||
Frontend picker is a TODO. Tested: `middleware_test/engine_selector_test.py`.
|
||||
- **Chat Copilot** (`middleware/copilot.py` + `branding/hanzo-copilot.{js,css}`):
|
||||
a sidebar chat that reads and MUTATES the workflow graph in natural language.
|
||||
Backend `POST /v1/copilot/chat` proxies one OpenAI tool-calling round to a
|
||||
chat endpoint (`STUDIO_COPILOT_URL`; default = local engine
|
||||
`127.0.0.1:1234` if its port is open, else `api.hanzo.ai`; `STUDIO_COPILOT_MODEL`
|
||||
default `zen`; token from `STUDIO_COPILOT_TOKEN`→`STUDIO_COMMERCE_TOKEN`→request
|
||||
bearer). It returns `{reply, ops}`; `add_node` ops are validated against
|
||||
`nodes.NODE_CLASS_MAPPINGS` server-side so the model can't invent node types.
|
||||
The **frontend** applies ops against `app.graph` (LiteGraph) — op schema:
|
||||
`add_node`(id?/pos?) · `set_widget` · `connect`(slots by name or index) ·
|
||||
`set_prompt` · `move_node` · `delete_node` · `layout` (columnar tidy) ·
|
||||
`queue`. Every op is guarded (unknown type/slot = skip + note, never throw).
|
||||
Injected into the prebuilt frontend by `branding/patch_copilot.py` (copies the
|
||||
bundle into static/, CSS `<link>` in `<head>`, deferred `<script>` before
|
||||
`</body>`; idempotent, wired as step 9 of `apply-branding.sh`) — survives
|
||||
frontend upgrades. `window.HanzoCopilot.applyOps` is exposed for tests. Tab
|
||||
registered via `app.extensionManager.registerSidebarTab` (id `hanzo-copilot`).
|
||||
Tested: `middleware_test/copilot_test.py` (op-validation + mocked tool loop) and
|
||||
`branding/copilot_smoke.py` (headless: applier mutates a mock graph + panel
|
||||
renders). Live end-to-end needs one server restart to register the route.
|
||||
- **Identity user menu + session API** (`middleware/session.py` +
|
||||
`branding/hanzo-identity.{js,css}`): the top-right user menu, driven entirely
|
||||
by the IAM-validated token. Backend (thin, `/v1` house style, registered in
|
||||
`add_routes` so it works auth-on and auth-off): `GET /v1/session` →
|
||||
`{user,email,org,orgs[],project,projects[],console_url,iam_url,authenticated}`
|
||||
read from `request["iam_user"]`; `POST /v1/session/org` sets the active org
|
||||
(validated against membership → `studio_active_org` cookie); `POST
|
||||
/v1/session/project` sets the workflow-workspace project scope; `GET|POST
|
||||
/logout` clears the session + selection cookies and bounces to the IAM
|
||||
end-session → `/login`. **Org switching is real, one hook**: the auth
|
||||
middleware applies `session.resolve_org(...)` to a COPY of the validated user
|
||||
(never the cached dict) so `iam_user["org_id"]` — the id every tenancy/output
|
||||
path already reads — reflects the switch; membership comes from
|
||||
`_orgs_from_claims` (`owner` + `organizations`/`orgs`/`groups`, list or CSV or
|
||||
`{name}` dicts, home first, deduped). Frontend is a SPA-agnostic fixed pill +
|
||||
dropdown (avatar/initials, user, org switcher, project switcher, Console
|
||||
link → `console.hanzo.ai`, Logout); 401 → "Sign in with Hanzo" → `/login`.
|
||||
Injected by `branding/patch_identity.py` (step 10 of `apply-branding.sh`,
|
||||
same seam as the copilot). `window.HanzoIdentity` is exposed for tests.
|
||||
Tested: `middleware_test/session_test.py` (resolve/orgs/routes/logout, real
|
||||
aiohttp app) + `scratchpad/studio-iam/test_identity.py` (headless: pill,
|
||||
dropdown, org switch re-scope, signed-out). The `/v1/session` routes need one
|
||||
server restart to register (static + de-Comfy load without restart).
|
||||
- **De-Comfy display strings** (`branding/patch_destring.py`, step 11 —
|
||||
runs LAST): the systematic English brand-word sweep the curated phrase list in
|
||||
`patch_frontend.py` structurally can't reach. Rule 1: whole-word `ComfyUI` →
|
||||
`Hanzo Studio` only where it reads as a display word (preceded by
|
||||
open-quote/space/`>`/`(`, followed by close-quote/space/sentence-punct) — that
|
||||
boundary skips every code use in the bundle (`ComfyUIStarted`, `.ComfyUI`,
|
||||
`=ComfyUI`, the `/^(ComfyUI-.../` regex, dotted i18n keys `.category.ComfyUI`)
|
||||
and hyphenated product names (`ComfyUI-Manager`). Rule 2: curated bare-`Comfy`
|
||||
**values** as exact `key:` value`` pairs so the Settings dialog **keys**
|
||||
(`Comfy`, `Comfy-Desktop`, `Comfy.ColorPalette`) are preserved while the
|
||||
displayed section header becomes **Studio** / **Studio Desktop** (this is the
|
||||
"Settings must not say Comfy" fix) + `Comfy Cloud` → `Hanzo Cloud`. Scope:
|
||||
English only — files above `LOCALE_RATIO` (0.15%) non-ASCII are skipped (all 33
|
||||
locale bundles are ≥0.31%; English bundles ~0.02%) and each match needs a
|
||||
pure-ASCII window, so the non-English `main-*/nodeDefs-*/commands-*` chunks are
|
||||
left as-is (noted, out of scope). Idempotent. Verified: served English display
|
||||
`ComfyUI` 63→0; a few example filesystem paths (`Documents/ComfyUI`) and the
|
||||
external comfy.org service labels (`Comfy API Key`, `Comfy Node Registry`,
|
||||
behind disabled API-nodes) are intentionally left. patch_frontend.py keeps its
|
||||
orthogonal concern (URLs/domains + `Comfy-Org` → `Hanzo AI`).
|
||||
|
||||
## Tests
|
||||
`tests-unit/{folder_paths_test,middleware_test,app_test,prompt_server_test}` —
|
||||
run `uv run pytest tests-unit/folder_paths_test tests-unit/middleware_test
|
||||
tests-unit/app_test tests-unit/prompt_server_test -q`. The auth/tenancy work is
|
||||
covered by `middleware_test/iam_auth_test.py`,
|
||||
`middleware_test/session_test.py` and
|
||||
`folder_paths_test/org_scoping_test.py`.
|
||||
@@ -0,0 +1,39 @@
|
||||
Hanzo Studio
|
||||
============
|
||||
|
||||
Hanzo Studio is a fork of ComfyUI (https://github.com/comfyanonymous/ComfyUI),
|
||||
Copyright (c) ComfyUI contributors, licensed under the GNU General Public
|
||||
License v3.0 (GPL-3.0).
|
||||
|
||||
Modifications Copyright (c) 2026 Hanzo Industries Inc, also released under the
|
||||
GNU General Public License v3.0 (GPL-3.0). See the LICENSE file for the full
|
||||
license text.
|
||||
|
||||
As required by the GPL-3.0, this fork remains under the same license as the
|
||||
upstream project. The original ComfyUI copyright notices are retained.
|
||||
|
||||
Modifications made in this fork
|
||||
-------------------------------
|
||||
|
||||
- Branding: user-visible name changed from "ComfyUI" to "Hanzo Studio",
|
||||
including the served page <title>, favicon, in-app logos, and display
|
||||
strings. The prebuilt frontend package (comfyui-frontend-package) is patched
|
||||
at install time by branding/apply-branding.sh; source assets live in
|
||||
branding/. Backend Python packages are namespaced under studio_* and the
|
||||
server reports itself as "Hanzo Studio".
|
||||
|
||||
- Hanzo Engine integration nodes (custom_nodes/hanzo_engine/): custom nodes
|
||||
that call a local Hanzo Engine OpenAI-compatible server — HanzoChat
|
||||
(chat/completions), HanzoImageGen (images/generations), and
|
||||
HanzoVisionCaption (vision chat/completions). Hanzo Engine is the native-Rust,
|
||||
all-modality inference and training backend.
|
||||
|
||||
- Fashion/swimwear starter workflows (user/default/workflows/fashion/):
|
||||
product-shot, garment-edit, and caption-dataset workflow templates for a
|
||||
designer's day-to-day use.
|
||||
|
||||
- Deployment/integration middleware for Hanzo infrastructure (IAM auth via
|
||||
hanzo.id, commerce/billing, metrics), wired through environment variables and
|
||||
the Dockerfile.
|
||||
|
||||
Third-party components retain their own upstream copyrights and licenses.
|
||||
@@ -1,4 +1,4 @@
|
||||
# The Comfy guide to Quantization
|
||||
# The Hanzo Studio guide to Quantization
|
||||
|
||||
|
||||
## How does quantization work?
|
||||
@@ -27,7 +27,7 @@ tensor_dq ~ tensor
|
||||
Given that additional information (scaling factor) is needed to "interpret" the quantized values, we describe those as derived datatypes.
|
||||
|
||||
|
||||
## Quantization in Comfy
|
||||
## Quantization in Hanzo Studio
|
||||
|
||||
```
|
||||
QuantizedTensor (torch.Tensor subclass)
|
||||
@@ -39,7 +39,7 @@ MixedPrecisionOps + Metadata Detection
|
||||
|
||||
### Representation
|
||||
|
||||
To represent these derived datatypes, ComfyUI uses a subclass of torch.Tensor to implements these using the `QuantizedTensor` class found in `comfy/quant_ops.py`
|
||||
To represent these derived datatypes, Hanzo Studio uses a subclass of torch.Tensor to implements these using the `QuantizedTensor` class found in `studio/quant_ops.py`
|
||||
|
||||
A `Layout` class defines how a specific quantization format behaves:
|
||||
- Required parameters
|
||||
@@ -47,7 +47,7 @@ A `Layout` class defines how a specific quantization format behaves:
|
||||
- De-Quantize method
|
||||
|
||||
```python
|
||||
from comfy.quant_ops import QuantizedLayout
|
||||
from studio.quant_ops import QuantizedLayout
|
||||
|
||||
class MyLayout(QuantizedLayout):
|
||||
@classmethod
|
||||
@@ -67,7 +67,7 @@ The first is a **generic registry** that handles operations common to all quanti
|
||||
|
||||
The second registry is layout-specific and allows to implement fast-paths like nn.Linear.
|
||||
```python
|
||||
from comfy.quant_ops import register_layout_op
|
||||
from studio.quant_ops import register_layout_op
|
||||
|
||||
@register_layout_op(torch.ops.aten.linear.default, MyLayout)
|
||||
def my_linear(func, args, kwargs):
|
||||
@@ -80,7 +80,7 @@ For any unsupported operation, QuantizedTensor will fallback to call `dequantize
|
||||
|
||||
### Mixed Precision
|
||||
|
||||
The `MixedPrecisionOps` class (lines 542-648 in `comfy/ops.py`) enables per-layer quantization decisions, allowing different layers in a model to use different precisions. This is activated when a model config contains a `layer_quant_config` dictionary that specifies which layers should be quantized and how.
|
||||
The `MixedPrecisionOps` class (lines 542-648 in `studio/ops.py`) enables per-layer quantization decisions, allowing different layers in a model to use different precisions. This is activated when a model config contains a `layer_quant_config` dictionary that specifies which layers should be quantized and how.
|
||||
|
||||
**Architecture:**
|
||||
|
||||
@@ -125,7 +125,7 @@ We define 4 possible scaling parameters that should cover most recipes in the ne
|
||||
|--------|---------------|--------------|----------------|-----------------|-------------|
|
||||
| float8_e4m3fn | float32 | float32 (scalar) | - | - | float32 (scalar) |
|
||||
|
||||
You can find the defined formats in `comfy/quant_ops.py` (QUANT_ALGOS).
|
||||
You can find the defined formats in `studio/quant_ops.py` (QUANT_ALGOS).
|
||||
|
||||
### Quantization Metadata
|
||||
|
||||
|
||||
@@ -1,92 +1,74 @@
|
||||
<p align="center"><img src=".github/hero.svg" alt="studio" width="880"></p>
|
||||
|
||||
<div align="center">
|
||||
|
||||
# ComfyUI
|
||||
# Hanzo Studio
|
||||
**The most powerful and modular visual AI engine and application.**
|
||||
|
||||
|
||||
[![Website][website-shield]][website-url]
|
||||
[![Dynamic JSON Badge][discord-shield]][discord-url]
|
||||
[![Twitter][twitter-shield]][twitter-url]
|
||||
[![Matrix][matrix-shield]][matrix-url]
|
||||
<br>
|
||||
[![][github-release-shield]][github-release-link]
|
||||
[![][github-release-date-shield]][github-release-link]
|
||||
[![][github-downloads-shield]][github-downloads-link]
|
||||
[![][github-downloads-latest-shield]][github-downloads-link]
|
||||
|
||||
[matrix-shield]: https://img.shields.io/badge/Matrix-000000?style=flat&logo=matrix&logoColor=white
|
||||
[matrix-url]: https://app.element.io/#/room/%23comfyui_space%3Amatrix.org
|
||||
[website-shield]: https://img.shields.io/badge/ComfyOrg-4285F4?style=flat
|
||||
[website-url]: https://www.comfy.org/
|
||||
[website-shield]: https://img.shields.io/badge/HanzoAI-4285F4?style=flat
|
||||
[website-url]: https://www.hanzo.ai/
|
||||
<!-- Workaround to display total user from https://github.com/badges/shields/issues/4500#issuecomment-2060079995 -->
|
||||
[discord-shield]: https://img.shields.io/badge/dynamic/json?url=https%3A%2F%2Fdiscord.com%2Fapi%2Finvites%2Fcomfyorg%3Fwith_counts%3Dtrue&query=%24.approximate_member_count&logo=discord&logoColor=white&label=Discord&color=green&suffix=%20total
|
||||
[discord-url]: https://www.comfy.org/discord
|
||||
[twitter-shield]: https://img.shields.io/twitter/follow/ComfyUI
|
||||
[twitter-url]: https://x.com/ComfyUI
|
||||
[discord-shield]: https://img.shields.io/badge/dynamic/json?url=https%3A%2F%2Fdiscord.com%2Fapi%2Finvites%2Fhanzoai%3Fwith_counts%3Dtrue&query=%24.approximate_member_count&logo=discord&logoColor=white&label=Discord&color=green&suffix=%20total
|
||||
[discord-url]: https://discord.gg/hanzoai
|
||||
[twitter-shield]: https://img.shields.io/twitter/follow/hanaboroai
|
||||
[twitter-url]: https://x.com/hanzoai
|
||||
|
||||
[github-release-shield]: https://img.shields.io/github/v/release/comfyanonymous/ComfyUI?style=flat&sort=semver
|
||||
[github-release-link]: https://github.com/comfyanonymous/ComfyUI/releases
|
||||
[github-release-date-shield]: https://img.shields.io/github/release-date/comfyanonymous/ComfyUI?style=flat
|
||||
[github-downloads-shield]: https://img.shields.io/github/downloads/comfyanonymous/ComfyUI/total?style=flat
|
||||
[github-downloads-latest-shield]: https://img.shields.io/github/downloads/comfyanonymous/ComfyUI/latest/total?style=flat&label=downloads%40latest
|
||||
[github-downloads-link]: https://github.com/comfyanonymous/ComfyUI/releases
|
||||
[github-release-shield]: https://img.shields.io/github/v/release/hanzoai/studio?style=flat&sort=semver
|
||||
[github-release-link]: https://github.com/hanzoai/studio/releases
|
||||
[github-release-date-shield]: https://img.shields.io/github/release-date/hanzoai/studio?style=flat
|
||||
[github-downloads-shield]: https://img.shields.io/github/downloads/hanzoai/studio/total?style=flat
|
||||
[github-downloads-latest-shield]: https://img.shields.io/github/downloads/hanzoai/studio/latest/total?style=flat&label=downloads%40latest
|
||||
[github-downloads-link]: https://github.com/hanzoai/studio/releases
|
||||
|
||||

|
||||

|
||||
</div>
|
||||
|
||||
ComfyUI lets you design and execute advanced stable diffusion pipelines using a graph/nodes/flowchart based interface. Available on Windows, Linux, and macOS.
|
||||
Hanzo Studio lets you design and execute advanced stable diffusion pipelines using a graph/nodes/flowchart based interface. Available on Windows, Linux, and macOS.
|
||||
|
||||
## Upstream & License
|
||||
|
||||
Hanzo Studio is a fork of [**ComfyUI**](https://github.com/comfyanonymous/ComfyUI) by comfyanonymous and the ComfyUI contributors — full credit to the upstream project for the engine this builds on. ComfyUI is licensed under the **GNU General Public License v3.0 (GPL-3.0)**, and this fork stays under the same license. All modifications in this fork are likewise GPL-3.0.
|
||||
|
||||
See [`LICENSE`](LICENSE) for the full license text and [`NOTICE`](NOTICE) for the fork's attribution and a summary of what it changes (branding, Hanzo Engine integration nodes, and fashion workflow templates).
|
||||
|
||||
## Get Started
|
||||
|
||||
#### [Desktop Application](https://www.comfy.org/download)
|
||||
- The easiest way to get started.
|
||||
- Available on Windows & macOS.
|
||||
|
||||
#### [Windows Portable Package](#installing)
|
||||
- Get the latest commits and completely portable.
|
||||
- Available on Windows.
|
||||
|
||||
#### [Manual Install](#manual-install-windows-linux)
|
||||
Supports all operating systems and GPU types (NVIDIA, AMD, Intel, Apple Silicon, Ascend).
|
||||
|
||||
## [Examples](https://comfyanonymous.github.io/ComfyUI_examples/)
|
||||
See what ComfyUI can do with the [example workflows](https://comfyanonymous.github.io/ComfyUI_examples/).
|
||||
#### Docker (Recommended for servers)
|
||||
```bash
|
||||
docker pull ghcr.io/hanzoai/studio:latest
|
||||
docker run -p 8188:8188 ghcr.io/hanzoai/studio:latest --listen --cpu
|
||||
```
|
||||
|
||||
## Features
|
||||
- Nodes/graph/flowchart interface to experiment and create complex Stable Diffusion workflows without needing to code anything.
|
||||
- Image Models
|
||||
- SD1.x, SD2.x ([unCLIP](https://comfyanonymous.github.io/ComfyUI_examples/unclip/))
|
||||
- [SDXL](https://comfyanonymous.github.io/ComfyUI_examples/sdxl/), [SDXL Turbo](https://comfyanonymous.github.io/ComfyUI_examples/sdturbo/)
|
||||
- [Stable Cascade](https://comfyanonymous.github.io/ComfyUI_examples/stable_cascade/)
|
||||
- [SD3 and SD3.5](https://comfyanonymous.github.io/ComfyUI_examples/sd3/)
|
||||
- Pixart Alpha and Sigma
|
||||
- [AuraFlow](https://comfyanonymous.github.io/ComfyUI_examples/aura_flow/)
|
||||
- [HunyuanDiT](https://comfyanonymous.github.io/ComfyUI_examples/hunyuan_dit/)
|
||||
- [Flux](https://comfyanonymous.github.io/ComfyUI_examples/flux/)
|
||||
- [Lumina Image 2.0](https://comfyanonymous.github.io/ComfyUI_examples/lumina2/)
|
||||
- [HiDream](https://comfyanonymous.github.io/ComfyUI_examples/hidream/)
|
||||
- [Qwen Image](https://comfyanonymous.github.io/ComfyUI_examples/qwen_image/)
|
||||
- [Hunyuan Image 2.1](https://comfyanonymous.github.io/ComfyUI_examples/hunyuan_image/)
|
||||
- [Flux 2](https://comfyanonymous.github.io/ComfyUI_examples/flux2/)
|
||||
- [Z Image](https://comfyanonymous.github.io/ComfyUI_examples/z_image/)
|
||||
- SD1.x, SD2.x, SDXL, SDXL Turbo
|
||||
- Stable Cascade, SD3 and SD3.5
|
||||
- Pixart Alpha and Sigma, AuraFlow, HunyuanDiT
|
||||
- Flux, Flux 2, Lumina Image 2.0, HiDream
|
||||
- Qwen Image, Hunyuan Image 2.1, Z Image
|
||||
- Image Editing Models
|
||||
- [Omnigen 2](https://comfyanonymous.github.io/ComfyUI_examples/omnigen/)
|
||||
- [Flux Kontext](https://comfyanonymous.github.io/ComfyUI_examples/flux/#flux-kontext-image-editing-model)
|
||||
- [HiDream E1.1](https://comfyanonymous.github.io/ComfyUI_examples/hidream/#hidream-e11)
|
||||
- [Qwen Image Edit](https://comfyanonymous.github.io/ComfyUI_examples/qwen_image/#edit-model)
|
||||
- Omnigen 2, Flux Kontext, HiDream E1.1, Qwen Image Edit
|
||||
- Video Models
|
||||
- [Stable Video Diffusion](https://comfyanonymous.github.io/ComfyUI_examples/video/)
|
||||
- [Mochi](https://comfyanonymous.github.io/ComfyUI_examples/mochi/)
|
||||
- [LTX-Video](https://comfyanonymous.github.io/ComfyUI_examples/ltxv/)
|
||||
- [Hunyuan Video](https://comfyanonymous.github.io/ComfyUI_examples/hunyuan_video/)
|
||||
- [Wan 2.1](https://comfyanonymous.github.io/ComfyUI_examples/wan/)
|
||||
- [Wan 2.2](https://comfyanonymous.github.io/ComfyUI_examples/wan22/)
|
||||
- [Hunyuan Video 1.5](https://docs.comfy.org/tutorials/video/hunyuan/hunyuan-video-1-5)
|
||||
- Stable Video Diffusion, Mochi, LTX-Video
|
||||
- Hunyuan Video, Wan 2.1, Wan 2.2, Hunyuan Video 1.5
|
||||
- Audio Models
|
||||
- [Stable Audio](https://comfyanonymous.github.io/ComfyUI_examples/audio/)
|
||||
- [ACE Step](https://comfyanonymous.github.io/ComfyUI_examples/audio/)
|
||||
- Stable Audio, ACE Step
|
||||
- 3D Models
|
||||
- [Hunyuan3D 2.0](https://docs.comfy.org/tutorials/3d/hunyuan3D-2)
|
||||
- Hunyuan3D 2.0
|
||||
- Asynchronous Queue system
|
||||
- Many optimizations: Only re-executes the parts of the workflow that changes between executions.
|
||||
- Smart memory management: can automatically run large models on GPUs with as low as 1GB vram with smart offloading.
|
||||
@@ -94,45 +76,23 @@ See what ComfyUI can do with the [example workflows](https://comfyanonymous.gith
|
||||
- Can load ckpt and safetensors: All in one checkpoints or standalone diffusion models, VAEs and CLIP models.
|
||||
- Safe loading of ckpt, pt, pth, etc.. files.
|
||||
- Embeddings/Textual inversion
|
||||
- [Loras (regular, locon and loha)](https://comfyanonymous.github.io/ComfyUI_examples/lora/)
|
||||
- [Hypernetworks](https://comfyanonymous.github.io/ComfyUI_examples/hypernetworks/)
|
||||
- Loras (regular, locon and loha)
|
||||
- Hypernetworks
|
||||
- Loading full workflows (with seeds) from generated PNG, WebP and FLAC files.
|
||||
- Saving/Loading workflows as Json files.
|
||||
- Nodes interface can be used to create complex workflows like one for [Hires fix](https://comfyanonymous.github.io/ComfyUI_examples/2_pass_txt2img/) or much more advanced ones.
|
||||
- [Area Composition](https://comfyanonymous.github.io/ComfyUI_examples/area_composition/)
|
||||
- [Inpainting](https://comfyanonymous.github.io/ComfyUI_examples/inpaint/) with both regular and inpainting models.
|
||||
- [ControlNet and T2I-Adapter](https://comfyanonymous.github.io/ComfyUI_examples/controlnet/)
|
||||
- [Upscale Models (ESRGAN, ESRGAN variants, SwinIR, Swin2SR, etc...)](https://comfyanonymous.github.io/ComfyUI_examples/upscale_models/)
|
||||
- [GLIGEN](https://comfyanonymous.github.io/ComfyUI_examples/gligen/)
|
||||
- [Model Merging](https://comfyanonymous.github.io/ComfyUI_examples/model_merging/)
|
||||
- [LCM models and Loras](https://comfyanonymous.github.io/ComfyUI_examples/lcm/)
|
||||
- Latent previews with [TAESD](#how-to-show-high-quality-previews)
|
||||
- Nodes interface can be used to create complex workflows like one for Hires fix or much more advanced ones.
|
||||
- Area Composition
|
||||
- Inpainting with both regular and inpainting models.
|
||||
- ControlNet and T2I-Adapter
|
||||
- Upscale Models (ESRGAN, ESRGAN variants, SwinIR, Swin2SR, etc...)
|
||||
- GLIGEN
|
||||
- Model Merging
|
||||
- LCM models and Loras
|
||||
- Latent previews with TAESD
|
||||
- Works fully offline: core will never download anything unless you want to.
|
||||
- Optional API nodes to use paid models from external providers through the online [Comfy API](https://docs.comfy.org/tutorials/api-nodes/overview) disable with: `--disable-api-nodes`
|
||||
- Optional API nodes to use paid models from external providers through the online API. Disable with: `--disable-api-nodes`
|
||||
- [Config file](extra_model_paths.yaml.example) to set the search paths for models.
|
||||
|
||||
Workflow examples can be found on the [Examples page](https://comfyanonymous.github.io/ComfyUI_examples/)
|
||||
|
||||
## Release Process
|
||||
|
||||
ComfyUI follows a weekly release cycle targeting Monday but this regularly changes because of model releases or large changes to the codebase. There are three interconnected repositories:
|
||||
|
||||
1. **[ComfyUI Core](https://github.com/comfyanonymous/ComfyUI)**
|
||||
- Releases a new stable version (e.g., v0.7.0) roughly every week.
|
||||
- Starting from v0.4.0 patch versions will be used for fixes backported onto the current stable release.
|
||||
- Minor versions will be used for releases off the master branch.
|
||||
- Patch versions may still be used for releases on the master branch in cases where a backport would not make sense.
|
||||
- Commits outside of the stable release tags may be very unstable and break many custom nodes.
|
||||
- Serves as the foundation for the desktop release
|
||||
|
||||
2. **[ComfyUI Desktop](https://github.com/Comfy-Org/desktop)**
|
||||
- Builds a new release using the latest stable core version
|
||||
|
||||
3. **[ComfyUI Frontend](https://github.com/Comfy-Org/ComfyUI_frontend)**
|
||||
- Weekly frontend updates are merged into the core repository
|
||||
- Features are frozen for the upcoming core release
|
||||
- Development continues for the next release cycle
|
||||
|
||||
## Shortcuts
|
||||
|
||||
| Keybind | Explanation |
|
||||
@@ -173,39 +133,6 @@ ComfyUI follows a weekly release cycle targeting Monday but this regularly chang
|
||||
|
||||
# Installing
|
||||
|
||||
## Windows Portable
|
||||
|
||||
There is a portable standalone build for Windows that should work for running on Nvidia GPUs or for running on your CPU only on the [releases page](https://github.com/comfyanonymous/ComfyUI/releases).
|
||||
|
||||
### [Direct link to download](https://github.com/comfyanonymous/ComfyUI/releases/latest/download/ComfyUI_windows_portable_nvidia.7z)
|
||||
|
||||
Simply download, extract with [7-Zip](https://7-zip.org) or with the windows explorer on recent windows versions and run. For smaller models you normally only need to put the checkpoints (the huge ckpt/safetensors files) in: ComfyUI\models\checkpoints but many of the larger models have multiple files. Make sure to follow the instructions to know which subfolder to put them in ComfyUI\models\
|
||||
|
||||
If you have trouble extracting it, right click the file -> properties -> unblock
|
||||
|
||||
The portable above currently comes with python 3.13 and pytorch cuda 13.0. Update your Nvidia drivers if it doesn't start.
|
||||
|
||||
#### Alternative Downloads:
|
||||
|
||||
[Experimental portable for AMD GPUs](https://github.com/comfyanonymous/ComfyUI/releases/latest/download/ComfyUI_windows_portable_amd.7z)
|
||||
|
||||
[Portable with pytorch cuda 12.8 and python 3.12](https://github.com/comfyanonymous/ComfyUI/releases/latest/download/ComfyUI_windows_portable_nvidia_cu128.7z).
|
||||
|
||||
[Portable with pytorch cuda 12.6 and python 3.12](https://github.com/comfyanonymous/ComfyUI/releases/latest/download/ComfyUI_windows_portable_nvidia_cu126.7z) (Supports Nvidia 10 series and older GPUs).
|
||||
|
||||
#### How do I share models between another UI and ComfyUI?
|
||||
|
||||
See the [Config file](extra_model_paths.yaml.example) to set the search paths for models. In the standalone windows build you can find this file in the ComfyUI directory. Rename this file to extra_model_paths.yaml and edit it with your favorite text editor.
|
||||
|
||||
|
||||
## [comfy-cli](https://docs.comfy.org/comfy-cli/getting-started)
|
||||
|
||||
You can install and start ComfyUI using comfy-cli:
|
||||
```bash
|
||||
pip install comfy-cli
|
||||
comfy install
|
||||
```
|
||||
|
||||
## Manual Install (Windows, Linux)
|
||||
|
||||
Python 3.14 works but some custom nodes may have issues. The free threaded variant works but some dependencies will enable the GIL so it's not fully supported.
|
||||
@@ -282,24 +209,24 @@ And install it again with the command above.
|
||||
|
||||
### Dependencies
|
||||
|
||||
Install the dependencies by opening your terminal inside the ComfyUI folder and:
|
||||
Install the dependencies by opening your terminal inside the Hanzo Studio folder and:
|
||||
|
||||
```pip install -r requirements.txt```
|
||||
|
||||
After this you should have everything installed and can proceed to running ComfyUI.
|
||||
After this you should have everything installed and can proceed to running Hanzo Studio.
|
||||
|
||||
### Others:
|
||||
|
||||
#### Apple Mac silicon
|
||||
|
||||
You can install ComfyUI in Apple Mac silicon (M1 or M2) with any recent macOS version.
|
||||
You can install Hanzo Studio in Apple Mac silicon (M1 or M2) with any recent macOS version.
|
||||
|
||||
1. Install pytorch nightly. For instructions, read the [Accelerated PyTorch training on Mac](https://developer.apple.com/metal/pytorch/) Apple Developer guide (make sure to install the latest pytorch nightly).
|
||||
1. Follow the [ComfyUI manual installation](#manual-install-windows-linux) instructions for Windows and Linux.
|
||||
1. Install the ComfyUI [dependencies](#dependencies). If you have another Stable Diffusion UI [you might be able to reuse the dependencies](#i-already-have-another-ui-for-stable-diffusion-installed-do-i-really-have-to-install-all-of-these-dependencies).
|
||||
1. Launch ComfyUI by running `python main.py`
|
||||
1. Follow the [Hanzo Studio manual installation](#manual-install-windows-linux) instructions for Windows and Linux.
|
||||
1. Install the Hanzo Studio [dependencies](#dependencies). If you have another Stable Diffusion UI you might be able to reuse the dependencies.
|
||||
1. Launch Hanzo Studio by running `python main.py`
|
||||
|
||||
> **Note**: Remember to add your models, VAE, LoRAs etc. to the corresponding Comfy folders, as discussed in [ComfyUI manual installation](#manual-install-windows-linux).
|
||||
> **Note**: Remember to add your models, VAE, LoRAs etc. to the corresponding folders, as discussed in [Hanzo Studio manual installation](#manual-install-windows-linux).
|
||||
|
||||
#### Ascend NPUs
|
||||
|
||||
@@ -308,7 +235,7 @@ For models compatible with Ascend Extension for PyTorch (torch_npu). To get star
|
||||
1. Begin by installing the recommended or newer kernel version for Linux as specified in the Installation page of torch-npu, if necessary.
|
||||
2. Proceed with the installation of Ascend Basekit, which includes the driver, firmware, and CANN, following the instructions provided for your specific platform.
|
||||
3. Next, install the necessary packages for torch-npu by adhering to the platform-specific instructions on the [Installation](https://ascend.github.io/docs/sources/pytorch/install.html#pytorch) page.
|
||||
4. Finally, adhere to the [ComfyUI manual installation](#manual-install-windows-linux) guide for Linux. Once all components are installed, you can run ComfyUI as described earlier.
|
||||
4. Finally, adhere to the [Hanzo Studio manual installation](#manual-install-windows-linux) guide for Linux. Once all components are installed, you can run Hanzo Studio as described earlier.
|
||||
|
||||
#### Cambricon MLUs
|
||||
|
||||
@@ -316,19 +243,19 @@ For models compatible with Cambricon Extension for PyTorch (torch_mlu). Here's a
|
||||
|
||||
1. Install the Cambricon CNToolkit by adhering to the platform-specific instructions on the [Installation](https://www.cambricon.com/docs/sdk_1.15.0/cntoolkit_3.7.2/cntoolkit_install_3.7.2/index.html)
|
||||
2. Next, install the PyTorch(torch_mlu) following the instructions on the [Installation](https://www.cambricon.com/docs/sdk_1.15.0/cambricon_pytorch_1.17.0/user_guide_1.9/index.html)
|
||||
3. Launch ComfyUI by running `python main.py`
|
||||
3. Launch Hanzo Studio by running `python main.py`
|
||||
|
||||
#### Iluvatar Corex
|
||||
|
||||
For models compatible with Iluvatar Extension for PyTorch. Here's a step-by-step guide tailored to your platform and installation method:
|
||||
|
||||
1. Install the Iluvatar Corex Toolkit by adhering to the platform-specific instructions on the [Installation](https://support.iluvatar.com/#/DocumentCentre?id=1&nameCenter=2&productId=520117912052801536)
|
||||
2. Launch ComfyUI by running `python main.py`
|
||||
2. Launch Hanzo Studio by running `python main.py`
|
||||
|
||||
|
||||
## [ComfyUI-Manager](https://github.com/Comfy-Org/ComfyUI-Manager/tree/manager-v4)
|
||||
## Manager
|
||||
|
||||
**ComfyUI-Manager** is an extension that allows you to easily install, update, and manage custom nodes for ComfyUI.
|
||||
The **Manager** extension allows you to easily install, update, and manage custom nodes for Hanzo Studio.
|
||||
|
||||
### Setup
|
||||
|
||||
@@ -337,7 +264,7 @@ For models compatible with Iluvatar Extension for PyTorch. Here's a step-by-step
|
||||
pip install -r manager_requirements.txt
|
||||
```
|
||||
|
||||
2. Enable the manager with the `--enable-manager` flag when running ComfyUI:
|
||||
2. Enable the manager with the `--enable-manager` flag when running Hanzo Studio:
|
||||
```bash
|
||||
python main.py --enable-manager
|
||||
```
|
||||
@@ -346,7 +273,7 @@ For models compatible with Iluvatar Extension for PyTorch. Here's a step-by-step
|
||||
|
||||
| Flag | Description |
|
||||
|------|-------------|
|
||||
| `--enable-manager` | Enable ComfyUI-Manager |
|
||||
| `--enable-manager` | Enable the Manager |
|
||||
| `--enable-manager-legacy-ui` | Use the legacy manager UI instead of the new UI (requires `--enable-manager`) |
|
||||
| `--disable-manager-ui` | Disable the manager UI and endpoints while keeping background features like security checks and scheduled installation completion (requires `--enable-manager`) |
|
||||
|
||||
@@ -365,7 +292,7 @@ For AMD 7600 and maybe other RDNA3 cards: ```HSA_OVERRIDE_GFX_VERSION=11.0.0 pyt
|
||||
|
||||
### AMD ROCm Tips
|
||||
|
||||
You can enable experimental memory efficient attention on recent pytorch in ComfyUI on some AMD GPUs using this command, it should already be enabled by default on RDNA3. If this improves speed for you on latest pytorch on your GPU please report it so that I can enable it by default.
|
||||
You can enable experimental memory efficient attention on recent pytorch on some AMD GPUs using this command, it should already be enabled by default on RDNA3. If this improves speed for you on latest pytorch on your GPU please report it so that we can enable it by default.
|
||||
|
||||
```TORCH_ROCM_AOTRITON_ENABLE_EXPERIMENTAL=1 python main.py --use-pytorch-cross-attention```
|
||||
|
||||
@@ -379,9 +306,9 @@ Only parts of the graph that change from each execution to the next will be exec
|
||||
|
||||
Dragging a generated png on the webpage or loading one will give you the full workflow including seeds that were used to create it.
|
||||
|
||||
You can use () to change emphasis of a word or phrase like: (good code:1.2) or (bad code:0.8). The default emphasis for () is 1.1. To use () characters in your actual prompt escape them like \\( or \\).
|
||||
You can use () to change emphasis of a word or phrase like: (good code:1.2) or (bad code:0.8). The default emphasis for () is 1.1. To use () characters in your actual prompt escape them like \( or \).
|
||||
|
||||
You can use {day|night}, for wildcard/dynamic prompts. With this syntax "{wild|card|test}" will be randomly replaced by either "wild", "card" or "test" by the frontend every time you queue the prompt. To use {} characters in your actual prompt escape them like: \\{ or \\}.
|
||||
You can use {day|night}, for wildcard/dynamic prompts. With this syntax "{wild|card|test}" will be randomly replaced by either "wild", "card" or "test" by the frontend every time you queue the prompt. To use {} characters in your actual prompt escape them like: \{ or \}.
|
||||
|
||||
Dynamic prompts also support C-style comments, like `// comment` or `/* comment */`.
|
||||
|
||||
@@ -394,7 +321,7 @@ To use a textual inversion concepts/embeddings in a text prompt put them in the
|
||||
|
||||
Use ```--preview-method auto``` to enable previews.
|
||||
|
||||
The default installation includes a fast latent preview method that's low-resolution. To enable higher-quality previews with [TAESD](https://github.com/madebyollin/taesd), download the [taesd_decoder.pth, taesdxl_decoder.pth, taesd3_decoder.pth and taef1_decoder.pth](https://github.com/madebyollin/taesd/) and place them in the `models/vae_approx` folder. Once they're installed, restart ComfyUI and launch it with `--preview-method taesd` to enable high-quality previews.
|
||||
The default installation includes a fast latent preview method that's low-resolution. To enable higher-quality previews with [TAESD](https://github.com/madebyollin/taesd), download the [taesd_decoder.pth, taesdxl_decoder.pth, taesd3_decoder.pth and taef1_decoder.pth](https://github.com/madebyollin/taesd/) and place them in the `models/vae_approx` folder. Once they're installed, restart Hanzo Studio and launch it with `--preview-method taesd` to enable high-quality previews.
|
||||
|
||||
## How to use TLS/SSL?
|
||||
Generate a self-signed certificate (not appropriate for shared/production use) and key by running the command: `openssl req -x509 -newkey rsa:4096 -keyout key.pem -out cert.pem -sha256 -days 3650 -nodes -subj "/C=XX/ST=StateName/L=CityName/O=CompanyName/OU=CompanySectionName/CN=CommonNameOrHostname"`
|
||||
@@ -406,55 +333,6 @@ Use `--tls-keyfile key.pem --tls-certfile cert.pem` to enable TLS/SSL, the app w
|
||||
|
||||
## Support and dev channel
|
||||
|
||||
[Discord](https://comfy.org/discord): Try the #help or #feedback channels.
|
||||
[Discord](https://discord.gg/hanzoai): Try the #help or #feedback channels.
|
||||
|
||||
[Matrix space: #comfyui_space:matrix.org](https://app.element.io/#/room/%23comfyui_space%3Amatrix.org) (it's like discord but open source).
|
||||
|
||||
See also: [https://www.comfy.org/](https://www.comfy.org/)
|
||||
|
||||
## Frontend Development
|
||||
|
||||
As of August 15, 2024, we have transitioned to a new frontend, which is now hosted in a separate repository: [ComfyUI Frontend](https://github.com/Comfy-Org/ComfyUI_frontend). This repository now hosts the compiled JS (from TS/Vue) under the `web/` directory.
|
||||
|
||||
### Reporting Issues and Requesting Features
|
||||
|
||||
For any bugs, issues, or feature requests related to the frontend, please use the [ComfyUI Frontend repository](https://github.com/Comfy-Org/ComfyUI_frontend). This will help us manage and address frontend-specific concerns more efficiently.
|
||||
|
||||
### Using the Latest Frontend
|
||||
|
||||
The new frontend is now the default for ComfyUI. However, please note:
|
||||
|
||||
1. The frontend in the main ComfyUI repository is updated fortnightly.
|
||||
2. Daily releases are available in the separate frontend repository.
|
||||
|
||||
To use the most up-to-date frontend version:
|
||||
|
||||
1. For the latest daily release, launch ComfyUI with this command line argument:
|
||||
|
||||
```
|
||||
--front-end-version Comfy-Org/ComfyUI_frontend@latest
|
||||
```
|
||||
|
||||
2. For a specific version, replace `latest` with the desired version number:
|
||||
|
||||
```
|
||||
--front-end-version Comfy-Org/ComfyUI_frontend@1.2.2
|
||||
```
|
||||
|
||||
This approach allows you to easily switch between the stable fortnightly release and the cutting-edge daily updates, or even specific versions for testing purposes.
|
||||
|
||||
### Accessing the Legacy Frontend
|
||||
|
||||
If you need to use the legacy frontend for any reason, you can access it using the following command line argument:
|
||||
|
||||
```
|
||||
--front-end-version Comfy-Org/ComfyUI_legacy_frontend@latest
|
||||
```
|
||||
|
||||
This will use a snapshot of the legacy frontend preserved in the [ComfyUI Legacy Frontend repository](https://github.com/Comfy-Org/ComfyUI_legacy_frontend).
|
||||
|
||||
# QA
|
||||
|
||||
### Which GPU should I buy for this?
|
||||
|
||||
[See this page for some recommendations](https://github.com/comfyanonymous/ComfyUI/wiki/Which-GPU-should-I-buy-for-ComfyUI)
|
||||
See also: [https://www.hanzo.ai/](https://www.hanzo.ai/)
|
||||
|
||||
@@ -63,7 +63,7 @@ version_path_separator = os
|
||||
# are written from script.py.mako
|
||||
# output_encoding = utf-8
|
||||
|
||||
sqlalchemy.url = sqlite:///user/comfyui.db
|
||||
sqlalchemy.url = sqlite:///user/hanzo_studio.db
|
||||
|
||||
|
||||
[post_write_hooks]
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
# ComfyUI Internal Routes
|
||||
# Hanzo Studio Internal Routes
|
||||
|
||||
All routes under the `/internal` path are designated for **internal use by ComfyUI only**. These routes are not intended for use by external applications may change at any time without notice.
|
||||
All routes under the `/internal` path are designated for **internal use by Hanzo Studio only**. These routes are not intended for use by external applications may change at any time without notice.
|
||||
|
||||
@@ -8,7 +8,7 @@ import os
|
||||
class InternalRoutes:
|
||||
'''
|
||||
The top level web router for internal routes: /internal/*
|
||||
The endpoints here should NOT be depended upon. It is for ComfyUI frontend use only.
|
||||
The endpoints here should NOT be depended upon. It is for Hanzo Studio frontend use only.
|
||||
Check README.md for more information.
|
||||
'''
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ class AppSettings():
|
||||
try:
|
||||
file = self.user_manager.get_request_user_filepath(
|
||||
request,
|
||||
"comfy.settings.json"
|
||||
"studio.settings.json"
|
||||
)
|
||||
except KeyError as e:
|
||||
logging.error("User settings not found.")
|
||||
@@ -29,7 +29,7 @@ class AppSettings():
|
||||
|
||||
def save_settings(self, request, settings):
|
||||
file = self.user_manager.get_request_user_filepath(
|
||||
request, "comfy.settings.json")
|
||||
request, "studio.settings.json")
|
||||
with open(file, "w") as f:
|
||||
f.write(json.dumps(settings, indent=4))
|
||||
|
||||
|
||||
@@ -37,7 +37,7 @@ def list_tree(base_dir: str) -> list[str]:
|
||||
def prefixes_for_root(root: RootType) -> list[str]:
|
||||
if root == "models":
|
||||
bases: list[str] = []
|
||||
for _bucket, paths in get_comfy_models_folders():
|
||||
for _bucket, paths in get_studio_models_folders():
|
||||
bases.extend(paths)
|
||||
return [os.path.abspath(p) for p in bases]
|
||||
if root == "input":
|
||||
@@ -74,16 +74,16 @@ def utcnow() -> datetime:
|
||||
"""Naive UTC timestamp (no tzinfo). We always treat DB datetimes as UTC."""
|
||||
return datetime.now(timezone.utc).replace(tzinfo=None)
|
||||
|
||||
def get_comfy_models_folders() -> list[tuple[str, list[str]]]:
|
||||
def get_studio_models_folders() -> list[tuple[str, list[str]]]:
|
||||
"""Build a list of (folder_name, base_paths[]) categories that are configured for model locations.
|
||||
|
||||
We trust `folder_paths.folder_names_and_paths` and include a category if
|
||||
*any* of its base paths lies under the Comfy `models_dir`.
|
||||
*any* of its base paths lies under the Studio `models_dir`.
|
||||
"""
|
||||
targets: list[tuple[str, list[str]]] = []
|
||||
models_root = os.path.abspath(folder_paths.models_dir)
|
||||
for name, values in folder_paths.folder_names_and_paths.items():
|
||||
paths, _exts = values[0], values[1] # NOTE: this prevents nodepacks that hackily edit folder_... from breaking ComfyUI
|
||||
paths, _exts = values[0], values[1] # NOTE: this prevents nodepacks that hackily edit folder_... from breaking Hanzo Studio
|
||||
if any(os.path.abspath(p).startswith(models_root + os.sep) for p in paths):
|
||||
targets.append((name, paths))
|
||||
return targets
|
||||
@@ -152,7 +152,7 @@ def get_relative_to_root_category_path_of_asset(file_path: str) -> tuple[Literal
|
||||
"""Given an absolute or relative file path, determine which root category the path belongs to:
|
||||
- 'input' if the file resides under `folder_paths.get_input_directory()`
|
||||
- 'output' if the file resides under `folder_paths.get_output_directory()`
|
||||
- 'models' if the file resides under any base path of categories returned by `get_comfy_models_folders()`
|
||||
- 'models' if the file resides under any base path of categories returned by `get_studio_models_folders()`
|
||||
|
||||
Returns:
|
||||
(root_category, relative_path_inside_that_root)
|
||||
@@ -185,7 +185,7 @@ def get_relative_to_root_category_path_of_asset(file_path: str) -> tuple[Literal
|
||||
|
||||
# 3) models (check deepest matching base to avoid ambiguity)
|
||||
best: tuple[int, str, str] | None = None # (base_len, bucket, rel_inside_bucket)
|
||||
for bucket, bases in get_comfy_models_folders():
|
||||
for bucket, bases in get_studio_models_folders():
|
||||
for b in bases:
|
||||
base_abs = os.path.abspath(b)
|
||||
if not _is_within(fp_abs, base_abs):
|
||||
@@ -232,7 +232,7 @@ def normalize_tags(tags: list[str] | None) -> list[str]:
|
||||
|
||||
def collect_models_files() -> list[str]:
|
||||
out: list[str] = []
|
||||
for folder_name, bases in get_comfy_models_folders():
|
||||
for folder_name, bases in get_studio_models_folders():
|
||||
rel_files = folder_paths.get_filename_list(folder_name) or []
|
||||
for rel_path in rel_files:
|
||||
abs_path = folder_paths.get_full_path(folder_name, rel_path)
|
||||
|
||||
@@ -3,7 +3,7 @@ import os
|
||||
import shutil
|
||||
from app.logger import log_startup_warning
|
||||
from utils.install_util import get_missing_requirements_message
|
||||
from comfy.cli_args import args
|
||||
from studio.cli_args import args
|
||||
|
||||
_DB_AVAILABLE = False
|
||||
Session = None
|
||||
@@ -24,7 +24,7 @@ except ImportError as e:
|
||||
------------------------------------------------------------------------
|
||||
Error importing dependencies: {e}
|
||||
{get_missing_requirements_message()}
|
||||
This error is happening because ComfyUI now uses a local sqlite database.
|
||||
This error is happening because Hanzo Studio now uses a local sqlite database.
|
||||
------------------------------------------------------------------------
|
||||
""".strip()
|
||||
)
|
||||
|
||||
@@ -19,7 +19,7 @@ from typing_extensions import NotRequired
|
||||
|
||||
from utils.install_util import get_missing_requirements_message, requirements_path
|
||||
|
||||
from comfy.cli_args import DEFAULT_VERSION_STRING
|
||||
from studio.cli_args import DEFAULT_VERSION_STRING
|
||||
import app.logger
|
||||
|
||||
|
||||
@@ -27,7 +27,7 @@ def frontend_install_warning_message():
|
||||
return f"""
|
||||
{get_missing_requirements_message()}
|
||||
|
||||
This error is happening because the ComfyUI frontend is no longer shipped as part of the main repo but as a pip package instead.
|
||||
This error is happening because the Hanzo Studio frontend is no longer shipped as part of the main repo but as a pip package instead.
|
||||
""".strip()
|
||||
|
||||
def parse_version(version: str) -> tuple[int, int, int]:
|
||||
@@ -87,7 +87,7 @@ ________________________________________________________________________
|
||||
""".strip()
|
||||
)
|
||||
else:
|
||||
logging.info("ComfyUI frontend version: {}".format(frontend_version_str))
|
||||
logging.info("Hanzo Studio frontend version: {}".format(frontend_version_str))
|
||||
except Exception as e:
|
||||
logging.error(f"Failed to check frontend version: {e}")
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ import time
|
||||
import logging
|
||||
import folder_paths
|
||||
import glob
|
||||
import comfy.utils
|
||||
import studio.utils
|
||||
from aiohttp import web
|
||||
from PIL import Image
|
||||
from io import BytesIO
|
||||
@@ -180,7 +180,7 @@ class ModelFileManager:
|
||||
|
||||
if safetensors_file:
|
||||
safetensors_filepath = os.path.join(dirname, safetensors_file)
|
||||
header = comfy.utils.safetensors_header(safetensors_filepath, max_size=8*1024*1024)
|
||||
header = studio.utils.safetensors_header(safetensors_filepath, max_size=8*1024*1024)
|
||||
if header:
|
||||
safetensors_metadata = json.loads(header)
|
||||
safetensors_images = safetensors_metadata.get("__metadata__", {}).get("ssmd_cover_images", None)
|
||||
|
||||
@@ -4,9 +4,9 @@ from aiohttp import web
|
||||
|
||||
from typing import TYPE_CHECKING, TypedDict
|
||||
if TYPE_CHECKING:
|
||||
from comfy_api.latest._io_public import NodeReplace
|
||||
from studio_api.latest._io_public import NodeReplace
|
||||
|
||||
from comfy_execution.graph_utils import is_link
|
||||
from studio_execution.graph_utils import is_link
|
||||
import nodes
|
||||
|
||||
class NodeStruct(TypedDict):
|
||||
|
||||
@@ -100,7 +100,7 @@ class SubgraphManager:
|
||||
if os.path.exists(blueprints_dir):
|
||||
for file in glob.glob(os.path.join(blueprints_dir, "*.json")):
|
||||
file = file.replace('\\', '/')
|
||||
entry_id, entry = self._create_entry(file, Source.templates, "comfyui")
|
||||
entry_id, entry = self._create_entry(file, Source.templates, "studio")
|
||||
subgraphs_dict[entry_id] = entry
|
||||
|
||||
self.cached_blueprint_subgraphs = subgraphs_dict
|
||||
|
||||
@@ -8,7 +8,7 @@ import shutil
|
||||
import logging
|
||||
from aiohttp import web
|
||||
from urllib import parse
|
||||
from comfy.cli_args import args
|
||||
from studio.cli_args import args
|
||||
import folder_paths
|
||||
from .app_settings import AppSettings
|
||||
from typing import TypedDict
|
||||
@@ -57,8 +57,22 @@ class UserManager():
|
||||
|
||||
def get_request_user_id(self, request):
|
||||
user = "default"
|
||||
if args.multi_user and "comfy-user" in request.headers:
|
||||
user = request.headers["comfy-user"]
|
||||
|
||||
# If IAM auth is active, extract user from IAM context
|
||||
iam_user = request.get("iam_user")
|
||||
if iam_user and iam_user.get("sub") and iam_user["sub"] != "local":
|
||||
user = iam_user["sub"]
|
||||
# A token subject must never escape into a System User namespace.
|
||||
if user.startswith(folder_paths.SYSTEM_USER_PREFIX):
|
||||
raise KeyError("Unknown user: " + user)
|
||||
# Auto-register IAM users
|
||||
if user not in self.users:
|
||||
display = iam_user.get("name") or iam_user.get("email") or user
|
||||
self.users[user] = display
|
||||
return user
|
||||
|
||||
if args.multi_user and "studio-user" in request.headers:
|
||||
user = request.headers["studio-user"]
|
||||
# Block System Users (use same error message to prevent probing)
|
||||
if user.startswith(folder_paths.SYSTEM_USER_PREFIX):
|
||||
raise KeyError("Unknown user: " + user)
|
||||
@@ -68,14 +82,23 @@ class UserManager():
|
||||
|
||||
return user
|
||||
|
||||
def get_request_org_id(self, request) -> str | None:
|
||||
"""Extract org_id from IAM context or fallback to CLI arg."""
|
||||
iam_user = request.get("iam_user")
|
||||
if iam_user and iam_user.get("org_id"):
|
||||
return iam_user["org_id"]
|
||||
return folder_paths.get_org_id()
|
||||
|
||||
def get_request_user_filepath(self, request, file, type="userdata", create_dir=True):
|
||||
if type == "userdata":
|
||||
root_dir = folder_paths.get_user_directory()
|
||||
# Use org-scoped user directory if multi-tenant
|
||||
org_id = self.get_request_org_id(request)
|
||||
root_dir = folder_paths.get_org_user_directory(org_id)
|
||||
else:
|
||||
raise KeyError("Unknown filepath type:" + type)
|
||||
|
||||
user = self.get_request_user_id(request)
|
||||
user_root = folder_paths.get_public_user_directory(user)
|
||||
user_root = folder_paths.get_public_user_directory(user, org_id)
|
||||
if user_root is None:
|
||||
return None
|
||||
path = user_root
|
||||
|
||||
@@ -1 +1 @@
|
||||
{"revision": 0, "last_node_id": 15, "last_link_id": 0, "nodes": [{"id": 15, "type": "24d8bbfd-39d4-4774-bff0-3de40cc7a471", "pos": [-1490, 2040], "size": [400, 260], "flags": {}, "order": 0, "mode": 0, "inputs": [{"name": "prompt", "type": "STRING", "widget": {"name": "prompt"}, "link": null}, {"label": "reference images", "name": "images", "type": "IMAGE", "link": null}], "outputs": [{"name": "STRING", "type": "STRING", "links": null}], "title": "Prompt Enhance", "properties": {"proxyWidgets": [["-1", "prompt"]], "cnr_id": "comfy-core", "ver": "0.14.1"}, "widgets_values": [""]}], "links": [], "version": 0.4, "definitions": {"subgraphs": [{"id": "24d8bbfd-39d4-4774-bff0-3de40cc7a471", "version": 1, "state": {"lastGroupId": 0, "lastNodeId": 15, "lastLinkId": 14, "lastRerouteId": 0}, "revision": 0, "config": {}, "name": "Prompt Enhance", "inputNode": {"id": -10, "bounding": [-2170, 2110, 138.876953125, 80]}, "outputNode": {"id": -20, "bounding": [-640, 2110, 120, 60]}, "inputs": [{"id": "aeab7216-00e0-4528-a09b-bba50845c5a6", "name": "prompt", "type": "STRING", "linkIds": [11], "pos": [-2051.123046875, 2130]}, {"id": "7b73fd36-aa31-4771-9066-f6c83879994b", "name": "images", "type": "IMAGE", "linkIds": [14], "label": "reference images", "pos": [-2051.123046875, 2150]}], "outputs": [{"id": "c7b0d930-68a1-48d1-b496-0519e5837064", "name": "STRING", "type": "STRING", "linkIds": [13], "pos": [-620, 2130]}], "widgets": [], "nodes": [{"id": 11, "type": "GeminiNode", "pos": [-1560, 1990], "size": [470, 470], "flags": {}, "order": 0, "mode": 0, "inputs": [{"localized_name": "images", "name": "images", "shape": 7, "type": "IMAGE", "link": 14}, {"localized_name": "audio", "name": "audio", "shape": 7, "type": "AUDIO", "link": null}, {"localized_name": "video", "name": "video", "shape": 7, "type": "VIDEO", "link": null}, {"localized_name": "files", "name": "files", "shape": 7, "type": "GEMINI_INPUT_FILES", "link": null}, {"localized_name": "prompt", "name": "prompt", "type": "STRING", "widget": {"name": "prompt"}, "link": 11}, {"localized_name": "model", "name": "model", "type": "COMBO", "widget": {"name": "model"}, "link": null}, {"localized_name": "seed", "name": "seed", "type": "INT", "widget": {"name": "seed"}, "link": null}, {"localized_name": "system_prompt", "name": "system_prompt", "shape": 7, "type": "STRING", "widget": {"name": "system_prompt"}, "link": null}], "outputs": [{"localized_name": "STRING", "name": "STRING", "type": "STRING", "links": [13]}], "properties": {"cnr_id": "comfy-core", "ver": "0.14.1", "Node name for S&R": "GeminiNode"}, "widgets_values": ["", "gemini-3-pro-preview", 42, "randomize", "You are an expert in prompt writing.\nBased on the input, rewrite the user's input into a detailed prompt.\nincluding camera settings, lighting, composition, and style.\nReturn the prompt only"], "color": "#432", "bgcolor": "#653"}], "groups": [], "links": [{"id": 11, "origin_id": -10, "origin_slot": 0, "target_id": 11, "target_slot": 4, "type": "STRING"}, {"id": 13, "origin_id": 11, "origin_slot": 0, "target_id": -20, "target_slot": 0, "type": "STRING"}, {"id": 14, "origin_id": -10, "origin_slot": 1, "target_id": 11, "target_slot": 0, "type": "IMAGE"}], "extra": {"workflowRendererVersion": "LG"}, "category": "Text generation/Prompt enhance"}]}, "extra": {}}
|
||||
{"revision": 0, "last_node_id": 15, "last_link_id": 0, "nodes": [{"id": 15, "type": "24d8bbfd-39d4-4774-bff0-3de40cc7a471", "pos": [-1490, 2040], "size": [400, 260], "flags": {}, "order": 0, "mode": 0, "inputs": [{"name": "prompt", "type": "STRING", "widget": {"name": "prompt"}, "link": null}, {"label": "reference images", "name": "images", "type": "IMAGE", "link": null}], "outputs": [{"name": "STRING", "type": "STRING", "links": null}], "title": "Prompt Enhance", "properties": {"proxyWidgets": [["-1", "prompt"]], "cnr_id": "studio-core", "ver": "0.14.1"}, "widgets_values": [""]}], "links": [], "version": 0.4, "definitions": {"subgraphs": [{"id": "24d8bbfd-39d4-4774-bff0-3de40cc7a471", "version": 1, "state": {"lastGroupId": 0, "lastNodeId": 15, "lastLinkId": 14, "lastRerouteId": 0}, "revision": 0, "config": {}, "name": "Prompt Enhance", "inputNode": {"id": -10, "bounding": [-2170, 2110, 138.876953125, 80]}, "outputNode": {"id": -20, "bounding": [-640, 2110, 120, 60]}, "inputs": [{"id": "aeab7216-00e0-4528-a09b-bba50845c5a6", "name": "prompt", "type": "STRING", "linkIds": [11], "pos": [-2051.123046875, 2130]}, {"id": "7b73fd36-aa31-4771-9066-f6c83879994b", "name": "images", "type": "IMAGE", "linkIds": [14], "label": "reference images", "pos": [-2051.123046875, 2150]}], "outputs": [{"id": "c7b0d930-68a1-48d1-b496-0519e5837064", "name": "STRING", "type": "STRING", "linkIds": [13], "pos": [-620, 2130]}], "widgets": [], "nodes": [{"id": 11, "type": "GeminiNode", "pos": [-1560, 1990], "size": [470, 470], "flags": {}, "order": 0, "mode": 0, "inputs": [{"localized_name": "images", "name": "images", "shape": 7, "type": "IMAGE", "link": 14}, {"localized_name": "audio", "name": "audio", "shape": 7, "type": "AUDIO", "link": null}, {"localized_name": "video", "name": "video", "shape": 7, "type": "VIDEO", "link": null}, {"localized_name": "files", "name": "files", "shape": 7, "type": "GEMINI_INPUT_FILES", "link": null}, {"localized_name": "prompt", "name": "prompt", "type": "STRING", "widget": {"name": "prompt"}, "link": 11}, {"localized_name": "model", "name": "model", "type": "COMBO", "widget": {"name": "model"}, "link": null}, {"localized_name": "seed", "name": "seed", "type": "INT", "widget": {"name": "seed"}, "link": null}, {"localized_name": "system_prompt", "name": "system_prompt", "shape": 7, "type": "STRING", "widget": {"name": "system_prompt"}, "link": null}], "outputs": [{"localized_name": "STRING", "name": "STRING", "type": "STRING", "links": [13]}], "properties": {"cnr_id": "studio-core", "ver": "0.14.1", "Node name for S&R": "GeminiNode"}, "widgets_values": ["", "gemini-3-pro-preview", 42, "randomize", "You are an expert in prompt writing.\nBased on the input, rewrite the user's input into a detailed prompt.\nincluding camera settings, lighting, composition, and style.\nReturn the prompt only"], "color": "#432", "bgcolor": "#653"}], "groups": [], "links": [{"id": 11, "origin_id": -10, "origin_slot": 0, "target_id": 11, "target_slot": 4, "type": "STRING"}, {"id": 13, "origin_id": 11, "origin_slot": 0, "target_id": -20, "target_slot": 0, "type": "STRING"}, {"id": 14, "origin_id": -10, "origin_slot": 1, "target_id": 11, "target_slot": 0, "type": "IMAGE"}], "extra": {"workflowRendererVersion": "LG"}, "category": "Text generation/Prompt enhance"}]}, "extra": {}}
|
||||
|
||||
@@ -1 +1 @@
|
||||
{"revision": 0, "last_node_id": 13, "last_link_id": 0, "nodes": [{"id": 13, "type": "cf95b747-3e17-46cb-8097-cac60ff9b2e1", "pos": [1120, 330], "size": [240, 58], "flags": {}, "order": 3, "mode": 0, "inputs": [{"localized_name": "video", "name": "video", "type": "VIDEO", "link": null}, {"name": "model_name", "type": "COMBO", "widget": {"name": "model_name"}, "link": null}], "outputs": [{"localized_name": "VIDEO", "name": "VIDEO", "type": "VIDEO", "links": []}], "title": "Video Upscale(GAN x4)", "properties": {"proxyWidgets": [["-1", "model_name"]], "cnr_id": "comfy-core", "ver": "0.14.1"}, "widgets_values": ["RealESRGAN_x4plus.safetensors"]}], "links": [], "version": 0.4, "definitions": {"subgraphs": [{"id": "cf95b747-3e17-46cb-8097-cac60ff9b2e1", "version": 1, "state": {"lastGroupId": 0, "lastNodeId": 13, "lastLinkId": 19, "lastRerouteId": 0}, "revision": 0, "config": {}, "name": "Video Upscale(GAN x4)", "inputNode": {"id": -10, "bounding": [550, 460, 120, 80]}, "outputNode": {"id": -20, "bounding": [1490, 460, 120, 60]}, "inputs": [{"id": "666d633e-93e7-42dc-8d11-2b7b99b0f2a6", "name": "video", "type": "VIDEO", "linkIds": [10], "localized_name": "video", "pos": [650, 480]}, {"id": "2e23a087-caa8-4d65-99e6-662761aa905a", "name": "model_name", "type": "COMBO", "linkIds": [19], "pos": [650, 500]}], "outputs": [{"id": "0c1768ea-3ec2-412f-9af6-8e0fa36dae70", "name": "VIDEO", "type": "VIDEO", "linkIds": [15], "localized_name": "VIDEO", "pos": [1510, 480]}], "widgets": [], "nodes": [{"id": 2, "type": "ImageUpscaleWithModel", "pos": [1110, 450], "size": [320, 46], "flags": {}, "order": 1, "mode": 0, "inputs": [{"localized_name": "upscale_model", "name": "upscale_model", "type": "UPSCALE_MODEL", "link": 1}, {"localized_name": "image", "name": "image", "type": "IMAGE", "link": 14}], "outputs": [{"localized_name": "IMAGE", "name": "IMAGE", "type": "IMAGE", "links": [13]}], "properties": {"cnr_id": "comfy-core", "ver": "0.10.0", "Node name for S&R": "ImageUpscaleWithModel"}}, {"id": 11, "type": "CreateVideo", "pos": [1110, 550], "size": [320, 78], "flags": {}, "order": 3, "mode": 0, "inputs": [{"localized_name": "images", "name": "images", "type": "IMAGE", "link": 13}, {"localized_name": "audio", "name": "audio", "shape": 7, "type": "AUDIO", "link": 16}, {"localized_name": "fps", "name": "fps", "type": "FLOAT", "widget": {"name": "fps"}, "link": 12}], "outputs": [{"localized_name": "VIDEO", "name": "VIDEO", "type": "VIDEO", "links": [15]}], "properties": {"cnr_id": "comfy-core", "ver": "0.10.0", "Node name for S&R": "CreateVideo"}, "widgets_values": [30]}, {"id": 10, "type": "GetVideoComponents", "pos": [1110, 330], "size": [320, 70], "flags": {}, "order": 2, "mode": 0, "inputs": [{"localized_name": "video", "name": "video", "type": "VIDEO", "link": 10}], "outputs": [{"localized_name": "images", "name": "images", "type": "IMAGE", "links": [14]}, {"localized_name": "audio", "name": "audio", "type": "AUDIO", "links": [16]}, {"localized_name": "fps", "name": "fps", "type": "FLOAT", "links": [12]}], "properties": {"cnr_id": "comfy-core", "ver": "0.10.0", "Node name for S&R": "GetVideoComponents"}}, {"id": 1, "type": "UpscaleModelLoader", "pos": [750, 450], "size": [280, 60], "flags": {}, "order": 0, "mode": 0, "inputs": [{"localized_name": "model_name", "name": "model_name", "type": "COMBO", "widget": {"name": "model_name"}, "link": 19}], "outputs": [{"localized_name": "UPSCALE_MODEL", "name": "UPSCALE_MODEL", "type": "UPSCALE_MODEL", "links": [1]}], "properties": {"cnr_id": "comfy-core", "ver": "0.10.0", "Node name for S&R": "UpscaleModelLoader", "models": [{"name": "RealESRGAN_x4plus.safetensors", "url": "https://huggingface.co/Comfy-Org/Real-ESRGAN_repackaged/resolve/main/RealESRGAN_x4plus.safetensors", "directory": "upscale_models"}]}, "widgets_values": ["RealESRGAN_x4plus.safetensors"]}], "groups": [], "links": [{"id": 1, "origin_id": 1, "origin_slot": 0, "target_id": 2, "target_slot": 0, "type": "UPSCALE_MODEL"}, {"id": 14, "origin_id": 10, "origin_slot": 0, "target_id": 2, "target_slot": 1, "type": "IMAGE"}, {"id": 13, "origin_id": 2, "origin_slot": 0, "target_id": 11, "target_slot": 0, "type": "IMAGE"}, {"id": 16, "origin_id": 10, "origin_slot": 1, "target_id": 11, "target_slot": 1, "type": "AUDIO"}, {"id": 12, "origin_id": 10, "origin_slot": 2, "target_id": 11, "target_slot": 2, "type": "FLOAT"}, {"id": 10, "origin_id": -10, "origin_slot": 0, "target_id": 10, "target_slot": 0, "type": "VIDEO"}, {"id": 15, "origin_id": 11, "origin_slot": 0, "target_id": -20, "target_slot": 0, "type": "VIDEO"}, {"id": 19, "origin_id": -10, "origin_slot": 1, "target_id": 1, "target_slot": 0, "type": "COMBO"}], "extra": {"workflowRendererVersion": "LG"}, "category": "Video generation and editing/Enhance video"}]}, "extra": {}}
|
||||
{"revision": 0, "last_node_id": 13, "last_link_id": 0, "nodes": [{"id": 13, "type": "cf95b747-3e17-46cb-8097-cac60ff9b2e1", "pos": [1120, 330], "size": [240, 58], "flags": {}, "order": 3, "mode": 0, "inputs": [{"localized_name": "video", "name": "video", "type": "VIDEO", "link": null}, {"name": "model_name", "type": "COMBO", "widget": {"name": "model_name"}, "link": null}], "outputs": [{"localized_name": "VIDEO", "name": "VIDEO", "type": "VIDEO", "links": []}], "title": "Video Upscale(GAN x4)", "properties": {"proxyWidgets": [["-1", "model_name"]], "cnr_id": "studio-core", "ver": "0.14.1"}, "widgets_values": ["RealESRGAN_x4plus.safetensors"]}], "links": [], "version": 0.4, "definitions": {"subgraphs": [{"id": "cf95b747-3e17-46cb-8097-cac60ff9b2e1", "version": 1, "state": {"lastGroupId": 0, "lastNodeId": 13, "lastLinkId": 19, "lastRerouteId": 0}, "revision": 0, "config": {}, "name": "Video Upscale(GAN x4)", "inputNode": {"id": -10, "bounding": [550, 460, 120, 80]}, "outputNode": {"id": -20, "bounding": [1490, 460, 120, 60]}, "inputs": [{"id": "666d633e-93e7-42dc-8d11-2b7b99b0f2a6", "name": "video", "type": "VIDEO", "linkIds": [10], "localized_name": "video", "pos": [650, 480]}, {"id": "2e23a087-caa8-4d65-99e6-662761aa905a", "name": "model_name", "type": "COMBO", "linkIds": [19], "pos": [650, 500]}], "outputs": [{"id": "0c1768ea-3ec2-412f-9af6-8e0fa36dae70", "name": "VIDEO", "type": "VIDEO", "linkIds": [15], "localized_name": "VIDEO", "pos": [1510, 480]}], "widgets": [], "nodes": [{"id": 2, "type": "ImageUpscaleWithModel", "pos": [1110, 450], "size": [320, 46], "flags": {}, "order": 1, "mode": 0, "inputs": [{"localized_name": "upscale_model", "name": "upscale_model", "type": "UPSCALE_MODEL", "link": 1}, {"localized_name": "image", "name": "image", "type": "IMAGE", "link": 14}], "outputs": [{"localized_name": "IMAGE", "name": "IMAGE", "type": "IMAGE", "links": [13]}], "properties": {"cnr_id": "studio-core", "ver": "0.10.0", "Node name for S&R": "ImageUpscaleWithModel"}}, {"id": 11, "type": "CreateVideo", "pos": [1110, 550], "size": [320, 78], "flags": {}, "order": 3, "mode": 0, "inputs": [{"localized_name": "images", "name": "images", "type": "IMAGE", "link": 13}, {"localized_name": "audio", "name": "audio", "shape": 7, "type": "AUDIO", "link": 16}, {"localized_name": "fps", "name": "fps", "type": "FLOAT", "widget": {"name": "fps"}, "link": 12}], "outputs": [{"localized_name": "VIDEO", "name": "VIDEO", "type": "VIDEO", "links": [15]}], "properties": {"cnr_id": "studio-core", "ver": "0.10.0", "Node name for S&R": "CreateVideo"}, "widgets_values": [30]}, {"id": 10, "type": "GetVideoComponents", "pos": [1110, 330], "size": [320, 70], "flags": {}, "order": 2, "mode": 0, "inputs": [{"localized_name": "video", "name": "video", "type": "VIDEO", "link": 10}], "outputs": [{"localized_name": "images", "name": "images", "type": "IMAGE", "links": [14]}, {"localized_name": "audio", "name": "audio", "type": "AUDIO", "links": [16]}, {"localized_name": "fps", "name": "fps", "type": "FLOAT", "links": [12]}], "properties": {"cnr_id": "studio-core", "ver": "0.10.0", "Node name for S&R": "GetVideoComponents"}}, {"id": 1, "type": "UpscaleModelLoader", "pos": [750, 450], "size": [280, 60], "flags": {}, "order": 0, "mode": 0, "inputs": [{"localized_name": "model_name", "name": "model_name", "type": "COMBO", "widget": {"name": "model_name"}, "link": 19}], "outputs": [{"localized_name": "UPSCALE_MODEL", "name": "UPSCALE_MODEL", "type": "UPSCALE_MODEL", "links": [1]}], "properties": {"cnr_id": "studio-core", "ver": "0.10.0", "Node name for S&R": "UpscaleModelLoader", "models": [{"name": "RealESRGAN_x4plus.safetensors", "url": "https://huggingface.co/Comfy-Org/Real-ESRGAN_repackaged/resolve/main/RealESRGAN_x4plus.safetensors", "directory": "upscale_models"}]}, "widgets_values": ["RealESRGAN_x4plus.safetensors"]}], "groups": [], "links": [{"id": 1, "origin_id": 1, "origin_slot": 0, "target_id": 2, "target_slot": 0, "type": "UPSCALE_MODEL"}, {"id": 14, "origin_id": 10, "origin_slot": 0, "target_id": 2, "target_slot": 1, "type": "IMAGE"}, {"id": 13, "origin_id": 2, "origin_slot": 0, "target_id": 11, "target_slot": 0, "type": "IMAGE"}, {"id": 16, "origin_id": 10, "origin_slot": 1, "target_id": 11, "target_slot": 1, "type": "AUDIO"}, {"id": 12, "origin_id": 10, "origin_slot": 2, "target_id": 11, "target_slot": 2, "type": "FLOAT"}, {"id": 10, "origin_id": -10, "origin_slot": 0, "target_id": 10, "target_slot": 0, "type": "VIDEO"}, {"id": 15, "origin_id": 11, "origin_slot": 0, "target_id": -20, "target_slot": 0, "type": "VIDEO"}, {"id": 19, "origin_id": -10, "origin_slot": 1, "target_id": 1, "target_slot": 0, "type": "COMBO"}], "extra": {"workflowRendererVersion": "LG"}, "category": "Video generation and editing/Enhance video"}]}, "extra": {}}
|
||||
|
||||
@@ -0,0 +1,104 @@
|
||||
#!/bin/bash
|
||||
# Apply Hanzo Studio branding to the prebuilt frontend package (comfyui-frontend-package).
|
||||
# Runs during the Docker build after pip install, and can be run against a venv
|
||||
# for local dev. Idempotent: safe to run more than once.
|
||||
set -e
|
||||
|
||||
STATIC_DIR=$(python3 -c "import comfyui_frontend_package, importlib.resources; print(str(importlib.resources.files(comfyui_frontend_package) / 'static'))")
|
||||
BRANDING_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||
|
||||
echo "=== Hanzo Studio Branding ==="
|
||||
echo "Target: $STATIC_DIR"
|
||||
|
||||
# --- 1. Replace in-app logos ---
|
||||
echo "[1/7] Replacing logos..."
|
||||
for logo_file in comfy-logo-single.svg comfy-logo-mono.svg comfy-cloud-logo.svg; do
|
||||
target="$STATIC_DIR/assets/images/$logo_file"
|
||||
if [ -f "$target" ]; then
|
||||
cp "$BRANDING_DIR/hanzo-logo.svg" "$target"
|
||||
echo " Replaced $logo_file"
|
||||
else
|
||||
echo " Not found: $logo_file (skipping)"
|
||||
fi
|
||||
done
|
||||
|
||||
# --- 2. Install favicon (Hanzo mark) ---
|
||||
# The frontend sets the runtime favicon to /assets/favicon.ico via useFavicon();
|
||||
# the web root copies cover the pre-boot page load and the default /favicon.ico
|
||||
# request. During a generation it instead cycles the 10 progress frames under
|
||||
# assets/images/favicon_progress_16x16/, so those must be Hanzo-branded too or the
|
||||
# tab reverts to the Comfy mark while a job runs. All PNG/ICO are committed
|
||||
# artifacts (rendered by make_favicon.py / make_progress_favicons.py) so the Docker
|
||||
# image needs no rasterizer.
|
||||
echo "[2/7] Installing favicon..."
|
||||
cp "$BRANDING_DIR/favicon.ico" "$STATIC_DIR/assets/favicon.ico"
|
||||
cp "$BRANDING_DIR/favicon.ico" "$STATIC_DIR/favicon.ico"
|
||||
cp "$BRANDING_DIR/favicon.svg" "$STATIC_DIR/favicon.svg"
|
||||
progress_dst="$STATIC_DIR/assets/images/favicon_progress_16x16"
|
||||
if [ -d "$progress_dst" ]; then
|
||||
cp "$BRANDING_DIR"/favicon_progress_16x16/frame_*.png "$progress_dst/"
|
||||
echo " Installed favicon.ico + favicon.svg + progress frames"
|
||||
else
|
||||
echo " Installed favicon.ico + favicon.svg (progress dir absent, skipped)"
|
||||
fi
|
||||
|
||||
# --- 3. Patch index.html (title + icon link + loading text) ---
|
||||
echo "[3/7] Patching index.html..."
|
||||
if [ -f "$STATIC_DIR/index.html" ]; then
|
||||
# Rename the title and inject icon links (the upstream head has none).
|
||||
sed -i 's#<title>ComfyUI</title>#<title>Hanzo Studio</title><link rel="icon" type="image/svg+xml" href="favicon.svg"/><link rel="alternate icon" href="favicon.ico"/>#g' "$STATIC_DIR/index.html"
|
||||
sed -i 's/Loading ComfyUI/Loading Hanzo Studio/g' "$STATIC_DIR/index.html"
|
||||
sed -i 's/content="ComfyUI/content="Hanzo Studio/g' "$STATIC_DIR/index.html"
|
||||
echo " Patched index.html"
|
||||
fi
|
||||
|
||||
# --- 4. Patch manifest JSON files ---
|
||||
echo "[4/7] Patching manifests..."
|
||||
find "$STATIC_DIR" -name "manifest*.json" -exec sed -i 's/"ComfyUI"/"Hanzo Studio"/g' {} \;
|
||||
find "$STATIC_DIR" -name "manifest*.json" -exec sed -i 's/studio\.org/hanzo.ai/g' {} \;
|
||||
echo " Done"
|
||||
|
||||
# --- 5. Smart JS/CSS/JSON display-string patching ---
|
||||
echo "[5/7] Smart JS patching..."
|
||||
python3 "$BRANDING_DIR/patch_frontend.py" "$STATIC_DIR"
|
||||
|
||||
# --- 6. Replace the inline sidebar logo (ComfyLogo Vue component) ---
|
||||
# This is the top-left in-app mark, an inline SVG compiled into a JS chunk — a
|
||||
# different asset from the file-based logos handled in step 1.
|
||||
echo "[6/7] Replacing in-app sidebar logo..."
|
||||
python3 "$BRANDING_DIR/patch_sidebar_logo.py" "$STATIC_DIR"
|
||||
|
||||
# --- 7. Apply the black + Hanzo-purple theme ---
|
||||
# Installs hanzo-theme.css, injects it as the last stylesheet in index.html, and
|
||||
# rewrites the "dark" palette's canvas clear color to pure black.
|
||||
echo "[7/8] Applying black + purple theme..."
|
||||
python3 "$BRANDING_DIR/patch_theme.py" "$STATIC_DIR" "$BRANDING_DIR"
|
||||
|
||||
# --- 8. Seed the default 25-tab fashion workspace ---
|
||||
# Injects a <head> bootstrap that pre-populates the localStorage keys the
|
||||
# frontend reads to restore open workflow tabs. Idempotent; bump SEED_VERSION
|
||||
# in patch_workspace_seed.py to force existing profiles to re-seed once.
|
||||
echo "[8/9] Seeding default workspace tabs..."
|
||||
python3 "$BRANDING_DIR/patch_workspace_seed.py" "$STATIC_DIR"
|
||||
|
||||
# --- 9. Inject the Copilot sidebar chat (frontend bundle + on-brand CSS) ---
|
||||
# Copies hanzo-copilot.{js,css} into static/ and injects them into index.html
|
||||
# (CSS link in <head>, deferred <script> before </body>). Pairs with the backend
|
||||
# route POST /v1/copilot/chat. Idempotent; bump BUNDLE_VERSION in patch_copilot.py.
|
||||
echo "[9/11] Injecting Copilot..."
|
||||
python3 "$BRANDING_DIR/patch_copilot.py" "$STATIC_DIR" "$BRANDING_DIR"
|
||||
|
||||
# --- 10. Inject the Identity user menu (frontend bundle + on-brand CSS) ---
|
||||
# Top-right user pill driven by GET /v1/session: login/logout, org switcher,
|
||||
# project scope, Console link. Idempotent; bump BUNDLE_VERSION in patch_identity.py.
|
||||
echo "[10/11] Injecting Identity menu..."
|
||||
python3 "$BRANDING_DIR/patch_identity.py" "$STATIC_DIR" "$BRANDING_DIR"
|
||||
|
||||
# --- 11. De-Comfy remaining English display strings ---
|
||||
# Sweeps the served English bundle for user-visible 'ComfyUI'/'Comfy' -> Studio
|
||||
# that the curated phrase list in patch_frontend.py cannot reach (mid-string),
|
||||
# plus the Settings dialog section headers. Runs LAST so it mops up everything.
|
||||
echo "[11/11] De-Comfy display strings..."
|
||||
python3 "$BRANDING_DIR/patch_destring.py" "$STATIC_DIR"
|
||||
|
||||
echo "=== Hanzo Studio branding complete ==="
|
||||
@@ -0,0 +1,127 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Headless smoke test for the Copilot frontend bundle (hanzo-copilot.js).
|
||||
|
||||
Loads the REAL bundle into a page against a stub window.app / window.LiteGraph
|
||||
(a minimal LiteGraph subset) and asserts:
|
||||
1. window.HanzoCopilot.applyOps mutates the mock graph: add / connect /
|
||||
set_widget / set_prompt / move / delete / layout / queue.
|
||||
2. The sidebar tab registers and its render(el) builds the chat panel DOM.
|
||||
|
||||
This is the frontend half of the copilot tests. Live end-to-end (the panel
|
||||
talking to POST /v1/copilot/chat) needs the one server restart that registers
|
||||
the backend route; the bundle itself is exercised here without a server.
|
||||
|
||||
Run: python3 branding/copilot_smoke.py (needs: pip install playwright)
|
||||
"""
|
||||
import os
|
||||
import sys
|
||||
|
||||
from playwright.sync_api import sync_playwright
|
||||
|
||||
BUNDLE = os.path.join(os.path.dirname(os.path.abspath(__file__)), "hanzo-copilot.js")
|
||||
|
||||
# Stub LiteGraph + app: the smallest surface applyOps touches.
|
||||
STUB = """
|
||||
window.__connections = [];
|
||||
window.__queued = null;
|
||||
window.LiteGraph = {
|
||||
_id: 1000,
|
||||
createNode(type) {
|
||||
return {
|
||||
type, id: ++window.LiteGraph._id, pos: [0, 0], size: [200, 100],
|
||||
inputs: [{ name: 'image' }, { name: 'model' }],
|
||||
outputs: [{ name: 'IMAGE' }],
|
||||
widgets: [{ name: 'text', value: '' }, { name: 'seed', value: 0 }],
|
||||
connect(outIdx, target, inIdx) {
|
||||
window.__connections.push({ from: this.id, outIdx, to: target.id, inIdx });
|
||||
return true;
|
||||
},
|
||||
};
|
||||
},
|
||||
};
|
||||
(function () {
|
||||
var nodes = [];
|
||||
window.__nodes = nodes;
|
||||
window.app = {
|
||||
extensionManager: { registerSidebarTab(t) { window.__tab = t; } },
|
||||
graph: {
|
||||
_nodes: nodes, links: {},
|
||||
add(n) { nodes.push(n); },
|
||||
remove(n) { var i = nodes.indexOf(n); if (i >= 0) nodes.splice(i, 1); },
|
||||
getNodeById(id) { return nodes.find(function (n) { return n.id == id; }) || null; },
|
||||
setDirtyCanvas() {},
|
||||
},
|
||||
queuePrompt(n) { window.__queued = n; },
|
||||
async graphToPrompt() { return { output: {} }; },
|
||||
};
|
||||
})();
|
||||
"""
|
||||
|
||||
OPS = [
|
||||
{"op": "add_node", "type": "CLIPTextEncode", "id": "pos", "pos": [10, 20]},
|
||||
{"op": "add_node", "type": "SaveImage", "id": "save"},
|
||||
{"op": "set_prompt", "node_id": "pos", "text": "a red one-piece at golden hour"},
|
||||
{"op": "set_widget", "node_id": "save", "name": "seed", "value": 7},
|
||||
{"op": "connect", "from_node": "pos", "from_slot": "IMAGE", "to_node": "save", "to_slot": "image"},
|
||||
{"op": "move_node", "node_id": "save", "pos": [300, 40]},
|
||||
{"op": "layout"},
|
||||
{"op": "queue"},
|
||||
{"op": "delete_node", "node_id": "pos"},
|
||||
{"op": "bogus", "node_id": "pos"},
|
||||
]
|
||||
|
||||
|
||||
def main() -> int:
|
||||
with sync_playwright() as p:
|
||||
browser = p.chromium.launch()
|
||||
page = browser.new_page()
|
||||
page.set_content("<!doctype html><html><head></head><body></body></html>")
|
||||
page.evaluate(STUB)
|
||||
page.add_script_tag(path=BUNDLE)
|
||||
|
||||
assert page.evaluate("typeof window.HanzoCopilot") == "object", "HanzoCopilot not exposed"
|
||||
|
||||
# Apply the op batch against the mock graph.
|
||||
res = page.evaluate("(ops) => window.HanzoCopilot.applyOps(ops)", OPS)
|
||||
print("applyOps ->", res)
|
||||
|
||||
nodes = page.evaluate("window.__nodes.map(function(n){return {id:n.id,type:n.type,pos:n.pos};})")
|
||||
conns = page.evaluate("window.__connections")
|
||||
queued = page.evaluate("window.__queued")
|
||||
save_seed = page.evaluate(
|
||||
"(function(){var n=window.__nodes.find(function(x){return x.type=='SaveImage';});"
|
||||
"return n.widgets.find(function(w){return w.name=='seed';}).value;})()"
|
||||
)
|
||||
|
||||
# 2 added, then 1 deleted -> only SaveImage remains.
|
||||
types = sorted(n["type"] for n in nodes)
|
||||
assert types == ["SaveImage"], f"expected only SaveImage after delete, got {types}"
|
||||
assert len(conns) == 1 and conns[0]["outIdx"] == 0 and conns[0]["inIdx"] == 0, f"bad connect: {conns}"
|
||||
assert save_seed == 7, f"set_widget failed: seed={save_seed}"
|
||||
assert queued == 0, f"queue op did not fire: {queued}"
|
||||
# bogus op is skipped, not thrown, and noted.
|
||||
assert any("bogus" in note for note in res["notes"]), f"bogus op not noted: {res['notes']}"
|
||||
# 8 good ops applied (add,add,set_prompt,set_widget,connect,move,layout,queue,delete = 9);
|
||||
# bogus skipped.
|
||||
assert res["applied"] == 9, f"expected 9 applied, got {res['applied']}"
|
||||
|
||||
# Sidebar tab registered; render builds the panel.
|
||||
page.wait_for_function("() => window.__tab !== undefined", timeout=5000)
|
||||
tab = page.evaluate("({id: window.__tab.id, title: window.__tab.title, icon: window.__tab.icon})")
|
||||
assert tab["id"] == "hanzo-copilot", f"bad tab id: {tab}"
|
||||
panel = page.evaluate(
|
||||
"(function(){var d=document.createElement('div');document.body.appendChild(d);"
|
||||
"window.__tab.render(d);return {panel:d.classList.contains('hanzo-copilot'),"
|
||||
"chips:d.querySelectorAll('.hz-chip').length,input:!!d.querySelector('textarea')};})()"
|
||||
)
|
||||
assert panel["panel"] and panel["input"] and panel["chips"] >= 3, f"panel not built: {panel}"
|
||||
|
||||
browser.close()
|
||||
print("tab ->", tab)
|
||||
print("panel ->", panel)
|
||||
print("PASS: copilot frontend smoke")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
|
After Width: | Height: | Size: 9.3 KiB |
@@ -0,0 +1,10 @@
|
||||
<svg viewBox="0 0 64 64" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect width="64" height="64" rx="8" fill="#000000"/>
|
||||
<g transform="translate(8, 8) scale(0.716)">
|
||||
<path d="M22.21 67V44.6369H0V67H22.21Z" fill="#ffffff"/>
|
||||
<path d="M66.7038 22.3184H22.2534L0.0878906 44.6367H44.4634L66.7038 22.3184Z" fill="#ffffff"/>
|
||||
<path d="M22.21 0H0V22.3184H22.21V0Z" fill="#ffffff"/>
|
||||
<path d="M66.7198 0H44.5098V22.3184H66.7198V0Z" fill="#ffffff"/>
|
||||
<path d="M66.7198 67V44.6369H44.5098V67H66.7198Z" fill="#ffffff"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 536 B |
|
After Width: | Height: | Size: 466 B |
|
After Width: | Height: | Size: 506 B |
|
After Width: | Height: | Size: 537 B |
|
After Width: | Height: | Size: 577 B |
|
After Width: | Height: | Size: 625 B |
|
After Width: | Height: | Size: 654 B |
|
After Width: | Height: | Size: 688 B |
|
After Width: | Height: | Size: 710 B |
|
After Width: | Height: | Size: 750 B |
|
After Width: | Height: | Size: 760 B |
@@ -0,0 +1,137 @@
|
||||
/* Hanzo Studio Copilot — black + Hanzo-purple sidebar chat.
|
||||
* Reuses the theme's CSS vars (hanzo-theme.css) so it tracks the shell palette;
|
||||
* hard-coded fallbacks keep it on-brand if a var is ever absent.
|
||||
*/
|
||||
.hanzo-copilot {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
background: var(--comfy-menu-bg, #0a0a0a);
|
||||
color: var(--fg-color, #fff);
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.hanzo-copilot .hz-head {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
padding: 12px 14px;
|
||||
font-weight: 600;
|
||||
letter-spacing: 0.02em;
|
||||
border-bottom: 1px solid var(--border-color, #1f1f1f);
|
||||
}
|
||||
.hanzo-copilot .hz-dot {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
border-radius: 50%;
|
||||
background: var(--p-primary-color, #8b5cf6);
|
||||
box-shadow: 0 0 8px var(--p-primary-color, #8b5cf6);
|
||||
}
|
||||
|
||||
.hanzo-copilot .hz-messages {
|
||||
flex: 1 1 auto;
|
||||
overflow-y: auto;
|
||||
padding: 12px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
}
|
||||
.hanzo-copilot .hz-msg { display: flex; }
|
||||
.hanzo-copilot .hz-user { justify-content: flex-end; }
|
||||
.hanzo-copilot .hz-assistant { justify-content: flex-start; }
|
||||
.hanzo-copilot .hz-bubble {
|
||||
max-width: 84%;
|
||||
padding: 8px 11px;
|
||||
border-radius: 12px;
|
||||
line-height: 1.45;
|
||||
word-wrap: break-word;
|
||||
white-space: normal;
|
||||
}
|
||||
.hanzo-copilot .hz-user .hz-bubble {
|
||||
background: var(--p-button-primary-background, #7c3aed);
|
||||
color: #fff;
|
||||
border-bottom-right-radius: 4px;
|
||||
}
|
||||
.hanzo-copilot .hz-assistant .hz-bubble {
|
||||
background: #1a1a1f;
|
||||
border: 1px solid var(--border-color, #1f1f1f);
|
||||
border-bottom-left-radius: 4px;
|
||||
}
|
||||
.hanzo-copilot .hz-typing { opacity: 0.6; letter-spacing: 3px; }
|
||||
|
||||
.hanzo-copilot .hz-chips {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 6px;
|
||||
padding: 8px 12px 4px;
|
||||
}
|
||||
.hanzo-copilot .hz-chip {
|
||||
background: transparent;
|
||||
color: #c4b5fd;
|
||||
border: 1px solid #2a2140;
|
||||
border-radius: 999px;
|
||||
padding: 5px 10px;
|
||||
font-size: 11.5px;
|
||||
cursor: pointer;
|
||||
transition: background 0.15s, border-color 0.15s;
|
||||
}
|
||||
.hanzo-copilot .hz-chip:hover {
|
||||
background: rgba(139, 92, 246, 0.16);
|
||||
border-color: var(--p-primary-color, #8b5cf6);
|
||||
}
|
||||
|
||||
.hanzo-copilot .hz-input {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
padding: 10px 12px 12px;
|
||||
border-top: 1px solid var(--border-color, #1f1f1f);
|
||||
align-items: flex-end;
|
||||
}
|
||||
.hanzo-copilot .hz-input textarea {
|
||||
flex: 1 1 auto;
|
||||
resize: none;
|
||||
background: var(--comfy-input-bg, #0a0a0a);
|
||||
color: var(--fg-color, #fff);
|
||||
border: 1px solid var(--border-color, #1f1f1f);
|
||||
border-radius: 8px;
|
||||
padding: 8px 10px;
|
||||
font: inherit;
|
||||
outline: none;
|
||||
}
|
||||
.hanzo-copilot .hz-input textarea:focus {
|
||||
border-color: var(--p-primary-color, #8b5cf6);
|
||||
}
|
||||
.hanzo-copilot .hz-send {
|
||||
background: var(--p-button-primary-background, #7c3aed);
|
||||
color: #fff;
|
||||
border: none;
|
||||
border-radius: 8px;
|
||||
padding: 9px 14px;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
transition: background 0.15s;
|
||||
}
|
||||
.hanzo-copilot .hz-send:hover { background: #6d28d9; }
|
||||
.hanzo-copilot .hz-send:disabled { opacity: 0.5; cursor: default; }
|
||||
|
||||
/* Fallback toast (used only when the app toast service is unavailable) */
|
||||
.hanzo-copilot-toast {
|
||||
position: fixed;
|
||||
bottom: 24px;
|
||||
left: 50%;
|
||||
transform: translate(-50%, 12px);
|
||||
background: #17171c;
|
||||
color: #fff;
|
||||
border: 1px solid #2a2140;
|
||||
border-left: 3px solid var(--p-primary-color, #8b5cf6);
|
||||
border-radius: 8px;
|
||||
padding: 10px 16px;
|
||||
font-size: 13px;
|
||||
z-index: 100000;
|
||||
opacity: 0;
|
||||
transition: opacity 0.25s, transform 0.25s;
|
||||
pointer-events: none;
|
||||
}
|
||||
.hanzo-copilot-toast.show { opacity: 1; transform: translate(-50%, 0); }
|
||||
@@ -0,0 +1,359 @@
|
||||
/* Hanzo Studio Copilot — a sidebar chat that reads and MUTATES the workflow graph.
|
||||
*
|
||||
* You talk to the canvas in natural language ("add a 4x upscale after the save
|
||||
* image"). The backend (POST /v1/copilot/chat) decides graph operations; THIS
|
||||
* file applies them against app.graph (LiteGraph). Every op is guarded — an
|
||||
* unknown node type or bad slot is skipped and noted, never thrown.
|
||||
*
|
||||
* Injected as the last <script> by branding/patch_copilot.py so window.app exists
|
||||
* (or is about to — we poll). window.HanzoCopilot.applyOps is exposed immediately
|
||||
* for programmatic use and headless tests.
|
||||
*/
|
||||
(function () {
|
||||
"use strict";
|
||||
var VERSION = "1";
|
||||
|
||||
// ---------------------------------------------------------------- helpers
|
||||
function app() { return window.app; }
|
||||
function graph() { return window.app && window.app.graph; }
|
||||
function LG() { return window.LiteGraph; }
|
||||
|
||||
function waitFor(pred, timeoutMs) {
|
||||
return new Promise(function (resolve, reject) {
|
||||
var t0 = Date.now();
|
||||
(function tick() {
|
||||
var v = pred();
|
||||
if (v) return resolve(v);
|
||||
if (Date.now() - t0 > timeoutMs) return reject(new Error("timeout"));
|
||||
setTimeout(tick, 120);
|
||||
})();
|
||||
});
|
||||
}
|
||||
|
||||
function resolveSlot(list, slot) {
|
||||
// slot may be an index (number) or a slot name (string). Returns an index.
|
||||
if (typeof slot === "number") return slot;
|
||||
if (slot == null) return 0;
|
||||
var name = String(slot).toLowerCase();
|
||||
for (var i = 0; i < (list || []).length; i++) {
|
||||
var s = list[i];
|
||||
var n = (s && (s.name || s.label)) || s;
|
||||
if (String(n).toLowerCase() === name) return i;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------- applier
|
||||
// Applies one batch of ops. Returns {applied, notes[]}. New nodes are referenced
|
||||
// in later ops by the id label the model gave them in add_node.
|
||||
function applyOps(ops) {
|
||||
var g = graph();
|
||||
var notes = [];
|
||||
var applied = 0;
|
||||
if (!g) { return { applied: 0, notes: ["no graph"] }; }
|
||||
var local = {}; // model-label / id -> node
|
||||
|
||||
function resolve(id) {
|
||||
if (id == null) return null;
|
||||
if (local[id]) return local[id];
|
||||
var n = g.getNodeById(typeof id === "string" && /^\d+$/.test(id) ? Number(id) : id);
|
||||
if (!n && typeof id === "number") n = g.getNodeById(id);
|
||||
return n || null;
|
||||
}
|
||||
function setWidget(node, name, value) {
|
||||
if (!node.widgets) return false;
|
||||
var lname = String(name).toLowerCase();
|
||||
for (var i = 0; i < node.widgets.length; i++) {
|
||||
var w = node.widgets[i];
|
||||
if (w.name && w.name.toLowerCase() === lname) {
|
||||
w.value = value;
|
||||
if (typeof w.callback === "function") { try { w.callback(value); } catch (e) {} }
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
(ops || []).forEach(function (op, idx) {
|
||||
try {
|
||||
switch (op.op) {
|
||||
case "add_node": {
|
||||
var Lg = LG();
|
||||
var node = Lg && Lg.createNode ? Lg.createNode(op.type) : null;
|
||||
if (!node) { notes.push("skip add_node: unknown type " + op.type); break; }
|
||||
g.add(node);
|
||||
if (Array.isArray(op.pos)) node.pos = [op.pos[0], op.pos[1]];
|
||||
if (op.id != null) local[op.id] = node;
|
||||
local[node.id] = node;
|
||||
applied++;
|
||||
break;
|
||||
}
|
||||
case "set_widget": {
|
||||
var n1 = resolve(op.node_id);
|
||||
if (!n1) { notes.push("skip set_widget: no node " + op.node_id); break; }
|
||||
if (!setWidget(n1, op.name, op.value)) { notes.push("skip set_widget: no widget " + op.name + " on " + op.node_id); break; }
|
||||
applied++;
|
||||
break;
|
||||
}
|
||||
case "set_prompt": {
|
||||
var n2 = resolve(op.node_id);
|
||||
if (!n2 || !n2.widgets) { notes.push("skip set_prompt: no node " + op.node_id); break; }
|
||||
var target = null;
|
||||
for (var j = 0; j < n2.widgets.length; j++) {
|
||||
if (n2.widgets[j].name && n2.widgets[j].name.toLowerCase() === "text") { target = n2.widgets[j]; break; }
|
||||
}
|
||||
if (!target) target = n2.widgets.find(function (w) { return typeof w.value === "string"; });
|
||||
if (!target) { notes.push("skip set_prompt: no text widget on " + op.node_id); break; }
|
||||
target.value = op.text;
|
||||
if (typeof target.callback === "function") { try { target.callback(op.text); } catch (e) {} }
|
||||
applied++;
|
||||
break;
|
||||
}
|
||||
case "connect": {
|
||||
var from = resolve(op.from_node), to = resolve(op.to_node);
|
||||
if (!from || !to) { notes.push("skip connect: missing node"); break; }
|
||||
var oi = resolveSlot(from.outputs, op.from_slot);
|
||||
var ii = resolveSlot(to.inputs, op.to_slot);
|
||||
from.connect(oi, to, ii);
|
||||
applied++;
|
||||
break;
|
||||
}
|
||||
case "move_node": {
|
||||
var n3 = resolve(op.node_id);
|
||||
if (!n3 || !Array.isArray(op.pos)) { notes.push("skip move_node: " + op.node_id); break; }
|
||||
n3.pos = [op.pos[0], op.pos[1]];
|
||||
applied++;
|
||||
break;
|
||||
}
|
||||
case "delete_node": {
|
||||
var n4 = resolve(op.node_id);
|
||||
if (!n4) { notes.push("skip delete_node: no node " + op.node_id); break; }
|
||||
g.remove(n4);
|
||||
applied++;
|
||||
break;
|
||||
}
|
||||
case "layout": {
|
||||
layout();
|
||||
applied++;
|
||||
break;
|
||||
}
|
||||
case "queue": {
|
||||
if (app() && typeof app().queuePrompt === "function") { app().queuePrompt(0); applied++; }
|
||||
else notes.push("skip queue: unavailable");
|
||||
break;
|
||||
}
|
||||
default:
|
||||
notes.push("skip unknown op " + op.op);
|
||||
}
|
||||
} catch (e) {
|
||||
notes.push("error on op " + idx + " (" + op.op + "): " + e.message);
|
||||
}
|
||||
});
|
||||
|
||||
if (app() && app().graph && app().graph.setDirtyCanvas) app().graph.setDirtyCanvas(true, true);
|
||||
return { applied: applied, notes: notes };
|
||||
}
|
||||
|
||||
// Simple left-to-right columnar tidy: column = longest link-path from a source.
|
||||
function layout() {
|
||||
var g = graph();
|
||||
if (!g || !g._nodes) return;
|
||||
var nodes = g._nodes;
|
||||
var col = {};
|
||||
nodes.forEach(function (n) { col[n.id] = 0; });
|
||||
for (var pass = 0; pass < nodes.length; pass++) {
|
||||
var changed = false;
|
||||
nodes.forEach(function (n) {
|
||||
(n.inputs || []).forEach(function (inp) {
|
||||
if (inp && inp.link != null && g.links && g.links[inp.link]) {
|
||||
var src = g.links[inp.link].origin_id;
|
||||
if (col[src] != null && col[n.id] < col[src] + 1) { col[n.id] = col[src] + 1; changed = true; }
|
||||
}
|
||||
});
|
||||
});
|
||||
if (!changed) break;
|
||||
}
|
||||
var COLW = 340, ROWH = 240, rowOf = {};
|
||||
nodes.slice().sort(function (a, b) { return (col[a.id] - col[b.id]) || (a.id - b.id); }).forEach(function (n) {
|
||||
var c = col[n.id] || 0;
|
||||
rowOf[c] = (rowOf[c] || 0);
|
||||
n.pos = [80 + c * COLW, 80 + rowOf[c] * ROWH];
|
||||
rowOf[c]++;
|
||||
});
|
||||
if (g.setDirtyCanvas) g.setDirtyCanvas(true, true);
|
||||
}
|
||||
|
||||
window.HanzoCopilot = { applyOps: applyOps, layout: layout, VERSION: VERSION };
|
||||
|
||||
// ---------------------------------------------------------------- catalog
|
||||
var _objectInfo = null;
|
||||
function getObjectInfo() {
|
||||
if (_objectInfo) return Promise.resolve(_objectInfo);
|
||||
return fetch("./object_info").then(function (r) { return r.json(); }).then(function (j) {
|
||||
_objectInfo = j; return j;
|
||||
});
|
||||
}
|
||||
function inputTypeStr(spec) {
|
||||
if (Array.isArray(spec)) {
|
||||
var t = spec[0];
|
||||
if (typeof t === "string") return t;
|
||||
if (Array.isArray(t)) return "COMBO";
|
||||
return "ANY";
|
||||
}
|
||||
return "ANY";
|
||||
}
|
||||
function buildCatalog(info, graphJson) {
|
||||
var types = Object.keys(info);
|
||||
var present = {};
|
||||
Object.keys(graphJson || {}).forEach(function (id) {
|
||||
var ct = graphJson[id] && graphJson[id].class_type;
|
||||
if (ct) present[ct] = true;
|
||||
});
|
||||
var detail = {};
|
||||
Object.keys(present).forEach(function (t) {
|
||||
var d = info[t]; if (!d) return;
|
||||
var ins = {};
|
||||
["required", "optional"].forEach(function (grp) {
|
||||
var block = d.input && d.input[grp];
|
||||
if (block) Object.keys(block).forEach(function (name) { ins[name] = inputTypeStr(block[name]); });
|
||||
});
|
||||
detail[t] = { inputs: ins, outputs: d.output_name || d.output || [] };
|
||||
});
|
||||
return { types: types, nodes: detail };
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------- toast
|
||||
function toast(msg) {
|
||||
try {
|
||||
var em = app() && app().extensionManager;
|
||||
if (em && em.toast && typeof em.toast.add === "function") {
|
||||
em.toast.add({ severity: "success", summary: "Copilot", detail: msg, life: 4000 });
|
||||
return;
|
||||
}
|
||||
} catch (e) {}
|
||||
var el = document.createElement("div");
|
||||
el.className = "hanzo-copilot-toast";
|
||||
el.textContent = msg;
|
||||
document.body.appendChild(el);
|
||||
setTimeout(function () { el.classList.add("show"); }, 10);
|
||||
setTimeout(function () { el.classList.remove("show"); setTimeout(function () { el.remove(); }, 300); }, 3800);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------- chat state + UI
|
||||
var state = { messages: [], busy: false };
|
||||
var CHIPS = [
|
||||
"Add a 4x upscale after the save image",
|
||||
"Connect the latent output to a new VAE decode",
|
||||
"Set the positive prompt to a red one-piece on a beach at golden hour",
|
||||
"Tidy up the layout",
|
||||
];
|
||||
|
||||
function esc(s) { return String(s).replace(/[&<>]/g, function (c) { return { "&": "&", "<": "<", ">": ">" }[c]; }); }
|
||||
|
||||
function renderMessages(box) {
|
||||
box.innerHTML = "";
|
||||
state.messages.forEach(function (m) {
|
||||
var row = document.createElement("div");
|
||||
row.className = "hz-msg hz-" + m.role;
|
||||
row.innerHTML = '<div class="hz-bubble">' + esc(m.content).replace(/\n/g, "<br>") + "</div>";
|
||||
box.appendChild(row);
|
||||
});
|
||||
if (state.busy) {
|
||||
var t = document.createElement("div");
|
||||
t.className = "hz-msg hz-assistant";
|
||||
t.innerHTML = '<div class="hz-bubble hz-typing">…</div>';
|
||||
box.appendChild(t);
|
||||
}
|
||||
box.scrollTop = box.scrollHeight;
|
||||
}
|
||||
|
||||
async function send(text, box) {
|
||||
if (!text || state.busy) return;
|
||||
state.messages.push({ role: "user", content: text });
|
||||
state.busy = true;
|
||||
renderMessages(box);
|
||||
try {
|
||||
var gp = await app().graphToPrompt();
|
||||
var graphJson = (gp && gp.output) || {};
|
||||
var info = await getObjectInfo().catch(function () { return {}; });
|
||||
var catalog = buildCatalog(info, graphJson);
|
||||
var resp = await fetch("./v1/copilot/chat", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ messages: state.messages, graph_json: graphJson, catalog: catalog }),
|
||||
});
|
||||
var data = await resp.json().catch(function () { return {}; });
|
||||
if (!resp.ok) throw new Error(data.error || ("HTTP " + resp.status));
|
||||
var reply = data.reply || "(no reply)";
|
||||
state.messages.push({ role: "assistant", content: reply });
|
||||
state.busy = false;
|
||||
renderMessages(box);
|
||||
var ops = data.ops || [];
|
||||
if (ops.length) {
|
||||
var res = applyOps(ops);
|
||||
var summary = res.applied + " change" + (res.applied === 1 ? "" : "s") + " applied";
|
||||
if (res.notes.length) summary += " · " + res.notes.length + " skipped";
|
||||
toast(summary);
|
||||
if (res.notes.length) {
|
||||
state.messages.push({ role: "assistant", content: res.notes.join("\n") });
|
||||
renderMessages(box);
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
state.busy = false;
|
||||
state.messages.push({ role: "assistant", content: "⚠ " + e.message });
|
||||
renderMessages(box);
|
||||
}
|
||||
}
|
||||
|
||||
function buildUI(el) {
|
||||
el.classList.add("hanzo-copilot");
|
||||
el.innerHTML =
|
||||
'<div class="hz-head"><span class="hz-dot"></span> Copilot</div>' +
|
||||
'<div class="hz-messages"></div>' +
|
||||
'<div class="hz-chips"></div>' +
|
||||
'<div class="hz-input"><textarea rows="2" placeholder="Tell the canvas what to do…"></textarea>' +
|
||||
'<button class="hz-send" title="Send">Send</button></div>';
|
||||
var box = el.querySelector(".hz-messages");
|
||||
var ta = el.querySelector("textarea");
|
||||
var chips = el.querySelector(".hz-chips");
|
||||
CHIPS.forEach(function (c) {
|
||||
var b = document.createElement("button");
|
||||
b.className = "hz-chip"; b.textContent = c;
|
||||
b.onclick = function () { ta.value = c; ta.focus(); };
|
||||
chips.appendChild(b);
|
||||
});
|
||||
function fire() { var v = ta.value.trim(); ta.value = ""; send(v, box); }
|
||||
el.querySelector(".hz-send").onclick = fire;
|
||||
ta.addEventListener("keydown", function (e) {
|
||||
if (e.key === "Enter" && !e.shiftKey) { e.preventDefault(); fire(); }
|
||||
});
|
||||
if (!state.messages.length) {
|
||||
state.messages.push({ role: "assistant", content: "Hi — I can edit this workflow for you. Try a chip below or just tell me what to change." });
|
||||
}
|
||||
renderMessages(box);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------- boot
|
||||
function register() {
|
||||
var a = app();
|
||||
if (a && a.extensionManager && typeof a.extensionManager.registerSidebarTab === "function") {
|
||||
a.extensionManager.registerSidebarTab({
|
||||
id: "hanzo-copilot",
|
||||
icon: "pi pi-comments",
|
||||
title: "Copilot",
|
||||
tooltip: "Chat Copilot — edit the workflow in natural language",
|
||||
type: "custom",
|
||||
render: function (el) { buildUI(el); },
|
||||
destroy: function () {},
|
||||
});
|
||||
console.log("[Hanzo Copilot] sidebar tab registered v" + VERSION);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
waitFor(function () { return app() && app().extensionManager; }, 30000)
|
||||
.then(register)
|
||||
.catch(function () { console.warn("[Hanzo Copilot] app.extensionManager not found; copilot tab not mounted"); });
|
||||
})();
|
||||