stanley-00 commited on
Commit
ef2120b
·
1 Parent(s): 33d116c

Change to playground

Browse files
Files changed (3) hide show
  1. README.md +15 -3
  2. app.py +1221 -635
  3. requirements.txt +7 -1
README.md CHANGED
@@ -1,6 +1,6 @@
1
  ---
2
- title: Z Image Turbo
3
- emoji: 🖼️
4
  colorFrom: yellow
5
  colorTo: yellow
6
  sdk: gradio
@@ -9,4 +9,16 @@ app_file: app.py
9
  pinned: true
10
  ---
11
 
12
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  ---
2
+ title: HF Model Playground
3
+ emoji: 🤗
4
  colorFrom: yellow
5
  colorTo: yellow
6
  sdk: gradio
 
9
  pinned: true
10
  ---
11
 
12
+ # 🤗 HuggingFace Model Playground
13
+
14
+ Enter any HuggingFace model ID — auto-detects the task and adapts the UI.
15
+
16
+ **Supported tasks:**
17
+ - 💬 Text Generation (chat, completion, translate via prompt)
18
+ - 📝 Summarization
19
+ - 🌐 Translation
20
+ - 🎨 Image Generation (with live step preview)
21
+ - 🔊 Text-to-Speech
22
+ - 🧠 Thinking models (DeepSeek-R1, QwQ)
23
+
24
+ **Quick-select presets** for popular small models in each category.
app.py CHANGED
@@ -1,107 +1,161 @@
1
- import spaces
2
- import queue
3
- import threading
4
- import asyncio
5
- import io
6
- import json
7
- import base64
8
- import torch
 
9
  import gradio as gr
10
- from diffusers import DiffusionPipeline
11
  import os
12
- from typing import List
13
-
14
- # Load the pipeline once at startup (CPU-optimized: float32, attention slicing)
15
- print("Loading Z-Image-Turbo pipeline...")
16
- pipe = DiffusionPipeline.from_pretrained(
17
- "Tongyi-MAI/Z-Image-Turbo",
18
- torch_dtype=torch.float32,
19
- low_cpu_mem_usage=True,
 
 
 
 
 
 
 
20
  )
21
- device = "cpu"
22
- pipe.to(device)
23
- pipe.enable_attention_slicing()
24
- print("Pipeline loaded!")
25
 
26
- # Global state for cancellation
27
- _generation_lock = threading.Lock()
28
- _current_interrupt = None
 
 
 
 
 
 
 
 
 
 
 
29
 
 
30
 
31
- def decode_latents(latents):
32
- """Decode latents to a PIL image using the same recipe as the pipeline."""
33
- l = latents.to(pipe.vae.dtype)
34
- scaling_factor = pipe.vae.config.scaling_factor
35
- shift_factor = getattr(pipe.vae.config, "shift_factor", 0)
36
- l = (l / scaling_factor) + shift_factor
37
- with torch.no_grad():
38
- image = pipe.vae.decode(l, return_dict=False)[0]
39
- return pipe.image_processor.postprocess(image, output_type="pil")[0]
40
-
41
-
42
- def generate_image(prompt, height, width, num_inference_steps, seed, randomize_seed, progress=gr.Progress(track_tqdm=True)):
43
- """Generate an image from the given prompt, yielding a preview after each denoising step.
44
- Cancels any previous generation automatically.
45
- """
46
- global _current_interrupt
47
-
48
- # Signal cancellation of previous generation (if any)
49
- with _generation_lock:
50
- if _current_interrupt is not None:
51
- _current_interrupt.set() # Signal previous thread to stop
52
- pipe._interrupt = True # Diffusers pipeline respects this flag
53
- # Create new interrupt event for this generation
54
- _current_interrupt = threading.Event()
55
- pipe._interrupt = False
56
 
57
- interrupt_event = _current_interrupt
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
58
 
59
- if randomize_seed:
60
- seed = torch.randint(0, 2**32 - 1, (1,)).item()
 
 
 
 
 
 
61
 
62
- generator = torch.Generator(device).manual_seed(int(seed))
63
 
64
- previews = queue.Queue()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
65
  step_counter = {"i": 0}
66
 
67
- def callback_on_step_end(pipe, i, t, callback_kwargs):
68
- # Check for cancellation at each step
69
- if interrupt_event.is_set():
70
- raise InterruptedError("Generation cancelled by user")
 
 
 
 
 
 
71
  step_counter["i"] += 1
72
- latents = callback_kwargs["latents"]
73
- img = decode_latents(latents)
74
- previews.put((img, step_counter["i"]))
75
- return callback_kwargs
 
 
76
 
77
  result = {}
78
- thread_exc = {}
79
 
80
  def run():
81
  try:
82
- out = pipe(
83
- prompt=prompt,
84
- height=int(height),
85
- width=int(width),
86
- num_inference_steps=int(num_inference_steps),
87
- guidance_scale=0.0,
88
- generator=generator,
89
- output_type="latent",
90
- callback_on_step_end=callback_on_step_end,
91
- callback_on_step_end_tensor_inputs=["latents"],
92
- )
93
- if not interrupt_event.is_set():
94
- # Keep batch dim: out.images is [1, C, H, W], not [0] which gives [C, H, W]
95
- final = decode_latents(out.images)
96
- result["final"] = final
97
- previews.put(None) # sentinel: done
98
  except Exception as e:
99
- thread_exc["exc"] = e
100
  previews.put(None)
101
 
102
- thread = threading.Thread(target=run)
103
- thread.start()
104
-
105
  last = None
106
  while True:
107
  item = previews.get()
