prajwaluppoor commited on
Commit
fa50451
·
verified ·
1 Parent(s): 12e5521

fallback to v1 stable

Browse files
Files changed (1) hide show
  1. app.py +227 -299
app.py CHANGED
@@ -1,37 +1,34 @@
1
- """
2
- Unity 3D AI Tools Hub - Production Engineered Build
3
- HF Spaces Stable + Router Compatible
4
- """
5
 
6
- # =========================================================
7
- # STABILITY FIXES
8
- # =========================================================
9
  import os
10
  os.environ["GRADIO_SSR_MODE"] = "false"
11
 
12
  import io
13
- import json
14
- import zipfile
15
  import tempfile
16
- import shutil
17
- import random
18
  from pathlib import Path
19
 
20
  import gradio as gr
21
  import requests
22
  from PIL import Image, ImageDraw
23
 
24
- # =========================================================
25
- # PATCH: Gradio Boolean Schema Bug (HF 5.x)
26
- # =========================================================
27
  def _patch_gradio_schema_bug():
 
 
 
 
 
 
 
 
28
  try:
29
  from gradio_client import utils as client_utils
30
  except Exception:
31
  return
32
 
33
  original_get_type = getattr(client_utils, "get_type", None)
34
- if original_get_type is None or getattr(original_get_type, "_patched", False):
35
  return
36
 
37
  def safe_get_type(schema):
@@ -39,355 +36,286 @@ def _patch_gradio_schema_bug():
39
  return "Any"
40
  return original_get_type(schema)
41
 
42
- safe_get_type._patched = True
43
  client_utils.get_type = safe_get_type
44
 
 
45
  _patch_gradio_schema_bug()
46
 
47
- # =========================================================
48
- # CONFIG
49
- # =========================================================
50
  TITLE = "Unity 3D AI Tools Hub"
51
  DESCRIPTION = """
52
- AI-powered toolkit to accelerate Unity game development.
53
- Production-stable build.
 
 
54
  """
55
 
56
  HF_TOKEN = os.getenv("HF_TOKEN", "")
57
- HF_BASE = "https://router.huggingface.co/hf-inference/models"
58
- TIMEOUT = 180
59
 
60
  STYLE_PRESETS = {
61
  "Game Texture": "seamless tileable game texture, {prompt}, pbr, 4k",
62
- "Concept Art": "concept art of {prompt}, ultra detailed, 4k, game ready",
63
- "UI/Icon": "game ui icon of {prompt}, flat vector style",
64
  }
65
 
66
- SCENE_STYLES = [
67
- "Photorealistic HDRP",
68
- "Stylized",
69
- "Low Poly",
70
- "Cyberpunk",
71
- "Industrial",
72
- "Sci-Fi",
73
- ]
74
-
75
  VOICE_MODELS = {
76
  "English": "facebook/mms-tts-eng",
77
  "Hindi": "facebook/mms-tts-hin",
78
  "Spanish": "facebook/mms-tts-spa",
79
  }
80
 
81
- # =========================================================
82
- # HF ROUTER
83
- # =========================================================
 
 
 
 
 
 
84
  def hf_inference(model_id, *, payload=None, data=None, token=""):
 
85
  api_token = token or HF_TOKEN
86
  headers = {"Authorization": f"Bearer {api_token}"} if api_token else {}
87
- url = f"{HF_BASE}/{model_id}"
88
 
 
 
 
 
 
 
 
89
  try:
90
- if data is not None:
91
- return requests.post(url, headers=headers, data=data, timeout=TIMEOUT)
 
92
 
93
- return requests.post(url, headers=headers, json=payload, timeout=TIMEOUT)
94
- except Exception:
95
- return None
96
 
97
- # =========================================================
98
- # TEXTURE GENERATOR
99
- # =========================================================
100
- def generate_texture(prompt, style, token):
101
- if not prompt:
102
- return None, "Enter prompt."
103
 
104
- api_token = token or HF_TOKEN
105
- if not api_token:
106
- return None, "HF Token required."
 
107
 
