Spaces:
Running on Zero
Running on Zero
| """The ``gradio.Server`` app: the Blocks UI mounted at ``/ui`` + streaming ``@app.api`` SSE endpoints. | |
| One process serves both the human control panel (``build_blocks``) and a programmatic API over the exact | |
| same core, so a future custom frontend (``@gradio/client`` → ``app.predict("/stream_page", …)``) needs no | |
| new logic. ``@app.get("/")`` redirects to the UI for now and is the slot that custom frontend will take. | |
| GPU entry points are wrapped in :func:`gpu_task` so a ZeroGPU Space allocates a GPU for each stream. | |
| """ | |
| from collections.abc import Iterator | |
| import gradio as gr | |
| from PIL import Image | |
| from diffu_studio.checkpoints import CheckpointMismatch | |
| from diffu_studio.configs import SampleSettings, build_gen_config, default_configs, list_templates | |
| from diffu_studio.core import list_checkpoints | |
| from diffu_studio.device import gpu_task, torch_dtype | |
| from diffu_studio.engine import StudioEngine | |
| from diffu_studio.line import stream_line, style_from_pil | |
| from diffu_studio.page import PageRequest, stream_page | |
| from diffu_studio.ui import build_blocks | |
| def build_server( | |
| engine: StudioEngine, | |
| *, | |
| gallery_paths: list[str] | None, | |
| default_ckpt: str | None, | |
| ) -> gr.Server: | |
| """Build the ``gradio.Server``: SSE endpoints over the core + the Blocks UI mounted at ``/ui``.""" | |
| app = gr.Server(title="Diffu Studio", description="Single-line + whole-page handwriting synthesis.") | |
| def _style(style_path: str): | |
| return style_from_pil( | |
| Image.open(style_path).convert("RGB"), engine.device.torch_device, torch_dtype(engine.device) | |
| ) | |
| def list_checkpoints_api() -> list[dict]: | |
| return [c.model_dump() for c in list_checkpoints(engine.ckpt_root)] | |
| def list_templates_api() -> list[str]: | |
| return list_templates() | |
| def default_configs_api() -> dict: | |
| return default_configs() | |
| def stream_line_api( | |
| text: str, | |
| style_path: str, | |
| checkpoint: str, | |
| steps: int = 24, | |
| cfg_scale: float = 5.0, | |
| auto_width: bool = True, | |
| ) -> Iterator[tuple[str, Image.Image | None]]: | |
| settings = SampleSettings(steps=steps, cfg_scale=cfg_scale, auto_width=auto_width) | |
| try: | |
| loaded = engine.model(checkpoint) | |
| except CheckpointMismatch as exc: | |
| yield f"⚠ {exc}", None | |
| return | |
| for step in stream_line(loaded, _style(style_path), text, settings, recognizer=engine.recognizer): | |
| yield (step.read or step.status), step.image | |
| def stream_page_api( | |
| text: str, | |
| style_path: str, | |
| checkpoint: str, | |
| template: str = "single_column", | |
| steps: int = 24, | |
| cfg_scale: float = 5.0, | |
| seed: int = 0, | |
| ) -> Iterator[tuple[str, Image.Image | None, dict | None]]: | |
| gen_cfg = build_gen_config(template=template, steps=steps, cfg_scale=cfg_scale) | |
| request = PageRequest( | |
| paragraphs=[p for p in text.split("\n") if p.strip()] or [text], | |
| gen=gen_cfg, | |
| settings=SampleSettings(steps=steps, cfg_scale=cfg_scale), | |
| seed=seed, | |
| animate=engine.device.interactive, | |
| ) | |
| try: | |
| loaded = engine.model(checkpoint) | |
| except CheckpointMismatch as exc: | |
| yield f"⚠ {exc}", None, None | |
| return | |
| for step in stream_page(loaded, _style(style_path), request): | |
| yield step.status, step.page, step.labels | |
| blocks = build_blocks(engine, gallery_paths=gallery_paths, default_ckpt=default_ckpt) | |
| gr.mount_gradio_app(app, blocks, path="/ui") | |
| def index(): # reserved for the future custom frontend; redirect to the mounted UI for now | |
| from starlette.responses import RedirectResponse | |
| return RedirectResponse("/ui") | |
| return app | |