diff --git a/plugins/antianqi/comfyui-studio/.gitignore b/plugins/antianqi/comfyui-studio/.gitignore new file mode 100644 index 0000000..c18dd8d --- /dev/null +++ b/plugins/antianqi/comfyui-studio/.gitignore @@ -0,0 +1 @@ +__pycache__/ diff --git a/plugins/antianqi/comfyui-studio/BETA.md b/plugins/antianqi/comfyui-studio/BETA.md new file mode 100644 index 0000000..dcdece6 --- /dev/null +++ b/plugins/antianqi/comfyui-studio/BETA.md @@ -0,0 +1,152 @@ +# 🧪 Beta Test Channel + +> **Status**: This is the beta test distribution of `comfyui-studio`. The official +> PR ([MiniMax-AI/MiniMax-Code-Plugins#15](https://github.com/MiniMax-AI/MiniMax-Code-Plugins/pull/15)) +> is open and waiting for review. While the merge is pending, the **standalone beta +> channel** lives at **[`antianqi/comfyui-studio`](https://github.com/antianqi/comfyui-studio)** +> (a single-plugin repo, not this monorepo fork). Please install from that repo, try the 6 +> scenarios, and report issues back so we can fix them before the official release. +> +> This monorepo fork (`MiniMax-Code-Plugins-1`) is only kept alive so the upstream PR +> can be force-pushed. The install instructions below point at the standalone repo. + +--- + +## TL;DR + +```bash +# 1. Clone the standalone beta repo (or download the latest beta release tarball) +git clone https://github.com/antianqi/comfyui-studio.git + +# 2. Symlink / copy the plugin into your MiniMax Code Plugins directory +ln -s "$(pwd)/comfyui-studio" \ + "$MCODE_HOME/plugins/antianqi/comfyui-studio" + +# 3. (Optional) Verify +ls "$MCODE_HOME/plugins/antianqi/comfyui-studio/plugin.json" +``` + +> Don't know `$MCODE_HOME`? On most installs it is `~/.minimax/plugins/` (Linux/macOS) or +> `%USERPROFILE%\.minimax\plugins\` (Windows). The Plugin manager UI in mcode also has a +> "Install from local path" button — point it at the standalone repo root and you are done — +> the plugin is the whole repo, not a sub-folder. + +## 3 install options + +### Option A — Clone the standalone beta repo (recommended for testing) + +```bash +git clone https://github.com/antianqi/comfyui-studio.git +# the plugin is the whole repo root +``` + +Pros: `git pull` later gets you the latest fixes without re-downloading. +Cons: requires git on the host. + +### Option B — Download a release tarball (recommended for one-shot test) + +Latest beta release: +**[`v0.2.0-beta.1`](https://github.com/antianqi/comfyui-studio/releases/tag/v0.2.0-beta.1)** + +```bash +curl -L https://github.com/antianqi/comfyui-studio/archive/refs/tags/v0.2.0-beta.1.tar.gz \ + | tar -xz +cd comfyui-studio-0.2.0-beta.1 +# the plugin is the whole folder +``` + +Pros: a frozen version, easy to roll back. +Cons: you have to re-download to get fixes. + +### Option C — Download just the files you need (smallest payload) + +[Browse the standalone beta repo at this tag](https://github.com/antianqi/comfyui-studio/tree/v0.2.0-beta.1), +hit "Download raw file" per file, OR use the GitHub CLI: + +```bash +gh release download v0.2.0-beta.1 \ + --repo antianqi/comfyui-studio \ + --pattern '*' \ + --dir comfyui-studio-beta +``` + +## Try it (5 minutes) + +After install, in any mcode session, paste this: + +```text +Use comfyui-studio to verify that my local ComfyUI is reachable, then submit scenario 3 +(改图 / flux2-klein-image-edit) with the prompt "the same person sitting in an office +chair, the same outfit, professional lighting" and report the saved image path. +``` + +Expected: the agent probes ComfyUI, submits the bundled workflow, polls until done, downloads +the image, and tells you where it was saved. The end-to-end walkthrough is in +[`examples/minimal-run.md`](examples/minimal-run.md). + +If you have a trained character LoRA handy, try scenario 1 ("用我的角色画一张自拍") and +scenario 2 ("照着这张照片再画一张同款") — these are the two presets that exercise the +full face-LoRA + style-LoRA + Z-Image stack. + +## What to test (the matrix) + +| Scenario | Trigger | Preset workflow | Notes for testers | +|---|---|---|---| +| 1 | 生图 | `workflows/selfie-text-to-image.json` | Try with and without `--trigger`; try changing `LoraLoaderModelOnly.strength_model` | +| 2 | 模仿 | `workflows/selfie-mimicry.json` | Needs `ComfyUI-LLaMA-CPP` + `comfyui_controlnet_aux`; **will fail on installs without those custom nodes** | +| 3 | 改图 | `workflows/flux2-klein-image-edit.json` | Try different `KSampler.denoise` values (0.4 / 0.75 / 0.95) | +| 4 | 融合 | `workflows/flux2-klein-image-edit-dual.json` | Try with the "person on the left" + "outfit on the right" semantic | +| 5 | 首帧 | `workflows/drama-first-frame.json` | Set the second character LoRA's `strength_model = 0` for single-character dramas | +| 6 | 出片 | `workflows/drama-image-to-video.json` | Distilled LTX-2.3 is VRAM-friendly; non-distilled 22B is 30–60 min/shot | + +## How to give feedback + +Pick whichever channel fits you best: + +- **GitHub issues on the standalone beta repo** (preferred for reproducible bugs): + https://github.com/antianqi/comfyui-studio/issues + Please include: your OS, your ComfyUI version, the scenario number, the prompt you used, + and the full error output (or the prompt_id if ComfyUI swallowed the job). + +- **Feishu mcode internal beta group**: just @ me in the channel where the announcement + landed. Best for "it works but the UX is awkward" / "the docs say X but the workflow does Y". + +- **PR comments**: https://github.com/MiniMax-AI/MiniMax-Code-Plugins/pull/15 + Best for structural / design feedback on the docs and the 6-scenario split. + +## Known issues / not-yet-implemented + +These are the things that are **intentionally out of scope** for the current PR but might +look like bugs: + +- **Scenario 2 needs custom nodes**: `selfie-mimicry.json` uses + `comfyui_controlnet_aux`, `ComfyUI-LLaMA-CPP`, `rgthree-comfy`, and + `ComfyUI-Impact-Pack`. If your install does not have them, the submission will fail with + `missing_node_type`. We are not bundling these because they vary across ComfyUI forks. + The full list is in `skills/comfyui-character/SKILL.md`. +- **LoRA placeholders**: workflows reference `your_face_lora.safetensors`, + `your_style_lora.safetensors`, `character_a_lora.safetensors`, etc. You **must** edit the + `lora_name` field in the workflow JSON to point at your own file before submission. The + Plugin does not bundle any private LoRAs. +- **Checkpoint filenames are the reference install's filenames**: the JSONs reference the + exact files that ship on the reference ComfyUI install that built this Plugin. On a + different install, you may need to edit the `ckpt_name` / `clip_name1` / `vae_name` / + `control_net_name` fields. The Plugin does not pick abstract placeholder names for + public generation models. +- **No TTS / no FFmpeg / no spreadsheet editor**: stages 2, 3, 6, 7 of the 7-stage drama + pipeline are intentionally out of scope. See `skills/comfyui-drama/SKILL.md` for the full + list. + +## Versioning + +- **`v0.2.0-beta.1`** — the version in `plugin.json` matches the PR #15 commit. The + standalone beta repo is at [`antianqi/comfyui-studio`](https://github.com/antianqi/comfyui-studio); + release tags live there, not in this fork. +- Future betas will tag on the standalone repo, with semver `-beta.N` suffixes. +- The first non-beta release (`v0.2.0`) will be published from the **official** repo + (`MiniMax-AI/MiniMax-Code-Plugins`), not the standalone beta repo. Once that happens, + the standalone beta repo will archive but stay readable as a historical reference. + +## License + +Apache-2.0. See [LICENSE](LICENSE). diff --git a/plugins/antianqi/comfyui-studio/LICENSE b/plugins/antianqi/comfyui-studio/LICENSE new file mode 100644 index 0000000..13a6129 --- /dev/null +++ b/plugins/antianqi/comfyui-studio/LICENSE @@ -0,0 +1,200 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of tracking or otherwise improving the Work, + but excludes communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for describing the origin of the Work and + reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Support. While redistributing the Work or + Derivative Works thereof, You may accept and charge a fee for, + acceptance of support, warranty, indemnity, or other liability + obligations and/or rights consistent with this License. However, + in accepting such obligations, You may act only on Your own + behalf and on Your sole responsibility, not on behalf of any other + Contributor, and only if You agree to indemnify, defend, and hold + each Contributor harmless for any liability incurred by, or claims + asserted against, such Contributor by reason of Your accepting any + such warranty or support. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file name and extension be included on the same line as the + copyright text for easy identification within third-party archives. + + Copyright 2026 antianqi + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/plugins/antianqi/comfyui-studio/README.md b/plugins/antianqi/comfyui-studio/README.md new file mode 100644 index 0000000..58a23ef --- /dev/null +++ b/plugins/antianqi/comfyui-studio/README.md @@ -0,0 +1,250 @@ +# ComfyUI Studio + +> 🧪 **Beta test build** — `v0.2.0-beta.1`. The official PR +> ([MiniMax-AI/MiniMax-Code-Plugins#15](https://github.com/MiniMax-AI/MiniMax-Code-Plugins/pull/15)) +> is open and waiting for review. **To participate in the beta**: install the standalone +> single-plugin repo ([`antianqi/comfyui-studio`](https://github.com/antianqi/comfyui-studio)) +> following the instructions in [`BETA.md`](BETA.md), then report issues back so we can +> fix them before the official merge. + +A generic, dependency-free toolkit for driving a local ComfyUI server. **Two parts in one Plugin**: +**A. a natural-language control layer** for submitting any ComfyUI workflow, polling the queue, and +downloading outputs, and **B. six preset workflow templates** that cover the most common recurring +tasks (selfie, mimicry, image edit, image fusion, drama first frame, drama image-to-video). +Bundles four Skills and a stdio MCP server. Ships zero native binaries, zero installers, zero +third-party dependencies. Apache-2.0. + +--- + +## The two parts + +| Part | What it does | Where to look | +|---|---|---| +| **A. Natural-language control** | The transport between an agent and a local ComfyUI server. Submit any workflow JSON, poll the queue, download outputs. The same three primitives are exposed through a Python CLI and a 0-dep stdio MCP server. | `comfyui-workflow` Skill + `server.mjs` + `scripts/submit_workflow.py` | +| **B. 6 preset workflows** | Six opinionated workflow JSONs for the most common ComfyUI tasks. Each is wired with prompt / image / LoRA markers so the agent can submit them with one call. | `comfyui-character` Skill (scenarios 1–4) + `comfyui-drama` Skill (scenarios 5–6) + the `workflows/*.json` files | + +Part A is the workhorse. Part B is six production-ready starting points built on top of Part A. + +--- + +## Part B — the 6 preset scenarios + +Numbered by how a user typically says it. The number is the **trigger scenario**, not a model id — +the agent reads the user's intent and picks the matching number. + +| # | Trigger | What the user typically says | Preset workflow | One-line description | +|---|---|---|---|---| +| **1** | **生图** (generate an image) | "用我的角色画一张自拍" / "生成一张赛博朋克风格的照片" | `workflows/selfie-text-to-image.json` | Text + 2 LoRAs (face + style) → one character image | +| **2** | **模仿** (mimic an image) | "照着这张照片再画一张同款" / "模仿这张图的姿势和光线" | `workflows/selfie-mimicry.json` | Reference image + ControlNet + vision LLM reprompt + 2 LoRAs → one character image in the same pose/lighting | +| **3** | **改图** (edit an image) | "把背景换成办公室" / "衣服换成西装,光线调亮" | `workflows/flux2-klein-image-edit.json` | 1 reference image + prompt → edited image | +| **4** | **融合** (fuse two images) | "把这张图里的人物放到那张图里" / "让她穿上这件衣服,背景换成海边" | `workflows/flux2-klein-image-edit-dual.json` | 2 reference images + prompt → fused image ("the person from image 1 in the scene of image 2") | +| **5** | **首帧** (drama first frame) | "生成这部短剧第 3 镜的首帧画面" | `workflows/drama-first-frame.json` | 2-character LoRA slots + prompt → one 16:9 first-frame image | +| **6** | **出片** (image to video) | "把这个首帧做成 4 秒视频" | `workflows/drama-image-to-video.json` | 1 first-frame image + motion prompt → one MP4 clip | + +> **Routing rule of thumb**: scenarios 1–4 → `comfyui-character`; scenarios 5–6 → `comfyui-drama`; +> anything else ("use ComfyUI to ...", "submit this workflow", "check the queue") → `comfyui-workflow` +> (Part A). + +The detailed recipes (model stack, custom-node requirements, prompt structure, knobs) live in each +Skill's `SKILL.md`. The table here is the index. + +--- + +## Part A — natural-language control + +No matter what the user wants, the last three steps are always the same: + +1. **Submit a workflow** → `POST /prompt`, capture `prompt_id` +2. **Poll the queue** → `GET /history/` until terminal status +3. **Download outputs** → `GET /view?filename=...&subfolder=...&type=output` + +Two equivalent entry points. Pick whichever the host agent supports. + +### A1. Python CLI (no MCP required) + +```bash +# Health probe +python skills/comfyui-workflow/scripts/submit_workflow.py --probe + +# Submit any workflow (one of the bundled presets or the user's own) +python skills/comfyui-workflow/scripts/submit_workflow.py \ + --workflow plugins/antianqi/comfyui-studio/workflows/flux2-klein-image-edit.json \ + --prompt "the same person sitting in an office chair, professional lighting" \ + --filename reference_face.png \ + --output-dir ./out +``` + +The script understands three marker conventions so you don't have to rewrite the workflow JSON +for every run: + +| Marker | Replaced with | +|---|---| +| `__PROMPT__` (in any `CLIPTextEncode.text` or `CR Text.text` field) | the value of `--prompt` | +| `__TRIGGER__` (in any `CR Text.text` field) | the value of `--trigger` (LoRA trigger word) | +| `__IMAGE1__` / `__IMAGE2__` (in any `LoadImage.image` field) | the value of `--filename` / `--filename2` | + +### A2. stdio MCP server (no Python required) + +`mcp.json` registers a 200-line stdio JSON-RPC server in plain Node stdlib. Zero npm dependencies. +The three tools mirror the Python script: + +| MCP tool | Mirrors CLI flag | What it does | +|---|---|---| +| `submit_prompt` | `--workflow` + `--prompt` | Submit a workflow JSON; return `prompt_id` | +| `check_queue` | `--queue` | Report running and pending counts and IDs | +| `get_image` | `--download` | Download a generated image by filename | + +If the host agent is MCP-aware, prefer this. If not, use the Python CLI — they are behaviourally +identical. + +--- + +## Model boundary (read this first) + +The Plugin draws a clean line between **public generation models** and **user-supplied LoRAs**. + +| Asset type | What you see in the workflow JSON | Why | +|---|---|---| +| **Public generation models** (checkpoints, VAE, CLIP, ControlNet, video models, vision LLMs) | **The actual filenames on disk in the reference ComfyUI install that built this Plugin.** | The workflow runs out of the box on the reference install. Edit the loader field to point at your own file on a different install. | +| **LoRAs** | **Generic placeholder names**: `your_face_lora.safetensors`, `your_style_lora.safetensors`, `character_a_lora.safetensors`, `character_b_lora.safetensors`, `any_motion_lora.safetensors`. | LoRAs are user-trained identity assets. The Plugin does not bundle or name anyone's private LoRAs. The user edits the `LoraLoader*.lora_name` field to point at their own file. | +| **Image inputs** | `__IMAGE1__` / `__IMAGE2__` | The Python CLI / MCP server replaces these at submit time. | +| **Prompt / trigger text** | `__PROMPT__` / `__TRIGGER__` | The Python CLI / MCP server replaces these at submit time. | + +The exact filenames / placeholders in the six bundled workflows: + +| # | Workflow | Checkpoint | CLIP / VAE | ControlNet / Vision LLM | LoRA placeholders | +|---|---|---|---|---|---| +| 1 | `selfie-text-to-image.json` | `Z-Image-Base-8steps-豹豹喵呜の白玉v2White_Marble-AIO_v2-bf16.safetensors` | bundled with CheckpointLoader | — | `your_face_lora.safetensors` + `your_style_lora.safetensors` | +| 2 | `selfie-mimicry.json` | `ZIT-moodyPornMix_zitV10R1DPO_fp16.safetensors` | bundled with UNETLoader | `Z-Image-Fun-Controlnet-Union-2.1.safetensors` (ControlNet) + `Qwen3.5-9B-Uncensored-HauhauCS-Aggressive-Q4_K_M.gguf` (vision LLM) | same 2 LoRA placeholders | +| 3 | `flux2-klein-image-edit.json` | `flux-2-klein-base-9b-fp8.safetensors` | `qwen_3_8b_fp8mixed.safetensors` + `diffusion_pytorch_model.safetensors` | — | (no LoRA) | +| 4 | `flux2-klein-image-edit-dual.json` | `flux-2-klein-base-9b-fp8.safetensors` | same CLIP/VAE as #3 | — | (no LoRA) | +| 5 | `drama-first-frame.json` | `Z-Image-Base-8steps-豹豹喵呜の白玉v2White_Marble-AIO_v2-bf16.safetensors` | bundled with CheckpointLoader | — | `character_a_lora.safetensors` + `character_b_lora.safetensors` | +| 6 | `drama-image-to-video.json` | `10Eros_v1-fp8mixed_learned.safetensors` | `ltx-2.3_text_projection_bf16.safetensors` + `gemma_3_12B_it_fp8_scaled.safetensors` + `LTX23_video_vae_bf16.safetensors` | — | `any_motion_lora.safetensors` | + +If your ComfyUI install uses a different filename for any of these — for example, +`sd_xl_base_1.0.safetensors` instead of `Z-Image-Base-8steps-豹豹喵呜の白玉v2White_Marble-AIO_v2-bf16.safetensors`, +or `ltx-2.3-distilled.safetensors` instead of `10Eros_v1-fp8mixed_learned.safetensors` — edit the +relevant `ckpt_name` / `clip_name1` / `clip_name2` / `vae_name` / `control_net_name` field in the +workflow JSON. ComfyUI will accept whatever file is on disk under the name you set. + +--- + +## Try it + +After installing the Plugin into MiniMax Code: + +```text +Use comfyui-studio to verify that my local ComfyUI is reachable, then submit scenario 3 +(改图 / flux2-klein-image-edit) with the prompt "the same person sitting in an office +chair, the same outfit, professional lighting" and report the saved image path. +``` + +Expected result: the agent probes ComfyUI, submits the bundled +`workflows/flux2-klein-image-edit.json`, polls until done, downloads the image, and tells you +where it was saved. The end-to-end walkthrough is in [`examples/minimal-run.md`](examples/minimal-run.md). + +--- + +## What you get + +``` +comfyui-studio/ +├── plugin.json # Plugin manifest (required) +├── mcp.json # Registers the stdio MCP server +├── server.mjs # 200-line zero-dep stdio JSON-RPC server +├── LICENSE # Apache-2.0 +├── README.md # This file +├── skills/ +│ ├── comfyui-studio/ # Routing layer: numbers 1-6 + freeform → sibling Skill +│ ├── comfyui-workflow/ # The basic transport (Part A) +│ │ ├── SKILL.md +│ │ ├── references/api-reference.md +│ │ └── scripts/submit_workflow.py +│ ├── comfyui-character/ # Scenarios 1, 2, 3, 4 (selfie + mimicry + Klein edits) +│ │ ├── SKILL.md +│ │ └── references/ +│ │ ├── prompt-patterns.md +│ │ └── lora-guide.md +│ └── comfyui-drama/ # Scenarios 5, 6 (first-frame + image-to-video) +│ └── SKILL.md +├── workflows/ # The 6 preset workflow JSONs +│ ├── selfie-text-to-image.json # scenario 1: 生图 +│ ├── selfie-mimicry.json # scenario 2: 模仿 +│ ├── flux2-klein-image-edit.json # scenario 3: 改图 +│ ├── flux2-klein-image-edit-dual.json # scenario 4: 融合 +│ ├── drama-first-frame.json # scenario 5: 首帧 +│ └── drama-image-to-video.json # scenario 6: 出片 +├── examples/ +│ ├── README.md +│ └── minimal-run.md # 5-minute end-to-end walkthrough +└── docs/ + ├── security-notes.md + └── troubleshooting.md +``` + +--- + +## Four Skills, one routing table + +The Plugin's `comfyui-studio` Skill is the entry point. It reads the user's intent and routes to +one of three siblings. Each sibling's `SKILL.md` is the actual implementation guide. + +| Skill | Job | When | +|---|---|---| +| `comfyui-studio` | Routing: read the user's intent, pick the number 1–6 or fall through to freeform | Always start here when the user says "use ComfyUI to ..." | +| `comfyui-workflow` | The transport (Part A). Submit / poll / download any workflow JSON. | "Submit this workflow", "check the queue", "download the image" — or as a building block by the sibling Skills. | +| `comfyui-character` | Scenarios 1, 2, 3, 4. Character selfies (with LoRA) and Flux.2 Klein image edits. | "Make a selfie", "mimic this photo", "edit / fuse this image" | +| `comfyui-drama` | Scenarios 5, 6 + the 7-stage pipeline around them (storyboard → TTS → first frame → image-to-video → subtitle burn → assembly). | "Make a short drama", "generate the first frame", "turn this into a video clip" | + +Read [`skills/comfyui-studio/SKILL.md`](skills/comfyui-studio/SKILL.md) for the full routing table. + +--- + +## Requirements + +- A running ComfyUI server reachable at `COMFYUI_URL` (default `http://127.0.0.1:8188`). +- The checkpoint / VAE / CLIP / ControlNet files referenced in each preset (see the model + boundary table above). Edit any loader field that does not match your install. +- For scenarios 1 and 2 (`comfyui-character` selfie workflows): a face LoRA and a style LoRA + in `models/loras/`. The Plugin does not bundle them; it uses placeholder names. +- For scenario 2 (`selfie-mimicry.json`): the custom-node pack list in + `skills/comfyui-character/SKILL.md` (controlnet_aux, LLaMA-CPP, ModelPatchLoader, etc.). +- For scenarios 3 and 4 (`comfyui-character` Klein workflows): the Flux.2 Klein 9B UNET, the + Qwen3 8B CLIP, and the bundled VAE. +- For scenarios 5 and 6 (`comfyui-drama`): two character LoRAs (for two-character dramas), an + LTX-Video checkpoint with its DualCLIP text encoders and VAE, a TTS pipeline of the user's + choice, and FFmpeg for the assembly stages. +- Python 3.10+ if you use the Python CLI. Node 18+ if you use the MCP server. +- No accounts. No paid services. No telemetry. No native binaries. No installers. + +--- + +## Data and network + +- The Plugin only talks to `COMFYUI_URL`. It makes no other network calls. +- The Plugin reads workflow JSON files the user names and writes outputs to the user-chosen + `--output-dir`. It does not read or upload model files, LoRAs, voice samples, or any other + identity asset. +- The Plugin does not log, persist, or transmit any user data. +- See [`docs/security-notes.md`](docs/security-notes.md) for the full threat model. + +--- + +## What the Plugin does not do + +- It does not train LoRAs. Training is its own project; this Plugin only consumes a LoRA the + user already has. +- It does not bundle or distribute any LoRA, model, face embedding, voice sample, or any other + identity asset. Every character and voice is user-supplied. +- It does not install packages, run post-install hooks, or download native binaries. +- It does not call any cloud service or third-party API. +- It does not run TTS, edit spreadsheets, burn subtitles, or assemble audio/video. The + `comfyui-drama` Skill documents the full 7-stage pipeline but only ships ComfyUI workflow + templates for the two image-side stages (scenarios 5 and 6); the other stages are user + pipeline steps that the Plugin deliberately does not assume. + +--- + +## License + +Apache-2.0. See [LICENSE](LICENSE). diff --git a/plugins/antianqi/comfyui-studio/docs/security-notes.md b/plugins/antianqi/comfyui-studio/docs/security-notes.md new file mode 100644 index 0000000..92dba51 --- /dev/null +++ b/plugins/antianqi/comfyui-studio/docs/security-notes.md @@ -0,0 +1,71 @@ +# Security notes + +This Plugin is designed to be safe to install in environments where ComfyUI is already running +locally. It does not introduce new network destinations, does not run any installers, and does +not request elevated privileges. + +## What the Plugin touches + +| Surface | Action | Notes | +|---|---|---| +| Local HTTP API (`COMFYUI_URL`) | Reads queue, submits workflows, downloads images | Default `http://127.0.0.1:8188`; loopback only unless overridden | +| Local filesystem | Reads workflow JSON files the user points at; writes outputs to a user-chosen directory | The script never writes outside `--output-dir` | +| Environment variables | Reads `COMFYUI_URL` and optional `COMFYUI_API_TOKEN` | Token is sent only to `COMFYUI_URL` | +| LoRA / checkpoint files | The Plugin does not read, write, copy, or upload any model file | Models and LoRAs are loaded by ComfyUI itself, on its own terms | + +The Plugin does not: + +- Make outbound requests to anywhere except `COMFYUI_URL`. +- Install, download, or update any package (no `npm install`, no `pip install`, no post-install + hook). The MCP server is a single-file `server.mjs` that uses only the Node 18+ stdlib. +- Collect telemetry, analytics, or crash reports. +- Read or upload any file outside of what the user explicitly passes as `--workflow`, + `--output-dir`, or `--filename`. +- Set environment variables, modify the user's shell, or change system settings. + +## Threat model + +The Plugin assumes: + +- The host running ComfyUI is a development workstation the user controls. +- The `COMFYUI_URL` address points to a process the user started and trusts. +- Any reverse proxy in front of ComfyUI is the user's own and is responsible for its own + authentication. + +The Plugin does not defend against a malicious ComfyUI server. If `COMFYUI_URL` points to a +server an attacker controls, the attacker can return any JSON they like from `/prompt`, +`/history`, and `/view` — the Plugin will display that JSON to the agent. Treat `COMFYUI_URL` +the same way you treat the path of any binary you run. + +## Authentication + +Plain ComfyUI has no authentication. If the user runs ComfyUI behind a reverse proxy that +requires a bearer token, set `COMFYUI_API_TOKEN` in the environment. The Plugin's MCP server +and the Python script both add it as `Authorization: Bearer ` to every request. The +token never leaves the host; it is not logged, echoed, or sent to any other destination. + +## The MCP server boundary + +The Plugin's `server.mjs` exposes three tools: `submit_prompt`, `check_queue`, and +`get_image`. Each maps to a single HTTP call against `COMFYUI_URL`. The server holds no state +between calls and does not open any socket other than the stdio JSON-RPC channel to its parent +agent. It is a thin adapter, not a service. + +## What you should review before adopting + +- **The bundled workflow JSON.** Open `workflows/text-to-image.json` and confirm you understand + every node. ComfyUI workflows are arbitrary code graphs; the Plugin's only assumption is the + `__PROMPT__` token convention, which is opt-in. +- **`mcp.json`.** Confirm the `COMFYUI_URL` it sets is the address you want. +- **`server.mjs`.** It is ~200 lines of plain Node stdlib; review it the same way you would + review any other small script you are about to run. +- **The `submit_workflow.py` script.** Single file, stdlib only; review it before running. + +## Reporting a security issue + +See the parent repository's [`SECURITY.md`](https://github.com/MiniMax-AI/MiniMax-Code-Plugins/blob/main/SECURITY.md) +for the responsible-disclosure process. + +## License + +Apache-2.0. See [LICENSE](../LICENSE). diff --git a/plugins/antianqi/comfyui-studio/docs/troubleshooting.md b/plugins/antianqi/comfyui-studio/docs/troubleshooting.md new file mode 100644 index 0000000..20c38de --- /dev/null +++ b/plugins/antianqi/comfyui-studio/docs/troubleshooting.md @@ -0,0 +1,88 @@ +# Troubleshooting + +Common failures when using the Plugin and how to recover from them. + +## Health probe fails + +```text +[probe] FAILED status=0 body=connection error: [Errno 111] Connection refused +``` + +- ComfyUI is not running, or is running on a different port. Start ComfyUI and confirm with + `curl http://127.0.0.1:8188/system_stats`. +- You set `COMFYUI_URL` to a different address. Confirm with `echo $COMFYUI_URL` (or + `echo $env:COMFYUI_URL` in PowerShell) and unset it if you do not need to override. + +## Workflow rejected with `node not found` + +```text +[submit] workflow rejected: {"type":"validation", "node_id": "10", "message": "..."} +``` + +The workflow references a node or class ComfyUI does not have. Two common causes: + +- The bundled workflow assumes a custom node you have not installed (e.g. `LoraLoader` from a + custom-node pack). Install the missing pack or remove the offending node from the workflow. +- The workflow was saved by a newer ComfyUI version and references a node that has been + renamed or removed. Re-save the workflow from your installed version. + +## Workflow accepted, but no output appears + +```text +[poll] success but no output files reported (workflow may not have a Save node) +``` + +The workflow does not have a `SaveImage`, `VHS_VideoCombine`, or other terminal node. Add one +or pick a workflow that ends in a save. + +## VRAM exhausted mid-run + +The Plugin does not have a VRAM watchdog; the user must watch `nvidia-smi` themselves for +long jobs (over 30 minutes). If the GPU reports `CUDA out of memory`: + +1. Stop the current run: `POST /queue/interrupt`. +2. Clear the queue: `POST /queue/clear`. +3. Reduce the resolution in the workflow (e.g. `EmptyLatentImage` width/height). +4. Reduce the model precision if the workflow supports it. +5. Restart ComfyUI to free leaked allocations. + +The Plugin never re-submits a failed run on its own — generation is expensive and the user +should be in the loop. + +## Poll times out after 15 minutes + +The Plugin's Python script and the documented recipe both cap polling at 15 minutes. For jobs +that take longer: + +- Use a smaller model or lower resolution so the job fits inside the cap. +- Or modify the script's `POLL_TIMEOUT_S` to your desired value. The Plugin does not change + this default; do not raise it silently. +- Or split the work into multiple submissions and run them serially. + +## The MCP server starts but the agent cannot see tools + +The Plugin's `mcp.json` registers the server under the name `comfyui-studio`. If the host +agent does not see the three tools (`submit_prompt`, `check_queue`, `get_image`): + +- Confirm the host agent supports the MCP stdio transport. +- Confirm Node.js 18+ is on the host's PATH. +- Run the server manually to see its startup banner: `node server.mjs`. It should print + `[comfyui-studio] ready; talking to http://127.0.0.1:8188` and stay running. + +## LoRA does nothing at inference + +See [`skills/comfyui-character/references/prompt-patterns.md`](../skills/comfyui-character/references/prompt-patterns.md). +The most common causes, in order of frequency: + +1. The trigger word is not the first token in the prompt. +2. `strength_model` is set too low (try 0.85 → 1.0). +3. The base model was changed after training. +4. The negative prompt is fighting the LoRA. + +## Identity drifts across generations + +Same reference as above; the prompt-patterns file walks through the fix-it-once checklist. + +## License + +Apache-2.0. See [LICENSE](../LICENSE). diff --git a/plugins/antianqi/comfyui-studio/examples/README.md b/plugins/antianqi/comfyui-studio/examples/README.md new file mode 100644 index 0000000..bfe512d --- /dev/null +++ b/plugins/antianqi/comfyui-studio/examples/README.md @@ -0,0 +1,24 @@ +# Examples + +The Plugin ships end-to-end walkthroughs that exercise every Skill in this Plugin. Start with +[`minimal-run.md`](minimal-run.md) — it assumes a fresh ComfyUI install and walks through a +complete run in under 5 minutes. + +| File | What it covers | +|---|---| +| `minimal-run.md` | Health probe, submit a workflow, poll, download. The "hello world" of this Plugin. | + +## Adding your own example + +If you build a workflow or pattern that other users would benefit from, contributions welcome. +The Plugin does not accept example prompts that: + +- Reference a private LoRA, voice sample, or face embedding you trained for a single user. +- Embed API tokens, account identifiers, or proprietary model names. +- Depend on a paid ComfyUI cloud provider that requires a secret. + +Generic patterns only. + +## License + +Apache-2.0. See [LICENSE](../LICENSE). diff --git a/plugins/antianqi/comfyui-studio/examples/minimal-run.md b/plugins/antianqi/comfyui-studio/examples/minimal-run.md new file mode 100644 index 0000000..e668be6 --- /dev/null +++ b/plugins/antianqi/comfyui-studio/examples/minimal-run.md @@ -0,0 +1,123 @@ +# Minimal end-to-end run + +This walkthrough takes a fresh ComfyUI install and runs the bundled `text-to-image.json` workflow +in under 5 minutes. It exercises every layer of the Plugin: the MCP server, the Python script, +both skills, and the health-probe / submit / poll / download flow. + +## What you need + +- A running ComfyUI server on `http://127.0.0.1:8188` (or another address — set `COMFYUI_URL`). +- At least one checkpoint model installed in ComfyUI's `models/checkpoints/` directory. +- For the character variant: a LoRA the user trained, dropped into `models/loras/`. + +You do **not** need Node.js, npm, or any third-party Python package. The Plugin ships a +zero-dependency stdio MCP server and a zero-dependency Python script. + +## Step 1 — verify ComfyUI is reachable + +```bash +cd +python skills/comfyui-workflow/scripts/submit_workflow.py --probe +``` + +Expected output: + +```text +[probe] OK comfyui_version=0.29.2 +[probe] device=cuda:0 vram_free=7.4GB/8.0GB +``` + +If the probe fails, see `docs/troubleshooting.md`. Do not continue. + +## Step 2 — pick a checkpoint + +Open the bundled workflow: + +``` +workflows/text-to-image.json +``` + +The `CheckpointLoaderSimple` node (node `3`) has `inputs.ckpt_name` set to `"any.safetensors"`. +Replace this with the actual filename of a checkpoint on your ComfyUI install. Use +`GET /object_info/CheckpointLoaderSimple` to list available names, or look in +`models/checkpoints/` yourself. + +```bash +curl -s http://127.0.0.1:8188/object_info/CheckpointLoaderSimple | \ + python -c "import json,sys; d=json.load(sys.stdin); print(d['CheckpointLoaderSimple']['input']['required']['ckpt_name'][0])" +``` + +That prints a JSON array of every checkpoint ComfyUI can see. Pick one and edit +`workflows/text-to-image.json` to use its filename. + +## Step 3 — submit a workflow + +```bash +python skills/comfyui-workflow/scripts/submit_workflow.py \ + --workflow workflows/text-to-image.json \ + --prompt "a tabby cat sleeping on a windowsill, morning light, photorealistic" \ + --output-dir ./out +``` + +The `--prompt` argument is substituted into the `__PROMPT__` token inside the workflow's +`CLIPTextEncode` node. If you remove that marker from the workflow, edit the workflow's `text` +field directly instead. + +Expected output: + +```text +[prompt] substituted __PROMPT__ in nodes: ['30'] +[submit] prompt_id= number=42 +[poll] status=success +[poll] saved: + out/comfyui_studio_00001_.png +``` + +The script polls every 2 seconds with a 15-minute cap. Long-running jobs need a manual cap; +see `docs/troubleshooting.md` for VRAM recovery if something goes wrong mid-run. + +## Step 4 — verify the output + +```bash +ls -la out/ +file out/comfyui_studio_00001_.png +``` + +You should see a real PNG, around 1-5 MB for a 1024×1024 generation. If the file is small or +zero bytes, the workflow likely had a downstream error; see Step 6 in the troubleshooting +guide. + +## Step 5 — same flow via the MCP server + +The Plugin's stdio MCP server exposes the same three primitives as the Python script. From any +MCP-capable agent: + +```text +Use the comfyui-studio MCP server to: + 1. check_queue to confirm ComfyUI is empty + 2. submit_prompt with the bundled text-to-image workflow and the prompt "a tabby cat..." + 3. wait until /history/ returns success + 4. get_image on the returned filename +``` + +The agent should report the prompt_id, the saved file path, and the time taken. The +[server.mjs](../server.mjs) file is the source; it is plain Node 18+ stdlib, no `package.json` +needed to run it. + +## What to try next + +- **Tweak the prompt.** Change the `--prompt` argument and re-run. The seed in the workflow + is fixed at `42`, so identical prompts produce identical images. +- **Switch the LoRA.** Edit `workflows/text-to-image.json` and set `inputs.lora_name` on node + `10` to a LoRA you placed in `models/loras/`. Read + [`skills/comfyui-character/references/prompt-patterns.md`](../skills/comfyui-character/references/prompt-patterns.md) + for the 4-module prompt structure that makes LoRAs work. +- **Use the image-to-image workflow.** `workflows/image-to-image.json` accepts an input image + through ComfyUI's `/upload/image` endpoint. Set the `image` field on node `20` after upload. +- **Bring your own workflow.** Drop a `.json` file into `workflows/` and pass its path via + `--workflow`. The Plugin only assumes the `__PROMPT__` convention for prompt override; + everything else passes through unchanged. + +## License + +Apache-2.0. See [LICENSE](../LICENSE). diff --git a/plugins/antianqi/comfyui-studio/mcp.json b/plugins/antianqi/comfyui-studio/mcp.json new file mode 100644 index 0000000..1da0c21 --- /dev/null +++ b/plugins/antianqi/comfyui-studio/mcp.json @@ -0,0 +1,13 @@ +{ + "$schema": "https://agent-plugins.org/schemas/1.0.0/mcp.schema.json", + "mcpServers": { + "comfyui-studio": { + "type": "stdio", + "command": "node", + "args": ["./server.mjs"], + "env": { + "COMFYUI_URL": "http://127.0.0.1:8188" + } + } + } +} diff --git a/plugins/antianqi/comfyui-studio/plugin.json b/plugins/antianqi/comfyui-studio/plugin.json new file mode 100644 index 0000000..46c7553 --- /dev/null +++ b/plugins/antianqi/comfyui-studio/plugin.json @@ -0,0 +1,21 @@ +{ + "$schema": "https://agent-plugins.org/schemas/1.0.0/plugin.schema.json", + "name": "comfyui-studio", + "version": "0.2.0-beta.1", + "description": "Generic toolkit for driving a local ComfyUI 8188 server: workflow submission, queue monitoring, image download, and reproducible generation patterns. Includes a dependency-free stdio MCP server and four Skills (comfyui-studio + comfyui-workflow + comfyui-character + comfyui-drama) covering 6 preset scenarios (生图 / 模仿 / 改图 / 融合 / 首帧 / 出片).", + "author": { + "name": "antianqi", + "url": "https://github.com/antianqi" + }, + "license": "Apache-2.0", + "keywords": [ + "minimax-code", + "plugin", + "comfyui", + "stable-diffusion", + "image-generation", + "mcp", + "workflow", + "local-inference" + ] +} diff --git a/plugins/antianqi/comfyui-studio/server.mjs b/plugins/antianqi/comfyui-studio/server.mjs new file mode 100644 index 0000000..d4585a4 --- /dev/null +++ b/plugins/antianqi/comfyui-studio/server.mjs @@ -0,0 +1,235 @@ +// ComfyUI Studio — dependency-free stdio MCP server +// +// Exposes three tools for driving a local ComfyUI 8188 server: +// - submit_prompt Submit a workflow JSON, return the prompt_id +// - check_queue Inspect running / pending queue +// - get_image Download a generated image by filename +// +// Configuration via environment variables: +// COMFYUI_URL ComfyUI base URL (default http://127.0.0.1:8188) +// COMFYUI_API_TOKEN Optional bearer token if ComfyUI is fronted by an auth proxy +// +// This server intentionally has zero npm dependencies. The MCP wire +// protocol is plain JSON-RPC over stdio, so we speak it directly with +// line-delimited JSON messages. + +import { createInterface } from "node:readline"; +import { stdin, stdout, stderr } from "node:process"; +import { request as httpRequest } from "node:http"; +import { request as httpsRequest } from "node:https"; +import { URL } from "node:url"; + +const COMFYUI_URL = (process.env.COMFYUI_URL || "http://127.0.0.1:8188").replace(/\/$/, ""); +const COMFYUI_API_TOKEN = process.env.COMFYUI_API_TOKEN || ""; + +const TOOLS = [ + { + name: "submit_prompt", + description: + "Submit a ComfyUI workflow JSON to the local server and return the prompt_id for later polling.", + inputSchema: { + type: "object", + properties: { + workflow: { + type: "object", + description: "ComfyUI workflow JSON (the value to put under the `prompt` key).", + }, + }, + required: ["workflow"], + additionalProperties: false, + }, + }, + { + name: "check_queue", + description: + "Return the current ComfyUI queue: counts of running and pending prompts and their prompt_ids.", + inputSchema: { type: "object", properties: {}, additionalProperties: false }, + }, + { + name: "get_image", + description: + "Download a generated image from ComfyUI's /view endpoint by filename and subfolder. Returns the image bytes as a base64 string plus the content type.", + inputSchema: { + type: "object", + properties: { + filename: { type: "string", description: "Image filename on the ComfyUI server." }, + subfolder: { type: "string", description: "Optional subfolder under the output directory." }, + folder_type: { + type: "string", + enum: ["output", "input", "temp"], + description: "ComfyUI folder type. Default: output.", + }, + }, + required: ["filename"], + additionalProperties: false, + }, + }, +]; + +function httpJson(method, path, body) { + return new Promise((resolve, reject) => { + const target = new URL(COMFYUI_URL + path); + const lib = target.protocol === "https:" ? httpsRequest : httpRequest; + const req = lib( + { + method, + hostname: target.hostname, + port: target.port || (target.protocol === "https:" ? 443 : 80), + path: target.pathname + target.search, + headers: { + "content-type": "application/json", + accept: "application/json", + ...(COMFYUI_API_TOKEN ? { authorization: `Bearer ${COMFYUI_API_TOKEN}` } : {}), + }, + }, + (res) => { + const chunks = []; + res.on("data", (c) => chunks.push(c)); + res.on("end", () => { + const text = Buffer.concat(chunks).toString("utf8"); + resolve({ status: res.statusCode, text }); + }); + } + ); + req.on("error", reject); + if (body !== undefined) req.write(typeof body === "string" ? body : JSON.stringify(body)); + req.end(); + }); +} + +async function callSubmitPrompt({ workflow }) { + const { status, text } = await httpJson("POST", "/prompt", { prompt: workflow }); + if (status >= 400) { + return { content: [{ type: "text", text: `ComfyUI error ${status}: ${text}` }], isError: true }; + } + let data; + try { + data = JSON.parse(text); + } catch (err) { + return { content: [{ type: "text", text: `Non-JSON response: ${text}` }], isError: true }; + } + if (data.error) { + return { + content: [{ type: "text", text: `Workflow rejected: ${JSON.stringify(data.error)}` }], + isError: true, + }; + } + return { + content: [ + { + type: "text", + text: JSON.stringify({ prompt_id: data.prompt_id, number: data.number }, null, 2), + }, + ], + }; +} + +async function callCheckQueue() { + const { status, text } = await httpJson("GET", "/queue"); + if (status >= 400) { + return { content: [{ type: "text", text: `ComfyUI error ${status}: ${text}` }], isError: true }; + } + const data = JSON.parse(text); + return { + content: [ + { + type: "text", + text: JSON.stringify( + { + running: (data.queue_running || []).length, + pending: (data.queue_pending || []).length, + running_ids: (data.queue_running || []).map((q) => q[1]), + pending_ids: (data.queue_pending || []).map((q) => q[1]), + }, + null, + 2 + ), + }, + ], + }; +} + +async function callGetImage({ filename, subfolder = "", folder_type = "output" }) { + const params = new URLSearchParams({ filename, subfolder, type: folder_type }); + const { status, text } = await httpJson("GET", `/view?${params.toString()}`); + if (status >= 400) { + return { content: [{ type: "text", text: `ComfyUI error ${status}: ${text}` }], isError: true }; + } + // We cannot send raw binary in a stdio JSON-RPC response, so we return the + // bytes as base64 alongside the inferred content type. + const buf = Buffer.from(text, "binary"); + const contentType = filename.toLowerCase().endsWith(".png") ? "image/png" : "image/octet-stream"; + return { + content: [ + { + type: "text", + text: JSON.stringify( + { filename, content_type: contentType, size_bytes: buf.length, base64: buf.toString("base64") }, + null, + 2 + ), + }, + ], + }; +} + +const HANDLERS = { + submit_prompt: callSubmitPrompt, + check_queue: callCheckQueue, + get_image: callGetImage, +}; + +function send(obj) { + stdout.write(JSON.stringify(obj) + "\n"); +} + +function log(...args) { + stderr.write("[comfyui-studio] " + args.join(" ") + "\n"); +} + +const rl = createInterface({ input: stdin, terminal: false }); +rl.on("line", async (line) => { + let msg; + try { + msg = JSON.parse(line); + } catch (err) { + log("discarding non-JSON line"); + return; + } + const { id, method, params } = msg; + try { + if (method === "initialize") { + send({ + jsonrpc: "2.0", + id, + result: { + protocolVersion: "2024-11-05", + serverInfo: { name: "comfyui-studio", version: "0.1.0" }, + capabilities: { tools: {} }, + }, + }); + } else if (method === "notifications/initialized") { + // no-op + } else if (method === "tools/list") { + send({ jsonrpc: "2.0", id, result: { tools: TOOLS } }); + } else if (method === "tools/call") { + const { name, arguments: args } = params || {}; + const handler = HANDLERS[name]; + if (!handler) { + send({ jsonrpc: "2.0", id, error: { code: -32601, message: `Unknown tool: ${name}` } }); + return; + } + const result = await handler(args || {}); + send({ jsonrpc: "2.0", id, result }); + } else if (method === "ping") { + send({ jsonrpc: "2.0", id, result: {} }); + } else { + send({ jsonrpc: "2.0", id, error: { code: -32601, message: `Method not found: ${method}` } }); + } + } catch (err) { + log("handler error:", err && err.stack ? err.stack : String(err)); + send({ jsonrpc: "2.0", id, error: { code: -32603, message: String(err && err.message || err) } }); + } +}); + +log("ready; talking to", COMFYUI_URL); diff --git a/plugins/antianqi/comfyui-studio/skills/comfyui-character/SKILL.md b/plugins/antianqi/comfyui-studio/skills/comfyui-character/SKILL.md new file mode 100644 index 0000000..af40092 --- /dev/null +++ b/plugins/antianqi/comfyui-studio/skills/comfyui-character/SKILL.md @@ -0,0 +1,235 @@ +--- +name: comfyui-character +description: Produce consistent character images and reference-driven edits inside ComfyUI. Use this Skill for scenarios 1–4 from `comfyui-studio`'s trigger index: 1=生图 (selfie from text), 2=模仿 (mimic a reference image), 3=改图 (Flux.2 Klein single-image edit), 4=融合 (Flux.2 Klein dual-image fuse). Ships four preset workflow templates: `workflows/selfie-text-to-image.json`, `workflows/selfie-mimicry.json`, `workflows/flux2-klein-image-edit.json`, `workflows/flux2-klein-image-edit-dual.json`. +--- + +# ComfyUI Character + +Four preset workflows for character-consistent selfies (with a user-trained LoRA) and +reference-image-driven edits (with Flux.2 Klein). The Plugin does **not** distribute any +character LoRA, face embedding, voice sample, or other private identity asset. Every character +this Skill produces is one the user trained themselves and dropped into ComfyUI's standard +`models/loras/` directory. + +## The 4 trigger scenarios + +These are scenarios 1–4 from `comfyui-studio`'s index. The number is the trigger, the workflow +is the implementation. + +| # | Trigger | User says | Preset workflow | Model stack at a glance | +|---|---|---|---|---| +| **1** | **生图** (generate) | "用我的角色画一张自拍" | `workflows/selfie-text-to-image.json` | Z-Image base + 2 LoRAs (face + style) | +| **2** | **模仿** (mimic) | "照着这张照片再画一张同款" | `workflows/selfie-mimicry.json` | ZIT-flavoured Z-Image finetune + 2 LoRAs + Z-Image Fun ControlNet + Qwen3.5 vision LLM | +| **3** | **改图** (edit) | "把背景换成办公室" | `workflows/flux2-klein-image-edit.json` | Flux.2 Klein 9B + Qwen3 8B + bundled VAE | +| **4** | **融合** (fuse) | "把这张图里的人物放到那张图里" | `workflows/flux2-klein-image-edit-dual.json` | same as #3, with 2x `LoadImage` inputs | + +Scenarios 1 and 2 share the **same two LoRA placeholders** (`your_face_lora.safetensors` for +the character's face, `your_style_lora.safetensors` for the artistic style). Scenarios 3 and 4 +do not use LoRAs at all — the character / object identity is carried by the reference image +itself, not by a LoRA. + +## Why scenarios 1 and 2 use different checkpoints + +The two selfie presets use **different checkpoints** for a reason: the direct selfie is the +"default look" (Z-Image base, 8 steps, the canonical character), while the mimicry preset is a +"reference-driven" workflow that needs a ZIT-flavoured Z-Image finetune plus a ControlNet and a +local vision LLM to do the imitation. They are not interchangeable — if you want scenario 1 +(direct generation), use the Z-Image base; if you want scenario 2 (mimicry), use the ZIT +finetune. + +--- + +## Scenario 1 — 生图 (generate) — `selfie-text-to-image.json` + +Pipeline: Checkpoint → `LoraLoaderModelOnly` (face) → `LoraLoaderModelOnly` (style) → KSampler. + +### Models + +| Field | Value (as written in the JSON) | Where it lives on disk | +|---|---|---| +| `CheckpointLoaderSimple.ckpt_name` | `Z-Image-Base-8steps-豹豹喵呜の白玉v2White_Marble-AIO_v2-bf16.safetensors` | `models/checkpoints/` | +| `LoraLoaderModelOnly[0].lora_name` (face) | `your_face_lora.safetensors` (placeholder — replace with your file) | `models/loras/` | +| `LoraLoaderModelOnly[1].lora_name` (style) | `your_style_lora.safetensors` (placeholder — replace with your file) | `models/loras/` | +| CLIP / VAE | bundled with the CheckpointLoader (outputs `[…, 1]` and `[…, 2]`) | — | + +### Submit + +1. **Place the two LoRAs** under `/models/loras/`. Edit the two + `LoraLoaderModelOnly.lora_name` fields in the JSON to point at your filenames if they differ + from the placeholders. +2. **Compose the 4-module prompt** (see `references/prompt-patterns.md`): + ``` + , , , + ``` + Pass via `--prompt` on the CLI; the `__PROMPT__` marker in the workflow gets replaced. +3. **Submit** via the Python script or the MCP server (see `comfyui-workflow/SKILL.md`). + +### Knobs + +| Knob | Default | What it does | When to change it | +|---|---|---|---| +| `LoraLoaderModelOnly[0].strength_model` (face) | 1.0 | How strongly the face LoRA controls the model | Lower to 0.7 if the character overpowers the scene | +| `LoraLoaderModelOnly[1].strength_model` (style) | 1.0 | How strongly the style LoRA controls the model | Lower to 0.7 if the style is too dominant | +| `KSampler.steps` | 8 | Distilled-model default; raise to 20+ for a non-distilled base | — | +| `KSampler.cfg` | 1.5 | Distilled-model default; raise to 6 for a non-distilled base | — | + +--- + +## Scenario 2 — 模仿 (mimic) — `selfie-mimicry.json` + +Pipeline: `LoadImage` (reference) → `AIO_Preprocessor` → ControlNet application → +`UNETLoader` (ZIT-flavoured finetune) → KSampler → `VAEDecode` → `SaveImage`, with two +`LoraLoaderModelOnly` nodes (face + style) and a `llama_cpp_instruct_adv` node that runs the +vision LLM on the reference to produce the positive prompt. + +This workflow does **not** use IP-Adapter. It uses a different mechanism: a ControlNet is fed +the reference image's preprocessed output, and a vision LLM (Qwen3.5) looks at the reference +and produces a text description that becomes the positive prompt. The two LoRAs then keep the +character identity stable. + +### Models + +| Field | Value (as written in the JSON) | Where it lives on disk | +|---|---|---| +| `UNETLoader.unet_name` | `ZIT-moodyPornMix_zitV10R1DPO_fp16.safetensors` (ZIT-flavoured Z-Image finetune) | `models/unet/` (or wherever your UNETLoader looks) | +| `ControlNetLoader.control_net_name` | `Z-Image-Fun-Controlnet-Union-2.1.safetensors` | `models/controlnet/` | +| `LoraLoaderModelOnly[0].lora_name` (face) | `your_face_lora.safetensors` (placeholder — replace) | `models/loras/` | +| `LoraLoaderModelOnly[1].lora_name` (style) | `your_style_lora.safetensors` (placeholder — replace) | `models/loras/` | +| `llama_cpp_instruct_adv.ckpt_name` (vision LLM) | `Qwen3.5-9B-Uncensored-HauhauCS-Aggressive-Q4_K_M.gguf` | `models/llama/` (or wherever the llama.cpp loader looks) | +| `LoadImage.image` | `__IMAGE1__` (replace at submit time with `--filename`) | `input/` | + +### Custom nodes required + +The `selfie-mimicry.json` workflow depends on several custom nodes that ship outside the default +ComfyUI install. Without these, submission will fail with `missing_node_type`. + +| Custom node | Used for | Install | +|---|---|---| +| `comfyui_controlnet_aux` (or any `AIO_Preprocessor` + `ZImageFunControlnet` provider) | ControlNet preprocessing and application | `https://github.com/Fannovel16/comfyui_controlnet_aux` | +| `ComfyUI-Manager` + the `ModelPatchLoader` / `ZImageFunControlnet` nodes | Loading the Z-Image Fun ControlNet union | any pack that ships `ModelPatchLoader` | +| `ComfyUI-LLaMA-CPP` (or a llama.cpp loader for ComfyUI) | Loading `Qwen3.5-9B-Uncensored-HauhauCS-Aggressive-Q4_K_M.gguf` for vision reprompting | `https://github.com/daniel-lewis-ab/ComfyUI-LLaMA-CPP` (or compatible) | +| `rgthree-comfy` (or compatible Any Switch / Image Comparer pack) | The `Any Switch (rgthree)` nodes in the original workflow | `https://github.com/rgthree/rgthree-comfy` | +| `ComfyUI-Impact-Pack` (or any `LayerUtility: ImageScaleByAspectRatio V2` provider) | Aspect-ratio-aware image scaling | `https://github.com/ltdrdata/ComfyUI-Impact-Pack` | + +### Submit + +1. **Install the custom nodes** above. +2. **Place the model files** under the standard ComfyUI directories. +3. **Drop a reference image** into the ComfyUI input folder and pass its filename with + `--filename` on the CLI (the `__IMAGE1__` marker in the workflow's `LoadImage.image` field + gets replaced). Or edit the `LoadImage.image` field directly in the JSON. +4. **Set the trigger word** via `--trigger` (the `__TRIGGER__` marker in the `CR Text` node + gets replaced). +5. **Submit** via the Python script or the MCP server. The script will poll until the run + finishes and download the saved image from `outputs/`. + +### The 4-module prompt (for the vision-LLM-reprompted positive prompt) + +For scenario 2 the prompt is produced by the vision LLM, not by the user. The user provides +only the **trigger word** (via `--trigger`). The vision LLM fills in the rest of the +description from the reference image. See `references/prompt-patterns.md` for the rules of +thumb on what the trigger word should and shouldn't include. + +--- + +## Scenario 3 — 改图 (edit) — `flux2-klein-image-edit.json` + +Pipeline: `LoadImage` (reference) → Flux.2 Klein UNET + CLIP + VAE → KSampler → `VAEDecode` → +`SaveImage`. One `LoadImage` input (`__IMAGE1__`). + +### Models + +| Field | Value (as written in the JSON) | Where it lives on disk | +|---|---|---| +| `UNETLoader.unet_name` | `flux-2-klein-base-9b-fp8.safetensors` | `models/unet/` | +| `DualCLIPLoader.clip_name1` | `qwen_3_8b_fp8mixed.safetensors` | `models/clip/` | +| `DualCLIPLoader.clip_name2` | `diffusion_pytorch_model.safetensors` | `models/clip/` | +| `VAELoader.vae_name` | bundled with the UNET, or the user's preferred Flux.2 Klein VAE | `models/vae/` | +| `LoadImage.image` | `__IMAGE1__` (replace at submit time with `--filename`) | `input/` | + +### Submit + +1. **Place the reference image** into the ComfyUI input folder and pass its filename with + `--filename` on the CLI. +2. **Pass the edit prompt** with `--prompt`. The prompt describes **what to change** (e.g. + "the same person sitting in an office chair, professional lighting"). The reference image + supplies the unchanged parts. +3. **Submit** via the Python script or the MCP server. + +### Knobs + +| Knob | Default | What it does | When to change it | +|---|---|---|---| +| `KSampler.steps` | 20 | Standard distilled-Klein default | Raise to 30+ for higher quality at the cost of latency | +| `KSampler.cfg` | 1.0 | Distilled-Klein default | Usually leave alone | +| `KSampler.denoise` | 0.75 | How much the prompt is allowed to change the reference | Lower to 0.4–0.5 for "subtle" edits; raise to 0.9+ for "full re-imagination" | + +--- + +## Scenario 4 — 融合 (fuse) — `flux2-klein-image-edit-dual.json` + +Same model stack as scenario 3, but with **two `LoadImage` inputs** (`__IMAGE1__` and +`__IMAGE2__`). The semantic is "**the subject of image 1 in the scene of image 2, with the +prompt describing the rest**" — e.g. "the person from image 1, now wearing the outfit from +image 2, in the office of image 2". + +### Models + +Identical to scenario 3, with one extra `LoadImage` input. Replace `__IMAGE1__` with +`--filename` and `__IMAGE2__` with `--filename2` at submit time. + +### Submit + +1. **Place both reference images** into the ComfyUI input folder. +2. **Pass filenames** with `--filename` (image 1, the subject) and `--filename2` (image 2, + the scene / outfit). +3. **Pass the fusion prompt** with `--prompt` describing how to combine them. +4. **Submit** via the Python script or the MCP server. + +### Knobs + +Same as scenario 3, plus an extra `ReferenceLatent` strength control (default 0.85) that +governs how much of image 2 the result should inherit. + +--- + +## Why most "consistent character" attempts fail (scenarios 1 and 2) + +A LoRA is a strong prior on identity, but it is not magic. Three things derail consistency: + +1. **Inconsistent training data.** Mix of angles, lighting, outfits, and stylizations. A LoRA + trained on a single photo or on stylistically inconsistent reference images will not + generalize. See `references/lora-guide.md` for the data-prep checklist. +2. **Weak prompt structure.** Putting the LoRA trigger word in a wall of adjectives loses the + signal. The LoRA trigger must lead the prompt and the prompt must avoid words that fight it. +3. **Wrong sampler / scheduler / CFG for the base model.** Some samplers oversmooth identity + features. See `references/prompt-patterns.md` for the rules of thumb. + +## LoRAs are required for scenarios 1 and 2 + +Both selfie workflows' `LoraLoaderModelOnly` nodes reference **placeholder** LoRA filenames +(`your_face_lora.safetensors` and `your_style_lora.safetensors`). If those files do not exist +on disk, ComfyUI will fail at submission. To swap in a different LoRA, edit the `lora_name` +field in the workflow JSON to point at your file. The LoRAs are user identity assets; the +Plugin does not bundle them and does not name anyone's private LoRAs. + +## What this Skill does not do + +- It does not run LoRA training. Training is its own project and has its own tooling (Kohya, + OneTrainer, ai-toolkit). The Skill only consumes a LoRA the user already has. +- It does not bundle or distribute any LoRA, model, face embedding, or voice sample. Every + identity asset is user-supplied. +- It does not invent characters. The user must define who the character is. + +## Requirements + +- ComfyUI running locally (see `comfyui-workflow/SKILL.md` for the transport). +- For scenarios 1 and 2: a face LoRA and a style LoRA in `models/loras/`. The workflow JSONs + reference them as `your_face_lora.safetensors` and `your_style_lora.safetensors`. +- For scenario 2: the custom-node pack list above. Plus a ZIT-flavoured Z-Image finetune, a + Z-Image Fun ControlNet, and a Qwen3.5 GGUF vision LLM. +- For scenarios 3 and 4: the Flux.2 Klein 9B UNET, the Qwen3 8B CLIP, and the bundled VAE. + +## License + +Apache-2.0. See [LICENSE](../../LICENSE). diff --git a/plugins/antianqi/comfyui-studio/skills/comfyui-character/references/lora-guide.md b/plugins/antianqi/comfyui-studio/skills/comfyui-character/references/lora-guide.md new file mode 100644 index 0000000..9d5db73 --- /dev/null +++ b/plugins/antianqi/comfyui-studio/skills/comfyui-character/references/lora-guide.md @@ -0,0 +1,87 @@ +# Training a character LoRA — data, settings, and where to put the file + +This Plugin consumes LoRAs the user trains themselves. It does not train LoRAs and does not +distribute any. This reference collects the data-prep and training-setting rules that make a +character LoRA actually deliver consistency at inference time. + +## Where the file goes + +When the user is done training, the LoRA file is a single `.safetensors` file. The user places +it under ComfyUI's standard models directory: + +``` +/models/loras/.safetensors +``` + +After placing the file, the user must press the "Refresh" button in ComfyUI's LoRA loader node +(or restart ComfyUI) for the file to appear in the dropdown. The Plugin never reads from any +other directory; ComfyUI's loader only sees files in the standard tree. + +The Plugin does not move, copy, upload, or symlink LoRA files. The user owns the file and the +path. + +## The training data checklist + +The LoRA will only be as consistent as the data it was trained on. Before clicking "Train", +work through this list. Skipping items here is the most common cause of "the LoRA does not +work" reports later. + +- **20–40 images is the sweet spot.** Fewer than 15 and the LoRA underfits. More than 60 and + it overfits and you cannot change outfits at inference. +- **Varied angles.** Front, three-quarter, side, back of head. A LoRA trained only on + front-facing photos will not survive a side profile. +- **Varied lighting.** Daylight, overcast, indoor warm, indoor cool. A LoRA trained only in + studio lighting will not survive a sunny outdoor scene. +- **Varied outfits.** At least 3 distinct outfits across the dataset. This is the only way + the LoRA learns to separate identity from clothing. +- **One character per LoRA.** A LoRA trained on two characters learns a confused average + unless the user tags them with mutually-exclusive trigger words. Even then, separate LoRAs + is simpler and more reliable. +- **Tight crops on the face** in at least 8–10 images. The LoRA needs to learn identity, and + full-body shots with small faces give it almost no signal. +- **Consistent tagging.** Use one tool (kohya-style or ai-toolkit captions), one tag format, + one set of trigger word placements. Mixed conventions produce mixed results. + +## Recommended training settings (starter values) + +These are sane defaults; the user should adjust based on their trainer's UI. Values are +intentionally generic; the user's training tool may name them differently. + +| Setting | Start value | Notes | +|---|---|---| +| Network rank (dim) | 32 | 16 for SD 1.5, 32 for SDXL, 64 for Flux | +| Network alpha | 16 | Usually half of rank; some trainers want equal | +| Learning rate | 1e-4 (SD1.5), 5e-5 (SDXL), 1e-4 (Flux) | Lower if loss plateaus, raise if loss does not move | +| Training steps | 1500–3000 | Stop when preview samples stop improving | +| Batch size | 1 or 2 | Effective batch size = bs * grad_accum | +| Resolution | Match your base model | 512 for SD 1.5, 1024 for SDXL/Flux | +| Mixed precision | bf16 if supported, else fp16 | | +| Optimizer | AdamW8bit or Prodigy | Prodigy removes the LR-tuning guesswork | +| Caption dropout | 0.05 | Forces the model to read the image, not the caption | + +The Plugin does not ship or endorse a particular trainer. Use whichever the user is comfortable +with. + +## What to verify before you ship the LoRA + +A trained LoRA is not "done" the moment training stops. Generate at least 20 test images +across three different scenes, three different outfits, and three different camera angles. If +identity holds in 18+ of them, ship it. If it holds in fewer than 15, retrain with more +diverse data — not with more steps, which overfits. + +## Common failure modes and what they mean + +- **Every output looks identical, ignoring the prompt** → overfit. Lower rank or training + steps, or add more diverse data. +- **Identity is unstable across prompts** → underfit or inconsistent training data. More + steps are usually not the answer; better-curated data is. +- **The trigger word does nothing** → either the trigger was not actually written into the + captions during training, or the base model's tokenizer is splitting it. Try a different + trigger. +- **Outputs look like a different character from the training set** → the LoRA learned the + "vibe" of the dataset, not the identity. The dataset was probably too stylistically + inconsistent. Recur with tighter curation. + +## License + +Apache-2.0. See [LICENSE](../../../LICENSE). diff --git a/plugins/antianqi/comfyui-studio/skills/comfyui-character/references/prompt-patterns.md b/plugins/antianqi/comfyui-studio/skills/comfyui-character/references/prompt-patterns.md new file mode 100644 index 0000000..528860b --- /dev/null +++ b/plugins/antianqi/comfyui-studio/skills/comfyui-character/references/prompt-patterns.md @@ -0,0 +1,99 @@ +# Prompt patterns for character-consistent generation + +These are the patterns that make a self-trained LoRA actually deliver a consistent character. +They are the result of empirical iteration; treat them as defaults and adjust when a specific +base model or LoRA has a measured reason to differ. + +## The 4-module prompt structure + +Always write the prompt in this order: + +``` +, , , +``` + +1. **Trigger word** — the single token the LoRA was trained to respond to. Always first. +2. **Identity block** — the small set of features the LoRA was trained on. Keep it short; the + LoRA is doing the heavy lifting here, not the prompt. +3. **Outfit block** — what the character wears in *this* generation. The character wears + different outfits across the series; do not pin a single outfit in the LoRA training. +4. **Pose + scene block** — what is happening in the image. This is the part that changes most. + +A prompt that contains material that does not fit one of these four modules is signalling +conflict to the base model. Strip it. + +## The 8 rules + +1. **Trigger word leads, always.** If the LoRA was trained on `mascotv1`, every prompt starts + with `mascotv1,`. Putting the trigger in the middle of adjectives weakens the signal. + +2. **No filler adjectives.** Strip "beautiful", "stunning", "professional", "cinematic", + "8k", "highly detailed" — these tell the base model to override the LoRA with its + photorealistic priors. The LoRA already encodes quality. + +3. **4 modules > 8 modules.** A long, comma-separated prompt with 40+ tokens dilutes the + signal. The 4-module structure beats verbose lists. + +4. **No fighting the LoRA.** If the LoRA was trained on a 30-year-old male character, do not + add "young woman, feminine face" to the identity block. The base model will try to satisfy + both and you get a half-morphed output. + +5. **Outfit is per-prompt, not per-LoRA.** Different scenes = different outfits. If every + generation of the character wears the same jacket, the LoRA will eventually bake the jacket + into identity and you can never change it. Vary the outfit in training data, too. + +6. **Be specific about pose.** "Standing" is too vague. "Leaning on a railing, right hand in + pocket, looking off-camera" gives the base model something concrete and reduces the chance + of pose drift between generations. + +7. **Lighting is its own sentence.** A second short clause after the 4 modules is fine: + "mascotv1, [identity], [outfit], [pose], natural window light from the left, shallow + depth of field". Macro lighting cues (volumetric, Tyndall, soft box) are reliable; micro + descriptors (specular highlight at 0.3cm) are not. + +8. **Negative prompts are short.** For Stable Diffusion 1.5 / SDXL / Flux the negative prompt + should be a small fixed set: `deformed, bad anatomy, extra fingers, blurry, low quality, + watermark`. For character consistency, *add* one phrase: `different character, identity + drift`. Do not stack 30+ negative tokens; they fight the LoRA. + +## What to do when identity drifts + +If you generate 10 images and 4 of them look like a different person: + +1. **Trigger word first.** Open the prompt and confirm the trigger is the first token. If you + accidentally moved it, identity drift is the most common symptom. +2. **Bump `strength_model`.** Try 0.9 → 1.0. If that fixes it, the previous value was simply + too low; the LoRA is fine. +3. **Check the negative prompt.** If the negative prompt contains phrases like + `same character as before` or `consistent identity`, the base model treats them as concepts + it has to interpret, and that interpretation varies per seed. Strip them. +4. **Check the base model.** If you trained the LoRA on SDXL 1.0 and now you are running it on + Flux, the LoRA still loads but its effect is unpredictable. Match the base model. +5. **Reduce scene complexity.** A character in a 30-element busy scene loses identity. Move to + a simpler scene; if identity recovers, the scene was the problem, not the LoRA. + +## What to do when the LoRA is overcooked + +If every image looks identical and the prompt's outfit / pose / scene are not coming through: + +1. Lower `strength_model` by 0.1 and regenerate. Often that is enough. +2. Move the trigger word out of the first position. Place it second, after a strong + scene-describing word. This works around overfit LoRAs that "lock" the first token. +3. Increase CFG slightly (e.g. 5 → 6) so the base model is allowed to push back against the + LoRA more. +4. Re-train with more diverse outfit / pose / lighting data. The LoRA was overtrained on a + narrow look. + +## Prompt length by base model + +A rough guide. Models vary; measure for your own LoRA. + +| Base model | Total prompt length | Notes | +|---|---|---| +| SD 1.5 | 50–70 tokens | Compressed, the 4 modules do most of the work | +| SDXL | 60–90 tokens | A bit more room; identity block can be 8–10 tokens | +| Flux | 80–120 tokens | Flux reads natural language well; the 4 modules can become sentences | + +## License + +Apache-2.0. See [LICENSE](../../../LICENSE). diff --git a/plugins/antianqi/comfyui-studio/skills/comfyui-drama/SKILL.md b/plugins/antianqi/comfyui-studio/skills/comfyui-drama/SKILL.md new file mode 100644 index 0000000..e7d8d1a --- /dev/null +++ b/plugins/antianqi/comfyui-studio/skills/comfyui-drama/SKILL.md @@ -0,0 +1,181 @@ +--- +name: comfyui-drama +description: Generate a complete short drama video from a storyboard. Use this Skill for scenarios 5–6 from `comfyui-studio`'s trigger index: 5=首帧 (drama first frame, two-character LoRA + prompt → 16:9 still) and 6=出片 (image-to-video, first frame + motion prompt → MP4 clip). The Skill also describes the full 7-stage pipeline around them (storyboard → TTS → script refinement → first frame → image-to-video → subtitle burn → audio/video assembly). Ships two preset workflow templates: `workflows/drama-first-frame.json` and `workflows/drama-image-to-video.json`. +--- + +# ComfyUI Drama + +A 7-stage pipeline for producing a short drama video from a storyboard. The Plugin ships two +preset ComfyUI workflows (scenarios 5 and 6) and describes the rest of the pipeline. The Plugin +does not distribute any character LoRA, voice sample, or video model — every asset is +user-supplied. + +## The 2 trigger scenarios (Part of this Skill) + +These are scenarios 5 and 6 from `comfyui-studio`'s index. + +| # | Trigger | User says | Preset workflow | What it produces | +|---|---|---|---|---| +| **5** | **首帧** (first frame) | "生成这部短剧第 X 镜的首帧" / "first frame of shot 3" | `workflows/drama-first-frame.json` | One 16:9 still image per shot, with two character LoRA slots | +| **6** | **出片** (image to video) | "把这个首帧做成 4 秒视频" / "turn this first frame into a clip" | `workflows/drama-image-to-video.json` | One MP4 clip per shot, ~4 seconds, 24 FPS, distilled LTX-2.3 | + +The Plugin provides templates and preset workflows for both stages. The user runs stages 1, 2, +3, 6, and 7 of the surrounding pipeline with their own tools (Excel, TTS pipeline, FFmpeg). This +boundary is intentional: the Plugin does not assume a particular TTS vendor or spreadsheet +format. + +## The 7 stages (the wider pipeline) + +| Stage | Output | Tool | Notes | +|---|---|---|---| +| 1. Storyboard | `desktop/_分镜.xlsx` | User's spreadsheet editor or any storyboard tool | 6-column format: 序号 / 时长 / 画面 / 图片 prompt / 视频 prompt / 台词 | +| 2. TTS dubbing | `_wav/00X_.wav` | User's TTS pipeline (Edge TTS, IndexTTS2, Qwen3-VoiceDesign, etc.) | **TTS comes BEFORE images** — the actual measured duration is the only truth for shot length | +| 3. Script refinement | Updated Excel with measured shot lengths | User's scripting tool | Re-cut shots whose actual TTS length differs from the script estimate by > 0.5s | +| 4. **First-frame image** | `drama_frame_00001_.png` per shot | **Scenario 5: `workflows/drama-first-frame.json`** | One image per shot, two-character LoRA slots | +| 5. **Image-to-video clip** | `drama_clip_00001_.mp4` per shot | **Scenario 6: `workflows/drama-image-to-video.json`** | One video per shot, 8 GB VRAM friendly | +| 6. Subtitle burn | Subtitled video per shot | FFmpeg `drawtext` filter | Source Han Sans CN, 28pt, white + black 2px stroke | +| 7. Audio/video assembly | Final drama MP4 | FFmpeg `-c:v copy -c:a aac` + concat demuxer | One output MP4 per drama | + +--- + +## Scenario 5 — 首帧 (first frame) — `drama-first-frame.json` + +A first-frame image generator with **two LoRA slots** (one per character in a two-character +drama). For single-character dramas, set the second slot's LoRA file to a placeholder LoRA +with `strength = 0`; for two-character dramas, fill both slots. + +Pipeline: Checkpoint → `LoraLoader` (character A) → `LoraLoader` (character B) → KSampler. + +### Models + +| Field | Value (as written in the JSON) | Where it lives on disk | +|---|---|---| +| `CheckpointLoaderSimple.ckpt_name` | `Z-Image-Base-8steps-豹豹喵呜の白玉v2White_Marble-AIO_v2-bf16.safetensors` | `models/checkpoints/` | +| `LoraLoader[0].lora_name` (character A) | `character_a_lora.safetensors` (placeholder — replace) | `models/loras/` | +| `LoraLoader[1].lora_name` (character B) | `character_b_lora.safetensors` (placeholder — replace) | `models/loras/` | +| CLIP / VAE | bundled with the CheckpointLoader | — | + +### Knobs + +| Knob | Default | What it does | +|---|---|---| +| `LoraLoader[0].strength_model` (character A) | 0.8 | Identity weight for character A | +| `LoraLoader[1].strength_model` (character B) | 0.8 | Identity weight for character B | +| `EmptyLatentImage.width` / `height` | 1280 / 720 | 16:9 landscape. Change for vertical (9:16) or square (1:1) | +| `KSampler.steps` | 8 | Distilled-model default; raise to 20 for a non-distilled base | +| `KSampler.cfg` | 1.5 | Distilled-model default; raise to 6 for a non-distilled base | + +### Submit + +1. **Place the two character LoRAs** under `/models/loras/`. Edit the two + `LoraLoader.lora_name` fields in the JSON to point at your filenames if they differ from + the placeholders. +2. **Pass the image prompt** with `--prompt`. For a two-character drama, lead with the trigger + words for both characters so the LoRAs can light up. +3. **Submit** via the Python script or the MCP server. + +### Critical prompt rule (first-frame) + +The image prompt describes the **first frame**, not the shot's action sequence. A shot where +character A hands a sword to character B should prompt the moment the hands are meeting, not +the full handoff motion. The video prompt (scenario 6) is responsible for the motion. + +--- + +## Scenario 6 — 出片 (image to video) — `drama-image-to-video.json` + +An image-to-video generator for a single shot. Takes a first-frame image (from scenario 5) and +the shot's video prompt (Excel column "视频 prompt"). Outputs a single MP4 per shot. + +Pipeline: CheckpointLoader + DualCLIPLoader + VAELoader + LoraLoader (optional motion) + +`LoadImage` (first frame) + KSampler + `VHS_VideoCombine`. + +### Models + +| Field | Value (as written in the JSON) | Where it lives on disk | +|---|---|---| +| `CheckpointLoaderSimple.ckpt_name` | `10Eros_v1-fp8mixed_learned.safetensors` (LTX-2.3 22B finetune) | `models/checkpoints/` | +| `DualCLIPLoader.clip_name1` | `ltx-2.3_text_projection_bf16.safetensors` | `models/clip/` | +| `DualCLIPLoader.clip_name2` | `gemma_3_12B_it_fp8_scaled.safetensors` | `models/clip/` | +| `VAELoader.vae_name` | `LTX23_video_vae_bf16.safetensors` | `models/vae/` | +| `LoraLoader.lora_name` (motion) | `any_motion_lora.safetensors` (placeholder — replace, or set `strength_model = 0` to disable) | `models/loras/` | +| `LoadImage.image` | `first_frame.png` (rename per shot, or pass `--filename` to drive the workflow from the CLI) | `input/` | + +### Knobs + +| Knob | Default | What it does | +|---|---|---| +| `LoadImage.image` | "first_frame.png" | The first-frame image to animate. Use predictable filenames per shot and pass them via `--filename` on the CLI. | +| `VHS_VideoCombine.frame_rate` | 24 | Output FPS. Pair with RIFE interpolation to reach 60 | +| `VHS_VideoCombine.format` | "video/h264-mp4" | Output container/codec | +| `KSampler.steps` | 8 | Distilled LTX default; raise for non-distilled | +| `KSampler.cfg` | 1.0 | Distilled LTX default | + +### Submit + +1. **Place the first-frame image** in the ComfyUI input folder with a predictable name + (`shot_001.png`, `shot_002.png`, ...). +2. **Pass the motion prompt** (Excel "视频 prompt" column value) with `--prompt`. The video + prompt describes motion only — see the critical rule below. +3. **Submit** via the Python script or the MCP server. The script will poll until done and + download the resulting MP4 from `outputs/`. + +### Critical prompt rule (motion-only) + +The video prompt describes motion only — camera move, character animation, environment +dynamics, expression changes. It does **not** describe the visible image (the model can see +the first frame and already knows what is in it). The shot's dialogue goes in a separate +`dialogue` field, not in the prompt. + +--- + +## How to run a full drama (loop scenarios 5 and 6) + +For each shot in the storyboard: + +1. **Scenario 5**: submit `drama-first-frame.json` with the image prompt → produces + `drama_frame_00001_.png`. +2. **Scenario 6**: rename / move the first-frame to `shot_001.png` in the ComfyUI input + folder, then submit `drama-image-to-video.json` with the motion prompt → produces + `drama_clip_00001_.mp4`. +3. **Stage 6** (out of scope of this Plugin): burn subtitles with FFmpeg `drawtext`. +4. **Stage 7** (out of scope of this Plugin): concat the clips and mux the TTS audio with + FFmpeg. + +Each shot is one ComfyUI submission per scenario (because each shot has a different first-frame +image and a different prompt). The Plugin does not implement a batch driver; if you need one, +use a wrapper loop in your own tool. + +## A note on long jobs and VRAM + +A distilled LTX-class video model running at 1280×720 with 24 FPS for a 4-second clip finishes +in roughly 2–5 minutes on an 8 GB GPU. A non-distilled 22B video model on the same shot can +take 30–60 minutes. Long jobs (over 30 minutes) should be watched with `nvidia-smi`; the Plugin +does not ship a VRAM watchdog. If your GPU is lost mid-run, the standard recovery is to +interrupt the queue and restart ComfyUI. + +## What this Skill does not do + +- It does not run LoRA training. Training is its own project; the Skill only consumes a LoRA + the user already has. +- It does not bundle or distribute any LoRA, model, voice sample, face embedding, or any other + identity asset. Every character and voice is user-supplied. +- It does not run TTS, edit Excel, burn subtitles, or assemble audio/video. Those are user + pipeline steps that the Plugin deliberately does not assume. +- It does not ship a VRAM watchdog or auto-retry on failure. Long jobs need a human watching. + +## Requirements + +- ComfyUI running locally (see `comfyui-workflow/SKILL.md` for the transport). +- A first-frame checkpoint and (optionally) two character LoRAs the user trained, dropped under + `models/checkpoints/` and `models/loras/`. The workflow JSONs reference the LoRAs as + `character_a_lora.safetensors` and `character_b_lora.safetensors` — replace with your + filenames. +- A distilled LTX-Video checkpoint and its CLIP / VAE files. The motion LoRA is referenced as + `any_motion_lora.safetensors` — replace or set its strength to 0 to disable. +- A TTS pipeline (any of the user's choice) for stage 2. +- FFmpeg for stages 6 and 7. + +## License + +Apache-2.0. See [LICENSE](../../LICENSE). diff --git a/plugins/antianqi/comfyui-studio/skills/comfyui-studio/SKILL.md b/plugins/antianqi/comfyui-studio/skills/comfyui-studio/SKILL.md new file mode 100644 index 0000000..ef2f428 --- /dev/null +++ b/plugins/antianqi/comfyui-studio/skills/comfyui-studio/SKILL.md @@ -0,0 +1,100 @@ +--- +name: comfyui-studio +description: Drive a local ComfyUI 8188 server for image and short-clip generation. Use this Skill when the task involves a ComfyUI workflow, a local Stable Diffusion / Flux / Z-Image checkpoint, character-consistent generation with a LoRA, or producing deterministic outputs from a saved workflow JSON. Triggers on requests like "use ComfyUI to ...", "submit this workflow", "what models do I have", "generate an image with my LoRA", or "check the queue". +--- + +# ComfyUI Studio + +A generic toolkit for driving a local ComfyUI server (default `http://127.0.0.1:8188`). It bundles +four Skills and two transport layers that work together. + +## Two parts, one Plugin + +| Part | What it covers | Where | +|---|---|---| +| **A. Basic control** | The transport between the agent and a local ComfyUI server. Submit any workflow, poll the queue, download outputs. | `comfyui-workflow` Skill + the stdio MCP server + the Python CLI | +| **B. Preset workflows** | Six opinionated workflow templates that solve common recurring tasks, numbered 1–6 by the user's trigger scenario. | `comfyui-character` (scenarios 1–4) + `comfyui-drama` (scenarios 5–6) + the `workflows/*.json` presets | + +The Plugin also ships a dependency-free stdio MCP server (see `mcp.json` + `server.mjs`) that +exposes the same Part-A primitives (`submit_prompt`, `check_queue`, `get_image`) to any MCP-capable +agent. + +## The 6 trigger scenarios (Part B) + +This is the routing index. The number is the trigger scenario — read the user's intent, pick a +number, and follow the pointer. + +| # | Trigger | User typically says | Preset workflow | Pointer | +|---|---|---|---|---| +| **1** | **生图** (generate an image) | "用我的角色画一张自拍" / "生成一张 XX 风格的照片" / "draw my character at a beach" | `workflows/selfie-text-to-image.json` | `comfyui-character` | +| **2** | **模仿** (mimic an image) | "照着这张照片再画一张同款" / "模仿这张图" / "use this photo as a reference" | `workflows/selfie-mimicry.json` | `comfyui-character` | +| **3** | **改图** (edit an image) | "把背景换成办公室" / "衣服换成西装" / "edit this photo: change the background" | `workflows/flux2-klein-image-edit.json` | `comfyui-character` | +| **4** | **融合** (fuse two images) | "把这张图里的人物放到那张图里" / "让她穿上这件衣服" / "put the person from this image onto that image" | `workflows/flux2-klein-image-edit-dual.json` | `comfyui-character` | +| **5** | **首帧** (drama first frame) | "生成这部短剧第 X 镜的首帧" / "first frame of shot 3" | `workflows/drama-first-frame.json` | `comfyui-drama` | +| **6** | **出片** (image to video) | "把这个首帧做成 4 秒视频" / "turn this first frame into a clip" | `workflows/drama-image-to-video.json` | `comfyui-drama` | + +If the user's request does not map cleanly to one of these six, fall through to **Part A** +(below) and use `comfyui-workflow` as a generic transport for any user-supplied workflow JSON. + +## Routing rules + +1. **Numbers 1–4** → read `skills/comfyui-character/SKILL.md` for the recipe (model stack, + custom-node requirements, prompt structure, LoRA slots, knobs). Submit via Part A. +2. **Numbers 5–6** → read `skills/comfyui-drama/SKILL.md` for the 7-stage pipeline and the + per-shot rules. Submit each shot via Part A. +3. **Anything else** ("submit this workflow", "check the queue", "what models do I have", + "use ComfyUI to ...", "run this JSON") → read `skills/comfyui-workflow/SKILL.md` directly. + The basic control layer handles all of these without reading a preset. +4. **Health probes** ("is ComfyUI running?") → `comfyui-workflow` — its first step is always + a `GET /system_stats` or `submit_workflow.py --probe`. + +Do not duplicate behaviour: if you only need to submit a workflow, do not read this Skill, read +`comfyui-workflow/SKILL.md` directly. This Skill is the entry point for **routing**, not the +implementation. + +## Required environment + +The Plugin assumes the user has: + +- A running ComfyUI server (default `http://127.0.0.1:8188`). Override with the `COMFYUI_URL` + environment variable or the `--url` flag on the Python scripts. +- The checkpoint / CLIP / VAE / ControlNet files referenced in each preset (see the **Model + boundary** section in `README.md`). Edit loader fields if your install uses different + filenames. +- For scenarios 1 and 2 (`comfyui-character` selfie workflows): a face LoRA and a style LoRA + in `models/loras/`. The workflow JSONs reference them as `your_face_lora.safetensors` and + `your_style_lora.safetensors` — replace these with your filenames before submitting. The + Plugin does not bundle LoRAs. +- For scenario 2 (`selfie-mimicry.json`): the custom-node pack list in + `comfyui-character/SKILL.md` (controlnet_aux, LLaMA-CPP, ModelPatchLoader, etc.). +- For scenarios 5 and 6 (`comfyui-drama`): two character LoRAs the user trained themselves + (referenced as `character_a_lora.safetensors` / `character_b_lora.safetensors` / + `any_motion_lora.safetensors` — replace with your filenames). + +The Plugin does **not** download or bundle model checkpoints, LoRAs, or any other binary asset. +Each Skill instructs the user to place their own files in the standard ComfyUI directory layout. + +## Data and network + +- Network access is local-only by default. The MCP server and the Python scripts both target + the address in `COMFYUI_URL`. No telemetry, no remote calls, no analytics. +- See `docs/security-notes.md` for the threat model and the data the Plugin handles. +- See `docs/troubleshooting.md` for common failures (ComfyUI offline, missing model, OOM). + +## Trying it out + +End-to-end smoke test (about 5 minutes, see `examples/minimal-run.md`): + +```text +Use comfyui-studio to verify that my local ComfyUI is reachable, then submit scenario 3 +(改图 / flux2-klein-image-edit) with the prompt "the same person sitting in an office +chair, the same outfit, professional lighting" and report the saved image path. +``` + +Expected outcome: the agent confirms ComfyUI is reachable, submits the bundled +`workflows/flux2-klein-image-edit.json`, polls until done, downloads the image, and tells you +where it was saved. + +## License + +Apache-2.0. See [LICENSE](../../LICENSE). diff --git a/plugins/antianqi/comfyui-studio/skills/comfyui-workflow/SKILL.md b/plugins/antianqi/comfyui-studio/skills/comfyui-workflow/SKILL.md new file mode 100644 index 0000000..3c17b97 --- /dev/null +++ b/plugins/antianqi/comfyui-studio/skills/comfyui-workflow/SKILL.md @@ -0,0 +1,124 @@ +--- +name: comfyui-workflow +description: Submit a ComfyUI workflow JSON to a local 8188 server, monitor the queue, and download generated images. Use this Skill when the user wants to run a saved workflow, check whether ComfyUI is busy, fetch a generated image, list available checkpoints, or automate a reproducible generation pipeline. +--- + +# ComfyUI Workflow + +Submit workflows, monitor the queue, and retrieve outputs from a local ComfyUI server. This is +the workhorse Skill; `comfyui-character` builds on top of it for character-consistent generation. + +## Scope + +This Skill covers the **transport** between an agent and a local ComfyUI server. It does not +edit workflows, train models, or invent prompts — those concerns live in sibling Skills. It +assumes the user already has a workflow JSON they want to run (either a file the user wrote or +one of the bundled `workflows/*.json` examples). + +## Two equivalent entry points + +You can drive ComfyUI from this Skill in either of two ways. Pick whichever the host environment +supports best. + +### A. Python script (CLI-friendly, no MCP required) + +```bash +# Health probe +python scripts/submit_workflow.py --probe + +# Submit a workflow +python scripts/submit_workflow.py \ + --workflow ./workflows/text-to-image.json \ + --prompt "a tabby cat sleeping on a windowsill, morning light, photorealistic" \ + --output-dir ./out + +# Check the queue without submitting +python scripts/submit_workflow.py --queue + +# Download a specific image +python scripts/submit_workflow.py --download \ + --filename ComfyUI_00001_.png --output-dir ./out +``` + +The script lives at `skills/comfyui-workflow/scripts/submit_workflow.py` (relative to the Plugin +root) and accepts `COMFYUI_URL` from the environment, falling back to `http://127.0.0.1:8188`. +Full flag list in `references/api-reference.md`. + +### B. stdio MCP server (no Python required) + +When the host agent is MCP-aware, the Plugin's `server.mjs` exposes three tools that mirror the +Python script: + +| Tool | Mirrors | +|---|---| +| `submit_prompt` | `--workflow` invocation | +| `check_queue` | `--queue` invocation | +| `get_image` | `--download` invocation | + +The MCP server has zero npm dependencies. It is a 200-line stdio JSON-RPC server in plain Node. +See `mcp.json` for the registration snippet and `server.mjs` for the source. + +## Workflow contract + +ComfyUI accepts a JSON object whose top-level keys are node IDs and whose values are +`{class_type, inputs}` records. When the user provides a workflow, **do not edit the structure** +unless they ask. Two patterns are common: + +- A workflow file saved from the ComfyUI web UI: keys are numeric strings, `class_type` matches + the node name in the editor, `inputs` reference other nodes by `[nodeId, outputIndex]`. +- An "API format" workflow produced by ComfyUI's "Save (API Format)" menu: same shape, but with + widget values inlined into `inputs`. + +Both work identically when POSTed to `/prompt`. The Plugin's `workflows/text-to-image.json` and +`workflows/image-to-image.json` are in the API format. + +## Overriding prompt values at submit time + +The bundled Python script understands a tiny templating convention so you do not have to rewrite +the workflow JSON for every run. In a workflow, a `CLIPTextEncode` node with `inputs.text` +starting with the literal string `__PROMPT__` will have that text replaced with the value of +`--prompt` on the command line before submission. This is a no-op for nodes that do not opt in. + +```json +{ + "30": { + "class_type": "CLIPTextEncode", + "inputs": { "text": "__PROMPT__", "clip": ["4", 0] } + } +} +``` + +For workflows without that marker, the user must edit the workflow JSON directly. + +## Step-by-step recipe + +1. **Health probe.** Either call `check_queue` via MCP or run `submit_workflow.py --probe`. Stop + and report if ComfyUI is unreachable; do not invent a successful run. +2. **Resolve the workflow.** If the user named a file, read it. If they named a workflow by + description (e.g. "the one with the upscale at the end"), ask for a path or a saved name. +3. **Apply prompt override if used.** If `--prompt` is provided and the workflow contains + `__PROMPT__`, substitute. Otherwise pass the workflow through unchanged. +4. **Submit.** POST to `/prompt` with `{"prompt": }`. Capture `prompt_id`. +5. **Poll.** Loop `GET /history/` with a 2-second sleep. Stop on terminal status + (`success`, `error`, or `cancelled`). Use a 15-minute cap; long jobs should be split. +6. **Resolve outputs.** The `outputs` object lists the `SaveImage` / `VHS_VideoCombine` files + produced. For each, fetch `GET /view?filename=...&subfolder=...&type=output`. +7. **Report.** Tell the user the prompt_id, the time taken, the saved file paths, and any + warnings (low VRAM, retries, partial outputs). + +## Long jobs and VRAM safety + +- ComfyUI 22B-class models can hold the GPU for hours. The Plugin does not implement a watchdog; + for jobs longer than 30 minutes, instruct the user to watch `nvidia-smi` themselves. +- If a poll returns an error status, capture the full `outputs` block and the error message; + report both. Do not retry automatically — generation is expensive. + +## Requirements + +- ComfyUI running locally (or reachable at `COMFYUI_URL`). +- Python 3.10+ if using the script. The MCP server requires Node 18+. +- No additional accounts, paid services, or network destinations. + +## License + +Apache-2.0. See [LICENSE](../../LICENSE). diff --git a/plugins/antianqi/comfyui-studio/skills/comfyui-workflow/references/api-reference.md b/plugins/antianqi/comfyui-studio/skills/comfyui-workflow/references/api-reference.md new file mode 100644 index 0000000..75f5fcf --- /dev/null +++ b/plugins/antianqi/comfyui-studio/skills/comfyui-workflow/references/api-reference.md @@ -0,0 +1,129 @@ +# ComfyUI 8188 API reference + +A focused subset of the ComfyUI HTTP API that this Plugin uses. The full API surface is +documented at the ComfyUI project; this file covers only the endpoints the Plugin calls. + +All endpoints are relative to the base URL in `COMFYUI_URL` (default `http://127.0.0.1:8188`). +All responses are JSON unless noted. All `POST` bodies are JSON. + +## `GET /system_stats` + +Reports ComfyUI version and runtime info. Used by the health probe. + +Response shape (subset): + +```json +{ + "system": { "comfyui_version": "0.29.2", "python_version": "3.11.x", "pytorch_version": "..." }, + "devices": [{ "name": "cuda:0", "type": "cuda", "vram_total": 8589934592, "vram_free": 6000000000 }] +} +``` + +## `GET /queue` + +Returns the current queue. + +Response shape: + +```json +{ + "queue_running": [["uuid", "prompt_id", {...workflow...}], ...], + "queue_pending": [["uuid", "prompt_id", {...workflow...}], ...] +} +``` + +The Plugin reports counts and prompt_ids; the full workflow body is not echoed back to the +agent to avoid blowing up the context window. + +## `POST /prompt` + +Submits a workflow. Body: + +```json +{ "prompt": { "": { "class_type": "...", "inputs": {...} } } } +``` + +Response (success): + +```json +{ "prompt_id": "uuid", "number": 42 } +``` + +Response (workflow-level error): + +```json +{ "error": { "type": "validation", "message": "...", "details": "...", "node_id": "..." } } +``` + +The Plugin surfaces the full error block to the agent instead of swallowing it. Generation is +expensive; we want the user to see exactly what went wrong. + +## `GET /history/` + +Polls the result of a submitted prompt. Returns the full `outputs` block once status is +terminal. + +Response shape (subset): + +```json +{ + "": { + "status": { "status_str": "success" | "error" | "cancelled" }, + "outputs": { + "": { + "images": [{ "filename": "ComfyUI_00001_.png", "subfolder": "", "type": "output" }], + "gifs": [...], + "videos": [...] + } + } + } +} +``` + +The Plugin polls this every 2 seconds with a 15-minute cap. For long jobs, increase the cap or +have the user re-run with explicit acknowledgement. + +## `GET /view` + +Downloads a generated image. Query parameters: + +| Parameter | Required | Notes | +|---|---|---| +| `filename` | yes | The filename reported in `/history` outputs | +| `subfolder` | no | Defaults to empty (root of the chosen folder) | +| `type` | no | One of `output`, `input`, `temp`. Default: `output` | +| `channel` | no | Used for 3D / multi-channel outputs | + +Response: the image bytes, `Content-Type: image/png` (or whatever the original was). + +## `POST /queue/clear` + +Clears pending queue items. Does not interrupt running items. Use with care — there is no +"are you sure" prompt at the API level. + +## `POST /queue/interrupt` + +Interrupts the currently-running prompt. Use when the user says "stop" or when a poll detects +the queue is stuck. + +## `GET /object_info/` + +Returns the input schema for a specific node. Useful when the agent needs to know which +checkpoint or LoRA names ComfyUI currently sees on disk. The Plugin does not call this +directly; it is here so the user (or an agent) can probe a workflow before submission. + +## Authentication + +Plain ComfyUI has no authentication. If the user runs ComfyUI behind a reverse proxy that +requires a bearer token, set `COMFYUI_API_TOKEN` in the environment; the Plugin's MCP server +and the Python script both add it as `Authorization: Bearer ` on every request. + +## Limits + +The Plugin enforces a 15-minute default poll cap. ComfyUI itself has no hard timeout, but +long-running jobs are a known risk for GPU memory; see `docs/troubleshooting.md` for VRAM +recovery steps. + +## License + +Apache-2.0. See [LICENSE](../../../LICENSE). diff --git a/plugins/antianqi/comfyui-studio/skills/comfyui-workflow/scripts/submit_workflow.py b/plugins/antianqi/comfyui-studio/skills/comfyui-workflow/scripts/submit_workflow.py new file mode 100644 index 0000000..e44f986 --- /dev/null +++ b/plugins/antianqi/comfyui-studio/skills/comfyui-workflow/scripts/submit_workflow.py @@ -0,0 +1,250 @@ +#!/usr/bin/env python3 +"""Submit a ComfyUI workflow, poll the queue, and download the output. + +This is a single-file CLI wrapper around the local ComfyUI HTTP API. It is +intentionally tiny: no third-party Python dependencies, no native extensions, +no telemetry. Everything it does is also exposed by the Plugin's stdio MCP +server (see ../../../server.mjs); use whichever fits the host agent. + +Environment: + COMFYUI_URL ComfyUI base URL. Default: http://127.0.0.1:8188 + COMFYUI_API_TOKEN Optional bearer token for auth-fronted ComfyUI + +Examples: + python submit_workflow.py --probe + python submit_workflow.py --workflow path/to/wf.json --prompt "a cat" + python submit_workflow.py --queue + python submit_workflow.py --download --filename out.png --output-dir ./out +""" +from __future__ import annotations + +import argparse +import json +import os +import sys +import time +import urllib.error +import urllib.parse +import urllib.request +from pathlib import Path + +DEFAULT_URL = "http://127.0.0.1:8188" +PROMPT_MARKER = "__PROMPT__" +POLL_INTERVAL_S = 2.0 +POLL_TIMEOUT_S = 15 * 60 # 15 minutes cap; long jobs are a VRAM risk + + +def base_url() -> str: + return os.environ.get("COMFYUI_URL", DEFAULT_URL).rstrip("/") + + +def auth_headers() -> dict: + token = os.environ.get("COMFYUI_API_TOKEN", "") + return {"authorization": f"Bearer {token}"} if token else {} + + +def http_json(method: str, path: str, body: dict | None = None) -> tuple[int, dict | str]: + url = base_url() + path + data = None if body is None else json.dumps(body).encode("utf-8") + req = urllib.request.Request( + url, + data=data, + method=method, + headers={"content-type": "application/json", "accept": "application/json", **auth_headers()}, + ) + try: + with urllib.request.urlopen(req, timeout=30) as resp: + raw = resp.read().decode("utf-8") + try: + return resp.status, json.loads(raw) + except json.JSONDecodeError: + return resp.status, raw + except urllib.error.HTTPError as e: + return e.code, e.read().decode("utf-8", errors="replace") + except urllib.error.URLError as e: + return 0, f"connection error: {e.reason}" + + +def http_download(path: str, dest: Path) -> int: + url = base_url() + path + req = urllib.request.Request(url, headers=auth_headers()) + try: + with urllib.request.urlopen(req, timeout=60) as resp: + dest.parent.mkdir(parents=True, exist_ok=True) + dest.write_bytes(resp.read()) + return resp.status + except urllib.error.URLError as e: + print(f"[download] {url}: {e.reason}", file=sys.stderr) + return 0 + + +def apply_prompt_override(workflow: dict, prompt_text: str | None) -> dict: + """If --prompt was given and the workflow has a __PROMPT__ marker, substitute. + + Walks every node in the workflow. For any CLIPTextEncode-style node whose + `text` input is exactly the literal marker, replace with the user's text. + Nodes that already have a value are left alone. + """ + if not prompt_text: + return workflow + changed = [] + for node_id, node in workflow.items(): + if not isinstance(node, dict): + continue + inputs = node.get("inputs") + if not isinstance(inputs, dict): + continue + text = inputs.get("text") + if text == PROMPT_MARKER: + inputs["text"] = prompt_text + changed.append(node_id) + if changed: + print(f"[prompt] substituted __PROMPT__ in nodes: {changed}", file=sys.stderr) + return workflow + + +def cmd_probe(_args) -> int: + status, body = http_json("GET", "/system_stats") + if status != 200: + print(f"[probe] FAILED status={status} body={body}") + return 2 + if isinstance(body, dict): + version = body.get("system", {}).get("comfyui_version", "?") + print(f"[probe] OK comfyui_version={version}") + for dev in body.get("devices", []) or []: + free_gb = (dev.get("vram_free") or 0) / (1024 ** 3) + total_gb = (dev.get("vram_total") or 0) / (1024 ** 3) + print(f"[probe] device={dev.get('name')} vram_free={free_gb:.1f}GB/{total_gb:.1f}GB") + else: + print(f"[probe] OK body={body}") + return 0 + + +def cmd_queue(_args) -> int: + status, body = http_json("GET", "/queue") + if status != 200 or not isinstance(body, dict): + print(f"[queue] FAILED status={status} body={body}") + return 2 + running = body.get("queue_running") or [] + pending = body.get("queue_pending") or [] + print(f"[queue] running={len(running)} pending={len(pending)}") + for item in running: + print(f" running prompt_id={item[1] if len(item) > 1 else '?'}") + for item in pending: + print(f" pending prompt_id={item[1] if len(item) > 1 else '?'}") + return 0 + + +def cmd_download(args) -> int: + out_dir = Path(args.output_dir or ".") + params = urllib.parse.urlencode({ + "filename": args.filename, + "subfolder": args.subfolder or "", + "type": args.folder_type or "output", + }) + status = http_download(f"/view?{params}", out_dir / args.filename) + if status == 200: + print(f"[download] saved {out_dir / args.filename}") + return 0 + return 2 + + +def cmd_submit(args) -> int: + if not args.workflow: + print("[submit] --workflow is required (or use --probe / --queue / --download)", file=sys.stderr) + return 2 + wf_path = Path(args.workflow) + if not wf_path.exists(): + print(f"[submit] workflow file not found: {wf_path}", file=sys.stderr) + return 2 + try: + workflow = json.loads(wf_path.read_text(encoding="utf-8")) + except json.JSONDecodeError as e: + print(f"[submit] invalid JSON: {e}", file=sys.stderr) + return 2 + + workflow = apply_prompt_override(workflow, args.prompt) + + status, body = http_json("POST", "/prompt", {"prompt": workflow}) + if status != 200: + print(f"[submit] HTTP {status}: {body}") + return 2 + if not isinstance(body, dict) or "error" in body: + print(f"[submit] workflow rejected: {body}") + return 2 + prompt_id = body.get("prompt_id") + print(f"[submit] prompt_id={prompt_id} number={body.get('number')}") + + deadline = time.time() + POLL_TIMEOUT_S + out_dir = Path(args.output_dir or ".") + while True: + if time.time() > deadline: + print(f"[poll] timed out after {POLL_TIMEOUT_S}s, prompt_id={prompt_id} still running", file=sys.stderr) + return 3 + time.sleep(POLL_INTERVAL_S) + status, hist = http_json("GET", f"/history/{prompt_id}") + if status != 200 or not isinstance(hist, dict): + continue + entry = hist.get(prompt_id) + if not entry: + continue + st = (entry.get("status") or {}).get("status_str") + if st not in (None,): + print(f"[poll] status={st}") + if st == "success": + outputs = entry.get("outputs") or {} + saved = [] + for node_out in outputs.values(): + for media in node_out.get("images", []) + node_out.get("gifs", []) + node_out.get("videos", []): + fname = media.get("filename") + sub = media.get("subfolder", "") + typ = media.get("type", "output") + qs = urllib.parse.urlencode({"filename": fname, "subfolder": sub, "type": typ}) + dest = out_dir / fname + s = http_download(f"/view?{qs}", dest) + if s == 200: + saved.append(str(dest)) + if not saved: + print("[poll] success but no output files reported (workflow may not have a Save node)") + return 0 + print("[poll] saved:") + for p in saved: + print(f" {p}") + return 0 + if st in ("error", "cancelled"): + print(f"[poll] {st}: {entry}") + return 4 + + +def main() -> int: + ap = argparse.ArgumentParser(description="Drive a local ComfyUI server.") + ap.add_argument("--url", help="Override COMFYUI_URL for this invocation only") + ap.add_argument("--workflow", help="Path to a workflow JSON file") + ap.add_argument("--prompt", help="Optional prompt override (replaces __PROMPT__ in the workflow)") + ap.add_argument("--output-dir", help="Directory to save generated outputs") + ap.add_argument("--filename", help="Filename to download (with --download)") + ap.add_argument("--subfolder", default="", help="Subfolder under ComfyUI output dir") + ap.add_argument("--folder-type", default="output", choices=["output", "input", "temp"]) + mode = ap.add_mutually_exclusive_group() + mode.add_argument("--probe", action="store_true", help="Health probe /system_stats") + mode.add_argument("--queue", action="store_true", help="Show running and pending queue") + mode.add_argument("--download", action="store_true", help="Download a file by name") + args = ap.parse_args() + + if args.url: + os.environ["COMFYUI_URL"] = args.url + + if args.probe: + return cmd_probe(args) + if args.queue: + return cmd_queue(args) + if args.download: + if not args.filename: + print("--download requires --filename", file=sys.stderr) + return 2 + return cmd_download(args) + return cmd_submit(args) + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/plugins/antianqi/comfyui-studio/workflows/drama-first-frame.json b/plugins/antianqi/comfyui-studio/workflows/drama-first-frame.json new file mode 100644 index 0000000..be1d9cf --- /dev/null +++ b/plugins/antianqi/comfyui-studio/workflows/drama-first-frame.json @@ -0,0 +1,79 @@ +{ + "3": { + "class_type": "CheckpointLoaderSimple", + "inputs": { + "ckpt_name": "Z-Image-Base-8steps-豹豹喵呜の白玉v2White_Marble-AIO_v2-bf16.safetensors" + } + }, + "10": { + "class_type": "LoraLoader", + "inputs": { + "model": ["3", 0], + "clip": ["3", 1], + "lora_name": "character_a_lora.safetensors", + "strength_model": 0.8, + "strength_clip": 0.8 + } + }, + "20": { + "class_type": "LoraLoader", + "inputs": { + "model": ["10", 0], + "clip": ["10", 1], + "lora_name": "character_b_lora.safetensors", + "strength_model": 0.8, + "strength_clip": 0.8 + } + }, + "30": { + "class_type": "CLIPTextEncode", + "inputs": { + "text": "__PROMPT__", + "clip": ["20", 1] + } + }, + "31": { + "class_type": "CLIPTextEncode", + "inputs": { + "text": "deformed, bad anatomy, extra fingers, blurry, low quality, watermark, blurry text, three people, four people, crowd", + "clip": ["20", 1] + } + }, + "40": { + "class_type": "EmptyLatentImage", + "inputs": { + "width": 1280, + "height": 720, + "batch_size": 1 + } + }, + "50": { + "class_type": "KSampler", + "inputs": { + "model": ["20", 0], + "positive": ["30", 0], + "negative": ["31", 0], + "latent_image": ["40", 0], + "seed": 42, + "steps": 8, + "cfg": 1.5, + "sampler_name": "euler", + "scheduler": "simple", + "denoise": 1.0 + } + }, + "60": { + "class_type": "VAEDecode", + "inputs": { + "samples": ["50", 0], + "vae": ["3", 2] + } + }, + "70": { + "class_type": "SaveImage", + "inputs": { + "images": ["60", 0], + "filename_prefix": "drama_frame" + } + } +} diff --git a/plugins/antianqi/comfyui-studio/workflows/drama-image-to-video.json b/plugins/antianqi/comfyui-studio/workflows/drama-image-to-video.json new file mode 100644 index 0000000..d64ee36 --- /dev/null +++ b/plugins/antianqi/comfyui-studio/workflows/drama-image-to-video.json @@ -0,0 +1,95 @@ +{ + "1": { + "class_type": "CheckpointLoaderSimple", + "inputs": { + "ckpt_name": "10Eros_v1-fp8mixed_learned.safetensors" + } + }, + "2": { + "class_type": "DualCLIPLoader", + "inputs": { + "clip_name1": "ltx-2.3_text_projection_bf16.safetensors", + "clip_name2": "gemma_3_12B_it_fp8_scaled.safetensors", + "type": "ltxv", + "device": "default" + } + }, + "3": { + "class_type": "VAELoader", + "inputs": { + "vae_name": "LTX23_video_vae_bf16.safetensors" + } + }, + "4": { + "class_type": "LoraLoader", + "inputs": { + "model": ["1", 0], + "clip": ["2", 0], + "lora_name": "any_motion_lora.safetensors", + "strength_model": 0.7, + "strength_clip": 0.7 + } + }, + "5": { + "class_type": "LoadImage", + "inputs": { + "image": "first_frame.png" + } + }, + "6": { + "class_type": "CLIPTextEncode", + "inputs": { + "text": "__PROMPT__", + "clip": ["2", 0] + } + }, + "7": { + "class_type": "CLIPTextEncode", + "inputs": { + "text": "static, no movement, jitter, flicker, watermark, blurry, low quality", + "clip": ["2", 0] + } + }, + "20": { + "class_type": "EmptyLatentImage", + "inputs": { + "width": 1280, + "height": 720, + "batch_size": 1 + } + }, + "30": { + "class_type": "KSampler", + "inputs": { + "model": ["4", 0], + "positive": ["6", 0], + "negative": ["7", 0], + "latent_image": ["20", 0], + "seed": 42, + "steps": 8, + "cfg": 1.0, + "sampler_name": "euler", + "scheduler": "simple", + "denoise": 1.0 + } + }, + "40": { + "class_type": "VAEDecode", + "inputs": { + "samples": ["30", 0], + "vae": ["3", 0] + } + }, + "50": { + "class_type": "VHS_VideoCombine", + "inputs": { + "images": ["40", 0], + "frame_rate": 24, + "loop_count": 0, + "filename_prefix": "drama_clip", + "format": "video/h264-mp4", + "save_output": true, + "pingpong": false + } + } +} diff --git a/plugins/antianqi/comfyui-studio/workflows/flux2-klein-image-edit-dual.json b/plugins/antianqi/comfyui-studio/workflows/flux2-klein-image-edit-dual.json new file mode 100644 index 0000000..83862eb --- /dev/null +++ b/plugins/antianqi/comfyui-studio/workflows/flux2-klein-image-edit-dual.json @@ -0,0 +1,605 @@ +{ + "9": { + "inputs": { + "filename_prefix": "klein_edit", + "images": [ + "75:65", + 0 + ] + }, + "class_type": "SaveImage", + "_meta": { + "title": "Save Image" + } + }, + "76": { + "inputs": { + "image": "" + }, + "class_type": "LoadImage", + "_meta": { + "title": "Load Image" + } + }, + "81": { + "inputs": { + "image": "" + }, + "class_type": "LoadImage", + "_meta": { + "title": "Load Image" + } + }, + "94": { + "inputs": { + "filename_prefix": "klein_edit", + "images": [ + "92:104", + 0 + ] + }, + "class_type": "SaveImage", + "_meta": { + "title": "Save Image" + } + }, + "75:61": { + "inputs": { + "sampler_name": "euler" + }, + "class_type": "KSamplerSelect", + "_meta": { + "title": "KSamplerSelect" + } + }, + "75:62": { + "inputs": { + "steps": 20, + "width": [ + "75:100", + 0 + ], + "height": [ + "75:100", + 1 + ] + }, + "class_type": "Flux2Scheduler", + "_meta": { + "title": "Flux2Scheduler" + } + }, + "75:63": { + "inputs": { + "cfg": 5, + "model": [ + "75:70", + 0 + ], + "positive": [ + "75:124", + 0 + ], + "negative": [ + "75:122", + 0 + ] + }, + "class_type": "CFGGuider", + "_meta": { + "title": "CFGGuider" + } + }, + "75:64": { + "inputs": { + "noise": [ + "75:73", + 0 + ], + "guider": [ + "75:63", + 0 + ], + "sampler": [ + "75:61", + 0 + ], + "sigmas": [ + "75:62", + 0 + ], + "latent_image": [ + "75:66", + 0 + ] + }, + "class_type": "SamplerCustomAdvanced", + "_meta": { + "title": "SamplerCustomAdvanced" + } + }, + "75:65": { + "inputs": { + "samples": [ + "75:64", + 0 + ], + "vae": [ + "75:72", + 0 + ] + }, + "class_type": "VAEDecode", + "_meta": { + "title": "VAE Decode" + } + }, + "75:73": { + "inputs": { + "noise_seed": 6 + }, + "class_type": "RandomNoise", + "_meta": { + "title": "RandomNoise" + } + }, + "75:70": { + "inputs": { + "unet_name": "flux-2-klein-base-9b-fp8.safetensors", + "weight_dtype": "default" + }, + "class_type": "UNETLoader", + "_meta": { + "title": "Load Diffusion Model" + } + }, + "75:71": { + "inputs": { + "clip_name": "qwen_3_8b_fp8mixed.safetensors", + "type": "flux2", + "device": "default" + }, + "class_type": "CLIPLoader", + "_meta": { + "title": "Load CLIP" + } + }, + "75:74": { + "inputs": { + "text": "__PROMPT__", + "clip": [ + "75:71", + 0 + ] + }, + "class_type": "CLIPTextEncode", + "_meta": { + "title": "CLIP Text Encode (Positive Prompt)" + } + }, + "75:67": { + "inputs": { + "text": "deformed, bad anatomy, extra fingers, blurry, low quality, watermark", + "clip": [ + "75:71", + 0 + ] + }, + "class_type": "CLIPTextEncode", + "_meta": { + "title": "CLIP Text Encode (Negative Prompt)" + } + }, + "75:72": { + "inputs": { + "vae_name": "diffusion_pytorch_model.safetensors" + }, + "class_type": "VAELoader", + "_meta": { + "title": "Load VAE" + } + }, + "75:66": { + "inputs": { + "width": [ + "75:100", + 0 + ], + "height": [ + "75:100", + 1 + ], + "batch_size": 1 + }, + "class_type": "EmptyFlux2LatentImage", + "_meta": { + "title": "Empty Flux 2 Latent" + } + }, + "75:80": { + "inputs": { + "upscale_method": "lanczos", + "megapixels": 1, + "resolution_steps": 1, + "image": [ + "76", + 0 + ] + }, + "class_type": "ImageScaleToTotalPixels", + "_meta": { + "title": "ImageScaleToTotalPixels" + } + }, + "75:100": { + "inputs": { + "image": [ + "75:80", + 0 + ] + }, + "class_type": "GetImageSize", + "_meta": { + "title": "Get Image Size" + } + }, + "75:122": { + "inputs": { + "conditioning": [ + "75:67", + 0 + ], + "latent": [ + "75:123", + 0 + ] + }, + "class_type": "ReferenceLatent", + "_meta": { + "title": "ReferenceLatent" + } + }, + "75:123": { + "inputs": { + "pixels": [ + "75:80", + 0 + ], + "vae": [ + "75:72", + 0 + ] + }, + "class_type": "VAEEncode", + "_meta": { + "title": "VAE Encode" + } + }, + "75:124": { + "inputs": { + "conditioning": [ + "75:74", + 0 + ], + "latent": [ + "75:123", + 0 + ] + }, + "class_type": "ReferenceLatent", + "_meta": { + "title": "ReferenceLatent" + } + }, + "92:102": { + "inputs": { + "sampler_name": "euler" + }, + "class_type": "KSamplerSelect", + "_meta": { + "title": "KSamplerSelect" + } + }, + "92:103": { + "inputs": { + "noise": [ + "92:105", + 0 + ], + "guider": [ + "92:114", + 0 + ], + "sampler": [ + "92:102", + 0 + ], + "sigmas": [ + "92:115", + 0 + ], + "latent_image": [ + "92:109", + 0 + ] + }, + "class_type": "SamplerCustomAdvanced", + "_meta": { + "title": "SamplerCustomAdvanced" + } + }, + "92:104": { + "inputs": { + "samples": [ + "92:103", + 0 + ], + "vae": [ + "92:107", + 0 + ] + }, + "class_type": "VAEDecode", + "_meta": { + "title": "VAE Decode" + } + }, + "92:105": { + "inputs": { + "noise_seed": 1079830163544628 + }, + "class_type": "RandomNoise", + "_meta": { + "title": "RandomNoise" + } + }, + "92:106": { + "inputs": { + "unet_name": "flux-2-klein-base-9b-fp8.safetensors", + "weight_dtype": "default" + }, + "class_type": "UNETLoader", + "_meta": { + "title": "Load Diffusion Model" + } + }, + "92:107": { + "inputs": { + "vae_name": "diffusion_pytorch_model.safetensors" + }, + "class_type": "VAELoader", + "_meta": { + "title": "Load VAE" + } + }, + "92:108": { + "inputs": { + "image": [ + "92:110", + 0 + ] + }, + "class_type": "GetImageSize", + "_meta": { + "title": "Get Image Size" + } + }, + "92:109": { + "inputs": { + "width": [ + "92:108", + 0 + ], + "height": [ + "92:108", + 1 + ], + "batch_size": 1 + }, + "class_type": "EmptyFlux2LatentImage", + "_meta": { + "title": "Empty Flux 2 Latent" + } + }, + "92:110": { + "inputs": { + "upscale_method": "lanczos", + "megapixels": 1, + "resolution_steps": 1, + "image": [ + "76", + 0 + ] + }, + "class_type": "ImageScaleToTotalPixels", + "_meta": { + "title": "ImageScaleToTotalPixels" + } + }, + "92:85": { + "inputs": { + "upscale_method": "lanczos", + "megapixels": 1, + "resolution_steps": 1, + "image": [ + "81", + 0 + ] + }, + "class_type": "ImageScaleToTotalPixels", + "_meta": { + "title": "ImageScaleToTotalPixels" + } + }, + "92:111": { + "inputs": { + "clip_name": "qwen_3_8b_fp8mixed.safetensors", + "type": "flux2", + "device": "default" + }, + "class_type": "CLIPLoader", + "_meta": { + "title": "Load CLIP" + } + }, + "92:113": { + "inputs": { + "text": "__PROMPT__", + "clip": [ + "92:111", + 0 + ] + }, + "class_type": "CLIPTextEncode", + "_meta": { + "title": "CLIP Text Encode (Positive Prompt)" + } + }, + "92:87": { + "inputs": { + "text": "deformed, bad anatomy, extra fingers, blurry, low quality, watermark", + "clip": [ + "92:111", + 0 + ] + }, + "class_type": "CLIPTextEncode", + "_meta": { + "title": "CLIP Text Encode ( Negative Prompt)" + } + }, + "92:114": { + "inputs": { + "cfg": 5, + "model": [ + "92:106", + 0 + ], + "positive": [ + "92:130", + 0 + ], + "negative": [ + "92:128", + 0 + ] + }, + "class_type": "CFGGuider", + "_meta": { + "title": "CFGGuider" + } + }, + "92:115": { + "inputs": { + "steps": 20, + "width": [ + "92:108", + 0 + ], + "height": [ + "92:108", + 1 + ] + }, + "class_type": "Flux2Scheduler", + "_meta": { + "title": "Flux2Scheduler" + } + }, + "92:125": { + "inputs": { + "conditioning": [ + "92:87", + 0 + ], + "latent": [ + "92:126", + 0 + ] + }, + "class_type": "ReferenceLatent", + "_meta": { + "title": "ReferenceLatent" + } + }, + "92:126": { + "inputs": { + "pixels": [ + "92:110", + 0 + ], + "vae": [ + "92:107", + 0 + ] + }, + "class_type": "VAEEncode", + "_meta": { + "title": "VAE Encode" + } + }, + "92:127": { + "inputs": { + "conditioning": [ + "92:113", + 0 + ], + "latent": [ + "92:126", + 0 + ] + }, + "class_type": "ReferenceLatent", + "_meta": { + "title": "ReferenceLatent" + } + }, + "92:128": { + "inputs": { + "conditioning": [ + "92:125", + 0 + ], + "latent": [ + "92:129", + 0 + ] + }, + "class_type": "ReferenceLatent", + "_meta": { + "title": "ReferenceLatent" + } + }, + "92:129": { + "inputs": { + "pixels": [ + "92:85", + 0 + ], + "vae": [ + "92:107", + 0 + ] + }, + "class_type": "VAEEncode", + "_meta": { + "title": "VAE Encode" + } + }, + "92:130": { + "inputs": { + "conditioning": [ + "92:127", + 0 + ], + "latent": [ + "92:129", + 0 + ] + }, + "class_type": "ReferenceLatent", + "_meta": { + "title": "ReferenceLatent" + } + } +} \ No newline at end of file diff --git a/plugins/antianqi/comfyui-studio/workflows/flux2-klein-image-edit.json b/plugins/antianqi/comfyui-studio/workflows/flux2-klein-image-edit.json new file mode 100644 index 0000000..7745918 --- /dev/null +++ b/plugins/antianqi/comfyui-studio/workflows/flux2-klein-image-edit.json @@ -0,0 +1,281 @@ +{ + "9": { + "inputs": { + "filename_prefix": "klein_edit", + "images": [ + "75:65", + 0 + ] + }, + "class_type": "SaveImage", + "_meta": { + "title": "Save Image" + } + }, + "76": { + "inputs": { + "image": "" + }, + "class_type": "LoadImage", + "_meta": { + "title": "Load Image" + } + }, + "81": { + "inputs": { + "image": "__IMAGE2__" + }, + "class_type": "LoadImage", + "_meta": { + "title": "Load Image" + } + }, + "75:61": { + "inputs": { + "sampler_name": "euler" + }, + "class_type": "KSamplerSelect", + "_meta": { + "title": "KSamplerSelect" + } + }, + "75:62": { + "inputs": { + "steps": 20, + "width": [ + "75:100", + 0 + ], + "height": [ + "75:100", + 1 + ] + }, + "class_type": "Flux2Scheduler", + "_meta": { + "title": "Flux2Scheduler" + } + }, + "75:63": { + "inputs": { + "cfg": 5, + "model": [ + "75:70", + 0 + ], + "positive": [ + "75:124", + 0 + ], + "negative": [ + "75:122", + 0 + ] + }, + "class_type": "CFGGuider", + "_meta": { + "title": "CFGGuider" + } + }, + "75:64": { + "inputs": { + "noise": [ + "75:73", + 0 + ], + "guider": [ + "75:63", + 0 + ], + "sampler": [ + "75:61", + 0 + ], + "sigmas": [ + "75:62", + 0 + ], + "latent_image": [ + "75:66", + 0 + ] + }, + "class_type": "SamplerCustomAdvanced", + "_meta": { + "title": "SamplerCustomAdvanced" + } + }, + "75:65": { + "inputs": { + "samples": [ + "75:64", + 0 + ], + "vae": [ + "75:72", + 0 + ] + }, + "class_type": "VAEDecode", + "_meta": { + "title": "VAE Decode" + } + }, + "75:73": { + "inputs": { + "noise_seed": 6 + }, + "class_type": "RandomNoise", + "_meta": { + "title": "RandomNoise" + } + }, + "75:70": { + "inputs": { + "unet_name": "flux-2-klein-base-9b-fp8.safetensors", + "weight_dtype": "default" + }, + "class_type": "UNETLoader", + "_meta": { + "title": "Load Diffusion Model" + } + }, + "75:71": { + "inputs": { + "clip_name": "qwen_3_8b_fp8mixed.safetensors", + "type": "flux2", + "device": "default" + }, + "class_type": "CLIPLoader", + "_meta": { + "title": "Load CLIP" + } + }, + "75:74": { + "inputs": { + "text": "__PROMPT__", + "clip": [ + "75:71", + 0 + ] + }, + "class_type": "CLIPTextEncode", + "_meta": { + "title": "CLIP Text Encode (Positive Prompt)" + } + }, + "75:67": { + "inputs": { + "text": "deformed, bad anatomy, extra fingers, blurry, low quality, watermark", + "clip": [ + "75:71", + 0 + ] + }, + "class_type": "CLIPTextEncode", + "_meta": { + "title": "CLIP Text Encode (Negative Prompt)" + } + }, + "75:72": { + "inputs": { + "vae_name": "diffusion_pytorch_model.safetensors" + }, + "class_type": "VAELoader", + "_meta": { + "title": "Load VAE" + } + }, + "75:66": { + "inputs": { + "width": [ + "75:100", + 0 + ], + "height": [ + "75:100", + 1 + ], + "batch_size": 1 + }, + "class_type": "EmptyFlux2LatentImage", + "_meta": { + "title": "Empty Flux 2 Latent" + } + }, + "75:80": { + "inputs": { + "upscale_method": "lanczos", + "megapixels": 1, + "resolution_steps": 1, + "image": [ + "76", + 0 + ] + }, + "class_type": "ImageScaleToTotalPixels", + "_meta": { + "title": "ImageScaleToTotalPixels" + } + }, + "75:100": { + "inputs": { + "image": [ + "75:80", + 0 + ] + }, + "class_type": "GetImageSize", + "_meta": { + "title": "Get Image Size" + } + }, + "75:122": { + "inputs": { + "conditioning": [ + "75:67", + 0 + ], + "latent": [ + "75:123", + 0 + ] + }, + "class_type": "ReferenceLatent", + "_meta": { + "title": "ReferenceLatent" + } + }, + "75:123": { + "inputs": { + "pixels": [ + "75:80", + 0 + ], + "vae": [ + "75:72", + 0 + ] + }, + "class_type": "VAEEncode", + "_meta": { + "title": "VAE Encode" + } + }, + "75:124": { + "inputs": { + "conditioning": [ + "75:74", + 0 + ], + "latent": [ + "75:123", + 0 + ] + }, + "class_type": "ReferenceLatent", + "_meta": { + "title": "ReferenceLatent" + } + } +} \ No newline at end of file diff --git a/plugins/antianqi/comfyui-studio/workflows/selfie-mimicry.json b/plugins/antianqi/comfyui-studio/workflows/selfie-mimicry.json new file mode 100644 index 0000000..b6a8441 --- /dev/null +++ b/plugins/antianqi/comfyui-studio/workflows/selfie-mimicry.json @@ -0,0 +1,513 @@ +{ + "89": { + "inputs": { + "text": "__PROMPT__", + "clip": [ + "406", + 1 + ] + }, + "class_type": "CLIPTextEncode", + "_meta": { + "title": "CLIP Text Encode (Prompt)" + } + }, + "251": { + "inputs": { + "rgthree_comparer": { + "images": [ + { + "name": "A", + "selected": true, + "url": "/api/view?filename=rgthree.compare._temp_pqzjp_00001_.png&type=temp&subfolder=&rand=0.7865590674908698" + }, + { + "name": "B", + "selected": true, + "url": "/api/view?filename=rgthree.compare._temp_pqzjp_00002_.png&type=temp&subfolder=&rand=0.4441934895916896" + } + ] + }, + "image_a": [ + "322", + 0 + ], + "image_b": [ + "314", + 0 + ] + }, + "class_type": "Image Comparer (rgthree)", + "_meta": { + "title": "Image Comparer (rgthree)" + } + }, + "314": { + "inputs": { + "image": "__IMAGE1__" + }, + "class_type": "LoadImage", + "_meta": { + "title": "Load Image" + } + }, + "316": { + "inputs": { + "preprocessor": "LineArtPreprocessor", + "resolution": [ + "403", + 0 + ], + "image": [ + "380", + 0 + ] + }, + "class_type": "AIO_Preprocessor", + "_meta": { + "title": "AIO Aux Preprocessor" + } + }, + "317": { + "inputs": { + "images": [ + "404", + 0 + ] + }, + "class_type": "PreviewImage", + "_meta": { + "title": "Preview Image" + } + }, + "319": { + "inputs": { + "strength": 0.8, + "model": [ + "477", + 0 + ], + "model_patch": [ + "320", + 0 + ], + "vae": [ + "406", + 2 + ], + "image": [ + "404", + 0 + ] + }, + "class_type": "ZImageFunControlnet", + "_meta": { + "title": "ZImageFunControlnet" + } + }, + "320": { + "inputs": { + "name": "Z-Image-Fun-Controlnet-Union-2.1.safetensors" + }, + "class_type": "ModelPatchLoader", + "_meta": { + "title": "ModelPatchLoader" + } + }, + "322": { + "inputs": { + "samples": [ + "410", + 0 + ], + "vae": [ + "406", + 2 + ] + }, + "class_type": "VAEDecode", + "_meta": { + "title": "VAE Decode" + } + }, + "380": { + "inputs": { + "aspect_ratio": "original", + "proportional_width": 1, + "proportional_height": 1, + "fit": "crop", + "method": "lanczos", + "round_to_multiple": "16", + "scale_to_side": "longest", + "scale_to_length": [ + "455", + 0 + ], + "background_color": "#000000", + "image": [ + "314", + 0 + ], + "mask": [ + "314", + 1 + ] + }, + "class_type": "LayerUtility: ImageScaleByAspectRatio V2", + "_meta": { + "title": "LayerUtility: ImageScaleByAspectRatio V2" + } + }, + "399": { + "inputs": { + "text": "画面风格:高清晰度商业人像摄影风格,采用暖色调室内布光,光影对比强烈,主体突出,背景虚化柔和,色彩饱和度适中,整体氛围神秘而高贵,具有强烈的时尚杂志质感,视觉焦点集中在人物面部与上半身,细节丰富且锐利。\n\n核心元素:年轻亚洲女性角色,深棕色长发自然垂落,略带波浪卷曲,发丝蓬松有光泽,部分头发向后梳理露出额头与耳朵,耳侧有碎发修饰脸型。人物佩戴一对大尺寸圆形珍珠耳环,左手无名指戴有一枚花朵造型戒指,右手手腕叠戴多层细珠链手镯,右臂内侧可见一条纤细红色手绳。身着黑色无袖紧身连衣裙,领口呈高领圆领设计,胸前垂直开衩并缀有两圈横向排列的珍珠装饰环,腰间系有一条镶嵌水钻的金属质感腰带,呈不规则环形结构,腰身两侧有褶皱收束设计,凸显身材曲线。左腿膝盖上方可见黑色吊袜带连接至腿部,暗示穿着丝袜或长筒袜。背景为暖金色调墙面,表面带有浮雕式纹理或壁纸图案,右侧隐约可见一盏壁灯轮廓,光线从左上方投射,在人物面部与肩部形成柔和的高光,阴影过渡自然,增强立体感。\n\n具体内容:女性角色五官精致立体,眉形流畅上扬,眼妆浓重且层次分明,眼线拉长上扬,睫毛纤长卷翘,瞳孔深邃有神,鼻梁高挺,唇色为柔和的裸粉色,唇形饱满,嘴角微抿,表情冷艳中带一丝慵懒,眼神直视镜头,充满自信与吸引力。身体姿态呈半坐半倚状态,左手轻托下巴靠近脸颊,手指修长,指甲修剪整齐,右手自然搭在身侧或椅背上,肩部微微内扣,脊柱挺直,胸部微挺,腰部收紧,腿部线条流畅,整体坐姿优雅且富有张力,展现出成熟女性的魅力与掌控力。\n\n构图方式:采用中心偏右构图,人物占据画面主要区域,头部略高于画面中线,视线方向朝向镜头,留出适当空间。前景为人物面部与手部特写,中景为躯干与服装细节,远景为模糊的背景墙面与灯具轮廓,形成清晰的前后景深层次。相机参数设定为中画幅或高端单反相机,使用85mm定焦镜头,光圈f/2.0-f/2.8,快门速度1/125秒,ISO 200-400,白平衡设置为3500K-4000K暖色温,对焦模式为单点对焦锁定于眼部,确保面部与珠宝细节极致清晰锐利。光线来自左前方45度角,主光源柔和均匀,辅光补亮右侧暗部,轮廓光从右后方勾勒发丝边缘,增强立体感。整体色彩基调以黑色服装与暖金色背景形成冷暖对比,辅以肤色、珍珠白、金属银等点缀色,营造奢华而性感的视觉体验。\n\n光影效果:主光源为暖色温柔光箱或大型LED平板灯,从左侧斜向照射,照亮人物正面与左侧轮廓,右侧保留适度阴影增强立体感。高光集中在额头、鼻梁、颧骨、锁骨及肩部,反射柔和,无明显硬边。阴影区域如颈部下方、腋下、裙摆内侧呈现渐变过渡,无突兀黑块。环境光来自右侧背景,轻微提亮暗部,平衡整体明暗。体积光不明显,但通过暖色背景反射营造出包围式柔光氛围,人物轮廓边缘有微弱辉光,增强分离感。\n\n材质质感:皮肤呈现健康光泽,毛孔与细微纹理清晰可见,尤其在脸颊与鼻翼处,体现真实肤质。头发丝缕分明,富有弹性与光泽,反射柔和光线。服装面料为哑光针织或弹性棉混纺,贴合身体曲线,褶皱自然流畅,腰部与胸前开衩处有轻微拉伸感。珍珠饰品表面光滑圆润,折射柔和光泽,金属腰带与手镯具有镜面反射特性,钻石或水钻部分呈现细碎闪光。\n\n纹理密度:皮肤纹理细腻,毛孔分布自然,无过度磨皮痕迹。头发密度适中,发根蓬松,发梢微卷,层次分明。服装织物纹理细腻,有轻微竖向条纹或编织结构,尤其在腰部与袖口处可见细微褶皱。饰品表面抛光度高,反光清晰,无划痕或瑕疵。\n\n解剖结构:人物身材比例匀称,肩宽适中,锁骨明显,胸部饱满,腰部纤细,臀部圆润,腿部修长,肌肉线条流畅但不夸张,关节弯曲自然,脊柱曲线优美,整体姿态符合人体工学,无僵硬或扭曲感。\n\n空间位置:人物位于画面右侧三分之二处,头部居中偏右,视线正对镜头,身体略微侧转,左手抬起靠近面部,右手自然下垂,双腿交叉或并拢,左腿在前,右腿在后,形成动态平衡。背景墙面占据左侧三分之一,灯具位于右上角,构成稳定的三角构图。\n\n姿势分析:整体姿态为半坐姿,躯干微倾,头部略低,眼神专注,双手一上一下形成视觉引导线,肩部放松但挺拔,手臂自然弯曲,肘部微屈,手腕柔韧,手指姿态优雅,大腿与膝盖角度协调,骨盆轻微旋转,展现柔美曲线,整体动作流畅自然,富有表现力。\n\n技术参数:相机型号建议为索尼A7R IV或佳能R5,镜头为索尼FE 85mm f/1.4 GM或佳能RF 85mm f/1.2 L USM,拍摄模式为手动档,焦距85mm,光圈f/2.2,快门1/125s,ISO 200,白平衡3800K,对焦模式AF-C + 眼部追踪,曝光补偿+0.3EV,使用反光板或柔光箱辅助补光,三脚架固定拍摄,确保稳定性与清晰度。", + "anything": [ + "457", + 0 + ] + }, + "class_type": "easy showAnything", + "_meta": { + "title": "Show Any" + } + }, + "403": { + "inputs": { + "value": "min(a,b)", + "a": [ + "380", + 3 + ], + "b": [ + "380", + 4 + ] + }, + "class_type": "SimpleMath+", + "_meta": { + "title": "🔧 Simple Math" + } + }, + "404": { + "inputs": { + "any_01": [ + "316", + 0 + ] + }, + "class_type": "Any Switch (rgthree)", + "_meta": { + "title": "Any Switch (rgthree)" + } + }, + "406": { + "inputs": { + "ckpt_name": "Z-Image-Base-8steps-豹豹喵呜の白玉v2White_Marble-AIO_v2-bf16.safetensors" + }, + "class_type": "CheckpointLoaderSimple", + "_meta": { + "title": "Load Checkpoint" + } + }, + "407": { + "inputs": { + "conditioning": [ + "89", + 0 + ] + }, + "class_type": "ConditioningZeroOut", + "_meta": { + "title": "ConditioningZeroOut" + } + }, + "408": { + "inputs": { + "pixels": [ + "380", + 0 + ], + "vae": [ + "406", + 2 + ] + }, + "class_type": "VAEEncode", + "_meta": { + "title": "VAE Encode" + } + }, + "409": { + "inputs": { + "seed": 46214279332840, + "steps": 8, + "cfg": 1, + "sampler_name": "res_2s", + "scheduler": "linear_quadratic", + "denoise": 1, + "model": [ + "319", + 0 + ], + "positive": [ + "89", + 0 + ], + "negative": [ + "407", + 0 + ], + "latent_image": [ + "408", + 0 + ] + }, + "class_type": "KSampler", + "_meta": { + "title": "KSampler" + } + }, + "410": { + "inputs": { + "add_noise": "enable", + "noise_seed": 276067442284220, + "steps": 8, + "cfg": 1, + "sampler_name": "euler", + "scheduler": "simple", + "start_at_step": 5, + "end_at_step": 10000, + "return_with_leftover_noise": "disable", + "model": [ + "411", + 0 + ], + "positive": [ + "89", + 0 + ], + "negative": [ + "407", + 0 + ], + "latent_image": [ + "409", + 0 + ] + }, + "class_type": "KSamplerAdvanced", + "_meta": { + "title": "KSampler (Advanced)" + } + }, + "411": { + "inputs": { + "unet_name": "ZIT-moodyPornMix_zitV10R1DPO_fp16.safetensors", + "weight_dtype": "default" + }, + "class_type": "UNETLoader", + "_meta": { + "title": "Load Diffusion Model" + } + }, + "455": { + "inputs": { + "value": 1920 + }, + "class_type": "INTConstant", + "_meta": { + "title": "整数常量" + } + }, + "456": { + "inputs": { + "text": "__TRIGGER__" + }, + "class_type": "CR Text", + "_meta": { + "title": "文本" + } + }, + "457": { + "inputs": { + "any_01": [ + "474", + 0 + ], + "any_02": [ + "456", + 0 + ] + }, + "class_type": "Any Switch (rgthree)", + "_meta": { + "title": "Any Switch (rgthree)" + } + }, + "458": { + "inputs": { + "offload_model": true, + "offload_cache": true, + "anything": [ + "322", + 0 + ] + }, + "class_type": "VRAMCleanup", + "_meta": { + "title": "释放显存" + } + }, + "459": { + "inputs": { + "clean_file_cache": true, + "clean_processes": true, + "clean_dlls": true, + "retry_times": 3, + "anything": [ + "322", + 0 + ] + }, + "class_type": "RAMCleanup", + "_meta": { + "title": "释放内存" + } + }, + "460": { + "inputs": { + "offload_model": true, + "offload_cache": true, + "anything": [ + "409", + 0 + ] + }, + "class_type": "VRAMCleanup", + "_meta": { + "title": "释放显存" + } + }, + "461": { + "inputs": { + "clean_file_cache": true, + "clean_processes": true, + "clean_dlls": true, + "retry_times": 3, + "anything": [ + "409", + 0 + ] + }, + "class_type": "RAMCleanup", + "_meta": { + "title": "释放内存" + } + }, + "471": { + "inputs": { + "filename_prefix": "selfie_mimic", + "images": [ + "322", + 0 + ] + }, + "class_type": "SaveImage", + "_meta": { + "title": "Save Image" + } + }, + "472": { + "inputs": { + "model": "Qwen3.5-9B-Uncensored-HauhauCS-Aggressive-Q4_K_M.gguf", + "mmproj": "mmproj-Qwen3.5-9B-Uncensored-HauhauCS-Aggressive-BF16.gguf", + "chat_handler": "Qwen3.5", + "n_ctx": 8192, + "vram_limit": -1, + "image_min_tokens": 0, + "image_max_tokens": 0 + }, + "class_type": "llama_cpp_model_loader", + "_meta": { + "title": "Llama-cpp Model Loader" + } + }, + "473": { + "inputs": { + "max_tokens": 4096, + "top_k": 30, + "top_p": 0.9, + "min_p": 0.05, + "typical_p": 1, + "temperature": 0.8, + "repeat_penalty": 1, + "frequency_penalty": 0, + "presence_penalty": 1, + "mirostat_mode": 0, + "mirostat_eta": 0.1, + "mirostat_tau": 5, + "state_uid": -1 + }, + "class_type": "llama_cpp_parameters", + "_meta": { + "title": "Llama-cpp Parameters" + } + }, + "474": { + "inputs": { + "preset_prompt": "Prompt Style - Extreme Detailed", + "custom_prompt": "严禁输出思考过程,只反推输出中文提示词", + "system_prompt": "你是专注于图像反推提示词生成的专业AI助手,需基于用户上传的角色照片,分析并提取详细的图片信息,最终为图像编辑模型生成提示词。具体执行步骤如下:\n\n### 最高指令\n1.语言自适应:识别用户输入语言。用户用中文提问,你输出中文指令;用户用英文提问,你输出英文指令;若未输入语言,默认输出中文指令。\n2.格式绝对纯净:严禁输出 Markdown 符号(如星号、井号)、严禁中英对照括号、严禁输出任何解释或前缀。\n3.输出提示词总字数控制在1000字以内。\n4.不要描述设计师名称、日期等文字水印。\n\n### 核心规则\n1.详细提取图片中的画面信息,当用户提供参考图时,需对图片进行全面详细分析,提取所有可见元素(包括主体、背景、文字、光影、材质、纹理、解剖结构等),确保无遗漏。\n2.生成的提示词必须进行多层次、多维度的细节挖掘,达到像素级的细节提取精度。;\n3.生成的提示词中禁止包含水印、边框等多余内容。\n4.所有提示词必须包含画面风格、核心元素、具体内容、构图方式四个核心模块,每个模块描述需达到AI可直接识别并生成的精度,不得遗漏。\n\n### 约束条件\n1.正向引导:禁用负面提示词,完全使用正向约束表达(例:不用不要模糊,而写极致锐利的对焦,画面细节清晰)。\n2.自然语言:使用自然语言语法,语句连贯、符合语法,禁止无逻辑的标签堆料,需用有逻辑、有流畅度的句式。\n3.拒绝模糊:禁止使用模糊性描述(如好看的颜色、大概的形状),需采用精准术语(如金黄色渐变、等边三角形的几何结构)。\n4.禁止使用微观悬浮粒子类描述:不得出现“微尘”“漂浮粒子”“空气中的细小颗粒”“光雾粒子”“盐粒”“花粉”“水雾微粒”等无法被主流图像模型稳定可视化的微观悬浮物描写。若需表现氛围感,必须使用“体积光”“丁达尔光束”“光晕扩散”“柔焦虚化”等可视觉化的宏观光学现象,并确保其描述方式符合主流图像生成模型的训练数据模式。。\n5.通用适配:提示词需适配所有主流AI绘图工具,避免使用工具专属语法。\n6.纯净格式:输出不得包含任何Markdown标记、代码块符号。文字之间需保留正常空格以确保语义通顺,但段落之间紧凑排列,不要出现多余空行。\n7.最大生成量:输出提示词总字数控制在1000字以内。\n\n### 提示词生成\n1.提示词生成内容应包含图片场景、构图比例、服装搭配、动作姿势、色彩基调、整体氛围、相机参数(如果为写实拍摄的情况下)等描述;\n1.若用户输入特定需求,按照用户要求对提示词中的元素进行变更;\n2.若用户无特定要求,严格根据参考图内容生成提示词;\n\n### 核心要素\n**场景描述**:详细描述背景环境、场景布局、整体氛围。\n**构图及空间描述**:详细描述图片的整体构图方式以及前景、中景、近景和远景等空间关系。\n**主体内容**:详细描述人物/物体主体特征,包含人物国籍、长相特征等。\n**服装搭配**:详细描述服装、配饰、道具。\n**动作姿势**:详细描述动作,动作必须自然。\n**面部表情**:详细描述整体氛围和面部情绪。\n**技术参数**:描述拍摄使用的相机参数。\n\n###细节特征\n**光影效果**:描述光源方向、强度、色温、阴影形状与过渡、高光位置;若需表现空气感或氛围,仅允许使用“丁达尔光束”“柔和体积光”“逆光轮廓辉光”“环境光遮蔽”等AI可识别的宏观光学现象,禁止引用不可见或微观粒子作为光影成因。\n**材质质感**:描述表面纹理、反光特性、透明度、柔软度。\n**纹理密度**:描述皮肤纹理、织物纹路、衣物褶皱、毛发细节。\n**解剖结构**:描述身体比例、肌肉线条、骨骼结构、面部特征。\n**空间位置**:明确人物朝向、元素相对位置、构图方式(如对称、三分法),并细化前景、中景、远景的层次关系。\n**姿势分析**:详细描述身体整体姿态、四肢位置角度、关节弯曲程度、肌肉紧张状态、运动方向和动态感。\n**技术参数(如果为写实拍摄的情况下)**:描述相机型号、镜头参数、拍摄模式、焦距、光圈等参数。\n\n###格式校验\n1.无模糊性描述,所有细节均精准可量化。\n2.姿势描述详细完整,包含身体整体姿态、四肢位置角度、关节弯曲程度、肌肉紧张状态、运动方向和动态感。\n3.符合了用户的特定要求(如有)。\n4.适配所有主流AI绘图工具,无工具专属语法。\n5.无Markdown符号,无多余空行。\n\n###输出规范\n1.所有提示词直接输出,不得包含任何额外内容(如好的,这是提示词)。\n2.提示词需详尽到AI可直接生成与描述完全一致的画面,每个元素都有充分的细节描述,不得遗漏任何关键细节。\n3.若生成多组提示词,每组之间仅用换行分隔,不得添加任何分隔符。\n4.最终输出提示词总字数控制在1000字以内。\n\n###实例\n画面风格:自然写实摄影风格,采用高清晰度与高分辨率呈现,光影过渡柔和细腻,色彩还原真实且富有层次感,整体氛围清新自然,具有强烈的沉浸式视觉体验。\n核心元素:年轻的亚洲女性角色,手持双束粉色玫瑰花,身着浅粉色针织开衫,背景为开阔的玫瑰花田,绿色植被与粉花形成鲜明色彩对比,人物位于画面中心偏右位置,构图采用三分法与视觉引导线结合,前景为人物与花束,中景为花田延伸,远景为模糊的绿色山丘轮廓,增强空间纵深感。\n具体内容:女性角色拥有深棕色长发,自然垂落肩头,面部五官精致,眼神柔和专注,嘴唇微启,表情自然放松。她双手各持一束粉色玫瑰,左手花束靠近面部,右手花束高举过肩,姿态优雅且富有动态感。服装为浅粉色V领针织开衫,纹理清晰可见,袖口自然收束。花束由多支盛开的粉色玫瑰组成,花瓣层次分明,绿叶点缀其间,茎秆挺拔。背景花田中玫瑰花密集分布,颜色从浅粉到深粉渐变,远处山丘轮廓柔和,天空呈现淡蓝色,无明显云层。\n构图方式:采用三分法构图,人物位于右侧三分之一处,视线方向留有适当空间。前景花束与人物面部形成视觉焦点,中景花田引导视线向远景延伸,形成自然的视觉动线。相机参数设定为全画幅传感器,使用85mm定焦镜头,光圈f/2.8,快门速度1/200秒,ISO 100,白平衡自动,对焦模式为单点对焦,确保人物面部与花束细节清晰锐利。光线来自左前方,形成柔和的侧逆光效果,人物轮廓边缘有轻微辉光,阴影过渡自然,无明显硬边。整体色彩基调以粉色与绿色为主,辅以淡蓝色天空,营造出温馨浪漫的春日氛围。", + "inference_mode": "images", + "max_frames": 24, + "max_size": 256, + "seed": 331347452927766, + "force_offload": true, + "save_states": false, + "llama_model": [ + "472", + 0 + ], + "parameters": [ + "473", + 0 + ], + "images": [ + "380", + 0 + ] + }, + "class_type": "llama_cpp_instruct_adv", + "_meta": { + "title": "Llama-cpp Instruct" + } + }, + "476": { + "inputs": { + "lora_name": "your_face_lora.safetensors", + "strength_model": 1, + "model": [ + "406", + 0 + ] + }, + "class_type": "LoraLoaderModelOnly", + "_meta": { + "title": "Load LoRA" + } + }, + "477": { + "inputs": { + "lora_name": "your_style_lora.safetensors", + "strength_model": 1, + "model": [ + "476", + 0 + ] + }, + "class_type": "LoraLoaderModelOnly", + "_meta": { + "title": "Load LoRA" + } + } +} \ No newline at end of file diff --git a/plugins/antianqi/comfyui-studio/workflows/selfie-text-to-image.json b/plugins/antianqi/comfyui-studio/workflows/selfie-text-to-image.json new file mode 100644 index 0000000..95c7719 --- /dev/null +++ b/plugins/antianqi/comfyui-studio/workflows/selfie-text-to-image.json @@ -0,0 +1,75 @@ +{ + "3": { + "class_type": "CheckpointLoaderSimple", + "inputs": { + "ckpt_name": "Z-Image-Base-8steps-豹豹喵呜の白玉v2White_Marble-AIO_v2-bf16.safetensors" + } + }, + "10": { + "class_type": "LoraLoaderModelOnly", + "inputs": { + "model": ["3", 0], + "lora_name": "your_face_lora.safetensors", + "strength_model": 1.0 + } + }, + "20": { + "class_type": "LoraLoaderModelOnly", + "inputs": { + "model": ["10", 0], + "lora_name": "your_style_lora.safetensors", + "strength_model": 1.0 + } + }, + "30": { + "class_type": "CLIPTextEncode", + "inputs": { + "text": "__PROMPT__", + "clip": ["3", 1] + } + }, + "31": { + "class_type": "CLIPTextEncode", + "inputs": { + "text": "deformed, bad anatomy, extra fingers, blurry, low quality, watermark, different character, identity drift, multiple people, two people, group", + "clip": ["3", 1] + } + }, + "50": { + "class_type": "EmptyLatentImage", + "inputs": { + "width": 1008, + "height": 1792, + "batch_size": 1 + } + }, + "60": { + "class_type": "KSampler", + "inputs": { + "model": ["20", 0], + "positive": ["30", 0], + "negative": ["31", 0], + "latent_image": ["50", 0], + "seed": 72125, + "steps": 8, + "cfg": 1.5, + "sampler_name": "euler", + "scheduler": "simple", + "denoise": 1.0 + } + }, + "70": { + "class_type": "VAEDecode", + "inputs": { + "samples": ["60", 0], + "vae": ["3", 2] + } + }, + "80": { + "class_type": "SaveImage", + "inputs": { + "images": ["70", 0], + "filename_prefix": "zib_selfie" + } + } +}