Kimodo Bot commited on
Commit
ad26024
·
1 Parent(s): 0bb30bb

Deploy Kimodo hackathon UI entrypoint

Browse files
Files changed (2) hide show
  1. app.py +13 -181
  2. kimodo/scripts/space_frontend.py +340 -0
app.py CHANGED
@@ -1,190 +1,22 @@
1
- import os
2
- import json
3
- from typing import Any
4
-
5
- import gradio as gr
6
-
7
-
8
- def _parse_character_ids(raw: str, count: int) -> list[str]:
9
- items = [part.strip() for part in (raw or "").split(",") if part.strip()]
10
- if not items:
11
- items = [f"char_{i + 1}" for i in range(count)]
12
- if len(items) < count:
13
- items.extend(f"char_{i + 1}" for i in range(len(items), count))
14
- return items[:count]
15
-
16
-
17
- def plan_script(scene_id: str, prompt: str, characters: int, character_ids_raw: str, transition: str, duration_sec: int) -> str:
18
- cleaned = (prompt or "").strip() or "Two characters wave and then walk together"
19
- count = max(1, int(characters))
20
- ids = _parse_character_ids(character_ids_raw, count)
21
-
22
- segments = []
23
- segment_duration = max(1.0, float(duration_sec) / 2.0)
24
- for idx, cid in enumerate(ids):
25
- target = ids[(idx + 1) % len(ids)] if len(ids) > 1 else None
26
- segments.append(
27
- {
28
- "character_id": cid,
29
- "segments": [
30
- {
31
- "segment_id": 0,
32
- "action_text": f"{cid} starts: {cleaned}",
33
- "duration_sec": segment_duration,
34
- "transition_policy": "smooth",
35
- "interaction_target": target,
36
- },
37
- {
38
- "segment_id": 1,
39
- "action_text": f"{cid} continues with {transition} transition",
40
- "duration_sec": segment_duration,
41
- "transition_policy": transition,
42
- "interaction_target": target,
43
- },
44
- ],
45
- }
46
- )
47
-
48
- payload = {
49
- "scene_id": scene_id.strip() or "space_scene",
50
- "status": "success",
51
- "scripts": segments,
52
- "metadata": {
53
- "source": "space_frontend_simulation",
54
- "cards_complete": list(range(0, 11)),
55
- },
56
- }
57
- return json.dumps(payload, indent=2)
58
-
59
-
60
- def execute_scene(script_json: str, fps: int, seed: int) -> tuple[str, dict[str, Any], str]:
61
- if not script_json.strip():
62
- return "", {"timeline": []}, "Execution failed: no script"
63
-
64
- try:
65
- payload = json.loads(script_json)
66
- except json.JSONDecodeError as exc:
67
- return "", {"timeline": []}, f"Execution failed: invalid JSON ({exc})"
68
-
69
- scripts = payload.get("scripts") or []
70
- characters = [item.get("character_id") for item in scripts if item.get("character_id")]
71
- frame_count = max(1, int(fps) * 4)
72
- timeline = [{"frame": i, "state_hash": f"{seed:04d}-{i:05d}"} for i in range(frame_count)]
73
-
74
- summary = {
75
- "scene_id": payload.get("scene_id", "space_scene"),
76
- "characters": characters,
77
- "planner_status": payload.get("status", "unknown"),
78
- "state_hash_count": len(timeline),
79
- "interaction_count": max(0, len(characters) - 1),
80
- "execution_mode": "space-simulated",
81
- }
82
- status = (
83
- f"Execution OK | chars={len(characters)} "
84
- f"frames={summary['state_hash_count']} interactions={summary['interaction_count']}"
85
- )
86
- return json.dumps(summary, indent=2), {"timeline": timeline}, status
87
-
88
-
89
- def render_frame(frame_idx: int, playback_state: dict[str, Any]) -> str:
90
- timeline = playback_state.get("timeline") or []
91
- if not timeline:
92
- return "No execution timeline yet. Click Execute Scene first."
93
- bounded = max(0, min(int(frame_idx), len(timeline) - 1))
94
- frame = timeline[bounded]
95
- return f"Frame {frame['frame']} | state_hash={frame['state_hash']}"
96
 
 
97
 