108
- full_prompt = STYLE_PRESETS[style].format(prompt=prompt)
 
 
109
 
110
- response = hf_inference(
111
- "black-forest-labs/FLUX.1-schnell",
112
- payload={"inputs": full_prompt},
113
- token=api_token,
114
- )
115
 
116
- if response and response.status_code == 200:
117
- return Image.open(io.BytesIO(response.content)), "Texture generated."
 
 
 
 
 
 
 
 
 
 
 
 
 
118
 
119
- return None, "Texture generation failed."
 
 
 
 
 
120
 
121
- # =========================================================
122
- # VOICE GENERATOR
123
- # =========================================================
124
- def generate_voice(text, language, token):
125
- if not text:
126
- return None, "Enter dialogue."
127
 
128
- response = hf_inference(
129
- VOICE_MODELS[language],
130
- payload={"inputs": text},
131
- token=token or HF_TOKEN,
132
- )
133
 
134
- if response and response.status_code == 200:
135
- with tempfile.NamedTemporaryFile(suffix=".wav", delete=False) as f:
136
- f.write(response.content)
137
- return f.name, "Voice ready."
138
 
139
- return None, "Voice generation failed."
 
140
 
141
- # =========================================================
142
- # OBJECT DETECTOR
143
- # =========================================================
144
- def detect_objects(image, confidence, token):
145
- if image is None:
146
- return None, "Upload image."
 
 
147
 
148
- img_bytes = io.BytesIO()
149
- image.save(img_bytes, format="PNG")
 
150
 
151
- response = hf_inference(
152
- "facebook/detr-resnet-50",
153
- data=img_bytes.getvalue(),
154
- token=token or HF_TOKEN,
155
- )
156
 
157
- if not response or response.status_code != 200:
158
- return image, "Detection failed."
 
 
159
 
160
- detections = response.json()
161
- annotated = image.copy()
162
- draw = ImageDraw.Draw(annotated)
163
 
164
- results = []
165
- for d in detections:
166
- if d["score"] >= confidence:
167
- box = d["box"]
168
- draw.rectangle(
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
169
  [box["xmin"], box["ymin"], box["xmax"], box["ymax"]],
170
  outline="red",
171
  width=3,
172
  )
173
- results.append(f"{d['label']} ({d['score']:.2f})")
174
-
175
- return annotated, "\n".join(results)
176
-
177
- # =========================================================
178
- # TRELLIS 3D GENERATOR (Safe)
179
- # =========================================================
180
- def generate_3d_asset(image):
181
- try:
182
- from gradio_client import Client, handle_file
183
 
184
- tmp = tempfile.NamedTemporaryFile(suffix=".png", delete=False)
185
- image.save(tmp.name)
186
- tmp.close()
187
 
188
- client = Client("trellis-community/TRELLIS")
 
 
189
 
190
- result = client.predict(
191
- image=handle_file(tmp.name),
192
- multiimages=[],
193
- is_multiimage=False,
194
- seed=42,
195
- ss_guidance_strength=7.5,
196
- ss_sampling_steps=12,
197
- slat_guidance_strength=7.5,
198
- slat_sampling_steps=12,
199
- multiimage_algo="stochastic",
200
- mesh_simplify=0.9,
201
- texture_size=1024,
202
- api_name="/generate_and_extract_glb",
203
- )
204
 
205
- os.unlink(tmp.name)
 
 
 
206
 
207
- if result and result[1] and os.path.exists(result[1]):
208
- return result[1]
209
 
210
- return None
211
- except Exception:
212
- return None
213
-
214
- # =========================================================
215
- # FALLBACK PLANNER
216
- # =========================================================
217
- def fallback_scene_planner(prompt):
218
- base = [
219
- "Ground",
220
- "Main Building",
221
- "Foreground Props",
222
- "Background Elements",
223
- "Lighting",
224
- "Skybox",
225
- "Vegetation",
226
- ]
227
-
228
- p = prompt.lower()
229
-
230
- if "factory" in p:
231
- base += ["Robotic Arm", "Assembly Line", "Conveyor Belt"]
232
-
233
- if "city" in p:
234
- base += ["Buildings", "Vehicles", "Street Lights"]
235
-
236
- return base
237
-
238
- # =========================================================
239
- # LLM PLANNER (Router Safe)
240
- # =========================================================
241
- def plan_scene_llm(prompt, token):
242
- if not token:
243
- return fallback_scene_planner(prompt)
244
-
245
- planning_prompt = f"""
246
- Return ONLY a JSON array of 3D asset names required for this scene.
247
-
248
- Scene:
249
- {prompt}
250
- """
251
 
