Skip to content

About

AetherCanvas AI: one local web app for visual AI on your own GPU: text-to-image, photo editing, inpainting, uncropping, video and face swap with Qwen-Image 2.1, Krea 2 and Wan 2.2 (GGUF), driving ComfyUI.

Topics

Resources

Stars

2 stars

Watchers

1 watching

Forks

Latest commit

 

History

29 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Twitter: @NorowaretaGemu License: MIT

Python Flask

AetherCanvas AI

One local web app for visual AI. A Flask server — web UI and JSON API — for text-to-image generation, instruction-based photo editing, masked inpainting, uncropping and video, with GGUF models on your own GPU, and the ComfyUI workflows it runs on: Qwen-Image 2.1 (from abenzerps/Qwen-Image-2.1-Uncensored-GGUF), optional Krea 2 Turbo for text-to-image, and optional Wan 2.2 TI2V-5B for video.

Site: cursedprograms.github.io/AetherCanvas-AI

  • Generate — text to image with Qwen-Image 2.1, Krea 2 Turbo (8 steps), SDXL (RealVisXL V5 Lightning, 6 steps, or any SDXL checkpoint) or Chroma1-HD (optional).
  • Edit photo — upload a photo and describe the change ("make the dress red"); the base model edits natively, no separate edit model needed.
  • Inpaint — paint a mask over part of a photo and regenerate only that area; everything outside the mask is kept pixel for pixel.
  • Uncrop — extend a photo beyond its borders (to 16:9, 1:1, "zoom out 2×", or per side); the model invents the surroundings and the original pixels are kept exactly.
  • Pose — upload a photo of someone in a pose; SDPose finds the skeleton and Qwen-Image draws something new in exactly that pose (you get the skeleton map too).
  • Upscale — enlarge any photo or result 2× or 4× with 4x-UltraSharp V2; every image result also has an upscale link.
  • Background — cut the subject out as a transparent PNG (BiRefNet, local), or keep the subject and describe a new background. Every image result has a cut out link.
  • Relight — describe the light ("golden hour from the left"); Qwen-Image changes only the lighting.
  • Video FX — smooth any video with RIFE in-between frames (2–4× the frame rate; every video result has a smooth link), or remove a video's background (green / blue screen, black, white).
  • Video — bring a photo to life, or make a video from a prompt alone, with Wan 2.2 TI2V-5B (optional): 2–5 seconds at 24 fps, sized to the photo's shape. A second video model, Wan 2.2 I2V-A14B (test), is tuned for faces and identity (photo to video only).
  • Gallery — everything in output/ (images and videos), newest first, labelled by what made it, with use as photo, upscale, smooth and download.
  • Models page — see which models are installed, and download the missing or optional ones (resumable) without leaving the app.
  • ✨ Enhance — rewrite a short prompt into a detailed one with a local LLM (Ollama, optional).
  • LoRA — stack a style/behaviour LoRA on any mode, with a strength slider.
  • Job queue — press the button as often as you like; each job gets a card with its queue position, a live progress bar and its own Cancel. use as photo keeps editing a result. Jobs are tracked on the server: close the tab and reopen it later, and running jobs and finished results are still there.

AetherCanvas: tool panel and gallery

watercolour lighthouse motorcycle on a neon street cat in a treehouse nook
Qwen-Image 2.1 Krea 2 Turbo (8 steps) Qwen-Image 2.1

Before and after an edit: red satin gown, same face and room
Edit photo: “a red satin evening gown”: face, pose and room untouched

Before and after: same woman, the studio replaced by a golden-hour beach Before and after: same photo relit with warm sunlight from the left
Background → Replace: “a sunlit beach at golden hour” · Relight: “warm golden-hour sunlight from the left”

Animated: the woman from the photo smiles, tucks her hair behind her ear and tilts her head
Video, face model (test): one photo → 3 s that keeps her face. Wan 2.2 I2V-A14B, 4 steps, 6 min on an RTX 2060

The Models page

📖 Overview

Overview

The Flask app in app.py serves a browser UI and a small JSON API. It does not run the model itself — it drives a ComfyUI backend over HTTP, patching the API-format workflow in this repo with your prompt and settings, waiting for the render, then returning the images to the browser and saving them under output/.