98
- def _update_slider(playback_state: dict[str, Any]) -> gr.Slider:
99
- timeline = playback_state.get("timeline") or []
100
- max_frame = max(0, len(timeline) - 1)
101
- return gr.Slider(label="Frame", minimum=0, maximum=max_frame, value=0, step=1)
102
-
103
-
104
- def _prev_frame(current: float) -> float:
105
- return max(0, int(current) - 1)
106
-
107
-
108
- def _next_frame(current: float, playback_state: dict[str, Any]) -> float:
109
- max_frame = max(0, len((playback_state or {}).get("timeline") or []) - 1)
110
- return min(max_frame, int(current) + 1)
111
-
112
-
113
- with gr.Blocks(title="Movimento") as demo:
114
- gr.Markdown("# Movimento")
115
- gr.Markdown(
116
- "Card 10 Space frontend: prompt input, script preview, execution status, and playback controls."
117
- )
118
-
119
- playback_state = gr.State({"timeline": []})
120
-
121
- with gr.Row():
122
- scene_id = gr.Textbox(label="Scene ID", value="space_scene")
123
- seed = gr.Number(label="Seed", value=42, precision=0)
124
- fps = gr.Slider(label="FPS", minimum=10, maximum=60, value=30, step=1)
125
-
126
- with gr.Row():
127
- prompt = gr.Textbox(label="Scene Prompt", lines=4, placeholder="Two characters greet and sit down")
128
-
129
- with gr.Row():
130
- characters = gr.Slider(label="Characters", minimum=1, maximum=6, step=1, value=2)
131
- character_ids = gr.Textbox(label="Character IDs (comma-separated)", value="lead,support")
132
- transition = gr.Dropdown(
133
- label="Transition Policy",
134
- choices=["smooth", "overlap", "hold", "cut"],
135
- value="smooth",
136
- )
137
- duration_sec = gr.Slider(label="Duration (sec)", minimum=10, maximum=120, step=5, value=30)
138
-
139
- plan_btn = gr.Button("Plan Script", variant="primary")
140
- script_preview = gr.Code(label="Script Preview (JSON)", language="json")
141
- status = gr.Textbox(label="Status", interactive=False)
142
-
143
- execute_btn = gr.Button("Execute Scene")
144
- summary = gr.Code(label="Execution Summary", language="json")
145
-
146
- with gr.Row():
147
- frame_slider = gr.Slider(label="Frame", minimum=0, maximum=1, value=0, step=1)
148
- frame_info = gr.Textbox(label="Playback", interactive=False)
149
 
150
- prev_btn = gr.Button("Prev Frame")
151
- next_btn = gr.Button("Next Frame")
 
 
 
 
152
 
153
- plan_btn.click(
154
- plan_script,
155
- inputs=[scene_id, prompt, characters, character_ids, transition, duration_sec],
156
- outputs=[script_preview],
157
- ).then(
158
- lambda: "Planner: SUCCESS",
159
- outputs=[status],
160
- )
161
 
162
- execute_btn.click(
163
- execute_scene,
164
- inputs=[script_preview, fps, seed],
165
- outputs=[summary, playback_state, status],
166
- ).then(
167
- _update_slider,
168
- inputs=[playback_state],
169
- outputs=[frame_slider],
170
- ).then(
171
- render_frame,
172
- inputs=[frame_slider, playback_state],
173
- outputs=[frame_info],
174
- )
175
 
176
- frame_slider.change(render_frame, inputs=[frame_slider, playback_state], outputs=[frame_info])
177
- prev_btn.click(_prev_frame, inputs=[frame_slider], outputs=[frame_slider]).then(
178
- render_frame,
179
- inputs=[frame_slider, playback_state],
180
- outputs=[frame_info],
181
- )
182
- next_btn.click(_next_frame, inputs=[frame_slider, playback_state], outputs=[frame_slider]).then(
183
- render_frame,
184
- inputs=[frame_slider, playback_state],
185
- outputs=[frame_info],
186
- )
187
 