252
- response = hf_inference(
253
- "mistralai/Mistral-7B-Instruct-v0.2",
254
- payload={"inputs": planning_prompt, "parameters": {"max_new_tokens": 200}},
255
- token=token,
256
- )
257
 
258
- if not response or response.status_code != 200:
259
- return fallback_scene_planner(prompt)
 
260
 
261
- try:
262
- data = response.json()
263
- text = data[0]["generated_text"]
264
- start = text.find("[")
265
- end = text.rfind("]")
266
- return json.loads(text[start:end+1])
267
- except:
268
- return fallback_scene_planner(prompt)
269
-
270
- # =========================================================
271
- # SCENE PIPELINE (Spatial + Manifest + Safe)
272
- # =========================================================
273
- def run_scene_pipeline(prompt, style, asset_count, token, progress=gr.Progress()):
274
 
275
- if not prompt:
276
- return None, "Enter scene description."
277
-
278
- progress(0.05, desc="Planning scene...")
279
- assets = plan_scene_llm(prompt, token)[:asset_count]
280
-
281
- root = Path(tempfile.mkdtemp())
282
- mesh_dir = root / "Meshes"
283
- tex_dir = root / "Textures"
284
- mesh_dir.mkdir()
285
- tex_dir.mkdir()
286
-
287
- manifest = {
288
- "scene_prompt": prompt,
289
- "style": style,
290
- "assets": []
291
- }
292
-
293
- for i, asset in enumerate(assets):
294
- progress(0.1 + i/len(assets)*0.7, desc=f"Generating {asset}")
295
-
296
- styled_prompt = f"{asset} for a {style} Unity scene"
297
-
298
- img, _ = generate_texture(styled_prompt, "Concept Art", token)
299
- if img is None:
300
- continue
301
-
302
- tex_path = tex_dir / f"{asset}.png"
303
- img.save(tex_path)
304
-
305
- glb_path = generate_3d_asset(img)
306
- if not glb_path:
307
- continue
308
-
309
- final_mesh = mesh_dir / f"{asset}.glb"
310
- shutil.copy(glb_path, final_mesh)
311
-
312
- manifest["assets"].append({
313
- "name": asset,
314
- "mesh": final_mesh.name,
315
- "texture": tex_path.name,
316
- "position": [
317
- round(random.uniform(-10, 10), 2),
318
- 0,
319
- round(random.uniform(-10, 10), 2)
320
- ],
321
- "rotation": [0, random.randint(0, 360), 0],
322
- "scale": [1, 1, 1]
323
- })
324
-
325
- with open(root / "scene_manifest.json", "w") as f:
326
- json.dump(manifest, f, indent=2)
327
-
328
- zip_path = tempfile.NamedTemporaryFile(suffix=".zip", delete=False).name
329
- with zipfile.ZipFile(zip_path, "w") as zipf:
330
- for file in root.rglob("*"):
331
- zipf.write(file, arcname=file.relative_to(root))
332
-
333
- progress(1.0, desc="Scene Complete")
334
- return zip_path, f"Generated {len(manifest['assets'])} assets."
335
-
336
- # =========================================================
337
- # UI
338
- # =========================================================
339
  with gr.Blocks(title=TITLE, theme=gr.themes.Soft()) as demo:
340
-
341
  gr.Markdown(f"# {TITLE}\n{DESCRIPTION}")
342
 
343
- token_input = gr.Textbox(label="Hugging Face Token", type="password")
 
 
 
 
344
 