@@ -109,587 +163,1119 @@ def generate_image(prompt, height, width, num_inference_steps, seed, randomize_s
109
  break
110
  img, step = item
111
  last = img
112
- yield img, seed, f"Step {step} / {int(num_inference_steps)}"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
113
 
114
- thread.join()
115
 
116
- # Re-raise any exception from the thread
117
- if "exc" in thread_exc:
118
- raise thread_exc["exc"]
 
 
 
 
 
119
 
120
- if interrupt_event.is_set():
121
- # Generation was cancelled - don't yield final, just return last preview
 
122
  yield last, seed, "Cancelled"
123
  else:
124
- yield result["final"], seed, f"Done ({int(num_inference_steps)} steps)"
125
-
126
-
127
- # ---------------------------------------------------------------------------
128
- # Hidden "conflict_check" API function (wired to a hidden .click handler below)
129
- # ---------------------------------------------------------------------------
130
- def _resolve_file_bytes(entry):
131
- """Turn a gr.File value into (filename, raw_bytes).
132
-
133
- Handles all the ways a file can arrive:
134
- * a plain filepath string (uploaded via /upload)
135
- * a FileData dict with a `path` (uploaded)
136
- * a FileData dict with an inline base64 `data:` URI (used when the
137
- caller cannot reach /upload, e.g. behind a proxy)
138
- * a bare `data:` URI string
139
- """
140
- if isinstance(entry, str):
141
- if entry.startswith("data:"):
142
- _, _, payload = entry.partition(",")
143
- return "upload", base64.b64decode(payload)
144
- with open(entry, "rb") as fh:
145
- return os.path.basename(entry), fh.read()
146
- if isinstance(entry, dict):
147
- name = entry.get("orig_name") or entry.get("name") or "upload"
148
- path = entry.get("path")
149
- if path:
150
- with open(path, "rb") as fh:
151
- return os.path.basename(path), fh.read()
152
- url = entry.get("url") or ""
153
- if url.startswith("data:"):
154
- _, _, payload = url.partition(",")
155
- return name, base64.b64decode(payload)
156
- if url:
157
- import urllib.request as _ur
158
- with _ur.urlopen(url, timeout=30) as resp:
159
- return name, resp.read()
160
- raise ValueError(f"Unsupported file value: {entry!r}")
161
-
162
-
163
- def _conflict_check_fn(files, config):
164
- """Hidden Gradio API (api_name="conflict_check"): compare uploaded files
165
- for conflicts. `files` is the hidden gr.JSON input (list of FileData dicts,
166
- each typically carrying an inline base64 `data:` URI, or a single dict).
167
- `config` is a gr.JSON dict of generation settings (model id + params)."""
168
- if not files:
169
- return {
170
- "conflicts_found": False,
171
- "summary": "No files were uploaded.",
172
- "conflicts": [],
173
- }
174
- if isinstance(files, (str, dict)):
175
- files = [files]
176
- raw_docs = []
177
- for entry in files:
178
  try:
179
- fname, content_bytes = _resolve_file_bytes(entry)
180
  except Exception as e:
181
- raw_docs.append({"filename": str(entry), "content": f"[Could not read file: {e}]"})
182
- continue
183
- raw_docs.append({
184
- "filename": fname,
185
- "content": _read_upload(fname, content_bytes),
186
- })
187
- try:
188
- if config and config.get("gpu"):
189
- return _analyze_conflicts_gpu(raw_docs, config)
190
- return _analyze_conflicts_cpu(raw_docs, config)
191
- except Exception as e:
192
- import traceback
193
- tb = traceback.format_exc()
194
- return {
195
- "conflicts_found": False,
196
- "summary": f"Conflict analysis failed: {type(e).__name__}: {e}",
197
- "conflicts": [],
198
- "error": tb[-3000:],
199
- }
200
-
201
-
202
- # Example prompts
203
- examples = [
204
- ["Young Chinese woman in red Hanfu, intricate embroidery. Impeccable makeup, red floral forehead pattern. Elaborate high bun, golden phoenix headdress, red flowers, beads. Holds round folding fan with lady, trees, bird. Neon lightning-bolt lamp, bright yellow glow, above extended left palm. Soft-lit outdoor night background, silhouetted tiered pagoda, blurred colorful distant lights."],
205
- ["A majestic dragon soaring through clouds at sunset, scales shimmering with iridescent colors, detailed fantasy art style"],
206
- ["Cozy coffee shop interior, warm lighting, rain on windows, plants on shelves, vintage aesthetic, photorealistic"],
207
- ["Astronaut riding a horse on Mars, cinematic lighting, sci-fi concept art, highly detailed"],
208
- ["Portrait of a wise old wizard with a long white beard, holding a glowing crystal staff, magical forest background"],
209
- ]
210
 
211
- # Custom theme with modern aesthetics (Gradio 6)
212
- custom_theme = gr.themes.Soft(
213
- primary_hue="yellow",
214
- secondary_hue="amber",
215
- neutral_hue="slate",
216
- font=gr.themes.GoogleFont("Inter"),
217
- text_size="lg",
218
- spacing_size="md",
219
- radius_size="lg"
220
- ).set(
221
- button_primary_background_fill="*primary_500",
222
- button_primary_background_fill_hover="*primary_600",
223
- block_title_text_weight="600",
224
- )
225
 
226
- # Build the Gradio interface
227
- with gr.Blocks(fill_height=True) as demo:
228
- # Header
229
- gr.Markdown(
230
- """
231
- # 🎨 Z-Image-Turbo
232
- **Ultra-fast AI image generation** • CPU mode • 256×256 default • live step-by-step preview
233
- """,
234
- elem_classes="header-text"
235
- )
236
 
237
- with gr.Row(equal_height=False):
238
- # Left column - Input controls
239
- with gr.Column(scale=1, min_width=320):
240
- prompt = gr.Textbox(
241
- label="✨ Your Prompt",
242
- placeholder="Describe the image you want to create...",
243
- lines=5,
244
- max_lines=10,
245
- autofocus=True,
246
- )
247
-
248
- with gr.Accordion("⚙️ Advanced Settings", open=False):
249
- with gr.Row():
250
- height = gr.Slider(
251
- minimum=32,
252
- maximum=1024,
253
- value=256,
254
- step=32,
255
- label="Height",
256
- info="Image height in pixels"
257
- )
258
- width = gr.Slider(
259
- minimum=32,
260
- maximum=1024,
261
- value=256,
262
- step=32,
263
- label="Width",
264
- info="Image width in pixels"
265
- )
266
-
267
- num_inference_steps = gr.Slider(
268
- minimum=1,
269
- maximum=20,
270
- value=9,
271
- step=1,
272
- label="Inference Steps",
273
- info="9 steps = 8 DiT forwards (recommended)"
274
- )
275
 
276
- with gr.Row():
277
- randomize_seed = gr.Checkbox(
278
- label="🎲 Random Seed",
279
- value=True,
280
- )
281
- seed = gr.Number(
282
- label="Seed",
283
- value=42,
284
- precision=0,
285
- visible=False,
286
- )
287
-
288
- def toggle_seed(randomize):
289
- return gr.Number(visible=not randomize)
290
-
291
- randomize_seed.change(
292
- toggle_seed,
293
- inputs=[randomize_seed],
294
- outputs=[seed]
295
- )
296
 
297
- generate_btn = gr.Button(
298
- "🚀 Generate Image",
299
- variant="primary",
300
- size="lg",
301
- scale=1
302
- )
303
-
304
- # Example prompts
305
- gr.Examples(
306
- examples=examples,
307
- inputs=[prompt],
308
- label="💡 Try these prompts",
309
- examples_per_page=5,
310
- )
311
-
312
- # Right column - Output
313
- with gr.Column(scale=1, min_width=320):
314
- output_image = gr.Image(
315
- label="Generated Image",
316
- type="pil",
317
- format="png",
318
- show_label=False,
319
- height=512,
320
- buttons=["download", "share"],
321
- )
322
-
323
- status = gr.Textbox(
324
- label="🔄 Progress",
325
- value="Idle",
326
- interactive=False,
327
- )
328
-
329
- used_seed = gr.Number(
330
- label="🎲 Seed Used",
331
- interactive=False,
332
- container=True,
333
- )
334
-
335
- # Footer credits
336
- gr.Markdown(
337
- """
338
- ---
339
- <div style="text-align: center; opacity: 0.7; font-size: 0.9em; margin-top: 1rem;">
340
- <strong>Model:</strong> <a href="https://huggingface.co/Tongyi-MAI/Z-Image-Turbo" target="_blank">Tongyi-MAI/Z-Image-Turbo</a> (Apache 2.0 License) •
341
- <strong>Demo by:</strong> <a href="https://x.com/realmrfakename" target="_blank">@mrfakename</a> •
342
- <strong>Redesign by:</strong> AnyCoder •
343
- <strong>CPU + step-preview mod</strong>
344
- </div>
345
- """,
346
- elem_classes="footer-text"
347
- )
348
 
349
- # Connect the generate button - new clicks cancel previous generation
350
- generate_event = generate_btn.click(
351
- fn=generate_image,
352
- inputs=[prompt, height, width, num_inference_steps, seed, randomize_seed],
353
- outputs=[output_image, used_seed, status],
354
- )
 
355
 
356
- # Also allow generating by pressing Enter in the prompt box - cancels previous
357
- prompt_event = prompt.submit(
358
- fn=generate_image,
359
- inputs=[prompt, height, width, num_inference_steps, seed, randomize_seed],
360
- outputs=[output_image, used_seed, status],
361
- )
 
 
362
 
363
- # Cross-cancel: button click cancels prompt submit and vice versa
364
- generate_event.cancels = [prompt_event]
365
- prompt_event.cancels = [generate_event]
366
-
367
- # Hidden conflict-check API (no visible UI). The components live in the
368
- # layout but are visually hidden via CSS (`.hidden_conflict`); the button's
369
- # .click handler is exposed as the /gradio_api/conflict_check endpoint.
370
- # Input is a gr.JSON that carries FileData dicts (inline base64 `data:` URIs)
371
- # so callers behind a proxy that blocks /upload can still send files.
372
- conflict_files = gr.JSON(label="files", elem_classes="hidden_conflict")
373
- conflict_cfg = gr.JSON(label="config", elem_classes="hidden_conflict", value={})
374
- conflict_out = gr.JSON(label="result", elem_classes="hidden_conflict")
375
- conflict_btn = gr.Button("Conflict check", elem_classes="hidden_conflict")
376
- conflict_btn.click(
377
- _conflict_check_fn,
378
- inputs=[conflict_files, conflict_cfg],
379
- outputs=[conflict_out],
380
- api_name="conflict_check",
381
- )
382
 
 
 
 
383
 
384
- # ---------------------------------------------------------------------------
385
- # Hidden "conflict-check" API (NOT wired to any UI component)
386
- # ---------------------------------------------------------------------------
387
- # Model used for conflict analysis. Runs entirely on CPU (no GPU usage).
388
- CONFLICT_MODEL_ID = "google/gemma-4-E4B-it"
389
-
390
- _CONFLICT_MODEL_ID = None # currently loaded model (single-slot cache)
391
- _CONFLICT_MODEL = None
392
- _CONFLICT_TOKENIZER = None
393
-
394
- CONFLICT_SYSTEM_PROMPT = """You are an expert Talent Acquisition Conflict Analyst. Compare multiple job description documents and identify conflicts, discrepancies, and items that need verification.
395
-
396
- ### Instructions:
397
- 1. Compare ALL provided documents carefully, including JSON fields (jdFormat, jdPublicFormat, etc.), PDFs, text files, and any other uploads.
398
- 2. Focus on these critical fields:
399
- - Identity: job_title, company, department
400
- - Role: seniority_level, contract_type, employment_type, reporting_to, team_size
401
- - Location: work_location, office_address, region, country, travel_requirements
402
- - Policy: work_policy (remote/hybrid/onsite), working_hours, timezone
403
- - Compensation: salary_range, currency, benefits, equity/stock, bonus
404
- - Requirements: must_have_skills, nice_to_have_skills, education_level, years_experience, certifications
405
- - Responsibilities: tasks_and_duties, key_responsibilities, kpis
406
- - Others: posting_date, closing_date, job_id, hiring_manager, language
407
- 3. If a field conflicts, include ALL document values. If a document doesn't have the field, skip it (don't include as null).
408
- 4. If a field is ambiguous, incomplete, or varies slightly between documents (e.g. "3-5 years" vs "5+ years"), mark severity as need_check.
409
- 5. Track which document each value came from, and include the field path/location within the document for easy cross-reference.
410
- 6. Output ONLY valid JSON. Do not wrap it in markdown code fences or add any commentary before or after the JSON.
411
-
412
- ### Severity Levels:
413
- - **high**: Direct contradiction (e.g. "remote" vs "on-site", different salary ranges)
414
- - **medium**: Notable difference that affects the role (e.g. different team sizes, missing vs present benefits)
415
- - **low**: Minor wording differences that likely mean the same thing
416
- - **need_check**: Ambiguous, incomplete, or potentially conflicting but needs human verification
417
-
418
- ### Output JSON Schema:
419
- {
420
- "conflicts_found": true | false,
421
- "summary": "Brief overall comparison summary",
422
- "conflicts": [
423
- {
424
- "field": "field_name",
425
- "severity": "high | medium | low | need_check",
426
- "values": [
427
- {
428
- "document": "filename",
429
- "value": "the value",
430
- "path": "field path within the document (e.g. jdFormat.location.type, or 'page 1 paragraph 2')"
431
- }
432
- ],
433
- "details": "Description of the discrepancy and why it matters"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
434
  }
435
- ]
436
- }"""
437
-
438
-
439
- def _load_conflict_model(model_id, device="cpu"):
440
- """Lazily load the conflict-analysis model. `device` is "cpu" or "cuda".
441
- Caches a single model keyed by (model_id, device); switching either reloads
442
- it (keeps RAM bounded for large models). Gemma 4 models use
443
- `apply_chat_template` for correct prompting; the tokenizer carries the
444
- template so we avoid the heavy `torchvision` image dependency."""
445
- global _CONFLICT_MODEL_ID, _CONFLICT_TOKENIZER, _CONFLICT_MODEL
446
- cache_key = (model_id, device)
447
- if _CONFLICT_MODEL is None or _CONFLICT_MODEL_ID != cache_key:
448
- from transformers import AutoModelForCausalLM, AutoTokenizer
449
- print(f"Loading conflict-analysis model {model_id} on {device}...")
450
- _CONFLICT_TOKENIZER = AutoTokenizer.from_pretrained(model_id)
451
- load_kwargs = dict(torch_dtype=torch.bfloat16, low_cpu_mem_usage=True)
452
- if device != "cpu":
453
- load_kwargs["device_map"] = "auto"
454
- _CONFLICT_MODEL = AutoModelForCausalLM.from_pretrained(model_id, **load_kwargs)
455
- # Note: with low_cpu_mem_usage=True the model is already materialised on
456
- # CPU, so do NOT call .to("cpu") (it would fail on the meta tensors).
457
- _CONFLICT_MODEL.eval()
458
- _CONFLICT_MODEL_ID = cache_key
459
- print("Conflict-analysis model loaded!")
460
- return _CONFLICT_TOKENIZER, _CONFLICT_MODEL
461
-
462
-
463
- def _simplify_json(data):
464
- """Recursively prune JSON: drop null/empty values, ids (except root `id`
465
- inside the `data` object), timestamps, single-field wrappers, and
466
- de-duplicate createdAt arrays to their first item."""
467
- def clean_obj(obj, is_data_root=False):
468
- if isinstance(obj, dict):
469
- cleaned_dict = {}
470
- for k, v in obj.items():
471
- cleaned_val = clean_obj(v)
472
- if cleaned_val in (None, [], {}):
473
- continue
474
- if k == "id" and not is_data_root:
475
- continue
476
- if k in ("createAt", "createdAt", "creatorId", "contactPoints") or k.endswith("Id"):
477
- continue
478
- cleaned_dict[k] = cleaned_val
479
- while isinstance(cleaned_dict, dict) and len(cleaned_dict) == 1:
480
- cleaned_dict = cleaned_dict[list(cleaned_dict.keys())[0]]
481
- return cleaned_dict
482
- elif isinstance(obj, list):
483
- if not obj:
484
- return []
485
- has_created_at = any(
486
- isinstance(item, dict) and ("createAt" in item or "createdAt" in item)
487
- for item in obj
488
- )
489
- if has_created_at:
490
- return clean_obj(obj[0])
491
- cleaned_list = []
492
- for item in obj:
493
- cleaned_item = clean_obj(item)
494
- if cleaned_item not in (None, [], {}):
495
- cleaned_list.append(cleaned_item)
496
- return cleaned_list
497
- return obj
498
-
499
- return clean_obj(data, True)
500
-
501
-
502
- def _read_upload(filename: str, raw: bytes) -> str:
503
- """Decode an uploaded file into a plain-text representation."""
504
- lower = filename.lower()
505
- if lower.endswith(".json"):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
506
  try:
507
- parsed = json.loads(raw.decode("utf-8", errors="replace"))
508
- # Simplify the JSON before sending it to the model
509
- if isinstance(parsed, dict) and "data" in parsed:
510
- parsed = parsed["data"]
511
- simplified = _simplify_json(parsed)
512
- return json.dumps(simplified, indent=2, ensure_ascii=False)
513
  except Exception:
514
- return raw.decode("utf-8", errors="replace")
515
- if lower.endswith(".pdf"):
516
- try:
517
- from pypdf import PdfReader
518
- reader = PdfReader(io.BytesIO(raw))
519
- pages = []
520
- for i, page in enumerate(reader.pages, start=1):
521
- text = page.extract_text() or ""
522
- pages.append(f"--- page {i} ---\n{text}")
523
- return "\n".join(pages)
524
- except Exception as e:
525
- return f"[Could not parse PDF '{filename}': {e}]"
526
- # Fallback: treat as plain text
527
- return raw.decode("utf-8", errors="replace")
528
-
529
-
530
- def _extract_json(text: str):
531
- """Best-effort extraction of a JSON object from model output.
532
- Handles the common ```json ... ``` markdown fence and truncated output."""
533
- t = text.strip()
534
- # Strip a leading ```json / ``` fence
535
- if t.startswith("```"):
536
- t = t.split("\n", 1)[1] if "\n" in t else t[3:]
537
- if t.endswith("```"):
538
- t = t[:-3]
539
- t = t.strip()
540
- start = t.find("{")
541
- end = t.rfind("}")
542
- if start == -1 or end == -1 or end <= start:
543
- raise ValueError(
544
- "No JSON object found in model output. RAW>>>"
545
- + text[:1000].replace("\n", "\\n")
546
- )
547
- return json.loads(t[start:end + 1])
548
-
549
-
550
- def _analyze_conflicts(documents, config=None):
551
- """Run the LLM conflict analysis on a list of {filename, content} dicts.
552
- `config` (dict) may override the model id and any generation parameters
553
- (max_new_tokens, temperature, top_p, top_k, do_sample, repetition_penalty)."""
554
- config = config or {}
555
- model_id = config.get("model") or CONFLICT_MODEL_ID
556
- device = "cuda" if (config.get("gpu") and torch.cuda.is_available()) else "cpu"
557
- tokenizer, model = _load_conflict_model(model_id, device)
558
-
559
- gen_kwargs = {
560
- "max_new_tokens": int(config.get("max_new_tokens", 4096)),
561
- "do_sample": bool(config.get("do_sample", False)),
562
- "temperature": float(config.get("temperature", 1.0)),
563
- }
564
- if config.get("top_p") is not None:
565
- gen_kwargs["top_p"] = float(config["top_p"])
566
- if config.get("top_k") is not None:
567
- gen_kwargs["top_k"] = int(config["top_k"])
568
- if config.get("repetition_penalty") is not None:
569
- gen_kwargs["repetition_penalty"] = float(config["repetition_penalty"])
570
-
571
- docs_block = "\n\n".join(
572
- f"===== DOCUMENT: {d['filename']} =====\n{d['content']}" for d in documents
573
  )
574
- user_content = (
575
- "Below are the job description documents to compare.\n\n"
576
- f"{docs_block}\n\n"
577
- "Analyze them for conflicts using the instructions provided and return ONLY the JSON result."
 
 
 
 
 
 
 
 
 
 
 
 
 
578
  )
579
 
580
- # Gemma 4 requires the chat template (and thinking disabled) so the model
581
- # emits a clean answer instead of reasoning tokens.
582
- messages = [
583
- {"role": "system", "content": CONFLICT_SYSTEM_PROMPT},
584
- {"role": "user", "content": user_content},
585
- ]
586
- try:
587
- text = tokenizer.apply_chat_template(
588
- messages, tokenize=False, add_generation_prompt=True, enable_thinking=False
589
- )
590
- except TypeError:
591
- text = tokenizer.apply_chat_template(
592
- messages, tokenize=False, add_generation_prompt=True
593
- )
594
- inputs = tokenizer(text, return_tensors="pt").to(model.device)
595
 
596
- with torch.no_grad():
597
- output = model.generate(**inputs, **gen_kwargs)
598
- generated = output[0][inputs.input_ids.shape[1]:]
599
- response_text = tokenizer.decode(generated, skip_special_tokens=False)
600
- try:
601
- return _extract_json(response_text)
602
- except ValueError:
603
- raise ValueError(
604
- f"JSON parse failed (gen_len={generated.shape[0]}). "
605
- f"tail={response_text[-300:]!r}"
606
- )
607
 
 
 
 
 
 
 
608
 
609
- @spaces.GPU
610
- def _analyze_conflicts_gpu(documents, config):
611
- """GPU-backed variant: when called, `spaces.GPU` runs this in the Space's
612
- GPU worker, where `torch.cuda.is_available()` is True so the model loads to CUDA."""
613
- return _analyze_conflicts(documents, config)
 
614
 
 
 
615
 
616
- def _analyze_conflicts_cpu(documents, config):
617
- """CPU variant: runs in the main process (no GPU)."""
618
- return _analyze_conflicts(documents, config)
 
 
 
 
 
 
 
 
 
 
 
 
 
619
 
620
 
621
- # NOTE: the conflict-check handler is `_conflict_check_fn` (defined above), wired to
622
- # the hidden `conflict_btn.click(...)` handler inside the Blocks block (api_name="conflict_check").
 
 
 
 
623
 
624
  if __name__ == "__main__":
625
- demo.queue().launch(
626
- theme=custom_theme,
627
- css="""
628
- .header-text h1 {
629
- font-size: 2.5rem !important;
630
- font-weight: 700 !important;
631
- margin-bottom: 0.5rem !important;
632
- background: linear-gradient(135deg, #fbbf24 0%, #f59e0b 100%);
633
- -webkit-background-clip: text;
634
- -webkit-text-fill-color: transparent;
635
- background-clip: text;
636
- }
637
-
638
- .header-text p {
639
- font-size: 1.1rem !important;
640
- color: #64748b !important;
641
- margin-top: 0 !important;
642
- }
643
-
644
- .footer-text {
645
- padding: 1rem 0;
646
- }
647
-
648
- .footer-text a {
649
- color: #f59e0b !important;
650
- text-decoration: none !important;
651
- font-weight: 500;
652
- }
653
-
654
- .footer-text a:hover {
655
- text-decoration: underline !important;
656
- }
657
-
658
- /* Mobile optimizations */
659
- @media (max-width: 768px) {
660
- .header-text h1 {
661
- font-size: 1.8rem !important;
662
- }
663
-
664
- .header-text p {
665
- font-size: 1rem !important;
666
- }
667
- }
668
-
669
- /* Smooth transitions */
670
- button, .gr-button {
671
- transition: all 0.2s ease !important;
672
- }
673
-
674
- button:hover, .gr-button:hover {
675
- transform: translateY(-1px);
676
- box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15) !important;
677
- }
678
-
679
- /* Better spacing */
680
- .gradio-container {
681
- max-width: 1400px !important;
682
- margin: 0 auto !important;
683
- }
684
-
685
- /* Hide the conflict-check API components (no visible UI) */
686
- .hidden_conflict {
687
- display: none !important;
688
- }
689
- """,
690
- footer_links=[
691
- "api",
692
- "gradio"
693
- ],
694
- mcp_server=True
695
- )
 
1
+ """
2
+ HuggingFace Model Playground
3
+ - Input any HF model ID → auto-detects task → shows suitable UI
4
+ - Text gen (chat/completion/translate), summarization, translation, image gen, TTS
5
+ - Thinking model support (DeepSeek-R1, QwQ, etc.)
6
+ - Quick-select presets for popular models
7
+ """
8
+ import spaces # MUST be first — signals ZeroGPU to allocate GPU worker
9
+
10
  import gradio as gr
11
+ import torch
12
  import os
13
+ import re
14
+ import json
15
+ import threading
16
+ import queue
17
+ import time
18
+ import traceback
19
+ import numpy as np
20
+ import asyncio
21
+ import sys
22
+ from typing import Optional, Dict, Any, List, Tuple, Generator
23
+ from huggingface_hub import HfApi
24
+
25
+ from transformers import (
26
+ AutoConfig, AutoTokenizer, AutoModelForCausalLM, AutoModelForSeq2SeqLM,
27
+ pipeline as hf_pipeline, set_seed, TextIteratorStreamer
28
  )
29
+ from diffusers import DiffusionPipeline
 
 
 
30
 
31
+ # Python 3.10 asyncio cleanup fix: suppress "Invalid file descriptor: -1" on shutdown
32
+ # https://bugs.python.org/issue46370
33
+ if sys.version_info >= (3, 10) and sys.platform == 'linux':
34
+ try:
35
+ _cls = asyncio.selector_events.BaseSelectorEventLoop
36
+ _orig = _cls._close_self_pipe
37
+ def _safe_close_self_pipe(self):
38
+ try:
39
+ _orig(self)
40
+ except ValueError:
41
+ pass
42
+ _cls._close_self_pipe = _safe_close_self_pipe
43
+ except Exception:
44
+ pass
45
 
46
+ torch.set_grad_enabled(False)
47
 
48
+ # ── GPU Workers (must be module-level @spaces.GPU) ──
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
49
 
50
+ @spaces.GPU
51
+ def gpu_text_generate(model_id: str, messages: list, gen_kwargs: dict):
52
+ """Text generation on GPU. Model is cached in the GPU worker across calls."""
53
+ import torch as _torch
54
+ from transformers import AutoModelForCausalLM as _AutoModelForCausalLM
55
+ from transformers import AutoTokenizer as _AutoTokenizer
56
+ from transformers import TextIteratorStreamer as _TextIteratorStreamer
57
+ from threading import Thread as _Thread
58
+
59
+ cache = gpu_text_generate.__dict__.setdefault('_cache', {})
60
+ if model_id not in cache:
61
+ tok = _AutoTokenizer.from_pretrained(model_id, trust_remote_code=True)
62
+ if tok.pad_token is None:
63
+ tok.pad_token = tok.eos_token
64
+ model = _AutoModelForCausalLM.from_pretrained(
65
+ model_id, torch_dtype=_torch.bfloat16, device_map="auto",
66
+ low_cpu_mem_usage=True, trust_remote_code=True,
67
+ )
68
+ model.eval()
69
+ cache[model_id] = (model, tok)
70
 
71
+ model, tokenizer = cache[model_id]
72
+ prompt = tokenizer.apply_chat_template(messages, tokenize=False, add_generation_prompt=True)
73
+ inputs = tokenizer(prompt, return_tensors="pt").to("cuda")
74
+ streamer = _TextIteratorStreamer(tokenizer, skip_prompt=True, skip_special_tokens=True)
75
+ kw = {**gen_kwargs, "streamer": streamer}
76
+ _Thread(target=model.generate, args=(inputs.input_ids,), kwargs=kw).start()
77
+ for text in streamer:
78
+ yield text
79
 
 
80
 
81
+ @spaces.GPU
82
+ def gpu_image_generate(model_id: str, prompt: str, height: int, width: int, steps: int, seed: int):
83
+ """Image generation on GPU."""
84
+ import torch as _torch
85
+ from diffusers import DiffusionPipeline as _DP
86
+
87
+ pipe = _DP.from_pretrained(
88
+ model_id, torch_dtype=_torch.bfloat16, low_cpu_mem_usage=True,
89
+ )
90
+ pipe.to("cuda")
91
+ if hasattr(pipe, "enable_attention_slicing"):
92
+ pipe.enable_attention_slicing()
93
+ gen = _torch.Generator("cuda").manual_seed(seed)
94
+ result = pipe(prompt=prompt, height=height, width=width,
95
+ num_inference_steps=steps, generator=gen)
96
+ return result.images[0]
97
+
98
+
99
+ @spaces.GPU
100
+ def gpu_image_generate_stream(model_id: str, prompt: str, height: int, width: int,
101
+ steps: int, seed: int):
102
+ """Image generation on GPU with step-by-step preview."""
103
+ import torch as _torch
104
+ from diffusers import DiffusionPipeline as _DP
105
+ from PIL import Image as _Image
106
+ import io as _io
107
+ import queue as _queue
108
+ import threading as _threading
109
+
110
+ pipe = _DP.from_pretrained(
111
+ model_id, torch_dtype=_torch.bfloat16, low_cpu_mem_usage=True,
112
+ )
113
+ pipe.to("cuda")
114
+ if hasattr(pipe, "enable_attention_slicing"):
115
+ pipe.enable_attention_slicing()
116
+
117
+ has_vae = hasattr(pipe, "vae") and hasattr(pipe, "image_processor")
118
+ gen = _torch.Generator("cuda").manual_seed(seed)
119
+ previews = _queue.Queue()
120
  step_counter = {"i": 0}
121
 
122
+ def decode_latents(latents):
123
+ l = latents.to(pipe.vae.dtype)
124
+ sf = pipe.vae.config.scaling_factor
125
+ sh = getattr(pipe.vae.config, "shift_factor", None) or 0
126
+ l = (l / sf) + sh
127
+ with _torch.no_grad():
128
+ img = pipe.vae.decode(l, return_dict=False)[0]
129
+ return pipe.image_processor.postprocess(img, output_type="pil")[0]
130
+
131
+ def cb(pipe, i, t, kw):
132
  step_counter["i"] += 1
133
+ if has_vae and "latents" in kw:
134
+ try:
135
+ previews.put((decode_latents(kw["latents"]), step_counter["i"]))
136
+ except Exception:
137
+ pass
138
+ return kw
139
 
140
  result = {}
141
+ exc = {}
142
 
143
  def run():
144
  try:
145
+ kw = dict(prompt=prompt, height=height, width=width,
146
+ num_inference_steps=steps, generator=gen)
147
+ if has_vae:
148
+ kw["output_type"] = "latent"
149
+ kw["callback_on_step_end"] = cb
150
+ kw["callback_on_step_end_tensor_inputs"] = ["latents"]
151
+ out = pipe(**kw)
152
+ result["img"] = decode_latents(out.images) if has_vae else out.images[0]
153
+ previews.put(None)
 
 
 
 
 
 
 
154
  except Exception as e:
155
+ exc["e"] = e
156
  previews.put(None)
157
 
158
+ _threading.Thread(target=run).start()
 
 
159
  last = None
160
  while True:
161
  item = previews.get()
 
163
  break
164
  img, step = item
165
  last = img
166
+ yield img, seed, f"Step {step}/{steps}"
167
+
168
+ if "e" in exc:
169
+ raise exc["e"]
170
+ yield result.get("img", last), seed, f"Done ({steps} steps)"
171
+
172
+
173
+ _gpu_flag = False
174
+ _cancel_flag = [False]
175
+
176
+ def _use_gpu() -> bool:
177
+ return _gpu_flag
178
+
179
+ def _set_gpu_flag(val: bool):
180
+ global _gpu_flag
181
+ _gpu_flag = val
182
+
183
+ # ============================================================
184
+ # CONSTANTS
185
+ # ============================================================
186
+
187
+
188
+ ARCH_TASK_MAP: Dict[str, str] = {
189
+ "LlamaForCausalLM": "text-generation",
190
+ "GPT2LMHeadModel": "text-generation",
191
+ "PhiForCausalLM": "text-generation",
192
+ "Phi3ForCausalLM": "text-generation",
193
+ "Phi3SmallForCausalLM": "text-generation",
194
+ "GemmaForCausalLM": "text-generation",
195
+ "Gemma2ForCausalLM": "text-generation",
196
+ "Gemma3ForCausalLM": "text-generation",
197
+ "Qwen2ForCausalLM": "text-generation",
198
+ "Qwen2MoeForCausalLM": "text-generation",
199
+ "MistralForCausalLM": "text-generation",
200
+ "MixtralForCausalLM": "text-generation",
201
+ "OPTForCausalLM": "text-generation",
202
+ "BloomForCausalLM": "text-generation",
203
+ "StableLmForCausalLM": "text-generation",
204
+ "FalconForCausalLM": "text-generation",
205
+ "GPTNeoXForCausalLM": "text-generation",
206
+ "DeepseekForCausalLM": "text-generation",
207
+ "DeepseekV2ForCausalLM": "text-generation",
208
+ "DeepseekV3ForCausalLM": "text-generation",
209
+ "OlmoForCausalLM": "text-generation",
210
+ "CohereForCausalLM": "text-generation",
211
+ "DbrxForCausalLM": "text-generation",
212
+ "MptForCausalLM": "text-generation",
213
+ "Starcoder2ForCausalLM": "text-generation",
214
+ "XGLMForCausalLM": "text-generation",
215
+ "PhiMoEForCausalLM": "text-generation",
216
+ "GraniteForCausalLM": "text-generation",
217
+ "GraniteMoeForCausalLM": "text-generation",
218
+ "ExaoneForCausalLM": "text-generation",
219
+ "RWForCausalLM": "text-generation",
220
+ "LlavaForConditionalGeneration": "text-generation",
221
+ "Qwen2VLForConditionalGeneration": "text-generation",
222
+ "T5ForConditionalGeneration": "text2text-generation",
223
+ "FlanT5ForConditionalGeneration": "text2text-generation",
224
+ "BartForConditionalGeneration": "summarization",
225
+ "PegasusForConditionalGeneration": "summarization",
226
+ "LongT5ForConditionalGeneration": "summarization",
227
+ "LEDForConditionalGeneration": "summarization",
228
+ "MarianMTModel": "translation",
229
+ "M2M100ForConditionalGeneration": "translation",
230
+ "NLLBForConditionalGeneration": "translation",
231
+ "NLLBMoeForConditionalGeneration": "translation",
232
+ }
233
+
234
+ DIFFUSION_ARCHS = {
235
+ "StableDiffusionPipeline", "StableDiffusionXLPipeline", "FluxPipeline",
236
+ "FluxFillPipeline", "LatentConsistencyModelPipeline",
237
+ "StableDiffusion3Pipeline", "WuerstchenPipeline",
238
+ "KandinskyPipeline", "KandinskyV22Pipeline",
239
+ "PixArtAlphaPipeline", "PixArtSigmaPipeline",
240
+ "StableCascadeCombinedPipeline", "CogView4Pipeline",
241
+ }
242
+
243
+ TTS_ARCHS = {
244
+ "SpeechT5ForTextToSpeech", "BarkModel", "BarkCausalModel",
245
+ "VitsModel", "MmsTextToSpeech", "MusicgenForConditionalGeneration",
246
+ }
247
+
248
+ THINKING_PATTERNS = [re.compile(p) for p in [
249
+ r"deepseek.*r1", r"qwq", r"thinking", r"reasoning", r"r1-distill",
250
+ ]]
251
+
252
+ PIPELINE_TAG_MAP = {
253
+ "text-generation": "text-generation",
254
+ "text2text-generation": "text2text-generation",
255
+ "summarization": "summarization",
256
+ "translation": "translation",
257
+ "text-to-image": "image-generation",
258
+ "image-to-image": "image-generation",
259
+ "image-segmentation": "image-generation",
260
+ "text-to-speech": "text-to-speech",
261
+ "fill-mask": "text-generation",
262
+ "feature-extraction": "text-generation",
263
+ }
264
+
265
+ TASK_UI_ORDER = ["text-generation", "text2text-generation", "summarization",
266
+ "translation", "image-generation", "text-to-speech"]
267
+
268
+ # ============================================================
269
+ # MODEL STATE
270
+ # ============================================================
271
+
272
+ _model_lock = threading.Lock()
273
+ _state = {
274
+ "model_id": None, "task": None, "pipeline": None,
275
+ "tokenizer": None, "model": None, "config": None,
276
+ "processor": None, "vocoder": None, "is_thinking": False,
277
+ "model_info": None, "device": "cpu",
278
+ }
279
+
280
+
281
+ def _is_thinking(model_id: str) -> bool:
282
+ return any(p.search(model_id.lower()) for p in THINKING_PATTERNS)
283
+
284
+
285
+ def _clear_state():
286
+ for k in list(_state.keys()):
287
+ v = _state[k]
288
+ if v is not None and hasattr(v, "__del__"):
289
+ try:
290
+ del v
291
+ except Exception:
292
+ pass
293
+ _state[k] = None
294
+ _state["model_id"] = None
295
+ _state["is_thinking"] = False
296
+ if torch.cuda.is_available():
297
+ torch.cuda.empty_cache()
298
+
299
+
300
+ # ============================================================
301
+ # MODEL DETECTION
302
+ # ============================================================
303
+
304
+ def detect_task(model_id: str) -> str:
305
+ try:
306
+ info = HfApi().model_info(model_id, timeout=10)
307
+ _state["model_info"] = info
308
+ tag = info.pipeline_tag
309
+ if tag and tag in PIPELINE_TAG_MAP:
310
+ return PIPELINE_TAG_MAP[tag]
311
+ except Exception:
312
+ pass
313
+ try:
314
+ config = AutoConfig.from_pretrained(model_id, trust_remote_code=True)
315
+ _state["config"] = config
316
+ archs = config.architectures or []
317
+ for arch in archs:
318
+ if arch in ARCH_TASK_MAP:
319
+ return ARCH_TASK_MAP[arch]
320
+ if arch in DIFFUSION_ARCHS:
321
+ return "image-generation"
322
+ if arch in TTS_ARCHS:
323
+ return "text-to-speech"
324
+ mt = getattr(config, "model_type", "")
325
+ if mt in ("llama", "gpt2", "phi", "phi3", "gemma", "gemma2",
326
+ "qwen2", "mistral", "mixtral", "opt", "bloom",
327
+ "falcon", "gpt_neox", "deepseek", "olmo", "cohere",
328
+ "stablelm", "mpt", "starcoder2", "exaone", "dbrx"):
329
+ return "text-generation"
330
+ if mt in ("t5", "mt5", "bart", "pegasus", "longt5", "led"):
331
+ return "text2text-generation"
332
+ if mt in ("marian", "m2m_100", "nllb"):
333
+ return "translation"
334
+ if mt in ("speecht5", "bark", "vits", "mms"):
335
+ return "text-to-speech"
336
+ except Exception:
337
+ pass
338
+ # Diffusers fallback: check model_index.json (hallmark of diffusers pipelines)
339
+ try:
340
+ from huggingface_hub import hf_hub_url
341
+ import requests as _req
342
+ r = _req.get(hf_hub_url(model_id, "model_index.json"), timeout=5)
343
+ if r.status_code == 200:
344
+ return "image-generation"
345
+ except Exception:
346
+ pass
347
+ return "text-generation"
348
+
349
+
350
+ # ============================================================
351
+ # MODEL LOADING
352
+ # ============================================================
353
+
354
+ def load_model(model_id: str, progress=gr.Progress()):
355
+ with _model_lock:
356
+ if _state["model_id"] == model_id and _state["model"] is not None:
357
+ return _build_load_result()
358
+ _clear_state()
359
+
360
+ progress(0, desc="Detecting model type...")
361
+ task = detect_task(model_id)
362
+ is_thinking = _is_thinking(model_id)
363
+
364
+ try:
365
+ if task in ("text-generation", "text2text-generation", "summarization", "translation"):
366
+ _load_text_model(model_id, task, progress)
367
+ elif task == "image-generation":
368
+ _load_image_model(model_id, progress)
369
+ elif task == "text-to-speech":
370
+ _load_tts_model(model_id, progress)
371
+ else:
372
+ _load_text_model(model_id, "text-generation", progress)
373
+
374
+ _state["task"] = task
375
+ _state["is_thinking"] = is_thinking
376
+ _state["model_id"] = model_id
377
+ except Exception as e:
378
+ _clear_state()
379
+ raise gr.Error(f"Load failed: {e}\n{traceback.format_exc()}")
380
+
381
+ return _build_load_result()
382
+
383
+
384
+ def _build_load_result():
385
+ info = _state["model_info"]
386
+ parts = []
387
+ if info:
388
+ if getattr(info, "author", None):
389
+ parts.append(f"**Author:** {info.author}")
390
+ if getattr(info, "pipeline_tag", None):
391
+ parts.append(f"**Task:** {info.pipeline_tag}")
392
+ parts.append(f"**Detected:** {_state['task']}")
393
+ if _state["is_thinking"]:
394
+ parts.append("🧠 **Thinking Model**")
395
+ return _state["task"], _state["is_thinking"], " · ".join(parts)
396
+
397
+
398
+ def _load_text_model(model_id: str, task: str, progress):
399
+ progress(0.2, desc="Loading tokenizer...")
400
+ tokenizer = AutoTokenizer.from_pretrained(model_id, trust_remote_code=True)
401
+ if tokenizer.pad_token is None:
402
+ tokenizer.pad_token = tokenizer.eos_token
403
+
404
+ progress(0.4, desc="Loading model...")
405
+ if task == "text-generation":
406
+ model = AutoModelForCausalLM.from_pretrained(
407
+ model_id, torch_dtype=torch.bfloat16,
408
+ low_cpu_mem_usage=True, trust_remote_code=True,
409
+ )
410
+ else:
411
+ model = AutoModelForSeq2SeqLM.from_pretrained(
412
+ model_id, torch_dtype=torch.bfloat16,
413
+ low_cpu_mem_usage=True, trust_remote_code=True,
414
+ )
415
+
416
+ # Always load on CPU for the fallback path.
417
+ # GPU inference goes through @spaces.GPU workers which handle CUDA internally.
418
+ model.to("cpu")
419
+ model.eval()
420
+
421
+ progress(0.8, desc="Ready!")
422
+ _state["model"] = model
423
+ _state["tokenizer"] = tokenizer
424
+ _state["device"] = "cpu"
425
+
426
+
427
+ def _load_image_model(model_id: str, progress):
428
+ progress(0.3, desc="Loading image model...")
429
+ pipe = DiffusionPipeline.from_pretrained(
430
+ model_id, torch_dtype=torch.bfloat16, low_cpu_mem_usage=True,
431
+ )
432
+ pipe.to("cpu")
433
+ pipe.enable_attention_slicing()
434
+ progress(0.8, desc="Ready!")
435
+ _state["pipeline"] = pipe
436
+ _state["device"] = "cpu"
437
+
438
+
439
+ def _load_tts_model(model_id: str, progress):
440
+ import torch as _torch
441
+ if "speecht5" in model_id.lower():
442
+ progress(0.3, desc="Loading SpeechT5 model...")
443
+ from transformers import AutoTokenizer as _AT
444
+ from transformers import SpeechT5ForTextToSpeech as _SFTTS
445
+ tokenizer = _AT.from_pretrained(model_id)
446
+ model = _SFTTS.from_pretrained(model_id)
447
+ model.eval()
448
+ progress(0.5, desc="Loading speaker embedding...")
449
+ speaker_embeddings = None
450
+ try:
451
+ from datasets import load_dataset as _load_ds
452
+ emb_ds = _load_ds("Matthijs/cmu-arctic-xvectors", split="training")
453
+ speaker_embeddings = _torch.tensor(emb_ds[7306]["xvector"]).unsqueeze(0)
454
+ except Exception:
455
+ speaker_embeddings = _torch.zeros((1, 512))
456
+ progress(0.8, desc="Ready!")
457
+ _state["model"] = model
458
+ _state["tokenizer"] = tokenizer
459
+ _state["speaker_embeddings"] = speaker_embeddings
460
+ _state["pipeline"] = None
461
+ _state["device"] = "cpu"
462
+ else:
463
+ progress(0.3, desc="Loading TTS model...")
464
+ pipe = hf_pipeline("text-to-speech", model=model_id, device=-1)
465
+ progress(0.8, desc="Ready!")
466
+ _state["pipeline"] = pipe
467
+ _state["device"] = "cpu"
468
+
469
+
470
+ # ============================================================
471
+ # GENERATION HELPERS
472
+ # ============================================================
473
+
474
+ def _gen_kwargs(max_tokens, temperature, top_p, top_k, repetition, sample):
475
+ return {
476
+ "max_new_tokens": int(max_tokens),
477
+ "temperature": float(temperature),
478
+ "top_p": float(top_p),
479
+ "top_k": int(top_k) if top_k > 0 else None,
480
+ "repetition_penalty": float(repetition),
481
+ "do_sample": bool(sample),
482
+ }
483
+
484
+ PRESET_CHOICES = [
485
+ ("💬 gemma-3-1b-it", "google/gemma-3-1b-it"),
486
+ ("💬 Qwen3-4B-Instruct", "Qwen/Qwen3-4B-Instruct-2507"),
487
+ ("💬 Phi-4-mini", "microsoft/Phi-4-mini-instruct"),
488
+ ("💬 SmolLM3-3B", "HuggingFaceTB/SmolLM3-3B"),
489
+ ("💬 Qwen2.5-3B", "Qwen/Qwen2.5-3B-Instruct"),
490
+ ("💬 LFM2.5-1.2B", "LiquidAI/LFM2.5-1.2B-Instruct"),
491
+ ("🧠 DeepSeek-R1-1.5B", "deepseek-ai/DeepSeek-R1-Distill-Qwen-1.5B"),
492
+ ("🧠 Qwen3-4B-Thinking", "Qwen/Qwen3-4B-Thinking-2507"),
493
+ ("🧠 Gemma-2-2B", "google/gemma-2-2b-it"),
494
+ ("🧠 LFM2.5-1.2B-Thinking", "LiquidAI/LFM2.5-1.2B-Thinking"),
495
+ ("🔤 FLAN-T5-Small", "google/flan-t5-small"),
496
+ ("🔤 FLAN-T5-Base", "google/flan-t5-base"),
497
+ ("📝 BART-Large-CNN", "facebook/bart-large-cnn"),
498
+ ("🌐 OPUS-EN→ES", "Helsinki-NLP/opus-mt-en-es"),
499
+ ("🌐 OPUS-EN→FR", "Helsinki-NLP/opus-mt-en-fr"),
500
+ ("🌐 NLLB-600M", "facebook/nllb-200-distilled-600M"),
501
+ ("🎨 Z-Image-Turbo", "Tongyi-MAI/Z-Image-Turbo"),
502
+ ("🎨 SD1.5", "runwayml/stable-diffusion-v1-5"),
503
+ ("🎨 SDXL-Turbo", "stabilityai/sdxl-turbo"),
504
+ ("🔊 SpeechT5", "microsoft/speecht5_tts"),
505
+ ("🔊 Bark-Small", "suno/bark-small"),
506
+ ("🔊 MMS-TTS-ENG", "facebook/mms-tts-eng"),
507
+ ("🔊 Bark", "suno/bark"),
508
+ ]
509
+
510
+
511
+ def _apply_template(tokenizer, messages, is_thinking=False):
512
+ try:
513
+ kwargs = {"tokenize": False, "add_generation_prompt": True}
514
+ if is_thinking:
515
+ try:
516
+ kwargs["enable_thinking"] = True
517
+ except TypeError:
518
+ pass
519
+ return tokenizer.apply_chat_template(messages, **kwargs)
520
+ except Exception:
521
+ return messages[-1]["content"]
522
+
523
+
524
+ def _stream(model, tokenizer, prompt, kwargs):
525
+ inputs = tokenizer(prompt, return_tensors="pt").to(model.device)
526
+ streamer = TextIteratorStreamer(tokenizer, skip_prompt=True, skip_special_tokens=True)
527
+ kwargs["streamer"] = streamer
528
+ t = threading.Thread(target=model.generate, args=(inputs.input_ids,), kwargs=kwargs)
529
+ t.start()
530
+ for text in streamer:
531
+ yield text
532
+
533
+
534
+ # ============================================================
535
+ # INFERENCE FUNCTIONS
536
+ # ============================================================
537
+
538
+ def _build_messages(history, message, system_prompt):
539
+ msgs = [{"role": "system", "content": system_prompt or "You are a helpful assistant."}]
540
+ if history:
541
+ msgs.extend(history)
542
+ msgs.append({"role": "user", "content": message})
543
+ return msgs
544
+
545
+
546
+ def chat_respond(message, history, system_prompt, max_tokens, temperature,
547
+ top_p, top_k, repetition, sample, thinking):
548
+ s = _state
549
+ if s["model"] is None:
550
+ raise gr.Error("No model loaded.")
551
+ history = history or []
552
+ is_thinking = thinking if thinking is not None else s["is_thinking"]
553
+ msgs = _build_messages(history, message, system_prompt)
554
+ kwargs = _gen_kwargs(max_tokens, temperature, top_p, top_k, repetition, sample)
555
+ history.append({"role": "user", "content": message})
556
+ history.append({"role": "assistant", "content": ""})
557
+
558
+ if _use_gpu() and s["task"] == "text-generation":
559
+ for chunk in gpu_text_generate(s["model_id"], msgs, kwargs):
560
+ history[-1]["content"] = chunk
561
+ yield history, ""
562
+ else:
563
+ prompt = _apply_template(s["tokenizer"], msgs, is_thinking)
564
+ full = ""
565
+ for chunk in _stream(s["model"], s["tokenizer"], prompt, kwargs):
566
+ full += chunk
567
+ history[-1]["content"] = full
568
+ yield history, ""
569
+ yield history, ""
570
+
571
+
572
+ def completion_fn(prompt, max_tokens, temperature,
573
+ top_p, top_k, repetition, sample, thinking):
574
+ s = _state
575
+ if s["model"] is None:
576
+ raise gr.Error("No model loaded.")
577
+ kwargs = _gen_kwargs(max_tokens, temperature, top_p, top_k, repetition, sample)
578
+ _cancel_flag[0] = False
579
+
580
+ if _use_gpu() and s["task"] == "text-generation":
581
+ msgs = [{"role": "user", "content": prompt}]
582
+ full = prompt + "\n"
583
+ for chunk in gpu_text_generate(s["model_id"], msgs, kwargs):
584
+ if _cancel_flag[0]:
585
+ break
586
+ full += chunk
587
+ yield full
588
+ else:
589
+ full = prompt
590
+ for chunk in _stream(s["model"], s["tokenizer"], prompt, kwargs):
591
+ if _cancel_flag[0]:
592
+ break
593
+ full += chunk
594
+ yield full
595
+
596
+
597
+ def translate_fn(text, src_lang, tgt_lang, max_tokens, temperature,
598
+ top_p, top_k, repetition, sample, thinking):
599
+ s = _state
600
+ if s["model"] is None:
601
+ raise gr.Error("No model loaded.")
602
+ is_thinking = thinking if thinking is not None else s["is_thinking"]
603
+ kwargs = _gen_kwargs(max_tokens, temperature, top_p, top_k, repetition, sample)
604
+
605
+ if _use_gpu() and s["task"] == "text-generation":
606
+ sp = f"Translate {src_lang} to {tgt_lang}. Output only the translation."
607
+ msgs = [{"role": "system", "content": sp}, {"role": "user", "content": text}]
608
+ for chunk in gpu_text_generate(s["model_id"], msgs, kwargs):
609
+ yield chunk
610
+ return
611
+
612
+ if s["task"] == "translation":
613
+ prompt = text
614
+ else:
615
+ sp = f"Translate {src_lang} to {tgt_lang}. Output only the translation."
616
+ if s["tokenizer"].chat_template:
617
+ msgs = [{"role": "system", "content": sp}, {"role": "user", "content": text}]
618
+ prompt = _apply_template(s["tokenizer"], msgs, is_thinking)
619
+ else:
620
+ prompt = f"{sp}\n\n{text}"
621
+ full = ""
622
+ for chunk in _stream(s["model"], s["tokenizer"], prompt, kwargs):
623
+ full += chunk
624
+ yield full
625
+
626
+
627
+ def summarize_fn(text, max_tokens, temperature, top_p, top_k, repetition, sample):
628
+ s = _state
629
+ if s["model"] is None:
630
+ raise gr.Error("No model loaded.")
631
+ kwargs = _gen_kwargs(max_tokens, temperature, top_p, top_k, repetition, sample)
632
+ if s["tokenizer"].chat_template:
633
+ msgs = [{"role": "user", "content": f"Summarize:\n\n{text}"}]
634
+ prompt = _apply_template(s["tokenizer"], msgs)
635
+ else:
636
+ prompt = f"Summarize the following:\n\n{text}\n\nSummary:"
637
+ full = ""
638
+ for chunk in _stream(s["model"], s["tokenizer"], prompt, kwargs):
639
+ full += chunk
640
+ yield full
641
+
642
+
643
+ # --- Image Generation ---
644
+
645
+ _img_lock = threading.Lock()
646
+ _img_interrupt = None
647
+
648
+
649
+ def _decode_latents(latents, pipe):
650
+ l = latents.to(pipe.vae.dtype)
651
+ sf = pipe.vae.config.scaling_factor
652
+ sh = getattr(pipe.vae.config, "shift_factor", None) or 0
653
+ l = (l / sf) + sh
654
+ with torch.no_grad():
655
+ img = pipe.vae.decode(l, return_dict=False)[0]
656
+ return pipe.image_processor.postprocess(img, output_type="pil")[0]
657
+
658
+
659
+ def generate_image(prompt, height, width, steps, seed, randomize, progress=gr.Progress(track_tqdm=True)):
660
+ global _img_interrupt
661
+ s = _state
662
+ model_id = s["model_id"]
663
+
664
+ if _use_gpu() and model_id:
665
+ if randomize:
666
+ seed = torch.randint(0, 2**32 - 1, (1,)).item()
667
+ for img, used_seed, status in gpu_image_generate_stream(
668
+ model_id, prompt, int(height), int(width), int(steps), int(seed),
669
+ ):
670
+ yield img, used_seed, status
671
+ return
672
+
673
+ pipe = s.get("pipeline")
674
+ if pipe is None:
675
+ raise gr.Error("No image model loaded.")
676
+
677
+ with _img_lock:
678
+ if _img_interrupt is not None:
679
+ _img_interrupt.set()
680
+ pipe._interrupt = True
681
+ _img_interrupt = threading.Event()
682
+ pipe._interrupt = False
683
+
684
+ ev = _img_interrupt
685
+ if randomize:
686
+ seed = torch.randint(0, 2**32 - 1, (1,)).item()
687
+ gen = torch.Generator(device=s["device"]).manual_seed(int(seed))
688
+
689
+ has_preview = hasattr(pipe, "vae") and hasattr(pipe, "image_processor")
690
+ q = queue.Queue()
691
+ cnt = {"i": 0}
692
+
693
+ def cb(pipe, i, t, kw):
694
+ if ev.is_set():
695
+ raise InterruptedError()
696
+ cnt["i"] += 1
697
+ if has_preview and "latents" in kw:
698
+ try:
699
+ q.put((_decode_latents(kw["latents"], pipe), cnt["i"]))
700
+ except Exception:
701
+ pass
702
+ return kw
703
+
704
+ res = {}
705
+ exc = {}
706
+
707
+ def run():
708
+ try:
709
+ kw = dict(prompt=prompt, height=int(height), width=int(width),
710
+ num_inference_steps=int(steps), generator=gen)
711
+ if has_preview:
712
+ kw["output_type"] = "latent"
713
+ kw["callback_on_step_end"] = cb
714
+ kw["callback_on_step_end_tensor_inputs"] = ["latents"]
715
+ out = pipe(**kw)
716
+ if not ev.is_set():
717
+ if has_preview:
718
+ res["final"] = _decode_latents(out.images, pipe)
719
+ else:
720
+ res["final"] = out.images[0]
721
+ q.put(None)
722
+ except InterruptedError:
723
+ q.put(None)
724
+ except Exception as e:
725
+ exc["e"] = e
726
+ q.put(None)
727
 
728
+ threading.Thread(target=run).start()
729
 
730
+ last = None
731
+ while True:
732
+ item = q.get()
733
+ if item is None:
734
+ break
735
+ img, step = item
736
+ last = img
737
+ yield img, seed, f"Step {step}/{int(steps)}"
738
 
739
+ if "e" in exc:
740
+ raise exc["e"]
741
+ if ev.is_set():
742
  yield last, seed, "Cancelled"
743
  else:
744
+ yield res.get("final", last), seed, f"Done ({int(steps)} steps)"
745
+
746
+
747
+ # --- TTS ---
748
+
749
+ def generate_speech(text):
750
+ pipe = _state.get("pipeline")
751
+ model = _state.get("model")
752
+ tokenizer = _state.get("tokenizer")
753
+
754
+ if pipe is not None:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
755
  try:
756
+ result = pipe(text)
757
  except Exception as e:
758
+ raise gr.Error(f"TTS failed: {e}")
759
+ sr = result.get("sampling_rate", 22050)
760
+ audio = result["audio"]
761
+ elif model is not None and tokenizer is not None:
762
+ speaker_embedding = _state.get("speaker_embeddings")
763
+ import torch as _torch
764
+ inputs = tokenizer(text, return_tensors="pt").to(model.device)
765
+ with _torch.no_grad():
766
+ speech = model.generate(input_ids=inputs["input_ids"], speaker_embeddings=speaker_embedding)
767
+ sr = 16000
768
+ audio = speech[0].cpu().numpy()
769
+ else:
770
+ raise gr.Error("No TTS model loaded.")
771
+ if isinstance(audio, list):
772
+ audio = audio[0]
773
+ if isinstance(audio, torch.Tensor):
774
+ audio = audio.cpu().numpy()
775
+ return (sr, audio)
 
 
 
 
 
 
 
 
 
 
 
776
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
777
 
778
+ # --- Code Templates ---
 
 
 
 
 
 
 
 
 
779
 
780
+ TEMPLATES = {
781
+ "Custom": "# Write any Python code here\n# Model state: model, tokenizer, pipe, device\nprint('Hello from HF Playground!')",
782
+ "💬 Chat": '''# Chat with the loaded model
783
+ import torch
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
784
 
785
+ messages = [{"role": "system", "content": "You are a helpful assistant."}]
786
+ messages.append({"role": "user", "content": "What is the capital of France?"})
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
787
 
788
+ if tokenizer.chat_template:
789
+ prompt = tokenizer.apply_chat_template(messages, tokenize=False, add_generation_prompt=True)
790
+ else:
791
+ prompt = messages[-1]["content"]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
792
 
793
+ inputs = tokenizer(prompt, return_tensors="pt").to(device)
794
+ with torch.no_grad():
795
+ out = model.generate(**inputs, max_new_tokens=256, temperature=0.7, do_sample=True)
796
+ resp = tokenizer.decode(out[0][inputs.input_ids.shape[1]:], skip_special_tokens=True)
797
+ print("Response:", resp)''',
798
+ "✏️ Completion": '''# Raw text completion
799
+ import torch
800
 
801
+ prompt = "Once upon a time, in a land far away,"
802
+ inputs = tokenizer(prompt, return_tensors="pt").to(device)
803
+ with torch.no_grad():
804
+ out = model.generate(**inputs, max_new_tokens=256, temperature=0.7, do_sample=True)
805
+ completion = tokenizer.decode(out[0][inputs.input_ids.shape[1]:], skip_special_tokens=True)
806
+ print(prompt + completion)''',
807
+ "🧠 Reasoning": '''# Step-by-step reasoning (for thinking models)
808
+ import torch
809
 
810
+ prompt = "How many r's are in the word 'strawberry'? Think step by step."
811
+ messages = [{"role": "system", "content": "Please reason step by step."},
812
+ {"role": "user", "content": prompt}]
813
+
814
+ if tokenizer.chat_template:
815
+ full = tokenizer.apply_chat_template(messages, tokenize=False, add_generation_prompt=True)
816
+ else:
817
+ full = prompt
818
+
819
+ inputs = tokenizer(full, return_tensors="pt").to(device)
820
+ with torch.no_grad():
821
+ out = model.generate(**inputs, max_new_tokens=512, temperature=0.6, do_sample=True)
822
+ resp = tokenizer.decode(out[0][inputs.input_ids.shape[1]:], skip_special_tokens=True)
823
+ print(resp)''',
824
+ "🌐 Translate": '''# Translation
825
+ import torch
 
 
 
826
 
827
+ src, tgt = "English", "Spanish"
828
+ text = "Hello, how are you today?"
829
+ prompt = f"Translate {src} to {tgt}: {text}"
830
 
831
+ inputs = tokenizer(prompt, return_tensors="pt").to(device)
832
+ with torch.no_grad():
833
+ out = model.generate(**inputs, max_new_tokens=128, temperature=0.3)
834
+ result = tokenizer.decode(out[0][inputs.input_ids.shape[1]:], skip_special_tokens=True)
835
+ print(result)''',
836
+ "📝 Summarize": '''# Summarization
837
+ import torch
838
+
839
+ doc = """The quick brown fox jumps over the lazy dog. \
840
+ This sentence contains every letter of the alphabet at least once. \
841
+ It is commonly used for typing practice and font display."""
842
+
843
+ inputs = tokenizer(doc, return_tensors="pt", truncation=True, max_length=512).to(device)
844
+ with torch.no_grad():
845
+ out = model.generate(**inputs, max_new_tokens=128, temperature=0.3, do_sample=False)
846
+ summary = tokenizer.decode(out[0][inputs.input_ids.shape[1]:], skip_special_tokens=True)
847
+ print("Summary:", summary)''',
848
+ "🎨 Image Gen": '''# Generate an image using the loaded pipeline
849
+ import torch
850
+
851
+ prompt = "A beautiful sunset over mountains, digital art"
852
+ height, width, steps = 256, 256, 9
853
+ seed = 42
854
+
855
+ if pipe is not None:
856
+ gen = torch.Generator(device=device).manual_seed(seed)
857
+ result = pipe(prompt=prompt, height=height, width=width,
858
+ num_inference_steps=steps, generator=gen)
859
+ img = result.images[0]
860
+ img.save("output.png")
861
+ print("Saved to output.png")
862
+ else:
863
+ print("No image pipeline loaded. Load a diffusers model first.")''',
864
+ "🔊 TTS": '''# Generate speech
865
+ text = "Hello, welcome to the Hugging Face playground."
866
+
867
+ if pipe is not None:
868
+ result = pipe(text)
869
+ sr, audio = result["sampling_rate"], result["audio"]
870
+ import scipy.io.wavfile as wav
871
+ wav.write("output.wav", sr, audio)
872
+ print("Saved to output.wav")
873
+ else:
874
+ print("No TTS pipeline loaded. Load a TTS model first.")''',
875
+ "📂 Read File": '''# Read and process a file from the workspace
876
+ import os
877
+
878
+ path = "README.md"
879
+ if os.path.exists(path):
880
+ with open(path, "r") as f:
881
+ content = f.read()
882
+ # Process with model
883
+ inputs = tokenizer(f"Summarize this:\\n{content[:500]}", return_tensors="pt").to(device)
884
+ with torch.no_grad():
885
+ out = model.generate(**inputs, max_new_tokens=128)
886
+ result = tokenizer.decode(out[0][inputs.input_ids.shape[1]:], skip_special_tokens=True)
887
+ print(result)
888
+ else:
889
+ print(f"File not found: {path}")''',
890
+ "#! Shell": '''# Shell commands with #! prefix
891
+ #!ls -la
892
+ #!pwd
893
+ #!echo "Hello from shell!"
894
+
895
+ # Mix with Python
896
+ import os
897
+ print("Python says:", os.getcwd())''',
898
+ }
899
+
900
+ TASK_TEMPLATE_MAP = {
901
+ "text-generation": "💬 Chat",
902
+ "text2text-generation": "✏️ Completion",
903
+ "summarization": "📝 Summarize",
904
+ "translation": "🌐 Translate",
905
+ "image-generation": "🎨 Image Gen",
906
+ "text-to-speech": "🔊 TTS",
907
+ }
908
+
909
+
910
+ # --- Custom Code ---
911
+
912
+ def run_custom_code(code):
913
+ import io as _io, sys as _sys, subprocess as _sp, traceback as _tb, shlex as _shlex
914
+ s = _state
915
+ env = {
916
+ "model": s.get("model"),
917
+ "tokenizer": s.get("tokenizer"),
918
+ "pipe": s.get("pipeline"),
919
+ "device": s.get("device", "cpu"),
920
+ "state": s,
921
+ "__builtins__": __builtins__,
922
  }
923
+ old_out, old_err = _sys.stdout, _sys.stderr
924
+ _sys.stdout = _io.StringIO()
925
+ _sys.stderr = _io.StringIO()
926
+ try:
927
+ py_blocks = []
928
+ for line in code.split("\n"):
929
+ if line.startswith("#!"):
930
+ if py_blocks:
931
+ exec("\n".join(py_blocks), env)
932
+ py_blocks = []
933
+ cmd = line[2:].strip()
934
+ result = _sp.run(cmd, shell=True, capture_output=True, text=True)
935
+ print(result.stdout, end="")
936
+ if result.stderr:
937
+ print(result.stderr, file=_sys.stderr, end="")
938
+ else:
939
+ py_blocks.append(line)
940
+ if py_blocks:
941
+ exec("\n".join(py_blocks), env)
942
+ except Exception:
943
+ print(_tb.format_exc(), file=_sys.stderr)
944
+ out = _sys.stdout.getvalue()
945
+ err = _sys.stderr.getvalue()
946
+ _sys.stdout, _sys.stderr = old_out, old_err
947
+ if err:
948
+ out += "\n--- stderr ---\n" + err
949
+ return out
950
+
951
+
952
+ # ============================================================
953
+ # UI
954
+ # ============================================================
955
+
956
+ CSS = """
957
+ .gradio-container { max-width:1400px;margin:0 auto; }
958
+ @media(max-width:768px){ h1{font-size:1.5rem} }
959
+ """
960
+
961
+ theme = gr.themes.Soft(
962
+ primary_hue="yellow", secondary_hue="amber", neutral_hue="slate",
963
+ font=gr.themes.GoogleFont("Inter"), text_size="lg", spacing_size="md", radius_size="lg",
964
+ ).set(button_primary_background_fill="*primary_500",
965
+ button_primary_background_fill_hover="*primary_600",
966
+ block_title_text_weight="600")
967
+
968
+
969
+ def build_app():
970
+ gr.Markdown("# 🤗 **HF Playground** — enter any model ID to test")
971
+
972
+ with gr.Row(equal_height=True):
973
+ model_input = gr.Dropdown(
974
+ label="Model ID", scale=5, container=True,
975
+ choices=PRESET_CHOICES, value="google/gemma-3-1b-it",
976
+ allow_custom_value=True,
977
+ )
978
+ load_btn = gr.Button("🚀 Load", variant="primary", scale=1, min_width=80)
979
+ use_gpu = gr.Checkbox(label="GPU", value=False, scale=1, show_label=False)
980
+
981
+ use_gpu.change(fn=_set_gpu_flag, inputs=[use_gpu], outputs=[])
982
+
983
+ model_info = gr.Markdown("_No model loaded_")
984
+
985
+ task_state = gr.State("text-generation")
986
+ thinking_state = gr.State(False)
987
+
988
+ # --- Always-visible Code + File Browser ---
989
+ with gr.Accordion("🐍 Python + 📁 Files", open=True):
990
+ with gr.Row():
991
+ with gr.Column(scale=1, min_width=280):
992
+ gr.Markdown("### 📁 Files")
993
+ file_list = gr.Dropdown(label="Workspace Files", choices=[], interactive=True, container=True)
994
+ refresh_btn = gr.Button("🔄 Refresh", scale=1)
995
+ gr.Markdown("---")
996
+ preview_img = gr.Image(label="Preview", type="filepath", visible=False, height=400)
997
+ preview_aud = gr.Audio(label="Preview", type="filepath", visible=False)
998
+ preview_vid = gr.Video(label="Preview", visible=False, height=400)
999
+ preview_txt = gr.Textbox(label="File Content", lines=20, visible=False)
1000
+ with gr.Column(scale=2):
1001
+ gr.Markdown("### 🐍 Python")
1002
+ template_dd = gr.Dropdown(
1003
+ label="Template", choices=list(TEMPLATES.keys()),
1004
+ value="Custom", container=True,
1005
+ )
1006
+ custom_code = gr.Code(label="Code", language="python", lines=12,
1007
+ value=TEMPLATES["Custom"])
1008
+ with gr.Row():
1009
+ custom_run = gr.Button("▶ Run", variant="primary")
1010
+ custom_out = gr.Textbox(label="Output", lines=8)
1011
+
1012
+ # ── Columns for each task type ──
1013
+ cols = {}
1014
+
1015
+ # --- TEXT GENERATION ---
1016
+ with gr.Column(visible=False) as c_text:
1017
+ cols["text-generation"] = c_text
1018
+ cols["text2text-generation"] = c_text
1019
+ gr.Markdown("### 💬 Text Generation")
1020
+ with gr.Tabs():
1021
+ with gr.TabItem("💬 Chat"):
1022
+ chatbot = gr.Chatbot(label="Conversation", height=400)
1023
+ chat_msg = gr.Textbox(label="Message", placeholder="Type...", scale=4, container=True)
1024
+ with gr.Row():
1025
+ chat_btn = gr.Button("Send", variant="primary")
1026
+ chat_clr = gr.Button("🗑 Clear")
1027
+ chat_sys = gr.Textbox(label="System Prompt", value="You are a helpful assistant.", lines=2)
1028
+ with gr.TabItem("✏️ Completion"):
1029
+ comp_in = gr.Textbox(label="Input", lines=5)
1030
+ comp_out = gr.Textbox(label="Output", lines=10)
1031
+ with gr.Row():
1032
+ comp_btn = gr.Button("Generate", variant="primary")
1033
+ comp_stop = gr.Button("⏹ Stop", variant="stop", visible=False)
1034
+ with gr.TabItem("🌐 Translate (prompt)"):
1035
+ tri_in = gr.Textbox(label="Text", lines=5)
1036
+ with gr.Row():
1037
+ tri_src = gr.Dropdown(label="Source", value="auto",
1038
+ choices=["auto","English","Spanish","French","German","Chinese"])
1039
+ tri_tgt = gr.Dropdown(label="Target", value="Spanish",
1040
+ choices=["English","Spanish","French","German","Chinese"])
1041
+ tri_out = gr.Textbox(label="Translation", lines=5)
1042
+ tri_btn = gr.Button("Translate", variant="primary")
1043
+ with gr.Accordion("⚙️ Parameters", open=False):
1044
+ with gr.Row():
1045
+ tk = gr.Slider(32, 4096, 512, 32, label="Max New Tokens")
1046
+ tm = gr.Slider(0, 2, 0.7, 0.05, label="Temperature")
1047
+ with gr.Row():
1048
+ tp = gr.Slider(0, 1, 0.9, 0.05, label="Top-P")
1049
+ tkk = gr.Slider(0, 200, 50, 1, label="Top-K (0=off)")
1050
+ with gr.Row():
1051
+ rp = gr.Slider(1, 2, 1.1, 0.05, label="Repetition Penalty")
1052
+ ds = gr.Checkbox(True, label="Sampling")
1053
+
1054
+ # --- SUMMARIZATION ---
1055
+ with gr.Column(visible=False) as c_summ:
1056
+ cols["summarization"] = c_summ
1057
+ gr.Markdown("### 📝 Summarization")
1058
+ sm_in = gr.Textbox(label="Document", lines=10)
1059
+ sm_out = gr.Textbox(label="Summary", lines=8)
1060
+ sm_btn = gr.Button("Summarize", variant="primary")
1061
+ with gr.Accordion("⚙️ Parameters", open=False):
1062
+ with gr.Row():
1063
+ sm_tk = gr.Slider(32, 1024, 150, 16, label="Max New Tokens")
1064
+ sm_tm = gr.Slider(0, 2, 0.7, 0.05, label="Temperature")
1065
+ with gr.Row():
1066
+ sm_tp = gr.Slider(0, 1, 0.9, 0.05, label="Top-P")
1067
+ sm_tkk = gr.Slider(0, 200, 50, 1, label="Top-K")
1068
+ with gr.Row():
1069
+ sm_rp = gr.Slider(1, 2, 1, 0.05, label="Repetition")
1070
+ sm_ds = gr.Checkbox(True, label="Sampling")
1071
+
1072
+ # --- TRANSLATION ---
1073
+ with gr.Column(visible=False) as c_trans:
1074
+ cols["translation"] = c_trans
1075
+ gr.Markdown("### 🌐 Translation")
1076
+ with gr.Row():
1077
+ dl_src = gr.Dropdown(label="Source", value="en",
1078
+ choices=["en","es","fr","de","zh","ja","ar"])
1079
+ dl_tgt = gr.Dropdown(label="Target", value="es",
1080
+ choices=["en","es","fr","de","zh","ja","ar"])
1081
+ dl_in = gr.Textbox(label="Text", lines=5)
1082
+ dl_out = gr.Textbox(label="Translation", lines=5)
1083
+ dl_btn = gr.Button("Translate", variant="primary")
1084
+ with gr.Accordion("⚙️ Parameters", open=False):
1085
+ with gr.Row():
1086
+ dl_tk = gr.Slider(32, 1024, 256, 16, label="Max New Tokens")
1087
+ dl_tm = gr.Slider(0, 2, 0.7, 0.05, label="Temperature")
1088
+ with gr.Row():
1089
+ dl_tp = gr.Slider(0, 1, 0.9, 0.05, label="Top-P")
1090
+ dl_tkk = gr.Slider(0, 200, 50, 1, label="Top-K")
1091
+ with gr.Row():
1092
+ dl_rp = gr.Slider(1, 2, 1, 0.05, label="Repetition")
1093
+ dl_ds = gr.Checkbox(True, label="Sampling")
1094
+
1095
+ # --- IMAGE GENERATION ---
1096
+ with gr.Column(visible=False) as c_img:
1097
+ cols["image-generation"] = c_img
1098
+ gr.Markdown("### 🎨 Image Generation")
1099
+ img_pr = gr.Textbox(label="Prompt", lines=3)
1100
+ with gr.Row():
1101
+ img_h = gr.Slider(64, 1024, 256, 32, label="Height")
1102
+ img_w = gr.Slider(64, 1024, 256, 32, label="Width")
1103
+ with gr.Row():
1104
+ img_st = gr.Slider(1, 50, 9, 1, label="Steps")
1105
+ img_rz = gr.Checkbox(True, label="Random Seed")
1106
+ img_sd = gr.Number(42, label="Seed", precision=0, visible=False)
1107
+ img_rz.change(fn=lambda r: gr.update(visible=not r), inputs=[img_rz], outputs=[img_sd])
1108
+ img_out = gr.Image(label="Generated Image", type="pil", height=512)
1109
+ img_sts = gr.Textbox(label="Status", value="Idle", interactive=False)
1110
+ img_used = gr.Number(label="Seed", interactive=False)
1111
+ img_btn = gr.Button("🎨 Generate Image", variant="primary")
1112
+ gr.Examples([["Young Chinese woman in red Hanfu."],
1113
+ ["Dragon soaring through clouds at sunset."],
1114
+ ["Astronaut riding a horse on Mars."]],
1115
+ inputs=[img_pr])
1116
+
1117
+ # --- TTS ---
1118
+ with gr.Column(visible=False) as c_tts:
1119
+ cols["text-to-speech"] = c_tts
1120
+ gr.Markdown("### 🔊 Text-to-Speech")
1121
+ tts_txt = gr.Textbox(label="Text", lines=3)
1122
+ tts_btn = gr.Button("🔊 Generate Speech", variant="primary")
1123
+ tts_aud = gr.Audio(label="Speech", type="numpy")
1124
+
1125
+ # ===========================
1126
+ # VISIBILITY MAPPING
1127
+ # ===========================
1128
+ ui_order = ["text-generation", "text2text-generation", "summarization",
1129
+ "translation", "image-generation", "text-to-speech"]
1130
+ ui_cols = [cols.get(t, c_text) for t in ui_order]
1131
+ # deduplicate by id
1132
+ seen = set()
1133
+ unique_cols = []
1134
+ for c in ui_cols:
1135
+ if id(c) not in seen:
1136
+ seen.add(id(c))
1137
+ unique_cols.append(c)
1138
+
1139
+ def set_visible(task):
1140
+ return [gr.update(visible=c == cols.get(task, c_text)) for c in unique_cols]
1141
+
1142
+ # File browser helpers
1143
+ import os as _os, glob as _glob
1144
+
1145
+ def _list_files():
1146
+ files = sorted(f for f in _glob.glob("**/*", recursive=True) if _os.path.isfile(f))
1147
+ return gr.update(choices=[(f, f) for f in files])
1148
+
1149
+ def _preview_file(path):
1150
+ if not path:
1151
+ return [gr.update(visible=False)] * 4
1152
+ ext = _os.path.splitext(path)[1].lower()
1153
+ img = {".png",".jpg",".jpeg",".gif",".bmp",".webp",".svg"}
1154
+ aud = {".mp3",".wav",".ogg",".flac",".m4a",".aac",".wma"}
1155
+ vid = {".mp4",".avi",".mov",".mkv",".webm",".m4v"}
1156
+ if ext in img:
1157
+ return [gr.update(visible=True, value=path), gr.update(visible=False), gr.update(visible=False), gr.update(visible=False)]
1158
+ if ext in aud:
1159
+ return [gr.update(visible=False), gr.update(visible=True, value=path), gr.update(visible=False), gr.update(visible=False)]
1160
+ if ext in vid:
1161
+ return [gr.update(visible=False), gr.update(visible=False), gr.update(visible=True, value=path), gr.update(visible=False)]
1162
  try:
1163
+ with open(path, "r", encoding="utf-8") as f:
1164
+ content = f.read()
 
 
 
 
1165
  except Exception:
1166
+ content = f"<binary or unreadable: {path}>"
1167
+ return [gr.update(visible=False), gr.update(visible=False), gr.update(visible=False), gr.update(visible=True, value=content)]
1168
+
1169
+ # ===========================
1170
+ # EVENT WIRING
1171
+ # ===========================
1172
+
1173
+ # Load model
1174
+ def on_load(model_id_val, progress=gr.Progress()):
1175
+ if not model_id_val:
1176
+ return [gr.update()] * (5 + n_vis)
1177
+ task, thinking, info = load_model(model_id_val, progress)
1178
+ vis_updates = set_visible(task)
1179
+ template_name = TASK_TEMPLATE_MAP.get(task, "Custom")
1180
+ template_code = TEMPLATES.get(template_name, TEMPLATES["Custom"])
1181
+ return [task, thinking, info] + vis_updates + [gr.update(value=template_name), gr.update(value=template_code)]
1182
+
1183
+ n_vis = len(unique_cols)
1184
+ load_outputs = [task_state, thinking_state, model_info] + unique_cols + [template_dd, custom_code]
1185
+
1186
+ model_input.change(fn=on_load, inputs=[model_input], outputs=load_outputs)
1187
+ load_btn.click(fn=on_load, inputs=[model_input], outputs=load_outputs)
1188
+
1189
+ # Chat
1190
+ chat_btn.click(
1191
+ fn=chat_respond,
1192
+ inputs=[chat_msg, chatbot, chat_sys, tk, tm, tp, tkk, rp, ds, thinking_state],
1193
+ outputs=[chatbot, chat_msg],
1194
+ )
1195
+ chat_msg.submit(
1196
+ fn=chat_respond,
1197
+ inputs=[chat_msg, chatbot, chat_sys, tk, tm, tp, tkk, rp, ds, thinking_state],
1198
+ outputs=[chatbot, chat_msg],
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1199
  )
1200
+ chat_clr.click(fn=lambda: ([], ""), outputs=[chatbot, chat_msg])
1201
+
1202
+ # Completion
1203
+ comp_btn.click(
1204
+ fn=lambda: (gr.update(visible=False), gr.update(visible=True)),
1205
+ outputs=[comp_btn, comp_stop],
1206
+ ).then(
1207
+ fn=completion_fn,
1208
+ inputs=[comp_in, tk, tm, tp, tkk, rp, ds, thinking_state],
1209
+ outputs=[comp_out],
1210
+ ).then(
1211
+ fn=lambda: (gr.update(visible=True), gr.update(visible=False)),
1212
+ outputs=[comp_btn, comp_stop],
1213
+ )
1214
+ comp_stop.click(
1215
+ fn=lambda: (_cancel_flag.__setitem__(0, True),) or (gr.update(visible=True), gr.update(visible=False)),
1216
+ outputs=[comp_btn, comp_stop],
1217
  )
1218
 
1219
+ # Translate (text gen tab)
1220
+ tri_btn.click(
1221
+ fn=translate_fn,
1222
+ inputs=[tri_in, tri_src, tri_tgt, tk, tm, tp, tkk, rp, ds, thinking_state],
1223
+ outputs=[tri_out],
1224
+ )
 
 
 
 
 
 
 
 
 
1225
 
1226
+ # Summarization
1227
+ sm_btn.click(
1228
+ fn=summarize_fn,
1229
+ inputs=[sm_in, sm_tk, sm_tm, sm_tp, sm_tkk, sm_rp, sm_ds],
1230
+ outputs=[sm_out],
1231
+ )
 
 
 
 
 
1232
 
1233
+ # Translation dedicated
1234
+ dl_btn.click(
1235
+ fn=translate_fn,
1236
+ inputs=[dl_in, dl_src, dl_tgt, dl_tk, dl_tm, dl_tp, dl_tkk, dl_rp, dl_ds, thinking_state],
1237
+ outputs=[dl_out],
1238
+ )
1239
 
1240
+ # Image
1241
+ img_btn.click(
1242
+ fn=generate_image,
1243
+ inputs=[img_pr, img_h, img_w, img_st, img_sd, img_rz],
1244
+ outputs=[img_out, img_used, img_sts],
1245
+ )
1246
 
1247
+ # TTS
1248
+ tts_btn.click(fn=generate_speech, inputs=[tts_txt], outputs=[tts_aud])
1249
 
1250
+ # Custom code
1251
+ refresh_btn.click(fn=_list_files, outputs=[file_list])
1252
+ file_list.change(fn=_preview_file, inputs=[file_list], outputs=[preview_img, preview_aud, preview_vid, preview_txt])
1253
+ template_dd.change(
1254
+ fn=lambda name: gr.update(value=TEMPLATES.get(name, TEMPLATES["Custom"])),
1255
+ inputs=[template_dd], outputs=[custom_code],
1256
+ )
1257
+ custom_run.click(fn=run_custom_code, inputs=[custom_code], outputs=[custom_out])
1258
+
1259
+ # Footer
1260
+ gr.Markdown("""
1261
+ ---
1262
+ <div style="text-align:center;opacity:0.7;font-size:0.85em;padding:0.5rem 0;">
1263
+ 🤗 HuggingFace Model Playground — Enter any model ID, auto-detect, test instantly.
1264
+ </div>
1265
+ """)
1266
 
1267
 
1268
+ # ============================================================
1269
+ # LAUNCH
1270
+ # ============================================================
1271
+
1272
+ with gr.Blocks(title="HF Model Playground", fill_height=True) as demo:
1273
+ build_app()
1274
 
1275
  if __name__ == "__main__":
1276
+ demo.queue(default_concurrency_limit=3).launch(
1277
+ server_name="0.0.0.0",
1278
+ theme=theme,
1279
+ css=CSS,
1280
+ footer_links=["api", "gradio"],
1281
+ )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
requirements.txt CHANGED
@@ -4,4 +4,10 @@ diffusers
4
  transformers
5
  accelerate
6
  torch
7
- pypdf
 
 
 
 
 
 
 
4
  transformers
5
  accelerate
6
  torch
7
+ huggingface_hub
8
+ sentencepiece
9
+ tiktoken
10
+ datasets
11
+ protobuf
12
+ soundfile
13
+ einops