Spaces:
Running
Running
| import base64 | |
| import os | |
| import tempfile | |
| import urllib.request | |
| import gradio as gr | |
| import spaces | |
| from gradio_client import Client, handle_file | |
| from daggr import FnNode, GradioNode, Graph | |
| # ==================== FREE TOOLS COLLECTION ==================== | |
| # These tools are free to use and don't consume credits | |
| def _hf_token(): | |
| """HF token so ZeroGPU quota is used (not 'unlogged user'). Prefer env, then huggingface_hub.""" | |
| token = os.environ.get("HF_TOKEN") | |
| if token: | |
| return token | |
| try: | |
| from huggingface_hub import get_token | |
| return get_token() | |
| except Exception: | |
| return None | |
| def _url_to_path(url): | |
| """Download image from URL to a temp file; Daggr needs file paths to display images.""" | |
| if not url or not isinstance(url, str) or not url.startswith("http"): | |
| return url | |
| try: | |
| ext = "png" | |
| if ".jpg" in url or ".jpeg" in url: | |
| ext = "jpg" | |
| elif ".webp" in url: | |
| ext = "webp" | |
| f = tempfile.NamedTemporaryFile(suffix=f".{ext}", delete=False) | |
| req = urllib.request.Request(url, headers={"User-Agent": "Gradio-Daggr/1.0"}) | |
| with urllib.request.urlopen(req, timeout=60) as r: | |
| f.write(r.read()) | |
| f.close() | |
| return f.name | |
| except Exception: | |
| return url | |
| # 1. Background Removal | |
| # API may return (original, result); we only need the result for "final_image". | |
| bg_remover = GradioNode( | |
| "hf-applications/background-removal", | |
| api_name="/image", | |
| run_locally=False, | |
| inputs={"image": gr.Image()}, | |
| postprocess=lambda *vals: vals[-1] if vals else None, | |
| outputs={ | |
| "final_image": gr.Image(label="Background Removed"), | |
| }, | |
| ) | |
| def _image_to_filepath(image): | |
| """Convert image input (list, dict, or path) to a single filepath string.""" | |
| if image is None: | |
| return None | |
| if isinstance(image, str): | |
| return image | |
| if isinstance(image, dict) and "path" in image and isinstance(image["path"], str): | |
| return image["path"] | |
| if isinstance(image, (list, tuple)) and len(image) > 0: | |
| first = image[0] | |
| if isinstance(first, str): | |
| return first | |
| if isinstance(first, dict) and "path" in first and isinstance(first["path"], str): | |
| return first["path"] | |
| return None | |
| def _path_for_api(path): | |
| """Convert path (file path, URL, or data URL) to a path handle_file can use. Data URLs become temp files.""" | |
| if not path or not isinstance(path, str): | |
| return None | |
| if path.startswith("data:"): | |
| # data:image/jpeg;base64,... -> decode and write to temp file (handle_file can't use data URL as filename) | |
| try: | |
| header, b64 = path.split(",", 1) | |
| ext = "png" | |
| if "jpeg" in header or "jpg" in header: | |
| ext = "jpg" | |
| elif "webp" in header: | |
| ext = "webp" | |
| data = base64.b64decode(b64) | |
| f = tempfile.NamedTemporaryFile(suffix=f".{ext}", delete=False) | |
| f.write(data) | |
| f.close() | |
| return f.name | |
| except Exception as e: | |
| raise RuntimeError(f"Upscaler: could not decode data URL: {e}") from e | |
| return path | |
| # 2. Image Upscaler (FnNode so we pass a single filepath string; GradioNode was receiving a list from Daggr) | |
| def run_upscaler(image, model_selection="4xBHI_dat2_real"): | |
| """Call Phips/Upscaler API with a single filepath string. Returns path to upscaled image.""" | |
| path = _image_to_filepath(image) | |
| if not path: | |
| return None | |
| path = _path_for_api(path) | |
| if not path: | |
| return None | |
| # Phips/Upscaler runs on a different server - send file content via handle_file so it can read the image. | |
| try: | |
| image_arg = handle_file(path) | |
| except Exception as e: | |
| raise RuntimeError(f"Upscaler: could not load image from path: {e}") from e | |
| try: | |
| client = Client("Phips/Upscaler", token=_hf_token()) | |
| result = client.predict(image_arg, model_selection, api_name="/upscale_image") | |
| except Exception as e: | |
| raise RuntimeError(f"Upscaler API error: {e}") from e | |
| # Returns tuple of 2: [comparison, filepath]; we need the upscaled image path (second element) | |
| out = None | |
| if result and len(result) >= 2 and result[1]: | |
| out = result[1] | |
| elif result and len(result) >= 1 and result[0]: | |
| r0 = result[0] | |
| if isinstance(r0, (list, tuple)) and len(r0) >= 2 and r0[1]: | |
| out = r0[1] | |
| elif isinstance(r0, str): | |
| out = r0 | |
| if out and isinstance(out, str) and out.startswith("http"): | |
| return _url_to_path(out) | |
| return out | |
| upscaler = FnNode( | |
| run_upscaler, | |
| name="Upscaler", | |
| inputs={ | |
| "image": gr.Image(label="Input Image"), | |
| "model_selection": gr.Dropdown( | |
| label="Model", | |
| choices=["4xBHI_dat2_real", "4xNomos8kDAT", "4xHFA2k", "2xEvangelion_dat2"], | |
| value="4xBHI_dat2_real", | |
| ), | |
| }, | |
| outputs={"upscaled_image": gr.Image(label="Upscaled Image")}, | |
| ) | |
| # 3. Z-Image Turbo (FnNode so prompt is sent as prompt, not mistaken for a file path — avoids "File name too long") | |
| def run_z_image_turbo(prompt, height=1024, width=1024, seed=42): | |
| """Call Z-Image-Turbo API with prompt as string. Returns image URL or path.""" | |
| if not prompt or not isinstance(prompt, str) or not prompt.strip(): | |
| return None | |
| try: | |
| client = Client("hf-applications/Z-Image-Turbo", token=_hf_token()) | |
| result = client.predict( | |
| prompt=prompt.strip(), | |
| height=float(height), | |
| width=float(width), | |
| seed=int(seed), | |
| api_name="/generate_image", | |
| ) | |
| except Exception as e: | |
| raise RuntimeError(f"Z-Image-Turbo API error: {e}") from e | |
| # Returns tuple of 2: [0] dict(path=..., url=..., ...), [1] seed used. Daggr needs local path. | |
| if result and len(result) >= 1 and result[0]: | |
| img = result[0] | |
| out = None | |
| if isinstance(img, dict): | |
| out = img.get("url") or img.get("path") | |
| elif isinstance(img, str): | |
| out = img | |
| if out and isinstance(out, str) and out.startswith("http"): | |
| return _url_to_path(out) | |
| return out | |
| return None | |
| z_image_turbo = FnNode( | |
| run_z_image_turbo, | |
| name="Z-Image-Turbo", | |
| inputs={ | |
| "prompt": gr.Textbox(label="Prompt", lines=3), | |
| "height": gr.Slider(512, 1024, value=1024, step=64, label="Height"), | |
| "width": gr.Slider(512, 1024, value=1024, step=64, label="Width"), | |
| "seed": gr.Number(value=42, label="Seed", precision=0), | |
| }, | |
| outputs={"generated_image": gr.Image(label="Generated Image")}, | |
| ) | |
| # 4. FLUX.2 Klein 9B (FnNode so prompt is sent as prompt, not mistaken for filename — avoids "File name too long") | |
| def run_flux_klein( | |
| prompt, | |
| mode_choice="Distilled (4 steps)", | |
| seed=0, | |
| randomize_seed=True, | |
| width=1024, | |
| height=1024, | |
| ): | |
| """Call FLUX.2-klein-9B API with prompt as string. Returns image URL or path.""" | |
| if not prompt or not isinstance(prompt, str) or not prompt.strip(): | |
| return None | |
| try: | |
| client = Client("black-forest-labs/FLUX.2-klein-9B", token=_hf_token()) | |
| result = client.predict( | |
| prompt=prompt.strip(), | |
| input_images=[], | |
| mode_choice=mode_choice, | |
| seed=float(seed), | |
| randomize_seed=bool(randomize_seed), | |
| width=float(width), | |
| height=float(height), | |
| num_inference_steps=4.0 if "Distilled" in mode_choice else 50.0, | |
| guidance_scale=1.0 if "Distilled" in mode_choice else 3.5, | |
| prompt_upsampling=False, | |
| api_name="/generate", | |
| ) | |
| except Exception as e: | |
| raise RuntimeError(f"FLUX.2-klein-9B API error: {e}") from e | |
| # Returns tuple of 2: [0] dict(path=..., url=...), [1] seed used. Daggr needs local path. | |
| if result and len(result) >= 1 and result[0]: | |
| img = result[0] | |
| out = None | |
| if isinstance(img, dict): | |
| out = img.get("url") or img.get("path") | |
| elif isinstance(img, str): | |
| out = img | |
| if out and isinstance(out, str) and out.startswith("http"): | |
| return _url_to_path(out) | |
| return out | |
| return None | |
| flux_klein = FnNode( | |
| run_flux_klein, | |
| name="FLUX.2-klein-9B", | |
| inputs={ | |
| "prompt": gr.Textbox(label="Prompt", lines=3), | |
| "mode_choice": gr.Radio( | |
| choices=["Distilled (4 steps)", "Base (50 steps)"], | |
| value="Distilled (4 steps)", | |
| label="Mode", | |
| ), | |
| "seed": gr.Number(value=0, label="Seed", precision=0), | |
| "randomize_seed": gr.Checkbox(value=True, label="Randomize seed"), | |
| "width": gr.Slider(512, 1024, value=1024, step=64, label="Width"), | |
| "height": gr.Slider(512, 1024, value=1024, step=64, label="Height"), | |
| }, | |
| outputs={"result": gr.Image(label="Generated Image")}, | |
| ) | |
| # Note: hysts-daggr/daggr-text-to-image-to-3d is a Daggr space itself, | |
| # so it doesn't expose a standard Gradio API and cannot be connected via GradioNode. | |
| # Users can access it directly at: https://huggingface.co/spaces/hysts-daggr/daggr-text-to-image-to-3d | |
| # ==================== GRAPH SETUP ==================== | |
| # Create the workflow graph with all free tools | |
| # Upscaler is an FnNode that calls Phips/Upscaler with a single path string (no list). | |
| graph = Graph( | |
| name="Imageat Workflow - Free Tools", | |
| nodes=[ | |
| bg_remover, | |
| upscaler, | |
| z_image_turbo, | |
| flux_klein, | |
| ], | |
| ) | |
| # Launch the Space | |
| graph.launch() | |