345
  with gr.Tabs():
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
346
 
347
- with gr.Tab("🎮 3D Asset"):
348
- img = gr.Image(type="pil")
349
- btn = gr.Button("Generate GLB")
350
- out = gr.File()
351
- status = gr.Textbox()
352
- btn.click(lambda i, t: (generate_3d_asset(i), "Done"), [img, token_input], [out, status])
353
-
354
- with gr.Tab("🎨 Texture"):
355
- prompt = gr.Textbox()
356
- style = gr.Dropdown(list(STYLE_PRESETS.keys()), value="Game Texture")
357
- btn = gr.Button("Generate")
358
- img_out = gr.Image()
359
- status = gr.Textbox()
360
- btn.click(generate_texture, [prompt, style, token_input], [img_out, status])
 
 
 
 
 
 
 
 
361
 
362
  with gr.Tab("🔍 Object Detector"):
363
- image = gr.Image(type="pil")
364
- conf = gr.Slider(0.1, 0.9, 0.7)
365
- btn = gr.Button("Detect")
366
- out = gr.Image()
367
- status = gr.Textbox()
368
- btn.click(detect_objects, [image, conf, token_input], [out, status])
369
-
370
- with gr.Tab("🎤 Voice"):
371
- text = gr.Textbox()
372
- lang = gr.Dropdown(list(VOICE_MODELS.keys()), value="English")
373
- btn = gr.Button("Generate Voice")
374
- audio = gr.Audio(type="filepath")
375
- status = gr.Textbox()
376
- btn.click(generate_voice, [text, lang, token_input], [audio, status])
377
-
378
- with gr.Tab("🌍 Scene Pipeline"):
379
- scene_prompt = gr.Textbox(label="Scene Description")
380
- style_scene = gr.Dropdown(SCENE_STYLES, value="Photorealistic HDRP")
381
- density = gr.Slider(1, 8, value=4)
382
- btn = gr.Button("Generate Full Scene", variant="primary")
383
- zip_out = gr.File()
384
- status = gr.Textbox()
385
- btn.click(
386
- run_scene_pipeline,
387
- [scene_prompt, style_scene, density, token_input],
388
- [zip_out, status],
 
 
 
 
 
 
 
389
  )
390
 
391
- gr.Markdown("--- Built by Prajwal Uppoor")
 
 
 
392
 
393
  demo.queue().launch(share=True,show_api=False)
 
1
+ """Unity 3D AI Tools Hub for Hugging Face Spaces."""
 
 
 
2
 
3
+ # Disable SSR mode for better Hugging Face Spaces stability.
 
 
4
  import os
5
  os.environ["GRADIO_SSR_MODE"] = "false"
6
 
7
  import io
 
 
8
  import tempfile
 
 
9
  from pathlib import Path
10
 
11
  import gradio as gr
12
  import requests
13
  from PIL import Image, ImageDraw
14
 
15
+
 
 
16
  def _patch_gradio_schema_bug():
17
+ """Patch Gradio client schema parsing for boolean JSON schema fragments.
18
+
19
+ HF Spaces with Gradio 5.12.x can intermittently throw:
20
+ `TypeError: argument of type 'bool' is not iterable`
21
+ while rendering `/` when API info is generated. The root cause is a bool
22
+ JSON schema node (e.g., `additionalProperties: true`) being passed into
23
+ `gradio_client.utils.get_type`, which expects a dict.
24
+ """
25
  try:
26
  from gradio_client import utils as client_utils
27
  except Exception:
28
  return
29
 
30
  original_get_type = getattr(client_utils, "get_type", None)
31
+ if original_get_type is None or getattr(original_get_type, "_hf_space_patched", False):
32
  return
33
 
34
  def safe_get_type(schema):
 
36
  return "Any"
37
  return original_get_type(schema)
38
 
39
+ safe_get_type._hf_space_patched = True
40
  client_utils.get_type = safe_get_type
41
 
42
+
43
  _patch_gradio_schema_bug()
44
 
 
 
 
45
  TITLE = "Unity 3D AI Tools Hub"
