castlebbs commited on
Commit
5fc21d8
ยท
1 Parent(s): ad0c29f

Add application

Browse files
Files changed (3) hide show
  1. README.md +10 -0
  2. app.py +335 -0
  3. requirements.txt +3 -0
README.md CHANGED
@@ -9,6 +9,16 @@ app_file: app.py
9
  pinned: false
10
  license: mit
11
  short_description: 3D Game Environment Builder MCP
 
 
12
  ---
13
 
 
 
 
 
 
 
 
 
14
  Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
9
  pinned: false
10
  license: mit
11
  short_description: 3D Game Environment Builder MCP
12
+ tags:
13
+ - mcp-server-track
14
  ---
15
 
16
+ Team members: castlebbs@ stargarnet@
17
+ Modeling of Hugging Face 3D: zinkenite@
18
+
19
+ Youtube video: https://www.youtube.com/watch?v=09Dk9OL65bc
20
+
21
+
22
+
23
+
24
  Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
app.py ADDED
@@ -0,0 +1,335 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import json
2
+ import re
3
+ import anthropic
4
+ import gradio as gr
5
+ import modal
6
+ import base64
7
+ import os
8
+ import tempfile
9
+
10
+ # Modal function to run the 3D generation pipeline
11
+ text_to_3d = modal.Function.from_name("flux-trellis-gguf-3d-pipeline", "text_to_3d")
12
+
13
+
14
+ def generate_3d_prompts(player_bio: str, num_assets: int = 10) -> list:
15
+ """
16
+ Analyze player bio using Claude Sonnet to generate 3D asset prompts.
17
+
18
+ Args:
19
+ player_bio (str): The player's biographical information
20
+ num_assets (int): Number of assets to generate (default: 10)
21
+
22
+ Returns:
23
+ list: List of 3D generation prompts
24
+ """
25
+ client = anthropic.Anthropic()
26
+
27
+ system_prompt = f"""You are an expert 3D scene designer for video games. Your task is to analyze a player's biographical information and identify specific 3D assets that would create a comfortable, personalized environment for them.
28
+
29
+ Based on the player's bio, generate a list of exactly {num_assets} specific 3D asset prompts that would compose a scene tailored to their interests, personality, and background.
30
+
31
+ Objects should be deeply personal and couldn't exist in any generic asset library. You should combine multiple elements into a single prompt if they are closely related. For instance, if the player loves both coffee and gaming, you might create a prompt for a "gaming desk with a custom coffee station."
32
+ Prefer 3d assets, which combine multiple elements instead of single objects like a piece of furniture or a decoration.
33
+ Each prompt should include: 3d isomorphic, white background. This is for 3D game asset generation.
34
+ Format your response as a simple JSON array of strings, where each string is a detailed prompt for 3D generation. Focus on objects like furniture, decorations, equipment, or environmental elements.
35
+
36
+ Follow this format:
37
+ <output>
38
+ ```json
39
+ [
40
+ "prompt 1",
41
+ "prompt 2",
42
+ "prompt 3",
43
+ ]
44
+ ```
45
+ </output>
46
+ """
47
+
48
+ response = client.messages.create(
49
+ model="claude-sonnet-4-20250514",
50
+ max_tokens=1024,
51
+ system=system_prompt,
52
+ messages=[{"role": "user", "content": f"Player bio: {player_bio}"}],
53
+ )
54
+
55
+ prompts_text = response.content[0].text
56
+
57
+ # Extract JSON from the response text using regex
58
+ try:
59
+ # Find JSON array between ```json ``` markers
60
+ json_pattern = r"```json\s*(\[.*?\])\s*```"
61
+ match = re.search(json_pattern, prompts_text, re.DOTALL)
62
+ if match:
63
+ json_str = match.group(1)
64
+ return json.loads(json_str)
65
+ else:
66
+ # Fallback: try to find any JSON array
67
+ array_pattern = r"(\[.*?\])"
68
+ match = re.search(array_pattern, prompts_text, re.DOTALL)
69
+ if match:
70
+ return json.loads(match.group(1))
71
+ else:
72
+ return [prompts_text]
73
+ except (json.JSONDecodeError, ValueError):
74
+ # Fallback: return the raw text if JSON parsing fails
75
+ return [prompts_text]
76
+
77
+
78
+ def generate_3d_assets(player_bio: str, num_assets: int = 10) -> str:
79
+ """
80
+ Generate 3D assets based on a player's bio for video game scene composition.
81
+
82
+ Args:
83
+ player_bio (str): The player's biographical information
84
+ num_assets (int): Number of assets to generate
85
+
86
+ Returns:
87
+ str: JSON string containing prompts and GLB file data
88
+ """
89
+ try:
90
+ # Analyze bio with Claude to get 3D prompts
91
+ prompts = generate_3d_prompts(player_bio, num_assets)
92
+
93
+ # Generate 3D assets using Modal function
94
+ modal_results = text_to_3d.map(prompts)
95
+
96
+ # Process results and prepare GLB data for API/MCP consumption
97
+ generated_assets = []
98
+ for i, result in enumerate(modal_results):
99
+ if "glb_file" in result:
100
+ generated_assets.append(
101
+ {
102
+ "prompt": prompts[i],
103
+ "glb_data": base64.b64encode(result["glb_file"]).decode(
104
+ "utf-8"
105
+ ),
106
+ "size_bytes": len(result["glb_file"]),
107
+ "asset_id": f"asset_{i+1}",
108
+ }
109
+ )
110
+
111
+ result_dict = {
112
+ "assets": generated_assets,
113
+ "total_assets": len(generated_assets),
114
+ }
115
+
116
+ return json.dumps(result_dict)
117
+
118
+ except Exception as e:
119
+ error_dict = {"error": str(e), "prompts": [], "assets": [], "total_assets": 0}
120
+ return json.dumps(error_dict)
121
+
122
+
123
+ def generate_3d_assets_with_display(player_bio: str, num_assets: int = 10) -> tuple:
124
+ """
125
+ Generate 3D assets and prepare them for both JSON output and 3D display.
126
+
127
+ Returns:
128
+ tuple: (json_output, model_paths_dict, num_assets)
129
+ """
130
+ result_json = generate_3d_assets(player_bio, num_assets)
131
+ result = json.loads(result_json)
132
+
133
+ if "error" in result:
134
+ return result_json, {}, num_assets
135
+
136
+ # Save GLB files to temporary directory for display
137
+ temp_dir = tempfile.mkdtemp()
138
+ model_paths = {}
139
+
140
+ for i, asset in enumerate(result.get("assets", [])):
141
+ if "glb_data" in asset:
142
+ # Decode base64 back to bytes and save to file
143
+ glb_bytes = base64.b64decode(asset["glb_data"])
144
+ model_path = os.path.join(temp_dir, f"model_{i+1}.glb")
145
+ with open(model_path, "wb") as f:
146
+ f.write(glb_bytes)
147
+ model_paths[f"model_{i+1}"] = model_path
148
+
149
+ return result_json, model_paths, num_assets
150
+
151
+ # Create a clean Gradio interface using Blocks
152
+ with gr.Blocks(theme=gr.themes.Monochrome(), title="3D Scene Asset Generator") as demo:
153
+
154
+ gr.Markdown("# ๐ŸŽฎ 3D Game Environment Builder ๐Ÿ—๏ธ")
155
+
156
+ with gr.Row():
157
+ with gr.Column(scale=2):
158
+ gr.Markdown(
159
+ """
160
+ Transform player personalities into immersive 3D game environments! Create fast personalized 3D environments to a player, by using information from
161
+ their bio. This tool will craft personalized 3D assets that reflect each player's unique interests, hobbies, and lifestyle, allowing you to build unique scenes for video games.
162
+
163
+
164
+ This space is used as a MCP Server, example usage:
165
+ ```bash
166
+ mcptools call generate_3d_assets --params '{"player_bio":"Elena is a music producer who [...]"}' https://{gradio-url}:7860/gradio_api/mcp/sse
167
+ ```
168
+ This returns a JSON with the generated 3D assets in GLB format, along with their description.
169
+ """
170
+ )
171
+ with gr.Column(scale=1):
172
+ gr.HTML(
173
+ """
174
+ <iframe width="100%" height="315" src="https://www.youtube.com/embed/09Dk9OL65bc"
175
+ frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
176
+ allowfullscreen></iframe>
177
+ """
178
+ )
179
+
180
+ gr.Image("images/pipeline.png", label="๐ŸŽฏ Pipeline Overview", show_label=True, height=200)
181
+
182
+ gr.Markdown(
183
+ """
184
+ <div style="background-color: #e7f3ff; border-left: 4px solid #2196F3; padding: 16px; margin: 16px 0; border-radius: 4px;">
185
+ <h4 style="color: #1976D2; margin-top: 0; margin-bottom: 8px;">โ„น๏ธ Testing Information</h4>
186
+ <p style="margin: 0; color: #333;">
187
+ <strong>Note:</strong> This space cannot be directly tested as we didn't want users to enter their own API keys for the various providers used in the pipeline.
188
+ </p>
189
+ <p style="margin: 8px 0 0 0; color: #333;">
190
+ However, we provide all the source code and the instructions to build your own and test it at:
191
+ <a href="https://github.com/castlebbs/gradio-mcp-hackathon/" target="_blank" style="color: #1976D2; text-decoration: none; font-weight: bold;">๐Ÿ”— GitHub Repository</a>
192
+ </p>
193
+ </div>
194
+ """
195
+ )
196
+
197
+ with gr.Row():
198
+ with gr.Column(scale=3):
199
+ bio_input = gr.Textbox(
200
+ label="๐Ÿ“ Player Bio",
201
+ placeholder=(
202
+ "Tell us about the player's interests, hobbies, personality, "
203
+ "and lifestyle...\n\n"
204
+ "Example: Sarah is an avid reader who loves fantasy novels and "
205
+ "cozy spaces. She enjoys knitting while listening to classical "
206
+ "music and has a collection of houseplants. Her ideal environment "
207
+ "would be warm and inviting with lots of natural elements."
208
+ ),
209
+ lines=8,
210
+ max_lines=10,
211
+ )
212
+
213
+ with gr.Column(scale=1):
214
+ num_assets_slider = gr.Slider(
215
+ minimum=1,
216
+ maximum=10,
217
+ value=5,
218
+ step=1,
219
+ label="๐ŸŽฏ Number of 3D Assets to Generate.",
220
+ info="Select how many 3D assets you want to generate (1-10).\n\nA"
221
+ "Assets generation run in parallel in dedicated pipelines."
222
+ "Each pipeline runs on a separate Modal Container with A100-40GB"
223
+ "attached to them",
224
+ )
225
+
226
+ with gr.Row():
227
+ generate_btn = gr.Button(
228
+ "โœจ Generate 3D Assets", variant="primary", size="lg"
229
+ )
230
+
231
+ with gr.Row():
232
+ with gr.Column(scale=1):
233
+ output_json = gr.JSON(label="๐ŸŽฏ Generated 3D Assets Results")
234
+ with gr.Column(scale=1):
235
+ # Create tabs that will be shown/hidden based on num_assets
236
+ model_tabs = gr.Tabs()
237
+ with model_tabs:
238
+ model_components = []
239
+ for i in range(1, 11):
240
+ with gr.Tab(f"Model {i}", visible=(i <= 5)) as tab:
241
+ model_3d = gr.Model3D(label=f"๐ŸŽจ 3D Model {i}", height=400)
242
+ model_components.append((tab, model_3d))
243
+
244
+ # Add examples
245
+ gr.Examples(
246
+ examples=[
247
+ [
248
+ "Alice is a nature lover who enjoys reading fantasy novels. She has "
249
+ "a collection of vintage books and loves to spend time in her cozy "
250
+ "garden with her cat, Whiskers. Alice also enjoys painting landscapes "
251
+ "and has a small easel set up in her living room.",
252
+ 5,
253
+ ],
254
+ [
255
+ "Marcus is a tech enthusiast and gaming streamer who loves mechanical "
256
+ "keyboards and collecting vintage arcade games. He's also a coffee "
257
+ "connoisseur who roasts his own beans and enjoys late-night coding "
258
+ "sessions.",
259
+ 7,
260
+ ],
261
+ [
262
+ "Elena is a music producer who plays multiple instruments including "
263
+ "piano and guitar. She has a home studio filled with vintage "
264
+ "synthesizers and loves vinyl records. She also practices yoga and "
265
+ "meditation in her spare time.",
266
+ 6,
267
+ ],
268
+ ],
269
+ inputs=[bio_input, num_assets_slider],
270
+ label="๐Ÿ’ก Try these example biographies:",
271
+ )
272
+
273
+ def update_models(player_bio: str, num_assets: int):
274
+ """Handle model updates for all tabs"""
275
+ json_result, model_paths, _ = generate_3d_assets_with_display(
276
+ player_bio, num_assets
277
+ )
278
+
279
+ # Prepare outputs for all components
280
+ tab_updates = []
281
+ model_updates = []
282
+
283
+ for i in range(1, 11):
284
+ # Update tab visibility
285
+ tab_updates.append(gr.update(visible=(i <= num_assets)))
286
+
287
+ # Update model content
288
+ model_key = f"model_{i}"
289
+ if i <= num_assets and model_key in model_paths:
290
+ model_updates.append(model_paths[model_key])
291
+ else:
292
+ model_updates.append(None)
293
+
294
+ return [json_result] + tab_updates + model_updates
295
+
296
+ # Update tab visibility when slider changes
297
+ def update_tab_visibility(num_assets: int):
298
+ tab_updates = []
299
+ for i in range(1, 11):
300
+ tab_updates.append(gr.update(visible=(i <= num_assets)))
301
+ return tab_updates
302
+
303
+ num_assets_slider.change(
304
+ fn=update_tab_visibility,
305
+ inputs=[num_assets_slider],
306
+ outputs=[tab for tab, _ in model_components],
307
+ api_name=False,
308
+ )
309
+
310
+ generate_btn.click(
311
+ fn=update_models,
312
+ inputs=[bio_input, num_assets_slider],
313
+ outputs=[output_json]
314
+ + [tab for tab, _ in model_components]
315
+ + [model for _, model in model_components],
316
+ show_progress=True,
317
+ api_name=False,
318
+ )
319
+
320
+ # API components
321
+ api_input = gr.Textbox(visible=False)
322
+ api_num_assets = gr.Number(visible=False, value=10)
323
+ api_output = gr.Textbox(visible=False)
324
+ api_btn = gr.Button(visible=False)
325
+
326
+ api_btn.click(
327
+ fn=generate_3d_assets,
328
+ inputs=[api_input, api_num_assets],
329
+ outputs=api_output,
330
+ api_name="generate_3d_assets",
331
+ )
332
+
333
+ # Launch the Gradio web interface and MCP server
334
+ if __name__ == "__main__":
335
+ demo.launch(mcp_server=True, share=False)
requirements.txt ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ gradio
2
+ modal
3
+ anthropic