File size: 9,685 Bytes
183468a
a6541eb
183468a
cda4df7
07622a9
0b1be36
8ed828d
25b40da
07622a9
f8dd3ca
 
 
cda4df7
a6541eb
 
 
 
 
 
 
 
 
 
 
 
cda4df7
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
f8dd3ca
cda4df7
07622a9
f9d0bb5
07622a9
392b2d6
07622a9
cda4df7
392b2d6
f8dd3ca
 
 
 
25b40da
8ed828d
 
25b40da
 
 
 
906aee4
 
25b40da
 
906aee4
 
 
 
25b40da
 
 
183468a
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
8ed828d
0b1be36
8ed828d
 
 
 
 
183468a
 
 
 
7d20112
 
 
 
 
e6fd579
7d20112
 
 
8ed828d
cda4df7
8ed828d
cda4df7
 
8ed828d
 
cda4df7
 
 
 
 
 
25b40da
8ed828d
 
 
 
f8dd3ca
8ed828d
 
 
 
 
 
392b2d6
8ed828d
07622a9
 
b79a512
0b1be36
b79a512
 
 
 
 
e6fd579
b79a512
 
 
 
 
 
 
 
 
cda4df7
b79a512
 
cda4df7
b79a512
cda4df7
 
 
 
 
 
b79a512
 
 
 
 
 
f8dd3ca
b79a512
 
 
 
f8dd3ca
b79a512
f8dd3ca
 
664e1fd
0b1be36
664e1fd
 
 
 
 
 
 
 
 
 
 
 
e6fd579
664e1fd
 
 
 
 
 
 
 
 
 
 
 
 
 
 
cda4df7
664e1fd
 
cda4df7
664e1fd
cda4df7
 
 
 
 
 
664e1fd
 
 
 
 
 
f8dd3ca
664e1fd
 
 
 
 
 
 
 
 
 
f8dd3ca
664e1fd
f8dd3ca
 
2d323f9
 
 
f8dd3ca
 
 
8ed828d
f8dd3ca
 
 
 
 
 
 
25b40da
f8dd3ca
 
80974ab
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
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)
@spaces.GPU
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")
@spaces.GPU
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")
@spaces.GPU
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()