Phospheneser akhaliq HF Staff commited on
Commit
3a3f89b
·
1 Parent(s): 2181622

gradio workflow app (#1)

Browse files

- Add workflow graph: 16 reference inputs -> generate -> gallery + status subjects, example 007 prefill (6efb787f49d4130c4e959ef839e0f95ad4035551)
- Replace demo entry point with gr.Workflow app (node canvas + styled App view) (f1539b30af4e45f532d9ab4b3d7e256eb4b5ef02)
- Add gradio_client dependency for media serialization helpers (aeba2d2e89cfb82127985c5317c8976f216ea2ec)
- README for the Workflow version: parity table, design notes, hf_oauth scopes (4dbd7c78cde6952af9bc81b548c0c2fa34b369e6)
- Add PR description (257f01db4ea8dbe92a27adee1f6f727ea8c5beee)
- Carry over upstream README badge row (homepage, discord, QQ, feishu) so the merge doesn't drop it (82c6b4f536fe04e02e046d0be552f4d8f2024c70)


Co-authored-by: AK <akhaliq@users.noreply.huggingface.co>

Files changed (4) hide show
  1. README.md +66 -73
  2. app.py +576 -36
  3. requirements.txt +1 -0
  4. workflow.json +191 -0
README.md CHANGED
@@ -1,5 +1,5 @@
1
  ---
2
- title: MOSS Image
3
  emoji: 🖼
4
  colorFrom: blue
5
  colorTo: indigo
@@ -7,12 +7,20 @@ sdk: gradio
7
  sdk_version: 6.30.0
8
  python_version: "3.12"
9
  app_file: app.py
 
 
 
 
10
  pinned: false
11
  license: apache-2.0
12
- short_description: Official demo of MOSS-Image.
 
 
13
  ---
14
 
15
- # MOSS-Image Demo
 
 
16
 
17
  <p align="center">
18
  <a href="https://huggingface.co/OpenMOSS-Team/MOSS-Image-Base"><img alt="Hugging Face · MOSS-Image-Base" src="https://img.shields.io/badge/🤗%20Hugging%20Face-MOSS--Image--Base-ffc107?style=flat-square"></a>
@@ -38,82 +46,67 @@ short_description: Official demo of MOSS-Image.
38
  <a href="https://discord.gg/nXFWEyvbb"><img alt="MOSS-Image Discord Community" src="https://img.shields.io/badge/Discord-Community-5865F2?style=flat-square&amp;logo=discord&amp;logoColor=white"></a>
39
  </p>
40
 
41
- Gradio interface for [MOSS-Image](https://github.com/OpenMOSS/MOSS-Image),
42
- including resolution and aspect-ratio presets, sampling controls, a selectable
43
- example gallery, advanced guidance controls, and live example/default-prompt updates.
44
-
45
- ## Model loading
46
-
47
- The Space preloads **MOSS-Image-Base** and **MOSS-Image-SFT** with
48
- `snapshot_download(repo_id, revision=resolved_commit)`, followed by
49
- `MOSSImagePipeline.from_pretrained(snapshot_path, local_files_only=True, ...)`.
50
- The Space explicitly loads its bundled pipeline, transformer and scheduler classes
51
- from `pipeline_moss_image.py` for the ZeroGPU AOT lifecycle. The bundled inference
52
- implementation follows the current Base pipeline, including negative-branch skipping
53
- and standard SLG. Progress is reported using the same bridge as the standalone app.
54
- Weights are downloaded into the Hugging Face cache; no local model directory
55
- or model weights in this repository are required.
56
-
57
- Select **MOSS-Image-Base** or **MOSS-Image-SFT** in the Model dropdown before
58
- clicking Generate. Base is selected by default. The optional `MOSS_MODEL_ID`
59
- Space variable changes this initial selection.
60
-
61
- Both transformers are loaded at startup. The text encoder, tokenizer and VAE
62
- are each shared only when their Hub file hashes match at the resolved revisions.
63
- Components that differ, such as model-specific VAEs, are loaded separately. Each model retains its own
64
- scheduler, prompt cache and revision metadata. Generation requests run serially;
65
- changing the dropdown does not change an already submitted request.
66
-
67
- While the model repositories are private, configure an `HF_TOKEN` **Space secret**
68
- with read access to both models. Do not put a token in source files or
69
- ordinary Space variables. Local development uses the same environment variable
70
- or your cached Hugging Face login.
71
-
72
- ## ZeroGPU
73
-
74
- Both pipelines are loaded and placed on CUDA before the UI starts. Generation runs
75
- under `@spaces.GPU(duration=80)`. At startup, a separate GPU allocation exports
76
- and AOT-compiles the repeated DiT and text-projection blocks with dynamic sequence
77
- lengths. Both models reuse the compiled graphs with their own weights. Compilation
78
- is validated against eager block outputs before the UI starts. This uses ZeroGPU
79
- AOTInductor support, not runtime `torch.compile`; GPU residency is fixed.
80
- Set the Space variable `MOSS_AOTI=0` to run the eager baseline. Compilation happens
81
- once per container start and is not part of image generation latency.
82
- The 80-second budget is scaled to 120 seconds by the current RTX PRO 6000
83
- ZeroGPU SDK configuration; it is not a measured latency guarantee.
84
- Large images or batches may require more time or memory than the allocation allows.
85
-
86
- Outputs and version-2 parameter JSON files are stored under `outputs/gradio/`. Records
87
- include the model repository and resolved revision, actual seed, sampling
88
- parameters, component configuration hashes, execution backend, and software versions. Space storage
89
- is ephemeral; save generated images you want to keep.
90
-
91
- ## Local development
92
 
93
- ```bash
94
- pip install -r requirements.txt
95
- python app.py
96
- ```
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
97
 
98
- To inspect the UI without downloading weights or allocating a GPU:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
99
 
100
  ```bash
101
- python app.py --preview --port 7861
 
 
102
  ```
103
 
104
- The UI and size presets follow the MOSS-Image development repository (main at
105
- `8a3223c`). CFG defaults to 3; advanced settings contain CFG, shift and the
106
- Off / Negative-branch skipping / Standard SLG modes. Aspect ratios span 1:8–8:1.
107
- Examples restore their prompts, sampling settings and seed; unknown seeds become
108
- random (-1). Example files and default prompts refresh every two seconds.
 
 
109
 
110
- ZeroGPU keeps the two official model choices, startup preloading, matching-component
111
- sharing and AOT compilation. Memory mode and runtime Compile DiT controls are not
112
- shown because GPU residency and compilation are managed at startup. The generation
113
- API returns the gallery and status, matching the standalone interface.
114
 
115
- ## Licenses
 
116
 
117
- Model weights use Apache-2.0. Bundled pipeline code retains its BSD 3-Clause
118
- license in [LICENSE-CODE](LICENSE-CODE). See [NOTICE.md](NOTICE.md) for component
119
- attribution and the licenses in `third_party/`.
 
1
  ---
2
+ title: MOSS-Image Workflow
3
  emoji: 🖼
4
  colorFrom: blue
5
  colorTo: indigo
 
7
  sdk_version: 6.30.0
8
  python_version: "3.12"
9
  app_file: app.py
10
+ hf_oauth: true
11
+ hf_oauth_scopes:
12
+ - inference-api
13
+ - write-repos
14
  pinned: false
15
  license: apache-2.0
16
+ tags:
17
+ - ml-intern
18
+ short_description: MOSS-Image text-to-image as a gr.Workflow node canvas.
19
  ---
20
 
21
+ [![Built with ML Intern](https://huggingface.co/datasets/huggingface/badges/resolve/main/built-with-ml-intern-sm.svg)](https://hf.co/chat?mode=ml-intern)
22
+
23
+ # MOSS-Image Workflow
24
 
25
  <p align="center">
26
  <a href="https://huggingface.co/OpenMOSS-Team/MOSS-Image-Base"><img alt="Hugging Face · MOSS-Image-Base" src="https://img.shields.io/badge/🤗%20Hugging%20Face-MOSS--Image--Base-ffc107?style=flat-square"></a>
 
46
  <a href="https://discord.gg/nXFWEyvbb"><img alt="MOSS-Image Discord Community" src="https://img.shields.io/badge/Discord-Community-5865F2?style=flat-square&amp;logo=discord&amp;logoColor=white"></a>
47
  </p>
48
 
49
+ A [Gradio Workflow](https://www.gradio.app/guides/workflows) (node-canvas) version of the official
50
+ [OpenMOSS-Team/MOSS-Image](https://huggingface.co/spaces/OpenMOSS-Team/MOSS-Image) demo, built on
51
+ `gr.Workflow` in gradio 6.30.0, with the App view styled after the MOSS-Image landing page.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
52
 
53
+ ## Feature parity with the official demo
54
+
55
+ Every sampling control of the original Space is a reference node wired into the
56
+ `MOSS-Image Generate` function node:
57
+
58
+ | Original demo | Here |
59
+ |---|---|
60
+ | Model dropdown (Base / SFT) | **Model** reference (`base` or `sft`); both checkpoints loaded at startup |
61
+ | Prompt / Negative Prompt | **Prompt** / **Negative Prompt** references (same default text) |
62
+ | Width × Height (presets) | **Width** / **Height** references (multiples of 16) |
63
+ | Steps / CFG / Shift / Seed | **Steps** / **CFG** / **Shift** / **Seed** references (`-1` = random) |
64
+ | Images per prompt | **Images** reference → gallery output |
65
+ | Max text length | **Max Text Length** reference (1–4096) |
66
+ | Skip-layer modes (Off / Negative-branch skipping / Standard SLG) | **Skip Layers** (`22` = negative-branch skipping, empty = off), **SLG Layers**, **SLG Scale**, **Skip Start**, **Skip Stop** references |
67
+ | Example gallery with one-click restore | **Examples strip** under the hero: the original Space's 20 showcase images, hot-linked; clicking one fetches its `parameters.json` and restores prompt, model, size, sampling and SLG settings into the form |
68
+
69
+ Not ported: showcase add/delete with live refresh (the Workflow App view is
70
+ generated from the graph and has no upload-to-gallery mechanism), and ZeroGPU AOT
71
+ compilation (this app runs the eager backend — set it up like the original if you
72
+ need the extra speed).
73
 
74
+ ## Design
75
+
76
+ The App view is auto-generated by `gr.Workflow`, so the landing-page look is applied
77
+ through `launch(theme=…, css=…, head=…)`: Inter type, a light gradient canvas, the
78
+ official MOSS-Image lockup (light/dark aware) above the
79
+ "Text-to-Image Generation with Native Layout Control" headline, a top nav with
80
+ model/code links, and a Soft blue theme. The canvas view is untouched.
81
+
82
+ ## Running it
83
+
84
+ Generation runs on GPU via `@spaces.GPU(duration=80)` per node call, so this Space needs
85
+ **ZeroGPU hardware** (Settings → Hardware). `hf_oauth: true` is enabled so the owner can edit the
86
+ canvas and signed-in visitors spend their own ZeroGPU quota; anonymous visitors fall back to the
87
+ small shared allowance.
88
+
89
+ Local development:
90
 
91
  ```bash
92
+ pip install -r requirements.txt
93
+ python app.py # full run, needs a CUDA GPU
94
+ MOSS_WORKFLOW_PREVIEW=1 python app.py # canvas + styling only, no model download
95
  ```
96
 
97
+ The bundled `pipeline_moss_image.py` is copied verbatim from the
98
+ [original Space](https://huggingface.co/spaces/OpenMOSS-Team/MOSS-Image) (BSD 3-Clause); model
99
+ weights are Apache-2.0. The lockup artwork is hot-linked from the original Space's assets.
100
+ The form is prefilled with showcase example 007 (the photorealistic Kazusa at
101
+ a Tokyo crosswalk: 1184×1776, 50 steps, CFG 3, shift 5, skip layer 22, seed
102
+ 3537712559) so visitors can just hit **Run** and reproduce it exactly. Other
103
+ defaults match the official demo: Euler sampling and negative-branch skipping.
104
 
105
+ ## API example
 
 
 
106
 
107
+ ```python
108
+ from gradio_client import Client
109
 
110
+ client = Client("akhaliq/moss-image-workflow")
111
+ client.view_api() # exact parameters of /generated_images
112
+ ```
app.py CHANGED
@@ -1,47 +1,587 @@
1
- """Space entry point. Load both Hub models before serving GPU requests."""
 
 
 
 
 
 
 
 
 
 
 
2
 
3
- import argparse
4
- import logging
5
  import os
 
 
 
 
 
6
 
7
- import spaces # Initialize ZeroGPU before importing PyTorch or Diffusers.
 
 
8
 
9
- from gradio_app import (
10
- DEFAULT_MODEL, EXAMPLES, MODEL_IDS, UI_ASSETS,
11
- ModelRunner, build_app, load_pipeline,
12
- )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
13
 
 
14
 
15
- def main():
16
- parser = argparse.ArgumentParser(description=__doc__)
17
- parser.add_argument("--preview", action="store_true", help="Preview UI without downloading weights")
18
- parser.add_argument("--port", type=int, default=7860)
19
- args = parser.parse_args()
20
- logging.basicConfig(level=logging.INFO)
21
- model = os.environ.get("MOSS_MODEL_ID", DEFAULT_MODEL)
22
- if model not in MODEL_IDS:
23
- parser.error("MOSS_MODEL_ID must be OpenMOSS-Team/MOSS-Image-Base or OpenMOSS-Team/MOSS-Image-SFT")
24
- runner = ModelRunner("outputs/gradio")
25
- if not args.preview:
26
- shared_pipe = load_pipeline(model)
27
- runner.pipelines[model] = shared_pipe
28
- for repo in MODEL_IDS:
29
- if repo != model:
30
- runner.pipelines[repo] = load_pipeline(repo, shared_pipe=shared_pipe)
31
- if os.environ.get("MOSS_AOTI", "1") == "1":
32
- from aoti import prepare_aoti
33
- prepare_aoti(runner.pipelines.values())
34
- demo = build_app(model=model, runner=runner)
35
- generate_fn = next(fn for fn in demo.fns.values() if fn.api_name == "generate")
36
- logging.info(
37
- "Generation queue: SPACES_ZERO_GPU=%r, concurrency_limit=%s",
38
- os.environ.get("SPACES_ZERO_GPU"), generate_fn.concurrency_limit,
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
39
  )
40
- demo.launch(
41
- server_name="0.0.0.0", server_port=args.port, share=False,
42
- allowed_paths=[str(runner.output_dir), str(EXAMPLES.resolve()), str(UI_ASSETS.resolve())],
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
43
  )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
44
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
45
 
46
  if __name__ == "__main__":
47
- main()
 
 
 
 
 
 
1
+ """MOSS-Image as a gr.Workflow app — node-canvas text-to-image pipeline.
2
+
3
+ A reimagining of https://huggingface.co/spaces/OpenMOSS-Team/MOSS-Image as a
4
+ Gradio Workflow (gradio 6.30.0): generation is a bound Python-function node on
5
+ a drag-and-drop canvas, fed by reference nodes (prompt, size, and every
6
+ sampling knob of the original demo) and writing into an output-gallery subject.
7
+ Edit the graph on the canvas or in workflow.json; the App view and REST API
8
+ stay in sync automatically.
9
+
10
+ Both checkpoints are loaded once at module scope (ZeroGPU packs and transfers
11
+ them when a GPU is actually allocated) and shared by every run of the node.
12
+ """
13
 
 
 
14
  import os
15
+ import secrets
16
+ import tempfile
17
+ import time
18
+ from collections import OrderedDict
19
+ from pathlib import Path
20
 
21
+ # `spaces` must be imported before anything that initializes CUDA.
22
+ import spaces # ty: ignore[unresolved-import]
23
+ from gradio_client import utils as client_utils
24
 
25
+ import gradio as gr
26
+ from gradio import workflow_api as _wf_api
27
+
28
+ # Workaround for gradio 6.30.0: the App view hands a subject's value to its
29
+ # output component via `_from_output`, which unwraps a top-level file dict to
30
+ # its path but leaves dicts nested in a LIST untouched — and gr.Gallery only
31
+ # accepts path strings, not file dicts. A gallery subject fed by an fn node
32
+ # (which must return file dicts for the canvas) therefore crashes the App
33
+ # view. Unwrap list items the same way so both views work.
34
+ _orig_from_output = _wf_api._from_output
35
+
36
+
37
+ def _from_output_unwrapping_lists(value):
38
+ if isinstance(value, list):
39
+ return [_orig_from_output(item) for item in value]
40
+ return _orig_from_output(value)
41
+
42
+
43
+ _wf_api._from_output = _from_output_unwrapping_lists
44
+
45
+
46
+ def _install_component_server_keepalive():
47
+ """Keep `/gradio_api/component_server` connections alive during long fn calls.
48
+
49
+ Canvas fn nodes call back through the component_server route — a plain
50
+ POST that awaits the bound function synchronously, unlike App-view
51
+ endpoints, which run through the queue with SSE heartbeats. A MOSS-Image
52
+ generation takes 45-130s, and the silent connection is dropped by the
53
+ proxy chain, surfacing on the canvas as "Connection errored out" (the
54
+ App view is unaffected). Wrap the route so it streams whitespace
55
+ heartbeat bytes — legal leading whitespace in JSON — until the real
56
+ result is ready.
57
+ """
58
+ import asyncio
59
+ import logging
60
+
61
+ from fastapi import Request
62
+ from fastapi.dependencies.utils import get_dependant
63
+ from fastapi.routing import APIRoute
64
+ from starlette.responses import StreamingResponse
65
+ from starlette.routing import request_response
66
+ from gradio.routes import App as _RoutesApp
67
+
68
+ original_create_app = _RoutesApp.create_app
69
+
70
+ def _wrap(endpoint):
71
+ async def component_server_with_keepalive(request: Request):
72
+ task = asyncio.create_task(endpoint(request))
73
+
74
+ async def body():
75
+ try:
76
+ while not task.done():
77
+ yield b" "
78
+ await asyncio.sleep(10)
79
+ payload = json.dumps(task.result(), default=str).encode()
80
+ except Exception as exc: # keep the canvas error path readable
81
+ payload = json.dumps({"error": str(exc)}).encode()
82
+ yield payload
83
+
84
+ return StreamingResponse(body(), media_type="application/json")
85
 
86
+ return component_server_with_keepalive
87
 
88
+ @staticmethod
89
+ def create_app(blocks, *args, **kwargs):
90
+ app = original_create_app(blocks, *args, **kwargs)
91
+ for route in app.routes:
92
+ if isinstance(route, APIRoute) and route.path.rstrip("/").endswith(
93
+ "/component_server"
94
+ ):
95
+ route.endpoint = _wrap(route.endpoint)
96
+ dependant = get_dependant(
97
+ path=route.path_format, endpoint=route.endpoint
98
+ )
99
+ # Keep the route-level dependencies (e.g. Depends(login_check)).
100
+ dependant.dependencies = (
101
+ route.dependant.dependencies + dependant.dependencies
102
+ )
103
+ route.dependant = dependant
104
+ route.app = request_response(route.get_route_handler())
105
+ logging.getLogger(__name__).info(
106
+ "component_server keep-alive wrapper installed"
107
+ )
108
+ return app
109
+
110
+ _RoutesApp.create_app = create_app
111
+
112
+
113
+ _install_component_server_keepalive()
114
+
115
+ MODEL_IDS = {
116
+ "base": "OpenMOSS-Team/MOSS-Image-Base",
117
+ "sft": "OpenMOSS-Team/MOSS-Image-SFT",
118
+ }
119
+
120
+ # Set MOSS_WORKFLOW_PREVIEW=1 to open the canvas without downloading weights
121
+ # (useful for local UI work on a CPU-only machine).
122
+ PREVIEW = os.environ.get("MOSS_WORKFLOW_PREVIEW") == "1"
123
+
124
+
125
+ def _load_pipeline(model_id, shared_pipe=None):
126
+ """Load one MOSS-Image checkpoint with the bundled inference classes.
127
+
128
+ Text encoder, tokenizer and VAE are shared with `shared_pipe` when their
129
+ Hub file hashes match — this is what keeps both checkpoints inside a
130
+ ZeroGPU worker's GPU memory (two full copies do not fit).
131
+ """
132
+ import torch
133
+ from huggingface_hub import HfApi, snapshot_download
134
+ from pipeline_moss_image import (
135
+ MOSSImagePipeline,
136
+ MOSSImageScheduler,
137
+ MOSSImageTransformer,
138
  )
139
+
140
+ info = HfApi().model_info(model_id, files_metadata=True)
141
+ revision = info.sha
142
+ shared_hashes = {
143
+ item.rfilename: item.lfs.sha256 if item.lfs else item.blob_id
144
+ for item in info.siblings
145
+ if item.rfilename.startswith(("text_encoder/", "tokenizer/", "vae/"))
146
+ }
147
+ components = {}
148
+ if shared_pipe is not None:
149
+ for name in ("text_encoder", "tokenizer", "vae"):
150
+ current = {k: v for k, v in shared_hashes.items() if k.startswith(f"{name}/")}
151
+ previous = {
152
+ k: v for k, v in shared_pipe._shared_component_hashes.items()
153
+ if k.startswith(f"{name}/")
154
+ }
155
+ if current and all(current.values()) and current == previous:
156
+ components[name] = getattr(shared_pipe, name)
157
+ snapshot = snapshot_download(
158
+ model_id, revision=revision, ignore_patterns=[f"{name}/*" for name in components]
159
  )
160
+ pipe = MOSSImagePipeline.from_pretrained(
161
+ snapshot,
162
+ custom_pipeline=str(Path(__file__).with_name("pipeline_moss_image")),
163
+ trust_remote_code=True,
164
+ transformer=MOSSImageTransformer.from_pretrained(
165
+ snapshot, subfolder="transformer", torch_dtype=torch.bfloat16
166
+ ),
167
+ scheduler=MOSSImageScheduler.from_pretrained(snapshot, subfolder="scheduler"),
168
+ local_files_only=True,
169
+ torch_dtype={"default": torch.bfloat16, "vae": torch.float32},
170
+ **components,
171
+ )
172
+ # On ZeroGPU the `spaces` package intercepts this move, packs the weights,
173
+ # and transfers them when a GPU is allocated — module scope costs no GPU time.
174
+ pipe.to("cuda")
175
+ pipe._prompt_cache = OrderedDict()
176
+ pipe._shared_component_hashes = shared_hashes
177
+ return pipe
178
+
179
+
180
+ PIPELINES = {}
181
+ if not PREVIEW:
182
+ PIPELINES["base"] = _load_pipeline(MODEL_IDS["base"])
183
+ PIPELINES["sft"] = _load_pipeline(MODEL_IDS["sft"], shared_pipe=PIPELINES["base"])
184
+
185
+
186
+ def _as_media(path: str) -> dict:
187
+ """The dict shape the workflow canvas renders media from."""
188
+ return {
189
+ "path": path,
190
+ "url": f"/gradio_api/file={client_utils.encode_file_path(path)}",
191
+ "is_file": True,
192
+ }
193
+
194
 
195
+ def _parse_layers(value: str, name: str) -> tuple:
196
+ """'22' -> (22,); '' -> (). Same rules as the original demo."""
197
+ if not (value or "").strip():
198
+ return ()
199
+ try:
200
+ layers = tuple(int(item.strip()) for item in value.split(","))
201
+ except ValueError:
202
+ raise gr.Error(
203
+ f"{name} must be comma-separated integer indices, e.g. 22."
204
+ ) from None
205
+ if any(layer < 0 for layer in layers) or len(set(layers)) != len(layers):
206
+ raise gr.Error(f"{name} indices must be nonnegative and unique.")
207
+ return layers
208
+
209
+
210
+ @spaces.GPU(duration=80)
211
+ def generate(
212
+ prompt: str,
213
+ negative_prompt: str,
214
+ model: str,
215
+ width: float,
216
+ height: float,
217
+ steps: float,
218
+ cfg: float,
219
+ shift: float,
220
+ seed: float,
221
+ num_images: float,
222
+ max_sequence_length: float,
223
+ uncond_skip_layers: str,
224
+ slg_layers: str,
225
+ slg_scale: float,
226
+ slg_start: float,
227
+ slg_stop: float,
228
+ ) -> list:
229
+ """Generate images with MOSS-Image (Base or SFT). Full sampling controls."""
230
+ import torch
231
+
232
+ if PREVIEW:
233
+ raise gr.Error("Preview mode: models are not loaded (MOSS_WORKFLOW_PREVIEW=1).")
234
+ if not (prompt or "").strip():
235
+ raise gr.Error("Enter a prompt.")
236
+ key = (model or "").strip().lower().replace("moss-image-", "")
237
+ if key not in PIPELINES:
238
+ raise gr.Error(f"Unknown model {model!r}. Choose 'base' or 'sft'.")
239
+ width, height, steps, num_images, max_sequence_length = (
240
+ round(width), round(height), round(steps), round(num_images),
241
+ round(max_sequence_length),
242
+ )
243
+ seed = round(seed)
244
+ if width <= 0 or height <= 0 or width % 16 or height % 16:
245
+ raise gr.Error("Width and height must be positive multiples of 16.")
246
+ if steps < 1 or num_images < 1:
247
+ raise gr.Error("Steps and image count must be positive.")
248
+ if not 0 <= cfg or shift <= 0:
249
+ raise gr.Error("CFG must be nonnegative; shift must be positive.")
250
+ if not 1 <= max_sequence_length <= 4096:
251
+ raise gr.Error("Maximum text length must be between 1 and 4096.")
252
+ if slg_scale < 0 or not 0 <= slg_start <= slg_stop <= 1:
253
+ raise gr.Error("Require SLG scale >= 0 and 0 <= SLG start <= SLG stop <= 1.")
254
+ if seed == -1:
255
+ seed = secrets.randbelow(2**32)
256
+ if not 0 <= seed < 2**53:
257
+ raise gr.Error("Seed must be -1 (random) or an integer from 0 to 2^53 - 1.")
258
+
259
+ pipe = PIPELINES[key]
260
+ depth = pipe.transformer.config.num_layers
261
+ skip = _parse_layers(uncond_skip_layers, "Skip layers")
262
+ slg = _parse_layers(slg_layers, "SLG layers")
263
+ if any(layer >= depth for layer in skip + slg):
264
+ raise gr.Error(f"Skip-layer indices must be below {depth}.")
265
+
266
+ pipe.scheduler = type(pipe.scheduler).from_config(pipe.scheduler.config, shift=shift)
267
+ start = time.monotonic()
268
+ try:
269
+ images = pipe(
270
+ prompt=prompt,
271
+ negative_prompt=negative_prompt or "",
272
+ width=width,
273
+ height=height,
274
+ num_inference_steps=steps,
275
+ guidance_scale=float(cfg),
276
+ num_images_per_prompt=num_images,
277
+ max_sequence_length=max_sequence_length,
278
+ uncond_skip_layers=skip,
279
+ slg_layers=slg,
280
+ slg_scale=float(slg_scale),
281
+ slg_start=float(slg_start),
282
+ slg_stop=float(slg_stop),
283
+ generator=torch.Generator("cpu").manual_seed(seed),
284
+ ).images
285
+ except torch.cuda.OutOfMemoryError as exc:
286
+ torch.cuda.empty_cache()
287
+ raise gr.Error("GPU memory exhausted. Try fewer or smaller images.") from exc
288
+
289
+ results = []
290
+ batch = os.urandom(8).hex()
291
+ for i, image in enumerate(images):
292
+ path = os.path.join(tempfile.gettempdir(), f"moss_{batch}_{i + 1:02d}.png")
293
+ image.save(path)
294
+ results.append(_as_media(path))
295
+ elapsed = time.monotonic() - start
296
+ status = (
297
+ f"{len(results)} image(s) · {MODEL_IDS[key].rsplit('/', 1)[-1]} "
298
+ f"· seed {seed} · {width}×{height} · {steps} steps "
299
+ f"· CFG {cfg:g} · shift {shift:g} · {elapsed:.1f}s"
300
+ )
301
+ # call_fn maps a fn's returned list/tuple onto the node's output PORTS
302
+ # (one element per port), so the gallery — one port holding many images —
303
+ # must come back as a single element containing the list: [results].
304
+ # Returning `results` bare would hand the gallery port only image #1.
305
+ return [results, status]
306
+
307
+
308
+ demo = gr.Workflow(
309
+ graph=str(Path(__file__).with_name("workflow.json")),
310
+ bind={"generate": generate},
311
+ )
312
+ demo.title = "MOSS-Image"
313
+
314
+ # --- Landing-page styling for the auto-generated App view -------------------
315
+ # gr.Workflow builds its App view inside #workflow-app; launch() threads
316
+ # theme/css/head straight through to Blocks.launch(). The hero uses the
317
+ # official lockup SVGs from the original Space (hot-linked, Apache-2.0).
318
+
319
+ _LOCKUP_LIGHT = (
320
+ "https://huggingface.co/spaces/OpenMOSS-Team/MOSS-Image"
321
+ "/resolve/main/assets/lockup-light.svg"
322
+ )
323
+ _LOCKUP_DARK = (
324
+ "https://huggingface.co/spaces/OpenMOSS-Team/MOSS-Image"
325
+ "/resolve/main/assets/lockup-dark.svg"
326
+ )
327
+
328
+ HEAD = """
329
+ <link rel="preconnect" href="https://fonts.googleapis.com">
330
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
331
+ <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&display=swap" rel="stylesheet">
332
+ """
333
+
334
+ # Custom JS for launch(js=...): the gradio frontend executes this function once
335
+ # the page is up. A <script> tag in head= is unreliable for this under gradio 6
336
+ # SSR — the frontend can inject head HTML after the window load event, and
337
+ # innerHTML-inserted <script> tags never execute.
338
+ #
339
+ # Two pieces: the top nav, and the examples strip — the original Space's
340
+ # showcase, hot-linked. Clicking a thumbnail fetches its parameters.json and
341
+ # restores every field of the generated App-view form, replicating the
342
+ # original's click-to-restore.
343
+ JS = """
344
+ () => {
345
+ const ORIGIN = "https://huggingface.co/spaces/OpenMOSS-Team/MOSS-Image/resolve/main/assets/images";
346
+ const IDS = Array.from({length: 20}, (_, i) => String(i + 1).padStart(3, "0"));
347
+
348
+ function setField(prefix, value) {
349
+ if (value === undefined || value === null) return;
350
+ const labels = Array.from(document.querySelectorAll('#workflow-app label'));
351
+ const match = labels.find(l => (l.textContent || "").trim().toLowerCase().startsWith(prefix));
352
+ if (!match) return;
353
+ let root = match;
354
+ for (let i = 0; i < 6 && root; i++) {
355
+ const input = root.querySelector('input:not([type="hidden"]), textarea');
356
+ if (input) {
357
+ input.value = value;
358
+ input.dispatchEvent(new Event("input", {bubbles: true}));
359
+ input.dispatchEvent(new Event("change", {bubbles: true}));
360
+ return;
361
+ }
362
+ root = root.parentElement;
363
+ }
364
+ }
365
+
366
+ function applyRecord(rec) {
367
+ const p = rec.parameters || {};
368
+ let uncond = p.uncond_skip_layers ?? "";
369
+ let slgLayers = p.slg_layers ?? "";
370
+ let slgScale = p.slg_scale ?? 0;
371
+ if ((rec.schema_version ?? 2) === 1) {
372
+ // v1 records called negative-branch skipping "slg_layers".
373
+ uncond = (p.slg ?? true) ? (p.slg_layers ?? "") : "";
374
+ slgLayers = "";
375
+ slgScale = 0;
376
+ }
377
+ const model = (rec.model_repo || rec.model || "").toLowerCase().includes("sft") ? "sft" : "base";
378
+ setField("negative", p.negative_prompt ?? "");
379
+ setField("prompt", p.prompt ?? "");
380
+ setField("model", model);
381
+ setField("width", p.width);
382
+ setField("height", p.height);
383
+ setField("steps", p.steps);
384
+ setField("cfg", p.cfg);
385
+ setField("shift", p.shift);
386
+ setField("seed", p.seed ?? -1);
387
+ setField("images", p.num_images ?? 1);
388
+ setField("max text", p.max_sequence_length ?? 4096);
389
+ setField("skip start", p.slg_start ?? 0);
390
+ setField("skip stop", p.slg_stop ?? 1);
391
+ setField("skip layers", uncond);
392
+ setField("slg layers", slgLayers);
393
+ setField("slg scale", slgScale);
394
+ }
395
+
396
+ const recordCache = {};
397
+ function fetchRecord(id) {
398
+ if (!recordCache[id]) {
399
+ recordCache[id] = fetch(`${ORIGIN}/${id}/parameters.json`)
400
+ .then(r => (r.ok ? r.json() : null))
401
+ .catch(() => null);
402
+ }
403
+ return recordCache[id];
404
+ }
405
+
406
+ function buildStrip(app) {
407
+ if (document.getElementById('moss-examples')) return true;
408
+ const wrap = document.createElement('div');
409
+ wrap.id = 'moss-examples';
410
+ const title = document.createElement('div');
411
+ title.className = 'mx-title';
412
+ title.textContent = 'Examples · click to restore prompt & settings';
413
+ wrap.appendChild(title);
414
+ const row = document.createElement('div');
415
+ row.className = 'mx-row';
416
+ IDS.forEach((id, i) => {
417
+ const fig = document.createElement('button');
418
+ fig.className = 'mx-item';
419
+ fig.type = 'button';
420
+ fig.title = 'Load this example into the form';
421
+ const img = document.createElement('img');
422
+ img.loading = 'lazy';
423
+ img.alt = `Example ${i + 1}`;
424
+ img.src = `${ORIGIN}/${id}/image.webp`;
425
+ img.onerror = () => fig.remove();
426
+ fig.appendChild(img);
427
+ fig.addEventListener('click', () => {
428
+ fetchRecord(id).then(rec => { if (rec) applyRecord(rec); });
429
+ });
430
+ row.appendChild(fig);
431
+ });
432
+ wrap.appendChild(row);
433
+ app.prepend(wrap);
434
+ return true;
435
+ }
436
+
437
+ if (!document.getElementById('moss-nav')) {
438
+ const nav = document.createElement('nav');
439
+ nav.id = 'moss-nav';
440
+ nav.innerHTML = `
441
+ <span class="moss-brand">MOSS-Image</span>
442
+ <span class="moss-links">
443
+ <a href="https://huggingface.co/OpenMOSS-Team/MOSS-Image-Base" target="_blank" rel="noopener">Base</a>
444
+ <a href="https://huggingface.co/OpenMOSS-Team/MOSS-Image-SFT" target="_blank" rel="noopener">SFT</a>
445
+ <a href="https://huggingface.co/spaces/OpenMOSS-Team/MOSS-Image" target="_blank" rel="noopener">Original Demo</a>
446
+ <a href="https://github.com/OpenMOSS/MOSS-Image" target="_blank" rel="noopener">GitHub &#8599;</a>
447
+ </span>`;
448
+ document.body.prepend(nav);
449
+ }
450
+
451
+ // #workflow-app appears when the frontend mounts; poll briefly for it.
452
+ let tries = 0;
453
+ const timer = setInterval(() => {
454
+ const app = document.getElementById('workflow-app');
455
+ if ((app && buildStrip(app)) || ++tries > 75) clearInterval(timer);
456
+ }, 400);
457
+ }
458
+ """
459
+
460
+ CSS = f"""
461
+ body, .gradio-container {{
462
+ font-family: 'Inter', ui-sans-serif, system-ui, sans-serif !important;
463
+ }}
464
+ .gradio-container {{
465
+ background: linear-gradient(180deg, #eef3fc 0%, #ffffff 460px) !important;
466
+ }}
467
+ .dark .gradio-container {{
468
+ background: linear-gradient(180deg, #0d1526 0%, #0b0f19 460px) !important;
469
+ }}
470
+
471
+ #moss-nav {{
472
+ display: flex;
473
+ align-items: center;
474
+ justify-content: space-between;
475
+ padding: 14px 24px 0;
476
+ max-width: 1200px;
477
+ margin: 0 auto;
478
+ font-size: 14px;
479
+ }}
480
+ #moss-nav .moss-brand {{
481
+ font-weight: 800;
482
+ font-size: 16px;
483
+ letter-spacing: -0.01em;
484
+ background: linear-gradient(90deg, #3b7fe0, #4aa5f2, #7fe6fb);
485
+ -webkit-background-clip: text;
486
+ background-clip: text;
487
+ color: transparent;
488
+ }}
489
+ #moss-nav .moss-links a {{
490
+ color: #475569;
491
+ text-decoration: none;
492
+ font-weight: 500;
493
+ margin-left: 22px;
494
+ }}
495
+ #moss-nav .moss-links a:hover {{ color: #2563eb; }}
496
+ .dark #moss-nav .moss-links a {{ color: #94a3b8; }}
497
+ .dark #moss-nav .moss-links a:hover {{ color: #7fe6fb; }}
498
+
499
+ #workflow-app::before {{
500
+ content: "MOSS-Image: Text-to-Image Generation\\Awith Native Layout Control";
501
+ white-space: pre-wrap;
502
+ display: block;
503
+ text-align: center;
504
+ font-weight: 800;
505
+ font-size: clamp(24px, 4.2vw, 40px);
506
+ line-height: 1.15;
507
+ letter-spacing: -0.02em;
508
+ color: #0b1220;
509
+ max-width: 860px;
510
+ margin: 0 auto 12px;
511
+ padding: 158px 16px 26px;
512
+ background: url("{_LOCKUP_LIGHT}") no-repeat top 26px center / min(480px, 78%) auto;
513
+ }}
514
+ .dark #workflow-app::before {{
515
+ color: #f1f5f9;
516
+ background-image: url("{_LOCKUP_DARK}");
517
+ }}
518
+
519
+ #workflow-app::after {{
520
+ content: "Models by the OpenMOSS Team · MOSS-Image-Base & MOSS-Image-SFT (Apache-2.0) · Rebuilt as a Gradio Workflow";
521
+ display: block;
522
+ text-align: center;
523
+ font-size: 13px;
524
+ color: #64748b;
525
+ padding: 8px 16px 36px;
526
+ }}
527
+ .dark #workflow-app::after {{ color: #94a3b8; }}
528
+
529
+ /* Examples strip (injected by the head script; hot-linked from the original Space). */
530
+ #moss-examples {{
531
+ max-width: 960px;
532
+ margin: 0 auto 18px;
533
+ padding: 0 16px;
534
+ }}
535
+ #moss-examples .mx-title {{
536
+ text-align: center;
537
+ font-size: 13px;
538
+ font-weight: 600;
539
+ letter-spacing: 0.02em;
540
+ color: #64748b;
541
+ margin-bottom: 10px;
542
+ }}
543
+ .dark #moss-examples .mx-title {{ color: #94a3b8; }}
544
+ #moss-examples .mx-row {{
545
+ display: flex;
546
+ gap: 10px;
547
+ overflow-x: auto;
548
+ padding-bottom: 6px;
549
+ scrollbar-width: thin;
550
+ }}
551
+ #moss-examples .mx-item {{
552
+ flex: 0 0 auto;
553
+ height: 128px;
554
+ padding: 0;
555
+ border: 2px solid transparent;
556
+ border-radius: 12px;
557
+ overflow: hidden;
558
+ cursor: pointer;
559
+ background: #e2e8f0;
560
+ transition: border-color 0.15s ease, transform 0.15s ease;
561
+ }}
562
+ #moss-examples .mx-item:hover {{
563
+ border-color: #2563eb;
564
+ transform: translateY(-2px);
565
+ }}
566
+ #moss-examples .mx-item img {{
567
+ height: 128px;
568
+ width: auto;
569
+ display: block;
570
+ }}
571
+
572
+ /* Soften the generated form into landing-page cards. */
573
+ #workflow-app .form, #workflow-app .panel, #workflow-app .block {{
574
+ border-radius: 14px !important;
575
+ }}
576
+ #workflow-app button.primary, #workflow-app button.lg {{
577
+ border-radius: 10px !important;
578
+ }}
579
+ """
580
 
581
  if __name__ == "__main__":
582
+ demo.launch(
583
+ theme=gr.themes.Soft(primary_hue="blue", neutral_hue="slate"),
584
+ css=CSS,
585
+ head=HEAD,
586
+ js=JS,
587
+ )
requirements.txt CHANGED
@@ -6,3 +6,4 @@ safetensors>=0.6
6
  Pillow>=10
7
  spaces>=0.51.3,<0.52
8
  gradio==6.30.0
 
 
6
  Pillow>=10
7
  spaces>=0.51.3,<0.52
8
  gradio==6.30.0
9
+ gradio_client>=1.13
workflow.json ADDED
@@ -0,0 +1,191 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "schema_version": "2",
3
+ "name": "MOSS-Image Text-to-Image",
4
+ "references": [
5
+ {
6
+ "id": "ref_prompt", "label": "Prompt", "role": "reference", "asset_type": "text",
7
+ "inputs": [{"id": "in", "label": "Text", "type": "text"}],
8
+ "outputs": [{"id": "out", "label": "Text", "type": "text"}],
9
+ "x": 60, "y": 60, "width": 280,
10
+ "data": {"out": "A young anime woman, Kazusa from Blue Archive, stands still at the edge of a crowded Tokyo crosswalk outside a busy train station on a damp, overcast dusk. Her short dark hair with soft magenta inner highlights frames her face, cat ears perked slightly beneath her distinctive glowing pink halo. She wears an oversized charcoal wool coat over a beige knit hoodie, a short pleated black skirt, black crew socks, and scuffed loafers, with a canvas tote bag slung over her shoulder. She holds a wired earphone in one hand as if pausing her music, looking slightly sideways with a quiet, unguarded expression while the evening rush hour flows around her.\n\nIn the immediate foreground, a heavily motion-blurred commuter in a dark coat walks right past the lens, partially framing the edge of the shot and giving it the raw, accidental feel of a candid street photograph. Around and behind Kazusa, a bustling crowd of out-of-focus pedestrians moves across the wet asphalt crosswalk under the muted glow of neon department store signs, traffic signals, and concrete office buildings. The image blends Kazusa’s anime appearance seamlessly into a gritty, photorealistic modern city environment, using natural ambient dusk lighting, shallow depth of field, motion blur on the passing crowd, muted cool tones, and a cinematic sense of stillness amid urban chaos."}
11
+ },
12
+ {
13
+ "id": "ref_negative", "label": "Negative Prompt", "role": "reference", "asset_type": "text",
14
+ "inputs": [{"id": "in", "label": "Text", "type": "text"}],
15
+ "outputs": [{"id": "out", "label": "Text", "type": "text"}],
16
+ "x": 60, "y": 250, "width": 280,
17
+ "data": {"out": "watermark, bad anatomy"}
18
+ },
19
+ {
20
+ "id": "ref_model", "label": "Model (base or sft)", "role": "reference", "asset_type": "text",
21
+ "inputs": [{"id": "in", "label": "Text", "type": "text"}],
22
+ "outputs": [{"id": "out", "label": "Text", "type": "text"}],
23
+ "x": 60, "y": 440, "width": 280,
24
+ "data": {"out": "base"}
25
+ },
26
+ {
27
+ "id": "ref_width", "label": "Width", "role": "reference", "asset_type": "number",
28
+ "inputs": [{"id": "in", "label": "Number", "type": "number"}],
29
+ "outputs": [{"id": "out", "label": "Number", "type": "number"}],
30
+ "x": 400, "y": 60, "width": 200,
31
+ "data": {"out": 1184}
32
+ },
33
+ {
34
+ "id": "ref_height", "label": "Height", "role": "reference", "asset_type": "number",
35
+ "inputs": [{"id": "in", "label": "Number", "type": "number"}],
36
+ "outputs": [{"id": "out", "label": "Number", "type": "number"}],
37
+ "x": 400, "y": 210, "width": 200,
38
+ "data": {"out": 1776}
39
+ },
40
+ {
41
+ "id": "ref_steps", "label": "Steps", "role": "reference", "asset_type": "number",
42
+ "inputs": [{"id": "in", "label": "Number", "type": "number"}],
43
+ "outputs": [{"id": "out", "label": "Number", "type": "number"}],
44
+ "x": 400, "y": 360, "width": 200,
45
+ "data": {"out": 50}
46
+ },
47
+ {
48
+ "id": "ref_cfg", "label": "CFG", "role": "reference", "asset_type": "number",
49
+ "inputs": [{"id": "in", "label": "Number", "type": "number"}],
50
+ "outputs": [{"id": "out", "label": "Number", "type": "number"}],
51
+ "x": 400, "y": 510, "width": 200,
52
+ "data": {"out": 3}
53
+ },
54
+ {
55
+ "id": "ref_shift", "label": "Shift", "role": "reference", "asset_type": "number",
56
+ "inputs": [{"id": "in", "label": "Number", "type": "number"}],
57
+ "outputs": [{"id": "out", "label": "Number", "type": "number"}],
58
+ "x": 400, "y": 660, "width": 200,
59
+ "data": {"out": 5}
60
+ },
61
+ {
62
+ "id": "ref_seed", "label": "Seed (-1 = random)", "role": "reference", "asset_type": "number",
63
+ "inputs": [{"id": "in", "label": "Number", "type": "number"}],
64
+ "outputs": [{"id": "out", "label": "Number", "type": "number"}],
65
+ "x": 60, "y": 630, "width": 200,
66
+ "data": {"out": 3537712559}
67
+ },
68
+ {
69
+ "id": "ref_num_images", "label": "Images", "role": "reference", "asset_type": "number",
70
+ "inputs": [{"id": "in", "label": "Number", "type": "number"}],
71
+ "outputs": [{"id": "out", "label": "Number", "type": "number"}],
72
+ "x": 60, "y": 790, "width": 200,
73
+ "data": {"out": 1}
74
+ },
75
+ {
76
+ "id": "ref_max_seq", "label": "Max Text Length", "role": "reference", "asset_type": "number",
77
+ "inputs": [{"id": "in", "label": "Number", "type": "number"}],
78
+ "outputs": [{"id": "out", "label": "Number", "type": "number"}],
79
+ "x": 660, "y": 60, "width": 200,
80
+ "data": {"out": 4096}
81
+ },
82
+ {
83
+ "id": "ref_uncond_skip", "label": "Skip Layers (uncond, empty = off)", "role": "reference", "asset_type": "text",
84
+ "inputs": [{"id": "in", "label": "Text", "type": "text"}],
85
+ "outputs": [{"id": "out", "label": "Text", "type": "text"}],
86
+ "x": 660, "y": 210, "width": 200,
87
+ "data": {"out": "22"}
88
+ },
89
+ {
90
+ "id": "ref_slg_layers", "label": "SLG Layers (empty = off)", "role": "reference", "asset_type": "text",
91
+ "inputs": [{"id": "in", "label": "Text", "type": "text"}],
92
+ "outputs": [{"id": "out", "label": "Text", "type": "text"}],
93
+ "x": 660, "y": 360, "width": 200,
94
+ "data": {"out": ""}
95
+ },
96
+ {
97
+ "id": "ref_slg_scale", "label": "SLG Scale", "role": "reference", "asset_type": "number",
98
+ "inputs": [{"id": "in", "label": "Number", "type": "number"}],
99
+ "outputs": [{"id": "out", "label": "Number", "type": "number"}],
100
+ "x": 660, "y": 510, "width": 200,
101
+ "data": {"out": 0}
102
+ },
103
+ {
104
+ "id": "ref_slg_start", "label": "Skip Start", "role": "reference", "asset_type": "number",
105
+ "inputs": [{"id": "in", "label": "Number", "type": "number"}],
106
+ "outputs": [{"id": "out", "label": "Number", "type": "number"}],
107
+ "x": 660, "y": 660, "width": 200,
108
+ "data": {"out": 0}
109
+ },
110
+ {
111
+ "id": "ref_slg_stop", "label": "Skip Stop", "role": "reference", "asset_type": "number",
112
+ "inputs": [{"id": "in", "label": "Number", "type": "number"}],
113
+ "outputs": [{"id": "out", "label": "Number", "type": "number"}],
114
+ "x": 660, "y": 810, "width": 200,
115
+ "data": {"out": 1}
116
+ },
117
+ {
118
+ "id": "ref_example_image", "label": "Example 007 Result (prefill target)", "role": "reference", "asset_type": "image",
119
+ "inputs": [{"id": "in", "label": "Image", "type": "image"}],
120
+ "outputs": [{"id": "out", "label": "Image", "type": "image"}],
121
+ "x": 1300, "y": 60, "width": 340,
122
+ "data": {"out": {"url": "https://huggingface.co/spaces/OpenMOSS-Team/MOSS-Image/resolve/main/assets/images/007/image.webp", "name": "image.webp", "mime": "image/webp"}}
123
+ }
124
+ ],
125
+ "operators": [
126
+ {
127
+ "id": "op_generate", "label": "MOSS-Image Generate", "role": "operator",
128
+ "kind": "fn", "fn": "generate",
129
+ "inputs": [
130
+ {"id": "prompt", "label": "Prompt", "type": "text", "required": true},
131
+ {"id": "negative_prompt", "label": "Negative Prompt", "type": "text", "required": false},
132
+ {"id": "model", "label": "Model", "type": "text", "required": true},
133
+ {"id": "width", "label": "Width", "type": "number", "required": true},
134
+ {"id": "height", "label": "Height", "type": "number", "required": true},
135
+ {"id": "steps", "label": "Steps", "type": "number", "required": true},
136
+ {"id": "cfg", "label": "CFG", "type": "number", "required": true},
137
+ {"id": "shift", "label": "Shift", "type": "number", "required": true},
138
+ {"id": "seed", "label": "Seed", "type": "number", "required": true},
139
+ {"id": "num_images", "label": "Images", "type": "number", "required": true},
140
+ {"id": "max_sequence_length", "label": "Max Text Length", "type": "number", "required": true},
141
+ {"id": "uncond_skip_layers", "label": "Skip Layers", "type": "text", "required": false},
142
+ {"id": "slg_layers", "label": "SLG Layers", "type": "text", "required": false},
143
+ {"id": "slg_scale", "label": "SLG Scale", "type": "number", "required": true},
144
+ {"id": "slg_start", "label": "Skip Start", "type": "number", "required": true},
145
+ {"id": "slg_stop", "label": "Skip Stop", "type": "number", "required": true}
146
+ ],
147
+ "outputs": [
148
+ {"id": "out_0", "label": "Images", "type": "gallery"},
149
+ {"id": "out_1", "label": "Status", "type": "text"}
150
+ ],
151
+ "x": 940, "y": 380, "width": 280,
152
+ "data": {}
153
+ }
154
+ ],
155
+ "subjects": [
156
+ {
157
+ "id": "sub_gallery", "label": "Generated Images", "role": "subject", "asset_type": "gallery",
158
+ "inputs": [{"id": "in", "label": "Images", "type": "gallery"}],
159
+ "outputs": [{"id": "out", "label": "Images", "type": "gallery"}],
160
+ "x": 1300, "y": 380, "width": 340,
161
+ "data": {}
162
+ },
163
+ {
164
+ "id": "sub_status", "label": "Status", "role": "subject", "asset_type": "text",
165
+ "inputs": [{"id": "in", "label": "Status", "type": "text"}],
166
+ "outputs": [{"id": "out", "label": "Status", "type": "text"}],
167
+ "x": 1300, "y": 620, "width": 340,
168
+ "data": {}
169
+ }
170
+ ],
171
+ "edges": [
172
+ {"id": "e_prompt", "from_node_id": "ref_prompt", "from_port_id": "out", "to_node_id": "op_generate", "to_port_id": "prompt", "type": "text"},
173
+ {"id": "e_negative", "from_node_id": "ref_negative", "from_port_id": "out", "to_node_id": "op_generate", "to_port_id": "negative_prompt", "type": "text"},
174
+ {"id": "e_model", "from_node_id": "ref_model", "from_port_id": "out", "to_node_id": "op_generate", "to_port_id": "model", "type": "text"},
175
+ {"id": "e_width", "from_node_id": "ref_width", "from_port_id": "out", "to_node_id": "op_generate", "to_port_id": "width", "type": "number"},
176
+ {"id": "e_height", "from_node_id": "ref_height", "from_port_id": "out", "to_node_id": "op_generate", "to_port_id": "height", "type": "number"},
177
+ {"id": "e_steps", "from_node_id": "ref_steps", "from_port_id": "out", "to_node_id": "op_generate", "to_port_id": "steps", "type": "number"},
178
+ {"id": "e_cfg", "from_node_id": "ref_cfg", "from_port_id": "out", "to_node_id": "op_generate", "to_port_id": "cfg", "type": "number"},
179
+ {"id": "e_shift", "from_node_id": "ref_shift", "from_port_id": "out", "to_node_id": "op_generate", "to_port_id": "shift", "type": "number"},
180
+ {"id": "e_seed", "from_node_id": "ref_seed", "from_port_id": "out", "to_node_id": "op_generate", "to_port_id": "seed", "type": "number"},
181
+ {"id": "e_num_images", "from_node_id": "ref_num_images", "from_port_id": "out", "to_node_id": "op_generate", "to_port_id": "num_images", "type": "number"},
182
+ {"id": "e_max_seq", "from_node_id": "ref_max_seq", "from_port_id": "out", "to_node_id": "op_generate", "to_port_id": "max_sequence_length", "type": "number"},
183
+ {"id": "e_uncond_skip", "from_node_id": "ref_uncond_skip", "from_port_id": "out", "to_node_id": "op_generate", "to_port_id": "uncond_skip_layers", "type": "text"},
184
+ {"id": "e_slg_layers", "from_node_id": "ref_slg_layers", "from_port_id": "out", "to_node_id": "op_generate", "to_port_id": "slg_layers", "type": "text"},
185
+ {"id": "e_slg_scale", "from_node_id": "ref_slg_scale", "from_port_id": "out", "to_node_id": "op_generate", "to_port_id": "slg_scale", "type": "number"},
186
+ {"id": "e_slg_start", "from_node_id": "ref_slg_start", "from_port_id": "out", "to_node_id": "op_generate", "to_port_id": "slg_start", "type": "number"},
187
+ {"id": "e_slg_stop", "from_node_id": "ref_slg_stop", "from_port_id": "out", "to_node_id": "op_generate", "to_port_id": "slg_stop", "type": "number"},
188
+ {"id": "e_gallery", "from_node_id": "op_generate", "from_port_id": "out_0", "to_node_id": "sub_gallery", "to_port_id": "in", "type": "gallery"},
189
+ {"id": "e_status", "from_node_id": "op_generate", "from_port_id": "out_1", "to_node_id": "sub_status", "to_port_id": "in", "type": "text"}
190
+ ]
191
+ }