File Purpose
app.py Flask web server (UI + API)
comfy_client.py Uploads photos, queues workflows on ComfyUI, relays progress, cancels and fetches results
prompt_helper.py ✨ Enhance: prompt rewriting via a local Ollama model
generate.py One-shot CLI that queues an image job (no server needed)
generate_video.py One-shot CLI for a Wan 2.2 video (no server needed)
templates/index.html The web UI markup
static/style.css, static/app.js The UI's styles and client logic
run.bat / run.ps1 / run.sh Create the venv and start the server on localhost
run-lan.bat Start the server on the whole LAN (0.0.0.0)
start_comfy.bat Starts ComfyUI if it isn't running (called by run.bat / run-lan.bat)
extra_model_paths.example.yaml Template to point ComfyUI at this repo's models/
workflows/qwen_image_2.1_gguf_t2i.json Drag into the ComfyUI UI
workflows/qwen_image_2.1_gguf_t2i_api.json API-format text-to-image graph the server/CLI drive
workflows/qwen_image_2.1_gguf_pose_api.json SDPose skeleton → Qwen-Image in that pose (Pose tab)
workflows/upscale_api.json 4x-UltraSharp V2 upscale (Upscale tab)
workflows/bg_remove_api.json, workflows/qwen_image_2.1_gguf_bg_replace_api.json Background: cut-out, and a new background around the subject
workflows/video_smooth_api.json, workflows/video_bg_remove_api.json Video FX: RIFE smoothing, per-frame background removal
workflows/sdxl_t2i_api.json, workflows/chroma1_hd_gguf_t2i_api.json SDXL and Chroma1-HD text-to-image
workflows/wan2.2_i2v_a14b_face_test_gguf_api.json Face video model (test): Wan 2.2 I2V-A14B, two experts
model_catalog.py, model_manager.py The Models page: what each model needs, status, downloads
workflows/qwen_image_2.1_gguf_edit_api.json API-format photo-edit graph
workflows/qwen_image_2.1_gguf_inpaint_api.json API-format masked-inpaint graph
workflows/krea2_turbo_gguf_t2i_api.json API-format Krea 2 Turbo text-to-image graph (optional)
workflows/wan2.2_ti2v_5b_gguf_api.json API-format Wan 2.2 TI2V-5B video graph (optional)
index.html, site/ The project's GitHub Pages site
link.bf A Brainfuck program that prints cursed-entertainment.itch.io

Architecture