46
  DESCRIPTION = """
47
+ ### AI-powered toolkit to accelerate Unity game development.
48
+ *Designed by Prajwal Uppoor — SWE III @ Walmart*
49
+
50
+ This hub proxies heavy AI tasks to existing Hugging Face Spaces and APIs, providing a lightweight, fast, and free experience.
51
  """
52
 
53
  HF_TOKEN = os.getenv("HF_TOKEN", "")
54
+ HF_INFERENCE_BASE = "https://router.huggingface.co/hf-inference/models"
55
+ REQUEST_TIMEOUT_SECONDS = 120
56
 
57
  STYLE_PRESETS = {
58
  "Game Texture": "seamless tileable game texture, {prompt}, pbr, 4k",
59
+ "Concept Art": "concept art, {prompt}, fantasy, high detail, artstation",
60
+ "UI/Icon": "game ui icon, {prompt}, flat, clean, vector",
61
  }
62
 
 
 
 
 
 
 
 
 
 
63
  VOICE_MODELS = {
64
  "English": "facebook/mms-tts-eng",
65
  "Hindi": "facebook/mms-tts-hin",
66
  "Spanish": "facebook/mms-tts-spa",
67
  }
68
 
69
+ GUIDE_MD = """
70
+ # LoRA Fine-Tuning Guide
71
+ 1. **Prepare**: Collect at least 10 images that match your target style.
72
+ 2. **Train**: Use [AutoTrain Advanced](https://huggingface.co/spaces/Caramelily/autotrain-advanced) to fine-tune a LoRA.
73
+ 3. **Export**: Download LoRA weights and config.
74
+ 4. **Integrate**: Generate assets in this hub and import them into Unity.
75
+ """
76
+
77
+
78
  def hf_inference(model_id, *, payload=None, data=None, token=""):
79
+ """Send an inference request through the HF router."""
80
  api_token = token or HF_TOKEN
81
  headers = {"Authorization": f"Bearer {api_token}"} if api_token else {}
82
+ url = f"{HF_INFERENCE_BASE}/{model_id}"
83
 
84
+ if data is not None:
85
+ return requests.post(url, headers=headers, data=data, timeout=REQUEST_TIMEOUT_SECONDS)
86
+
87
+ return requests.post(url, headers=headers, json=payload, timeout=REQUEST_TIMEOUT_SECONDS)
88
+
89
+
90
+ def _format_http_error(response, fallback_message="Request failed"):
91
  try:
92
+ details = response.json()
93
+ except ValueError:
94
+ details = response.text.strip()
95
 
96
+ return f"{fallback_message} ({response.status_code}): {details or 'No details returned'}"
 
 
97
 
 
 
 
 
 
 
98
 
99
+ def generate_3d_asset(image, seed, guidance, steps, token):
100
+ """Proxy image-to-GLB generation to TRELLIS."""
101
+ if image is None:
102
+ return None, "Please upload a reference image first."
103
 
104
+ tmp_path = None
105
+ try:
106
+ from gradio_client import Client, handle_file
107
 
108
+ with tempfile.NamedTemporaryFile(suffix=".png", delete=False) as tmp_img:
109
+ image.save(tmp_img.name)
110
+ tmp_path = Path(tmp_img.name)
 
 
111
 
112
+ client = Client("trellis-community/TRELLIS")
113
+ result = client.predict(
114
+ image=handle_file(str(tmp_path)),
115
+ multiimages=[],
116
+ is_multiimage=False,
117
+ seed=int(seed),
118
+ ss_guidance_strength=float(guidance),
119
+ ss_sampling_steps=int(steps),
120
+ slat_guidance_strength=7.5,
121
+ slat_sampling_steps=12,
122
+ multiimage_algo="stochastic",
123
+ mesh_simplify=0.95,
124
+ texture_size=1024,
125
+ api_name="/generate_and_extract_glb",
126
+ )
127
 
