Kimodo Bot commited on
Commit
075f5be
·
1 Parent(s): ad26024

Use self-contained UI with Kimodo 3D viewer tab

Browse files
Files changed (1) hide show
  1. app.py +191 -11
app.py CHANGED
@@ -1,22 +1,202 @@
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()
 
1
+ """Movimento Space UI: hackathon planner shell + Kimodo native 3D viewer."""
2
 
3
  from __future__ import annotations
4
 
5
+ import json
6
  import os
7
+ from typing import Any
8
 
9
+ import gradio as gr
 
 
 
 
 
10
 
11
 
12
+ def _parse_character_ids(raw: str, count: int) -> list[str]:
13
+ items = [part.strip() for part in (raw or "").split(",") if part.strip()]
14
+ if not items:
15
+ items = [f"char_{i + 1}" for i in range(count)]
16
+ if len(items) < count:
17
+ items.extend(f"char_{i + 1}" for i in range(len(items), count))
18
+ return items[:count]
19
 
20
+
21
+ def plan_script(scene_id: str, prompt: str, characters: int, character_ids_raw: str, transition: str, duration_sec: int) -> str:
22
+ cleaned = (prompt or "").strip() or "Two characters wave and then walk together"
23
+ count = max(1, int(characters))
24
+ ids = _parse_character_ids(character_ids_raw, count)
25
+
26
+ scripts: dict[str, list[dict[str, Any]]] = {}
27
+ segment_duration = max(1.0, float(duration_sec) / 2.0)
28
+ for idx, cid in enumerate(ids):
29
+ target = ids[(idx + 1) % len(ids)] if len(ids) > 1 else None
30
+ scripts[cid] = [
31
+ {
32
+ "segment_id": 0,
33
+ "action_text": f"{cid} starts: {cleaned}",
34
+ "duration_sec": segment_duration,
35
+ "transition_policy": "smooth",
36
+ "interaction_target": target,
37
+ },
38
+ {
39
+ "segment_id": 1,
40
+ "action_text": f"{cid} continues with {transition} transition",
41
+ "duration_sec": segment_duration,
42
+ "transition_policy": transition,
43
+ "interaction_target": target,
44
+ },
45
+ ]
46
+
47
+ payload = {
48
+ "scene_id": scene_id.strip() or "space_scene",
49
+ "status": "success",
50
+ "scripts": scripts,
51
+ "total_duration_sec": float(duration_sec),
52
+ "metadata": {
53
+ "source": "movimento_space_hackathon_shell",
54
+ },
55
+ }
56
+ return json.dumps(payload, indent=2)
57
+
58
+
59
+ def execute_scene(script_json: str, fps: int, seed: int) -> tuple[str, dict[str, Any], str]:
60
+ if not script_json.strip():
61
+ return "", {"timeline": []}, "Execution failed: no script"
62
+
63
+ try:
64
+ payload = json.loads(script_json)
65
+ except json.JSONDecodeError as exc:
66
+ return "", {"timeline": []}, f"Execution failed: invalid JSON ({exc})"
67
+
68
+ scripts = payload.get("scripts") or {}
69
+ characters = list(scripts.keys()) if isinstance(scripts, dict) else []
70
+ frame_count = max(1, int(fps) * 4)
71
+ timeline = [{"frame": i, "state_hash": f"{seed:04d}-{i:05d}"} for i in range(frame_count)]
72
+
73
+ summary = {
74
+ "scene_id": payload.get("scene_id", "space_scene"),
75
+ "characters": characters,
76
+ "planner_status": payload.get("status", "unknown"),
77
+ "state_hash_count": len(timeline),
78
+ "interaction_count": max(0, len(characters) - 1),
79
+ "viewer": "Use the Kimodo Native 3D tab for full motion visualization",
80
+ }
81
+ status = (
82
+ f"Execution OK | chars={len(characters)} "
83
+ f"frames={summary['state_hash_count']} interactions={summary['interaction_count']}"
84
+ )
85
+ return json.dumps(summary, indent=2), {"timeline": timeline}, status
86
+
87
+
88
+ def render_frame(frame_idx: int, playback_state: dict[str, Any]) -> str:
89
+ timeline = playback_state.get("timeline") or []
90
+ if not timeline:
91
+ return "No execution timeline yet. Click Execute Scene first."
92
+ bounded = max(0, min(int(frame_idx), len(timeline) - 1))
93
+ frame = timeline[bounded]
94
+ return f"Frame {frame['frame']} | state_hash={frame['state_hash']}"
95
+
96
+
97
+ def _update_slider(playback_state: dict[str, Any]) -> gr.Slider:
98
+ timeline = playback_state.get("timeline") or []
99
+ max_frame = max(0, len(timeline) - 1)
100
+ return gr.Slider(label="Frame", minimum=0, maximum=max_frame, value=0, step=1)
101
+
102
+
103
+ def _prev_frame(current: float) -> float:
104
+ return max(0, int(current) - 1)
105
+
106
+
107
+ def _next_frame(current: float, playback_state: dict[str, Any]) -> float:
108
+ max_frame = max(0, len((playback_state or {}).get("timeline") or []) - 1)
109
+ return min(max_frame, int(current) + 1)
110
+
111
+
112
+ def _kimodo_iframe_html() -> str:
113
+ src = os.environ.get("KIMODO_UI_URL", "https://nvidia-kimodo.hf.space").strip()
114
+ return (
115
+ "<div style='border:1px solid #d9e7ef;border-radius:12px;overflow:hidden'>"
116
+ f"<iframe src='{src}' title='Kimodo Native UI' style='width:100%;border:0' height='820' loading='lazy'></iframe>"
117
+ "</div>"
118
+ )
119
+
120
+
121
+ with gr.Blocks(title="Movimento") as demo:
122
+ gr.Markdown("# Movimento")
123
+ gr.Markdown("Hackathon module: prompt planning + execution trace + Kimodo native 3D visualization")
124
+
125
+ with gr.Tabs():
126
+ with gr.Tab("Hackathon Copilot"):
127
+ playback_state = gr.State({"timeline": []})
128
+
129
+ with gr.Row():
130
+ scene_id = gr.Textbox(label="Scene ID", value="space_scene")
131
+ seed = gr.Number(label="Seed", value=42, precision=0)
132
+ fps = gr.Slider(label="FPS", minimum=10, maximum=60, value=30, step=1)
133
+
134
+ with gr.Row():
135
+ prompt = gr.Textbox(label="Scene Prompt", lines=4, placeholder="Two characters greet and sit down")
136
+
137
+ with gr.Row():
138
+ characters = gr.Slider(label="Characters", minimum=1, maximum=6, step=1, value=2)
139
+ character_ids = gr.Textbox(label="Character IDs (comma-separated)", value="lead,support")
140
+ transition = gr.Dropdown(
141
+ label="Transition Policy",
142
+ choices=["smooth", "overlap", "hold", "cut"],
143
+ value="smooth",
144
+ )
145
+ duration_sec = gr.Slider(label="Duration (sec)", minimum=10, maximum=120, step=5, value=30)
146
+
147
+ plan_btn = gr.Button("Plan Script", variant="primary")
148
+ script_preview = gr.Code(label="Script Preview (JSON)", language="json")
149
+ status = gr.Textbox(label="Status", interactive=False)
150
+
151
+ execute_btn = gr.Button("Execute Scene")
152
+ summary = gr.Code(label="Execution Summary", language="json")
153
+
154
+ with gr.Row():
155
+ frame_slider = gr.Slider(label="Frame", minimum=0, maximum=1, value=0, step=1)
156
+ frame_info = gr.Textbox(label="Playback", interactive=False)
157
+
158
+ prev_btn = gr.Button("Prev Frame")
159
+ next_btn = gr.Button("Next Frame")
160
+
161
+ plan_btn.click(
162
+ plan_script,
163
+ inputs=[scene_id, prompt, characters, character_ids, transition, duration_sec],
164
+ outputs=[script_preview],
165
+ ).then(
166
+ lambda: "Planner: SUCCESS",
167
+ outputs=[status],
168
+ )
169
+
170
+ execute_btn.click(
171
+ execute_scene,
172
+ inputs=[script_preview, fps, seed],
173
+ outputs=[summary, playback_state, status],
174
+ ).then(
175
+ _update_slider,
176
+ inputs=[playback_state],
177
+ outputs=[frame_slider],
178
+ ).then(
179
+ render_frame,
180
+ inputs=[frame_slider, playback_state],
181
+ outputs=[frame_info],
182
+ )
183
+
184
+ frame_slider.change(render_frame, inputs=[frame_slider, playback_state], outputs=[frame_info])
185
+ prev_btn.click(_prev_frame, inputs=[frame_slider], outputs=[frame_slider]).then(
186
+ render_frame,
187
+ inputs=[frame_slider, playback_state],
188
+ outputs=[frame_info],
189
+ )
190
+ next_btn.click(_next_frame, inputs=[frame_slider, playback_state], outputs=[frame_slider]).then(
191
+ render_frame,
192
+ inputs=[frame_slider, playback_state],
193
+ outputs=[frame_info],
194
+ )
195
+
196
+ with gr.Tab("Kimodo Native 3D Viewer"):
197
+ gr.Markdown("Full 3D character visualization is provided by the Kimodo native UI below.")
198
+ gr.HTML(_kimodo_iframe_html())
199
 
200
 
201
  if __name__ == "__main__":
202
+ demo.launch(server_name="0.0.0.0", server_port=int(os.environ.get("PORT", "7860")))