browser ──HTTP──▶ Flask (app.py) ──HTTP + websocket──▶ ComfyUI ──▶ Qwen-Image 2.1 / Krea 2 / Wan 2.2 (GGUF)
   ▲                   │  │                                │
   └── results ◀───────┘  └──HTTP──▶ Ollama (✨ Enhance, optional)
                 output/*.png, *.mp4 ◀─────────────────────┘

The Flask server is a thin client. ComfyUI must be running with the Qwen-Image 2.1 GGUF models installed (steps 1–2 below). Then start the server (step 3).

Requirements

  • GPU: NVIDIA with 6 GB VRAM or more (tested on an RTX 2060 6 GB).
  • RAM: 32 GB minimum, 64 GB recommended if you use both Qwen-Image 2.1 and Krea 2.
  • Disk: about 15 GB for the default Qwen files, plus about 14 GB for Krea 2 and 9.4 GB for Wan 2.2 video.
  • Windows page file: at least 32 GB. ComfyUI reserves far more memory than it touches (the models, plus up to ~26 GB of pinned memory), and Windows refuses an allocation once RAM + page file is used up, even with plenty of RAM free. With an automatic page file (often only ~11 GB), ComfyUI fails with WinError 1455 or gets killed for low memory. Set it by hand: System Properties → Advanced → Performance Settings → Advanced → Virtual memory → Change, untick Automatically manage paging file size, pick a drive with room, choose Custom size, set Initial 32768 MB and Maximum 49152 MB, click Set, then OK, and restart.

1. Install ComfyUI-GGUF

Use the leejet fork. It supports Qwen-Image 2.1 natively.

cd ComfyUI/custom_nodes
git clone https://github.com/leejet/ComfyUI-GGUF

If you have the older city96/ComfyUI-GGUF and get Unknown model architecture!, switch to the fork.

2. Download the models

Keep the weights in this repo's models/ folder (the layout mirrors ComfyUI's):

models/
├── diffusion_models/qwen-image-2.1-UC-Q4_K_M.gguf       # or Q4_0 / Q5_K_M / Q6_K / Q8_0 / BF16
├── text_encoders/qwen3vl_8b_int8_convrot.safetensors    # or qwen3vl_8b_bf16.safetensors
└── vae/qwen_image_2.1_vae_bf16.safetensors

With the Hugging Face CLI (pip install huggingface_hub; the command is hf):

REPO=abenzerps/Qwen-Image-2.1-Uncensored-GGUF
hf download $REPO qwen-image-2.1-UC-Q4_K_M.gguf --local-dir models/diffusion_models
hf download $REPO text_encoders/qwen3vl_8b_int8_convrot.safetensors --local-dir models
hf download $REPO vae/qwen_image_2.1_vae_bf16.safetensors --local-dir models

The default set is about 14.6 GB (4.6 + 9.35 + 0.68). Q4_K_M uses about 4.6 GB of VRAM; the int8 text encoder is ~9.4 GB and can sit in system RAM. If you run out of VRAM, start ComfyUI with --lowvram (recommended for 6–8 GB cards).

Check the downloads

A download that was interrupted and resumed can end up the right size but full of zeros, and ComfyUI loads it without complaint (see Troubleshooting). Compare SHA-256 hashes before you run anything:

File SHA-256
qwen-image-2.1-UC-Q4_K_M.gguf e79c8a009f2ecbdb6c70fd663d9aea9ee304a0d91f347e4169a756b8ad141b41
qwen3vl_8b_int8_convrot.safetensors 8bfd0f6e12abf2d2d697ecc888e5e90b0d6741d6708f05799f53afa560452e8f
qwen_image_2.1_vae_bf16.safetensors bb21f7473051e1ac368515dd3f2e15cd44d7a11748ee8823e1ddca3e4876b7c9
# Windows (PowerShell)
Get-FileHash models\text_encoders\qwen3vl_8b_int8_convrot.safetensors
# macOS / Linux / Git Bash
sha256sum models/text_encoders/qwen3vl_8b_int8_convrot.safetensors

If a hash doesn't match, delete the file and its entry under models/.cache/huggingface and download it again. pip install hf_xet makes Hugging Face downloads faster and more reliable.

Point ComfyUI at these models

Rather than copying 14 GB into ComfyUI's own folder, copy extra_model_paths.example.yaml from this repo to extra_model_paths.yaml in the ComfyUI root (edit base_path to match) so ComfyUI reads the weights in place:

qwen_image_gguf:
    base_path: C:/cc/AetherCanvas-AI/models
    diffusion_models: diffusion_models
    unet: diffusion_models
    text_encoders: text_encoders
    clip: text_encoders
    vae: vae
    loras: loras

LoRA files go in models/loras/ (or ComfyUI's own models/loras/). ComfyUI reads extra_model_paths.yaml only at startup, so restart it after editing the file.

Krea 2 Turbo (optional)

A second text-to-image model: Krea 2 Turbo, a 12B distilled DiT that renders in 8 steps at CFG 1. It is text-to-image only (Edit and Inpaint always use Qwen-Image 2.1). Its license is the Krea 2 Community License (free below a revenue / seat threshold — read it on the model page, which needs a Hugging Face login, before any commercial use).

File Size From Folder
krea2_turbo_bf16-Q4_0.gguf 8.31 GB molbal/krea2-gguf diffusion_models/
qwen3vl_4b_fp8_scaled.safetensors 5.24 GB Comfy-Org/Krea-2 text_encoders/
qwen_image_vae.safetensors 0.25 GB Comfy-Org/Krea-2 vae/
KREA=F:/AIModels/krea2          # any drive with ~14 GB free
hf download Comfy-Org/Krea-2 vae/qwen_image_vae.safetensors     text_encoders/qwen3vl_4b_fp8_scaled.safetensors --local-dir $KREA
hf download molbal/krea2-gguf krea2_turbo_bf16-Q4_0.gguf --local-dir $KREA/diffusion_models

Then add the krea2: entry from extra_model_paths.example.yaml (with base_path set to that folder) and restart ComfyUI. The leejet ComfyUI-GGUF fork already knows the krea2 architecture. Use molbal's files: some other Krea 2 GGUFs are tagged qwen_image and may not load. Krea 2 needs about 8.3 GB for Q4_0, so on a 6 GB card part of it is offloaded to RAM; it is still quick because it only takes 8 steps. Style LoRAs made for Krea 2 are in Comfy-Org/Krea-2/loras (each has a trigger phrase listed in ComfyUI's Krea 2 template).

Wan 2.2 video (optional)

The Video tab runs Wan 2.2 TI2V-5B, the 5-billion-parameter model that does both text-to-video and image-to-video, as GGUF. It is Apache 2.0.

File Size From Folder
Wan2.2-TI2V-5B-Q5_K_M.gguf 3.81 GB QuantStack/Wan2.2-TI2V-5B-GGUF diffusion_models/
umt5-xxl-encoder-Q5_K_M.gguf 4.15 GB city96/umt5-xxl-encoder-gguf text_encoders/
wan2.2_vae.safetensors 1.41 GB Comfy-Org/Wan_2.2_ComfyUI_Repackaged vae/
WAN=F:/AIModels/wan22           # any drive with ~10 GB free
hf download QuantStack/Wan2.2-TI2V-5B-GGUF Wan2.2-TI2V-5B-Q5_K_M.gguf --local-dir $WAN/diffusion_models
hf download city96/umt5-xxl-encoder-gguf umt5-xxl-encoder-Q5_K_M.gguf --local-dir $WAN/text_encoders
hf download Comfy-Org/Wan_2.2_ComfyUI_Repackaged split_files/vae/wan2.2_vae.safetensors --local-dir $WAN
mv $WAN/split_files/vae $WAN/vae

Then add the wan22: entry from extra_model_paths.example.yaml and restart ComfyUI. Q4_K_M, Q6_K and Q8_0 of the model (and Q8_0 of the encoder) work too; pick them under Models & LoRA.

More models: SDXL, Chroma and the face video model (test)

All of these can be downloaded from the Models page (header → Models), which also shows what is installed. The files go into this repo's models/ (MODELS_DIR), which ComfyUI already reads, so they appear after a page reload. Base models are AetherCanvas's standard set; optional ones are extras.

Model Kind Files Size License
RealVisXL V5 Lightning SDXL image, base RealVisXL_V5.0_Lightning_fp16.safetensors → checkpoints/ 6.9 GB OpenRAIL++
Wan 2.2 I2V-A14B, faces (test) video, base LightX2V 4-step high- + low-noise Q3_K_M GGUFs → diffusion_models/, wan_2.1_vae.safetensors → vae/ 14.6 GB Apache 2.0
Chroma1-HD image, optional Chroma1-HD-Q4_K_M.gguf, t5xxl_fp8_e4m3fn_scaled.safetensors, ae.safetensors 11 GB Apache 2.0
NoobAI-XL 1.1 SDXL anime, optional NoobAI-XL-v1.1.safetensors → checkpoints/ 7.1 GB Fair AI Public License
Juggernaut XI SDXL photo, optional Juggernaut-XI-byRunDiffusion.safetensors → checkpoints/ 7.1 GB CC BY-NC-ND 4.0
BiRefNet background removal, base birefnet.safetensors → background_removal/ 0.44 GB MIT
RIFE v4.26 frame interpolation, base rife_v4.26.safetensors → frame_interpolation/ 23 MB MIT / Apache 2.0
DMD2 4-step LoRA SDXL speed-up, optional dmd2_sdxl_4step_lora_fp16.safetensors → loras/ 0.4 GB CC BY-NC 4.0

The face video model (test) reuses the Wan text encoder you already have; its VAE is the older Wan 2.1 one, which is a different file from the 5B model's wan2.2_vae. It renders at 16 fps in 4 steps, split between two 14B experts, so expect it to be slower than the 5B model on a 6 GB card (layers are swapped through system RAM).

Pose and Upscale models

Both run on ComfyUI's own nodes (no custom nodes); they only need two files in this repo's models/, which the checkpoints: and upscale_models: lines of the qwen_image_gguf: entry in extra_model_paths.example.yaml point ComfyUI at (restart ComfyUI after adding them).

File Size From Folder License
sdpose_wholebody_fp16.safetensors 1.92 GB Comfy-Org/SDPose (checkpoints/) checkpoints/ MIT
4x-UltraSharpV2.safetensors 140 MB Kim2091/UltraSharpV2 upscale_models/ CC BY-NC-SA 4.0 (non-commercial)
hf download Comfy-Org/SDPose checkpoints/sdpose_wholebody_fp16.safetensors --local-dir models
hf download Kim2091/UltraSharpV2 4x-UltraSharpV2.safetensors --local-dir models/upscale_models

Start ComfyUI

On a 6 GB card (tested on an RTX 2060), these flags cut render time from about 10 s to 4 s per step:

cd ComfyUI
python main.py --disable-comfy-compiler --disable-dynamic-vram --disable-async-offload --cache-ram 6 16

--cache-ram 6 16 keeps both model families in RAM when the queue mixes them. By default ComfyUI drops every model the current job doesn't use (its "inactive" threshold is all of your RAM), so each Qwen ↔ Krea 2 switch re-read the other model from disk — about 2.5 min for Krea 2 on a hard disk. With the flag, models are only dropped once free RAM falls below 16 GB. Qwen and Krea 2 together take about 28 GB, so lower the second number on a 32 GB machine.

Add --output-directory <this repo>/output if you want images you make in ComfyUI's own UI to land in this repo too. Images made through the Flask server are always saved to output/.

3. Run the Flask server

# Windows
run.bat
# PowerShell
./run.ps1
# macOS / Linux
./run.sh

run.bat, run-lan.bat and run.ps1 first start ComfyUI in its own window if it isn't already answering (from COMFYUI_DIR, with the flags above), and wait for it to come up. Each script creates a venv, installs requirements.txt and launches the server at http://127.0.0.1:7870. Open it, confirm the status dot reads ComfyUI connected, type a prompt and click Generate. To run it by hand:

pip install -r requirements.txt
python app.py

To expose it on the whole local network instead of just this machine, use run-lan.bat (binds 0.0.0.0:7870), or set HOST=0.0.0.0 by hand.

Using the web UI

The Tool dropdown at the top of the panel switches mode. In the Model and Video model pickers, hover (or arrow to) a model to see a card about it: what it's best at, speed, license, and whether it's installed. Each part of the form (photo, prompt, size & quality, models) folds open and closed, and « hides the whole panel so the gallery gets the full width; the page remembers both. Click an image to see it full size. The Project page ↗ link in the header opens the GitHub Pages site.

Quick options: under the prompt, every tool has one-click options that write a ready instruction for you (change the words if you like, then run it):

Tool Quick options
Generate styles (photo, cinematic, portrait, anime, watercolour, oil, sketch, 3D, pixel art, neon, black & white) and shots (close-up, full body, wide, from above, low angle), added to your prompt
Edit photo hair length (long, shoulder length, bob, short, pixie, shaved), hair style (bangs, curly, wavy, straight, ponytail, bun, braids), hair colour, face (smile, beard, glasses), makeup (natural, glam, smoky eyes, winged liner, red, nude or berry lips, glow, soft glam, gothic, bridal, freckles, no makeup), age, outfit, scene (night, snow, rain, golden hour, remove people, tidy up), art style
Inpaint remove it, flowers, a dress, sunglasses, a tattoo, blue sky
Uncrop same scene, beach, city, forest, studio, room
Pose astronaut, knight, dancer, superhero, anime girl, robot
Background beach, city at night, grey or white studio, forest, office, mountains, space, cyberpunk
Relight golden hour, window, studio, neon, candlelight, moonlight, rim light, overcast, sunset
Video motion (turn and smile, wind, look around, walk, laugh, wave, dance), camera (push in, orbit, pan, still), scene (rain, snow, fire, water)
  • Generate — prompt, size, steps, CFG, batch, seed. Model switches between Qwen-Image 2.1 and Krea 2 Turbo (steps jump to 8 and the negative prompt is hidden, since Turbo runs at CFG 1).

  • Edit photo — choose a photo, then type an instruction such as "change the dress to a red evening gown". Resolution sets the output size (about resolution² pixels); the photo's aspect ratio is kept.

  • Inpaint — choose a photo and paint over the area to change (brush size slider, Erase, Clear). The prompt describes only what goes inside the painted area. Denoise below 1 keeps more of the original's shape and colour. Press Inpaint again to re-roll with a new seed (leave Seed blank); the mask stays.

  • Uncrop — choose a photo, then pick Expand to (1:1, 4:3, 16:9, 9:16, zoom out 1.5×/2×, …) or drag the Left / Right / Top / Bottom sliders (extra canvas as % of the photo). The preview shows the grey area that will be generated. The prompt is optional — describe the wider scene ("a sunlit beach with a boardwalk") to steer it. Resolution is the size of the whole expanded image. Large expansions work best in steps: uncrop a little, use as photo, uncrop again.

  • Pose — choose (or drop) a photo of someone in the pose you want, then describe the new picture: "an astronaut on the moon, cinematic light". SDPose reads the body, hands and face and Qwen-Image draws the prompt in that pose, at the photo's aspect ratio. The skeleton map is saved next to the result, so you can see what was detected. It copies the pose, not the person.

  • Background — choose a photo, then Remove (a transparent PNG; no prompt) or Replace: describe the new background ("a sunlit beach at golden hour"). The subject is found by BiRefNet and kept pixel for pixel; Qwen-Image paints everything around it.

  • Relight — choose a photo and describe the light: "neon pink and blue from both sides", "soft overcast daylight". It is an Edit that is told to change only the lighting.

  • Video FX — choose (or drop) a video. Smooth adds in-between frames with RIFE (2× turns the face model's 16 fps into 32). Remove background runs BiRefNet on every frame and puts the subject on green, blue, black or white. Short clips work best.

  • Upscale — choose a photo and 2× or 4×. No prompt; it adds detail rather than just pixels. Faster: click upscale under any image result (2×). Large results open from output/ by URL instead of being sent inline.

  • Video — Video model picks Wan 2.2 TI2V-5B (prompt or photo) or the face model (test) (photo only, 16 fps). Optionally choose a start photo (it becomes the first frame and comes to life; × removes it for a video from the prompt alone), then describe the motion: "she slowly turns toward the camera and smiles, slow push-in". Size matches the photo's shape at about 832×480 (fast) or 1280×704 (HD), or pick a fixed size; Length is 2, 3.4 or 5 seconds at 24 fps. Steps default to 20 and CFG to 5. The result plays in the gallery, looping.

Every image result has use as photo, which makes it the new working photo (from Generate it switches to Edit) so you can keep refining; ← Previous photo steps back. That history lives in the browser tab — all results are also saved in output/.

The × in a result's corner removes it from the gallery, and it stays gone when you reload the page. The file itself is kept in output/.

Enter in the prompt box runs the current tab (Shift+Enter adds a new line).

Seed: leave it blank for a new random seed every run. 🎲 Random fills in a random seed that stays put, so you can change the prompt or settings and compare like for like; clear the box to go back to a fresh seed each run. Click seed … under any result to reuse that seed.

Queueing: the Generate / Edit / Inpaint / Uncrop / Video button never locks — every press queues a new job, and ComfyUI runs them one after another. Each job gets a card at the top of the gallery showing its prompt, Queued — 2 jobs ahead → Loading models → step n/25 — % with a progress bar, and its own Cancel, which removes it from the ComfyUI queue or interrupts it mid-render (other jobs are not touched). The card turns into the result when it finishes; earlier results stay below, newest first. Settings are captured when you press the button, so you can change the prompt, photo or mask for the next job right away. (Browsers allow about 6 connections per site, so keep it to 4–5 queued jobs per tab.) Waiting in the queue never times out; the time limit (30 min for Generate, 60 min for Edit / Inpaint / Uncrop, 2 hours for Video, or the timeout API field in seconds) only starts when the job actually runs, and a job that runs past it is stopped.

Jobs keep running if you close or reload the tab. The server remembers each browser's jobs (the last 200 across all browsers, until the Flask server restarts), so reopening the page brings back running jobs with their progress and the finished results.

Models & LoRA (collapsed, under Seed) picks the GGUF quantization and text encoder for the current model, and a LoRA with its Strength. The LoRA list is whatever ComfyUI can see; use a LoRA trained for the model you're running (a Krea 2 LoRA won't do anything useful on Qwen-Image, and vice versa).

✨ Enhance next to the prompt rewrites it with the local LLM for the current tab (a rich scene for Generate, a precise instruction for Edit, the masked area's contents for Inpaint, the motion and camera for Video); Undo restores what you typed.

✨ Enhance setup (optional)

Install Ollama and pull a small model:

ollama pull gemma3:4b-it-qat

It runs on the CPU by default (OLLAMA_NUM_GPU=0) so it never takes VRAM from ComfyUI. A rewrite takes about 6–20 s once the model is loaded (the first call after a while loads it, ~1 minute); while ComfyUI is rendering, the two share the CPU and it is slower. Avoid "thinking" models such as qwen3:4b — they spend the time and output on reasoning instead of the prompt. Without Ollama everything else works — the button just reports that it can't reach it.

Serve it as http://qwen.flask:7870

Give the server a friendly name on your LAN. Map qwen.flask to the host's IP — per device in its hosts file:

192.168.0.101   qwen.flask

or network-wide with a local DNS entry (dnsmasq / Pi-hole: address=/qwen.flask/192.168.0.101). Open port 7870 in the host's firewall so other devices can reach it. Then browse to http://qwen.flask:7870 (use http://, and skip the www.).

Environment variables

Variable Default Purpose
COMFY_SERVER http://127.0.0.1:8188 Where ComfyUI is listening
COMFYUI_DIR C:\cc\ComfyUI ComfyUI folder the Windows run scripts start it from
COMFYUI_ARGS (the flags in Start ComfyUI) Flags the run scripts pass to main.py
MODELS_DIR models/ in this repo Where the Models page downloads to
HOST 127.0.0.1 Interface Flask binds (0.0.0.0 to expose on the LAN)
PORT 7870 Port Flask binds (outside 5000–5999, which the DREAM Robotics fleet uses)
FLASK_DEBUG (off) Any non-empty value enables Flask debug mode
OLLAMA_SERVER http://127.0.0.1:11434 Where Ollama is listening (✨ Enhance)
OLLAMA_MODEL gemma3:4b-it-qat Model used to rewrite prompts (prefer a non-"thinking" model)
OLLAMA_NUM_GPU 0 Layers on the GPU (0 = CPU only; e.g. 99 = all on GPU)
COMFY_SERVER=http://192.168.1.50:8188 python app.py

Run on a separate machine (Raspberry Pi, LAN)

The server is a thin HTTP client, so the web UI and the GPU work can live on different machines. A low-power box (e.g. a Raspberry Pi 5) can host the UI around the clock while a GPU PC does the generation:

Raspberry Pi 5 (Flask UI, always on)  ──HTTP──▶  GPU PC (ComfyUI + Qwen-Image 2.1)

On the GPU PC, start ComfyUI so it accepts LAN connections:

python main.py --listen --lowvram --cache-ram 6 16   # --listen binds 0.0.0.0:8188

On the Pi only Flask and its tiny dependencies are needed — no torch, no models:

pip install -r requirements.txt
HOST=0.0.0.0 COMFY_SERVER=http://<gpu-pc-ip>:8188 python app.py

Then browse to http://<pi-ip>:7870. The Pi cannot generate on its own — a 20B model needs the GPU PC — it just serves the page and relays requests.

API

POST /api/generate — JSON in, JSON out.

curl -X POST http://127.0.0.1:7870/api/generate \
  -H "Content-Type: application/json" \
  -d '{"prompt": "a watercolour lighthouse at dusk", "width": 1344, "height": 768, "steps": 30}'
Field Default Notes
prompt (required) Text prompt
model qwen qwen (Qwen-Image 2.1) or krea2 (Krea 2 Turbo)
negative "" Only used when cfg > 1
width / height 1024 Multiples of 32, up to 2048
steps 25 (8 for krea2) Official Qwen pipeline uses 40–50; Krea 2 Turbo uses 8
cfg 1.0 1 is the official setting
seed random Integer for reproducibility
batch 1 Images per request
unet Q4_K_M GGUF file in models/diffusion_models
clip int8 Text encoder in models/text_encoders
lora (none) LoRA filename as listed by /api/loras
lora_strength 1.0 LoRA weight (negative values invert it)
job (none) Any id you choose, to poll progress or cancel (see below)
client (none) Any id you choose; GET /api/jobs?client= lists the jobs sent with it
timeout 1800 (3600 for edit / inpaint) Max seconds of running time; queue waiting doesn't count

Response: { "prompt_id", "seed", "images": [{ "filename", "url", "data" }] } where data is a base64 PNG and url serves the saved file from output/.

POST /api/edit — multipart form: image (file) + prompt (the edit instruction), plus optional resolution (default 1024), negative, steps, cfg, seed, unet, clip, lora, lora_strength, job.

curl -X POST http://127.0.0.1:7870/api/edit \
  -F image=@photo.png -F "prompt=change the dress to a red evening gown" -F seed=42

POST /api/inpaint — multipart form: image and mask files of the same size, in multiples of 32 (white = regenerate, black = keep; the red channel is read), prompt for the masked area, plus optional denoise (default 1.0), negative, steps, cfg, seed, unet, clip, lora, lora_strength, job.

curl -X POST http://127.0.0.1:7870/api/inpaint \
  -F image=@photo_960x1088.png -F mask=@mask_960x1088.png -F "prompt=a blue sequined dress"

Both return the same shape as /api/generate. Uncrop is /api/inpaint with a padded image (the photo on a larger grey canvas) and a mask that is white over the new border; send kind=uncrop to save results as Qwen_image_2.1_gguf_uncrop_*.png.

POST /api/video — multipart form: prompt (the motion and scene) and an optional image file (the first frame; without it, text-to-video), plus optional width / height (multiples of 32, default 832×480), frames (4n+1, default 49 = 2 s), fps (24), steps (20), cfg (5), negative (blank = a default list of artefacts to avoid), seed, unet, clip, lora, lora_strength, job, client, timeout (7200). Same response shape as /api/generate; the .mp4 is in images with "video": true, and data is its URL instead of base64.

curl -X POST http://127.0.0.1:7870/api/video \
  -F image=@photo.png -F "prompt=she turns toward the camera and smiles" -F frames=81

GET /api/gallery — everything in output/, newest first: {"items": [{"filename", "url", "video", "kind", "size", "mtime", "prompt"?, "seed"?}], "total"}; ?type=image|video, ?offset=, ?limit= (60, max 200). kind comes from the filename prefix (Qwen-Image, Edit, Relight, Face video, …).

GET /api/models — every model in the catalog with installed, per-file state (installed / partial / writing / missing), sizes, license and any download progress. POST /api/models/<id>/download queues its missing files (resumable); POST /api/models/<id>/cancel stops it. /api/video takes model=wanface for the face model (test), which needs image. /api/generate takes model = qwen, krea2, sdxl or chroma.

POST /api/background — multipart form: image and mode (remove: transparent PNG; replace: plus prompt for the new background, the image at multiples of 32, and the usual steps, cfg, seed, unet, clip, lora, …). POST /api/videofx — multipart form: video and effect (smooth with multiplier 2–4, or remove_bg with color = green / blue / black / white). Relight is /api/edit with kind=relight (the prompt describes only the light).

POST /api/pose — multipart form: image (a photo showing the pose) and prompt (the new picture), plus optional resolution (1024), negative, steps, cfg, seed, unet, clip, lora, lora_strength, job, client, timeout. Same response as /api/generate; images holds the result and the pose_map_*.png skeleton.

POST /api/upscale — multipart form: image, scale (2 or 4, default 2) and quality (fast: UltraSharp V2 Lite, ~10 s; best: the full V2, minutes per image), plus optional job, client, timeout. seed in the response is null.

curl -X POST http://127.0.0.1:7870/api/upscale -F image=@photo.png -F scale=4

POST /api/enhance — {"prompt": "...", "mode": "generate"|"edit"|"inpaint"|"uncrop"|"video"} → {"prompt": "<rewritten>", "model": "gemma3:4b-it-qat"}; HTTP 502 if Ollama is unreachable.

GET /api/progress/<job> — {"id", "kind", "prompt", "client", "created", "state", "value", "max", "prompt_id", "ahead"} for a request sent with that job id. state is starting, queued, running, done (with result: the response body minus the base64 data), failed (with error) or cancelled. value/max are sampler steps, and ahead is how many ComfyUI jobs run before it while it's queued. {"state": "unknown"} if the server doesn't know the id, for example after a restart.

GET /api/jobs?client=<id> — {"jobs": [...]}: every job sent with that client id, newest first, in the same shape as /api/progress. The web UI uses this to restore its gallery.

POST /api/cancel/<job> — dequeue or interrupt that job; the original request then returns HTTP 409 {"error": "Cancelled.", "cancelled": true}.

GET /api/loras — {"loras": ["file.safetensors", ...]}: every LoRA ComfyUI can see.

DELETE /api/jobs/<job>?image=<filename> — removes a finished result from the gallery (without image, the whole job). The file stays in output/.

GET /api/health — {"comfy": "up"|"down", "server": "..."}; HTTP 503 when ComfyUI is down.

CLI (no server)

generate.py queues a single job straight on ComfyUI:

python generate.py "a watercolour lighthouse on a rocky coast at dusk" --width 1344 --height 768
python generate.py "..." --unet qwen-image-2.1-UC-Q8_0.gguf --steps 40 --seed 42

generate_video.py does the same for a Wan 2.2 video (saved to output/):

python generate_video.py "she turns toward the camera and smiles" --image photo.png
python generate_video.py "waves crashing on a rocky shore at sunset" --frames 121   # text-to-video

Settings

  • Video (Wan 2.2): 20 steps, CFG 5 and shift 8 are the defaults. 832×480 is the fast size and 1280×704 the model's native one. On an RTX 2060 (6 GB), 832×480 × 49 frames (2 s) takes about 3.5 minutes, including loading the model; time grows with size × frames. Use a 4n+1 frame count.

  • cfg 1 is the official setting. The negative prompt is ignored unless cfg is above 1.

  • Steps: the official pipeline uses 40–50 euler steps. The default here is 25, which is faster.

  • Resolution: native up to 2048×2048. Use multiples of 32.

  • Edit / inpaint are slower than text-to-image: the reference photo roughly doubles the sequence the model attends over. On an RTX 2060 (6 GB) expect about 24 s per step, so 25 steps ≈ 10 minutes, versus about 4 s per step for plain generation.

Pipeline

Text-to-image: UnetLoaderGGUF → KSampler (euler / simple) → VAEDecode → SaveImage. Conditioning comes from CLIPLoader (type qwen_image) → TextEncodeQwenImage21. The graph follows the official Comfy-Org Qwen-Image 2.1 template, except that it loads the model with the GGUF loader and leaves out the optional prompt-rewriter LLM.

Edit: LoadImage feeds TextEncodeQwenImage21 as reference image 1 (with the VAE), so the Qwen3-VL encoder sees the photo and its VAE latents are spliced into the sequence. The node's latent output (an empty latent at the photo's size) goes to KSampler.

Krea 2 Turbo: UnetLoaderGGUF → KSampler (8 steps, cfg 1, euler / simple) → VAEDecode → SaveImage, with CLIPLoader (type krea2) → CLIPTextEncode for the prompt and ConditioningZeroOut as the negative, following Comfy-Org's Krea 2 Turbo template.

LoRA (any graph): when a LoRA is chosen, LoraLoaderModelOnly is inserted between the model loader and KSampler.

Uncrop: the browser places the photo on a larger mid-grey canvas (multiples of 32, about resolution² pixels) and builds a mask that is white over the border and reaches 4 % into the photo, feathered, so the seam is regenerated and blended. That pair runs through the Inpaint graph below, with a default "extend the photo outward…" instruction plus your optional scene text — and the unpadded photo is sent as the reference image (reference field): shown the grey canvas, the model just reproduces the grey border instead of filling it.

Inpaint: as Edit, with the photo also VAEEncoded and masked by SetLatentNoiseMask (mask from LoadImageMask), so only the masked area is resampled. ImageCompositeMasked then pastes the decoded result back onto the original through the mask. The browser scales photo and mask to the same multiple-of-32 size and feathers the mask edge before upload.

Troubleshooting

Every image is the same flat grey texture, whatever the prompt. The text encoder file is damaged. Its embedding table is partly zero-filled, so the model gets no prompt and denoises pure noise. This happens when a download is interrupted and later "resumed" from a full-size but incomplete file. Check the hashes in Check the downloads and re-download any file that doesn't match.

"Cannot reach ComfyUI" from the API or the red status dot in the UI: ComfyUI isn't running, or COMFY_SERVER points at the wrong host or port.

✨ Enhance says "Cannot reach Ollama" — Ollama isn't running (start the Ollama app or ollama serve), or OLLAMA_SERVER is wrong. It takes minutes — ComfyUI is rendering and both are fighting over the CPU; wait for the render, or set OLLAMA_NUM_GPU=99 if you have spare VRAM.

No progress bar, just a spinner — the websocket-client package is missing from the Flask venv; run pip install -r requirements.txt. Generation still works without it.

OSError: [WinError 1455] The paging file is too small when ComfyUI starts: Windows ran out of virtual memory (RAM + page file) while loading PyTorch, or ComfyUI gets killed for low memory while plenty of RAM is free. Set the page file to at least 32 GB (see Requirements) and close other programs that reserve a lot of memory, then start ComfyUI again.

License

The Qwen-Image weights are under the Qwen Research License, Krea 2 under the Krea 2 Community License, and Wan 2.2 under Apache 2.0. The workflow and scripts are under the LICENSE in this repo.



Farica☆Kimora © Cursed Entertainment 2026


About

AetherCanvas AI: one local web app for visual AI on your own GPU: text-to-image, photo editing, inpainting, uncropping, video and face swap with Qwen-Image 2.1, Krea 2 and Wan 2.2 (GGUF), driving ComfyUI.

Topics

Resources

Stars

2 stars

Watchers

1 watching

Forks

Releases

Sponsor this project

Packages

Contributors

Languages