128
+ return result[1], "3D model generated! Download the .GLB and import it into Unity."
129
+ except Exception as exc:
130
+ return None, f"3D generation failed: {exc}"
131
+ finally:
132
+ if tmp_path and tmp_path.exists():
133
+ tmp_path.unlink()
134
 
 
 
 
 
 
 
135
 
136
+ def generate_texture(prompt, style_preset, token):
137
+ """Generate textures or concept art using FLUX Schnell."""
138
+ if not prompt:
139
+ return None, "Please enter a prompt."
 
140
 
141
+ api_token = token or HF_TOKEN
142
+ if not api_token:
143
+ return None, "Auth required: provide a Hugging Face token."
 
144
 
145
+ prompt_template = STYLE_PRESETS.get(style_preset, "{prompt}")
146
+ full_prompt = prompt_template.format(prompt=prompt)
147
 
148
+ try:
149
+ response = hf_inference(
150
+ "black-forest-labs/FLUX.1-schnell",
151
+ payload={"inputs": full_prompt},
152
+ token=api_token,
153
+ )
154
+ if response.status_code == 200:
155
+ return Image.open(io.BytesIO(response.content)), "Texture generated successfully."
156
 
157
+ return None, _format_http_error(response, "Texture generation failed")
158
+ except Exception as exc:
159
+ return None, f"Texture generation failed: {exc}"
160
 
 
 
 
 
 
161
 
162
+ def detect_objects(image, confidence_threshold, token):
163
+ """Run object detection via DETR and draw bounding boxes."""
164
+ if image is None:
165
+ return None, "Please upload an image."
166
 
167
+ try:
168
+ img_bytes = io.BytesIO()
169
+ image.save(img_bytes, format="PNG")
170
 
171
+ response = hf_inference(
172
+ "facebook/detr-resnet-50",
173
+ data=img_bytes.getvalue(),
174
+ token=token or HF_TOKEN,
175
+ )
176
+ if response.status_code != 200:
177
+ return image, _format_http_error(response, "Object detection failed")
178
+
179
+ detections = response.json()
180
+ annotated = image.copy()
181
+ drawer = ImageDraw.Draw(annotated)
182
+
183
+ lines = ["Found objects:"]
184
+ kept = 0
185
+ for detection in detections:
186
+ score = detection.get("score", 0)
187
+ if score < confidence_threshold:
188
+ continue
189
+
190
+ box = detection["box"]
191
+ drawer.rectangle(
192
  [box["xmin"], box["ymin"], box["xmax"], box["ymax"]],
193
  outline="red",
194
  width=3,
195
  )
196
+ lines.append(f"- {detection['label']} ({score:.1%})")
197
+ kept += 1
 
 
 
 
 
 
 
 
198
 
199
+ if kept == 0:
200
+ lines.append("- No objects matched the confidence threshold.")
 
201
 
202
+ return annotated, "\n".join(lines)
203
+ except Exception as exc:
204
+ return image, f"Object detection failed: {exc}"
205
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
206
 
207
+ def generate_voice(text, language, token):
208
+ """Generate speech audio from text via MMS TTS."""
209
+ if not text:
210
+ return None, "Please enter dialogue text."
211
 
212
+ model_id = VOICE_MODELS.get(language, VOICE_MODELS["English"])
 
213
 
214
+ try:
215
+ response = hf_inference(model_id, payload={"inputs": text}, token=token or HF_TOKEN)
216
+ if response.status_code != 200:
217
+ return None, _format_http_error(response, "Voice generation failed")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
218
 
219
+ with tempfile.NamedTemporaryFile(suffix=".wav", delete=False) as tmp_file:
220
+ tmp_file.write(response.content)
221
+ tmp_audio_path = tmp_file.name
 
 
222
 
223
+ return tmp_audio_path, "Voice clip generated successfully."
224
+ except Exception as exc:
225
+ return None, f"Voice generation failed: {exc}"
226
 
 
 
 
 
 
 
 
 
 
 
 
 
 
227
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
228
  with gr.Blocks(title=TITLE, theme=gr.themes.Soft()) as demo:
 
229
  gr.Markdown(f"# {TITLE}\n{DESCRIPTION}")
230
 