188
 
189
  if __name__ == "__main__":
190
- demo.launch(server_name="0.0.0.0", server_port=int(os.environ.get("PORT", "7860")))
 
1
+ """Hugging Face Space entrypoint for Movimento hackathon frontend."""
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2
 
3
+ from __future__ import annotations
4
 
5
+ import os
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
6
 
7
+ # Configure environment for HF Spaces runtime.
8
+ os.environ.setdefault("HF_MODE", "1")
9
+ os.environ.setdefault("SERVER_NAME", "0.0.0.0")
10
+ os.environ.setdefault("SERVER_PORT", "7860")
11
+ os.environ.setdefault("MAX_ACTIVE_USERS", "5")
12
+ os.environ.setdefault("MAX_SESSION_MINUTES", "5.0")
13
 
 
 
 
 
 
 
 
 
14
 
15
+ def main() -> None:
16
+ from kimodo.scripts.space_frontend import main as frontend_main
 
 
 
 
 
 
 
 
 
 
 
17
 
18
+ frontend_main()
 
 
 
 
 
 
 
 
 
 
19
 
20
 
21
  if __name__ == "__main__":
22
+ main()
kimodo/scripts/space_frontend.py ADDED
@@ -0,0 +1,340 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Card 10: Gradio Space frontend shell for prompt->script->execution flow."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+ import os
7
+ import time
8
+ from dataclasses import dataclass
9
+ from pathlib import Path
10
+ from typing import Any, Optional
11
+
12
+ import gradio as gr
13
+
14
+ from kimodo.model import DEFAULT_MODEL, load_model
15
+ from kimodo.pipeline.scheduler_runtime import run_scheduled_scene
16
+ from kimodo.planner import QwenPlannerAdapter
17
+ from kimodo.runtime import runtime_health_report
18
+ from kimodo.schemas import CharacterDefinition, CharacterGenerationState, GeneratorRequest, PlannerRequest, PlannerResponse
19
+
20
+ from .gradio_theme import get_gradio_theme
21
+
22
+
23
+ @dataclass
24
+ class FrontendConfig:
25
+ execution_mode: str
26
+ default_model: str
27
+ default_scene_id: str = "space_scene"
28
+
29
+
30
+ class _FakeKimodoModel:
31
+ """Fast fallback model for cold-start demo flow."""
32
+
33
+ def __call__(self, prompts, num_frames, **kwargs):
34
+ return {
35
+ "posed_joints": [[0.0]],
36
+ "global_rot_mats": [[0.0]],
37
+ "foot_contacts": [[0.0]],
38
+ "prompts": prompts,
39
+ "num_frames": num_frames,
40
+ "meta": kwargs,
41
+ }
42
+
43
+
44
+ _MODEL_CACHE: dict[str, Any] = {}
45
+
46
+
47
+ def build_kimodo_iframe_html(space_url: str, *, height_px: int = 760) -> str:
48
+ """Build embeddable iframe HTML for the upstream Kimodo UI."""
49
+ url = (space_url or "").strip() or "https://nvidia-kimodo.hf.space"
50
+ height = max(480, int(height_px))
51
+ return (
52
+ "<div style='border:1px solid #d0dde6;border-radius:12px;overflow:hidden;'>"
53
+ f"<iframe src='{url}' title='Kimodo UI' "
54
+ "style='width:100%;border:0;' "
55
+ f"height='{height}' loading='lazy' referrerpolicy='origin'></iframe>"
56
+ "</div>"
57
+ )
58
+
59
+
60
+ def _parse_character_ids(raw: str, count: int) -> list[str]:
61
+ items = [part.strip() for part in (raw or "").split(",") if part.strip()]
62
+ if not items:
63
+ items = [f"char_{i+1}" for i in range(count)]
64
+ if len(items) < count:
65
+ items.extend(f"char_{i+1}" for i in range(len(items), count))
66
+ return items[:count]
67
+
68
+
69
+ def _build_planner_request(scene_id: str, prompt: str, character_ids: list[str], duration_limit_sec: float) -> PlannerRequest:
70
+ return PlannerRequest(
71
+ scene_id=scene_id,
72
+ user_prompt=prompt,
73
+ duration_limit_sec=duration_limit_sec,
74
+ characters=[CharacterDefinition(character_id=item, skeleton_type="soma") for item in character_ids],
75
+ )
76
+
77
+
78
+ def _planner_response_to_generator_request(response: PlannerResponse, seed: int) -> GeneratorRequest:
79
+ characters: list[CharacterGenerationState] = []
80
+ for character_id, segments in response.scripts.items():
81
+ characters.append(
82
+ CharacterGenerationState(
83
+ character_id=character_id,
84
+ skeleton_type="soma",
85
+ segments=segments,
86
+ )
87
+ )
88
+ return GeneratorRequest(
89
+ scene_id=response.scene_id,
90
+ characters=characters,
91
+ seed=seed,
92
+ num_samples=1,
93
+ )
94
+
95
+
96
+ def _get_or_load_model(config: FrontendConfig, requested_model: str, requested_device: Optional[str]) -> Any:
97
+ if config.execution_mode == "simulate":
98
+ return _FakeKimodoModel()
99
+
100
+ cache_key = f"{requested_model}:{requested_device or 'auto'}"
101
+ if cache_key in _MODEL_CACHE:
102
+ return _MODEL_CACHE[cache_key]
103
+
104
+ report = runtime_health_report(requested_device)
105
+ model = load_model(requested_model, device=report.selected_device)
106
+ _MODEL_CACHE[cache_key] = model
107
+ return model
108
+
109
+
110
+ def plan_script(
111
+ scene_id: str,
112
+ prompt: str,
113
+ character_count: int,
114
+ character_ids_raw: str,
115
+ duration_limit_sec: float,
116
+ ) -> tuple[str, str]:
117
+ start = time.time()
118
+ character_ids = _parse_character_ids(character_ids_raw, int(character_count))
119
+ request = _build_planner_request(scene_id.strip() or "space_scene", prompt, character_ids, duration_limit_sec)
120
+ adapter = QwenPlannerAdapter()
121
+ response = adapter.plan(request)
122
+ payload = json.dumps(response.model_dump(), indent=2)
123
+ elapsed_ms = int((time.time() - start) * 1000)
124
+ status = f"Planner: {response.status.upper()} in {elapsed_ms} ms | characters={len(response.scripts)}"
125
+ return payload, status
126
+
127
+
128
+ def execute_script(
129
+ planned_script_json: str,
130
+ seed: int,
131
+ fps: int,
132
+ requested_device: str,
133
+ execution_mode: str,
134
+ model_name: str,
135
+ ) -> tuple[str, dict[str, Any], str]:
136
+ if not planned_script_json.strip():
137
+ return "", {"timeline": []}, "Execution failed: script preview is empty"
138
+
139
+ try:
140
+ response = PlannerResponse.model_validate_json(planned_script_json)
141
+ except Exception as exc: # pylint: disable=broad-except
142
+ return "", {"timeline": []}, f"Execution failed: invalid planner JSON ({exc})"
143
+
144
+ try:
145
+ config = FrontendConfig(execution_mode=execution_mode, default_model=model_name)
146
+ model = _get_or_load_model(config, model_name, requested_device)
147
+ request = _planner_response_to_generator_request(response, seed=seed)
148
+ result = run_scheduled_scene(model, request, fps=float(fps), seed=seed)
149
+
150
+ summary = {
151
+ "scene_id": response.scene_id,
152
+ "characters": list(result.outputs.keys()),
153
+ "errors": result.errors,
154
+ "state_hash_count": len(result.state_hashes),
155
+ "interaction_count": len(result.interactions),
156
+ "completed_segments": result.completed_segments,
157
+ }
158
+
159
+ timeline = [
160
+ {
161
+ "frame": index,
162
+ "state_hash": state_hash,
163
+ }
164
+ for index, state_hash in enumerate(result.state_hashes)
165
+ ]
166
+
167
+ status = (
168
+ f"Execution: OK | chars={len(summary['characters'])} "
169
+ f"frames={summary['state_hash_count']} interactions={summary['interaction_count']}"
170
+ )
171
+ return json.dumps(summary, indent=2), {"timeline": timeline}, status
172
+ except Exception as exc: # pylint: disable=broad-except
173
+ return "", {"timeline": []}, f"Execution failed: {exc}"
174
+
175
+
176
+ def render_frame(frame_idx: int, playback_state: dict[str, Any]) -> str:
177
+ timeline = playback_state.get("timeline") or []
178
+ if not timeline:
179
+ return "No execution timeline yet. Click Execute Scene first."
180
+ bounded = max(0, min(int(frame_idx), len(timeline) - 1))
181
+ frame = timeline[bounded]
182
+ return f"Frame {frame['frame']} | state_hash={frame['state_hash']}"
183
+
184
+
185
+ def create_app() -> gr.Blocks:
186
+ theme, css = get_gradio_theme(remove_gradio_footer=True)
187
+
188
+ execution_mode = os.environ.get("SPACE_EXECUTION_MODE", "simulate").strip().lower()
189
+ default_model = os.environ.get("DEFAULT_MODEL", DEFAULT_MODEL)
190
+ kimodo_ui_url = os.environ.get("KIMODO_UI_URL", "https://nvidia-kimodo.hf.space").strip()
191
+
192
+ app_css = css + """
193
+ :root {
194
+ --brand-primary: #0d3b66;
195
+ --brand-accent: #f95738;
196
+ --brand-muted: #faf6f1;
197
+ }
198
+ .movimento-hero {
199
+ background: linear-gradient(130deg, var(--brand-muted) 0%, #e5f4f9 100%);
200
+ border: 1px solid #d9e7ef;
201
+ border-radius: 14px;
202
+ padding: 18px;
203
+ margin-bottom: 12px;
204
+ }
205
+ .movimento-hero h1 {
206
+ color: var(--brand-primary);
207
+ margin: 0;
208
+ }
209
+ .movimento-hero p {
210
+ margin: 6px 0 0 0;
211
+ color: #264653;
212
+ }
213
+ """
214
+
215
+ with gr.Blocks(title="Movimento", css=app_css, theme=theme) as demo:
216
+ gr.HTML(
217
+ """
218
+ <div class=\"movimento-hero\">
219
+ <h1>Movimento - Multi-Character Motion Copilot</h1>
220
+ <p>Prompt -> Qwen script plan -> scheduled execution trace. Built for lablab.ai x AMD.</p>
221
+ </div>
222
+ """
223
+ )
224
+
225
+ playback_state = gr.State({"timeline": []})
226
+
227
+ with gr.Tabs():
228
+ with gr.Tab("Multi-Character Copilot"):
229
+ with gr.Row():
230
+ scene_id = gr.Textbox(label="Scene ID", value="space_scene")
231
+ model_name = gr.Textbox(label="Model", value=default_model)
232
+ requested_device = gr.Textbox(label="Device (auto/cpu/amd/rocm/cuda)", value="auto")
233
+
234
+ prompt = gr.Textbox(
235
+ label="Story Prompt",
236
+ lines=4,
237
+ value="Two characters meet, greet each other, and walk in sync while a third observes.",
238
+ )
239
+
240
+ with gr.Row():
241
+ character_count = gr.Slider(label="Characters", minimum=1, maximum=6, value=3, step=1)
242
+ character_ids = gr.Textbox(label="Character IDs (comma-separated)", value="lead,support,observer")
243
+ duration_limit_sec = gr.Slider(label="Duration Limit (sec)", minimum=10, maximum=180, value=60, step=5)
244
+
245
+ plan_button = gr.Button("Plan Script", variant="primary")
246
+ script_preview = gr.Code(label="Script Preview (JSON)", language="json")
247
+ status_line = gr.Textbox(label="Status", interactive=False)
248
+
249
+ with gr.Row():
250
+ seed = gr.Number(label="Seed", value=42, precision=0)
251
+ fps = gr.Slider(label="Playback FPS", minimum=10, maximum=60, value=30, step=1)
252
+ execution_mode_box = gr.Dropdown(
253
+ label="Execution Mode",
254
+ choices=["simulate", "model"],
255
+ value=execution_mode if execution_mode in {"simulate", "model"} else "simulate",
256
+ )
257
+
258
+ execute_button = gr.Button("Execute Scene")
259
+ execution_summary = gr.Code(label="Execution Summary", language="json")
260
+
261
+ with gr.Row():
262
+ frame_slider = gr.Slider(label="Frame", minimum=0, maximum=1, value=0, step=1)
263
+ frame_info = gr.Textbox(label="Playback", interactive=False)
264
+
265
+ prev_btn = gr.Button("Prev Frame")
266
+ next_btn = gr.Button("Next Frame")
267
+
268
+ with gr.Tab("Kimodo Native UI"):
269
+ gr.Markdown(
270
+ "Use the original Kimodo UI for visual authoring, while keeping multi-character planning "
271
+ "and scheduler flow in this Space."
272
+ )
273
+ gr.Markdown(f"Kimodo UI URL: {kimodo_ui_url}")
274
+ gr.HTML(build_kimodo_iframe_html(kimodo_ui_url, height_px=820))
275
+
276
+ def _update_frame_slider(playback: dict[str, Any]) -> gr.Slider:
277
+ timeline = playback.get("timeline") or []
278
+ max_frame = max(0, len(timeline) - 1)
279
+ return gr.Slider(label="Frame", minimum=0, maximum=max_frame, value=0, step=1)
280
+
281
+ def _prev_frame(cur: float) -> float:
282
+ return max(0, int(cur) - 1)
283
+
284
+ def _next_frame(cur: float, playback: dict[str, Any]) -> float:
285
+ max_frame = max(0, len((playback or {}).get("timeline") or []) - 1)
286
+ return min(max_frame, int(cur) + 1)
287
+
288
+ plan_button.click(
289
+ fn=plan_script,
290
+ inputs=[scene_id, prompt, character_count, character_ids, duration_limit_sec],
291
+ outputs=[script_preview, status_line],
292
+ )
293
+
294
+ execute_button.click(
295
+ fn=execute_script,
296
+ inputs=[script_preview, seed, fps, requested_device, execution_mode_box, model_name],
297
+ outputs=[execution_summary, playback_state, status_line],
298
+ ).then(
299
+ fn=_update_frame_slider,
300
+ inputs=[playback_state],
301
+ outputs=[frame_slider],
302
+ ).then(
303
+ fn=render_frame,
304
+ inputs=[frame_slider, playback_state],
305
+ outputs=[frame_info],
306
+ )
307
+
308
+ frame_slider.change(fn=render_frame, inputs=[frame_slider, playback_state], outputs=[frame_info])
309
+ prev_btn.click(fn=_prev_frame, inputs=[frame_slider], outputs=[frame_slider]).then(
310
+ fn=render_frame,
311
+ inputs=[frame_slider, playback_state],
312
+ outputs=[frame_info],
313
+ )
314
+ next_btn.click(fn=_next_frame, inputs=[frame_slider, playback_state], outputs=[frame_slider]).then(
315
+ fn=render_frame,
316
+ inputs=[frame_slider, playback_state],
317
+ outputs=[frame_info],
318
+ )
319
+
320
+ return demo
321
+
322
+
323
+ def main() -> None:
324
+ server_name = os.environ.get("GRADIO_SERVER_NAME", os.environ.get("SERVER_NAME", "0.0.0.0"))
325
+ server_port = int(os.environ.get("GRADIO_SERVER_PORT") or os.environ.get("PORT", "7860"))
326
+ favicon_path = Path(__file__).resolve().parents[1] / "assets" / "demo" / "nvidia_logo.png"
327
+
328
+ demo = create_app()
329
+ launch_kwargs = {
330
+ "server_name": server_name,
331
+ "server_port": server_port,
332
+ }
333
+ if favicon_path.exists():
334
+ launch_kwargs["favicon_path"] = str(favicon_path)
335
+
336
+ demo.launch(**launch_kwargs)
337
+
338
+
339
+ if __name__ == "__main__":
340
+ main()