231
+ token_input = gr.Textbox(
232
+ label="Hugging Face Token (optional, required for some models)",
233
+ type="password",
234
+ placeholder="hf_xxx...",
235
+ )
236
 
237
  with gr.Tabs():
238
+ with gr.Tab("🎮 3D Asset Generator"):
239
+ with gr.Row():
240
+ with gr.Column():
241
+ img_3d = gr.Image(type="pil", label="Reference Image")
242
+ with gr.Row():
243
+ seed_3d = gr.Number(value=42, label="Seed", precision=0)
244
+ guidance_3d = gr.Slider(1, 15, 7.5, label="Guidance")
245
+ steps_3d = gr.Slider(8, 32, 12, step=1, label="Sampling Steps")
246
+ btn_3d = gr.Button("Generate .GLB", variant="primary")
247
+ with gr.Column():
248
+ out_3d = gr.File(label="Download Mesh (.glb)")
249
+ status_3d = gr.Textbox(label="Status")
250
+
251
+ btn_3d.click(
252
+ fn=generate_3d_asset,
253
+ inputs=[img_3d, seed_3d, guidance_3d, steps_3d, token_input],
254
+ outputs=[out_3d, status_3d],
255
+ )
256
 
257
+ with gr.Tab("🎨 Texture Generator"):
258
+ with gr.Row():
259
+ with gr.Column():
260
+ prompt_texture = gr.Textbox(
261
+ label="Prompt",
262
+ placeholder="Stone wall texture for a fantasy dungeon",
263
+ )
264
+ style_texture = gr.Dropdown(
265
+ choices=list(STYLE_PRESETS.keys()),
266
+ value="Game Texture",
267
+ label="Style Preset",
268
+ )
269
+ btn_texture = gr.Button("Generate Texture", variant="primary")
270
+ with gr.Column():
271
+ out_texture = gr.Image(label="Generated Image")
272
+ status_texture = gr.Textbox(label="Status")
273
+
274
+ btn_texture.click(
275
+ fn=generate_texture,
276
+ inputs=[prompt_texture, style_texture, token_input],
277
+ outputs=[out_texture, status_texture],
278
+ )
279
 
280
  with gr.Tab("🔍 Object Detector"):
281
+ with gr.Row():
282
+ with gr.Column():
283
+ image_detect = gr.Image(type="pil", label="Reference Scene")
284
+ confidence_detect = gr.Slider(0.1, 0.9, 0.7, label="Confidence Threshold")
285
+ btn_detect = gr.Button("Detect Objects", variant="primary")
286
+ with gr.Column():
287
+ out_detect = gr.Image(label="Detection Result")
288
+ status_detect = gr.Textbox(label="Results")
289
+
290
+ btn_detect.click(
291
+ fn=detect_objects,
292
+ inputs=[image_detect, confidence_detect, token_input],
293
+ outputs=[out_detect, status_detect],
294
+ )
295
+
296
+ with gr.Tab("🎤 Voice Generator"):
297
+ with gr.Row():
298
+ with gr.Column():
299
+ text_voice = gr.Textbox(label="Dialogue", placeholder="Welcome to Factory XR Lab.")
300
+ language_voice = gr.Dropdown(
301
+ choices=list(VOICE_MODELS.keys()),
302
+ value="English",
303
+ label="Language",
304
+ )
305
+ btn_voice = gr.Button("Generate Voice", variant="primary")
306
+ with gr.Column():
307
+ out_voice = gr.Audio(label="Generated Audio", type="filepath")
308
+ status_voice = gr.Textbox(label="Status")
309
+
310
+ btn_voice.click(
311
+ fn=generate_voice,
312
+ inputs=[text_voice, language_voice, token_input],
313
+ outputs=[out_voice, status_voice],
314
  )
315
 
316
+ with gr.Tab("📚 LoRA Guide"):
317
+ gr.Markdown(GUIDE_MD)
318
+
319
+ gr.Markdown("--- Built by **Prajwal Uppoor**")
320
 
321
  demo.queue().launch(share=True,show_api=False)