jandan138 commited on
Commit
b24a39c
·
1 Parent(s): b560e9d

change ui add 3d show

Browse files
Files changed (6) hide show
  1. .env.example +0 -0
  2. app.py +44 -14
  3. app_old.py +277 -0
  4. config.py +20 -0
  5. navigation_ui.py +575 -0
  6. ui_components.py +21 -3
.env.example ADDED
File without changes
app.py CHANGED
@@ -1,11 +1,11 @@
1
  # main.py
2
  # 主入口文件,负责启动 Gradio UI
3
  import gradio as gr
4
- from config import SCENE_CONFIGS, MODEL_CHOICES, MODE_CHOICES
5
  from backend_api import submit_to_backend, get_task_status, get_task_result
6
  from logging_utils import log_access, log_submission, is_request_allowed
7
  from simulation import stream_simulation_results, convert_to_h264, create_final_video_from_oss_images
8
- from ui_components import update_history_display, update_scene_display, update_log_display, get_scene_instruction
9
  from oss_utils import download_oss_file, get_user_tmp_dir, cleanup_user_tmp_dir, oss_file_exists, clean_oss_result_path
10
  import os
11
  from datetime import datetime
@@ -136,6 +136,12 @@ custom_css = """
136
  .history-accordion {
137
  margin-bottom: 10px;
138
  }
 
 
 
 
 
 
139
  """
140
 
141
  header_html = """
@@ -174,18 +180,29 @@ with gr.Blocks(title="InternNav Model Inference Demo", css=custom_css) as demo:
174
  with gr.Row():
175
  with gr.Column(elem_id="simulation-panel"):
176
  gr.Markdown("### Simulation Settings")
177
- scene_dropdown = gr.Dropdown(
178
- label="Choose a scene",
179
- choices=list(SCENE_CONFIGS.keys()),
180
- value="demo1",
181
- interactive=True
182
- )
183
- scene_description = gr.Markdown("")
184
- scene_preview = gr.Image(
185
- label="Scene Preview",
186
- elem_classes=["scene-preview"],
187
- interactive=False
188
- )
 
 
 
 
 
 
 
 
 
 
 
189
  prompt_input = gr.Textbox(
190
  label="Navigation Prompt",
191
  value="Walk past the left side of the bed and stop in the doorway.",
@@ -209,6 +226,16 @@ with gr.Blocks(title="InternNav Model Inference Demo", css=custom_css) as demo:
209
  fn=lambda scene: [update_scene_display(scene)[0], update_scene_display(scene)[1], get_scene_instruction(scene)],
210
  inputs=scene_dropdown,
211
  outputs=[scene_description, scene_preview, prompt_input]
 
 
 
 
 
 
 
 
 
 
212
  )
213
 
214
  submit_btn = gr.Button("Start Navigation Simulation", variant="primary")
@@ -257,6 +284,9 @@ with gr.Blocks(title="InternNav Model Inference Demo", css=custom_css) as demo:
257
  demo.load(
258
  fn=lambda: update_scene_display("demo1"),
259
  outputs=[scene_description, scene_preview]
 
 
 
260
  )
261
  demo.load(
262
  fn=record_access,
 
1
  # main.py
2
  # 主入口文件,负责启动 Gradio UI
3
  import gradio as gr
4
+ from config import SCENE_CONFIGS, MODEL_CHOICES, MODE_CHOICES, EPISODE_CONFIGS
5
  from backend_api import submit_to_backend, get_task_status, get_task_result
6
  from logging_utils import log_access, log_submission, is_request_allowed
7
  from simulation import stream_simulation_results, convert_to_h264, create_final_video_from_oss_images
8
+ from ui_components import update_history_display, update_scene_display, update_episode_display, update_log_display, get_scene_instruction
9
  from oss_utils import download_oss_file, get_user_tmp_dir, cleanup_user_tmp_dir, oss_file_exists, clean_oss_result_path
10
  import os
11
  from datetime import datetime
 
136
  .history-accordion {
137
  margin-bottom: 10px;
138
  }
139
+ .scene-preview {
140
+ height: 400px;
141
+ border: 1px solid #ddd;
142
+ border-radius: 8px;
143
+ overflow: hidden;
144
+ }
145
  """
146
 
147
  header_html = """
 
180
  with gr.Row():
181
  with gr.Column(elem_id="simulation-panel"):
182
  gr.Markdown("### Simulation Settings")
183
+ with gr.Row():
184
+ scene_dropdown = gr.Dropdown(
185
+ label="Choose a scene",
186
+ choices=list(SCENE_CONFIGS.keys()),
187
+ value="demo1",
188
+ interactive=True
189
+ )
190
+ episode_dropdown = gr.Dropdown(
191
+ label="Select Start Position",
192
+ choices=list(EPISODE_CONFIGS.keys()),
193
+ value="episode_1",
194
+ interactive=True
195
+ )
196
+
197
+ with gr.Row():
198
+ scene_preview = gr.Model3D(
199
+ elem_classes=["scene-preview"],
200
+ camera_position=(90.0, 120, 20000.0)
201
+ )
202
+ fps_preview = gr.Image(label="FPS Preview")
203
+
204
+ scene_description = gr.Markdown("### Scene preview")
205
+
206
  prompt_input = gr.Textbox(
207
  label="Navigation Prompt",
208
  value="Walk past the left side of the bed and stop in the doorway.",
 
226
  fn=lambda scene: [update_scene_display(scene)[0], update_scene_display(scene)[1], get_scene_instruction(scene)],
227
  inputs=scene_dropdown,
228
  outputs=[scene_description, scene_preview, prompt_input]
229
+ ).then(
230
+ update_episode_display,
231
+ inputs=[scene_dropdown, episode_dropdown],
232
+ outputs=[fps_preview]
233
+ )
234
+
235
+ episode_dropdown.change(
236
+ update_episode_display,
237
+ inputs=[scene_dropdown, episode_dropdown],
238
+ outputs=[fps_preview]
239
  )
240
 
241
  submit_btn = gr.Button("Start Navigation Simulation", variant="primary")
 
284
  demo.load(
285
  fn=lambda: update_scene_display("demo1"),
286
  outputs=[scene_description, scene_preview]
287
+ ).then(
288
+ fn=lambda: update_episode_display("demo1", "episode_1"),
289
+ outputs=[fps_preview]
290
  )
291
  demo.load(
292
  fn=record_access,
app_old.py ADDED
@@ -0,0 +1,277 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # main.py
2
+ # 主入口文件,负责启动 Gradio UI
3
+ import gradio as gr
4
+ from config import SCENE_CONFIGS, MODEL_CHOICES, MODE_CHOICES
5
+ from backend_api import submit_to_backend, get_task_status, get_task_result
6
+ from logging_utils import log_access, log_submission, is_request_allowed
7
+ from simulation import stream_simulation_results, convert_to_h264, create_final_video_from_oss_images
8
+ from ui_components import update_history_display, update_scene_display, update_log_display, get_scene_instruction
9
+ from oss_utils import download_oss_file, get_user_tmp_dir, cleanup_user_tmp_dir, oss_file_exists, clean_oss_result_path
10
+ import os
11
+ from datetime import datetime
12
+
13
+ SESSION_TASKS = {}
14
+
15
+ def run_simulation(scene, model, mode, prompt, history, request: gr.Request):
16
+ timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
17
+ scene_desc = SCENE_CONFIGS.get(scene, {}).get("description", scene)
18
+ user_ip = request.client.host if request else "unknown"
19
+ session_id = request.session_hash
20
+
21
+ if not is_request_allowed(user_ip):
22
+ log_submission(scene, prompt, model, user_ip, "IP blocked temporarily")
23
+ raise gr.Error("Too many requests from this IP. Please wait and try again one minute later.")
24
+
25
+ # 提交任务到后端
26
+ submission_result = submit_to_backend(scene, prompt, mode, model, user_ip)
27
+ if submission_result.get("status") != "pending":
28
+ log_submission(scene, prompt, model, user_ip, "Submission failed")
29
+ raise gr.Error(f"Submission failed: {submission_result.get('message', 'unknown issue')}")
30
+
31
+ try:
32
+ task_id = submission_result["task_id"]
33
+ SESSION_TASKS[session_id] = task_id
34
+ gr.Info(f"Simulation started, task_id: {task_id}")
35
+
36
+ import time
37
+ time.sleep(5)
38
+ status = get_task_status(task_id)
39
+ # OSS上的结果文件夹路径,不再检查本地路径是否存在
40
+ result_folder = clean_oss_result_path(status.get("result", f"gradio_demo/tasks/{task_id}"), task_id)
41
+
42
+ except Exception as e:
43
+ log_submission(scene, prompt, model, user_ip, str(e))
44
+ raise gr.Error(f"error occurred when parsing submission result from backend: {str(e)}")
45
+
46
+ # 流式输出视频片段(从OSS读取)
47
+ try:
48
+ for video_path in stream_simulation_results(result_folder, task_id, request):
49
+ if video_path:
50
+ yield video_path, history
51
+ except Exception as e:
52
+ log_submission(scene, prompt, model, user_ip, str(e))
53
+ raise gr.Error(f"流式输出过程中出错: {str(e)}")
54
+
55
+ # 获取最终任务状态
56
+ status = get_task_status(task_id)
57
+ if status.get("status") == "completed":
58
+ try:
59
+ # 从OSS上的所有图片拼接成最终视频(6帧每秒)
60
+ gr.Info("Creating final video from all OSS images...")
61
+ video_path = create_final_video_from_oss_images(result_folder, task_id, request, fps=6)
62
+ gr.Info(f"Final video created successfully with 6 fps!")
63
+
64
+ except Exception as e:
65
+ print(f"Error creating final video from OSS images: {e}")
66
+ log_submission(scene, prompt, model, user_ip, f"Final video creation failed: {str(e)}")
67
+ video_path = None
68
+ new_entry = {
69
+ "timestamp": timestamp,
70
+ "scene": scene,
71
+ "model": model,
72
+ "mode": mode,
73
+ "prompt": prompt,
74
+ "video_path": video_path
75
+ }
76
+ updated_history = history + [new_entry]
77
+ if len(updated_history) > 10:
78
+ updated_history = updated_history[:10]
79
+ log_submission(scene, prompt, model, user_ip, "success")
80
+ gr.Info("Simulation completed successfully!")
81
+ yield None, updated_history
82
+ elif status.get("status") == "failed":
83
+ log_submission(scene, prompt, model, user_ip, status.get('result', 'backend error'))
84
+ raise gr.Error(f"任务执行失败: {status.get('result', 'backend 未知错误')}")
85
+ yield None, history
86
+ elif status.get("status") == "terminated":
87
+ log_submission(scene, prompt, model, user_ip, "terminated")
88
+ # 对于终止的任务,不再检查本地文件
89
+ yield None, history
90
+ else:
91
+ log_submission(scene, prompt, model, user_ip, "missing task's status from backend")
92
+ raise gr.Error("missing task's status from backend")
93
+ yield None, history
94
+
95
+ def cleanup_session(request: gr.Request):
96
+ session_id = request.session_hash
97
+ task_id = SESSION_TASKS.pop(session_id, None)
98
+ from config import BACKEND_URL
99
+ import requests
100
+ if task_id:
101
+ try:
102
+ requests.post(f"{BACKEND_URL}/predict/terminate/{task_id}", timeout=3)
103
+ except Exception:
104
+ pass
105
+
106
+ # 清理用户临时目录
107
+ cleanup_user_tmp_dir(session_id)
108
+
109
+ def record_access(request: gr.Request):
110
+ user_ip = request.client.host if request else "unknown"
111
+ user_agent = request.headers.get("user-agent", "unknown")
112
+ log_access(user_ip, user_agent)
113
+ return update_log_display()
114
+
115
+
116
+
117
+ custom_css = """
118
+ #simulation-panel {
119
+ border-radius: 8px;
120
+ padding: 20px;
121
+ background: #f9f9f9;
122
+ box-shadow: 0 2px 4px rgba(0,0,0,0.1);
123
+ }
124
+ #result-panel {
125
+ border-radius: 8px;
126
+ padding: 20px;
127
+ background: #f0f8ff;
128
+ }
129
+ .dark #simulation-panel { background: #2a2a2a; }
130
+ .dark #result-panel { background: #1a2a3a; }
131
+ .history-container {
132
+ max-height: 600px;
133
+ overflow-y: auto;
134
+ margin-top: 20px;
135
+ }
136
+ .history-accordion {
137
+ margin-bottom: 10px;
138
+ }
139
+ """
140
+
141
+ header_html = """
142
+ <div style="display: flex; justify-content: space-between; align-items: center; width: 100%; margin-bottom: 20px; padding: 20px; background: linear-gradient(135deg, #e0e5ec 0%, #a7b5d0 100%); border-radius: 8px; box-shadow: 0 2px 8px rgba(0,0,0,0.1);">
143
+ <div style="display: flex; align-items: center;">
144
+ <img src="https://www.shlab.org.cn/static/img/index_14.685f6559.png" alt="Institution Logo" style="height: 60px; margin-right: 20px;">
145
+ <div>
146
+ <h1 style="margin: 0; color: #2c3e50; font-weight: 600;">🤖 InternNav Model Inference Demo</h1>
147
+ <p style="margin: 4px 0 0 0; color: #5d6d7e; font-size: 0.9em;">Model trained on InternNav framework</p>
148
+ </div>
149
+ </div>
150
+ <div style="display: flex; gap: 15px; align-items: center;">
151
+ <a href="https://github.com/OpenRobotLab" target="_blank" style="text-decoration: none; transition: transform 0.2s;" onmouseover="this.style.transform='scale(1.1)'" onmouseout="this.style.transform='scale(1)'">
152
+ <img src="https://github.githubassets.com/images/modules/logos_page/GitHub-Mark.png" alt="GitHub" style="height: 30px;">
153
+ </a>
154
+ <a href="https://huggingface.co/OpenRobotLab" target="_blank" style="text-decoration: none; transition: transform 0.2s;" onmouseover="this.style.transform='scale(1.1)'" onmouseout="this.style.transform='scale(1)'">
155
+ <img src="https://huggingface.co/front/assets/huggingface_logo-noborder.svg" alt="HuggingFace" style="height: 30px;">
156
+ </a>
157
+ <a href="https://huggingface.co/spaces/OpenRobotLab/InternManip-eval-demo" target="_blank">
158
+ <button style="padding: 8px 15px; background: #3498db; color: white; border: none; border-radius: 4px; cursor: pointer; font-weight: 500; transition: all 0.2s;"
159
+ onmouseover="this.style.backgroundColor='#2980b9'; this.style.transform='scale(1.05)'"
160
+ onmouseout="this.style.backgroundColor='#3498db'; this.style.transform='scale(1)'">
161
+ Go to InternManip Demo
162
+ </button>
163
+ </a>
164
+ </div>
165
+ </div>
166
+ """
167
+
168
+
169
+
170
+ with gr.Blocks(title="InternNav Model Inference Demo", css=custom_css) as demo:
171
+ gr.HTML(header_html)
172
+
173
+ history_state = gr.State([])
174
+ with gr.Row():
175
+ with gr.Column(elem_id="simulation-panel"):
176
+ gr.Markdown("### Simulation Settings")
177
+ scene_dropdown = gr.Dropdown(
178
+ label="Choose a scene",
179
+ choices=list(SCENE_CONFIGS.keys()),
180
+ value="demo1",
181
+ interactive=True
182
+ )
183
+ scene_description = gr.Markdown("")
184
+ scene_preview = gr.Image(
185
+ label="Scene Preview",
186
+ elem_classes=["scene-preview"],
187
+ interactive=False
188
+ )
189
+ prompt_input = gr.Textbox(
190
+ label="Navigation Prompt",
191
+ value="Walk past the left side of the bed and stop in the doorway.",
192
+ placeholder="e.g.: 'Walk past the left side of the bed and stop in the doorway.'",
193
+ lines=2,
194
+ max_lines=4
195
+ )
196
+ model_dropdown = gr.Dropdown(
197
+ label="Chose a pretrained model",
198
+ choices=MODEL_CHOICES,
199
+ value=MODEL_CHOICES[0],
200
+ interactive=True
201
+ )
202
+ mode_dropdown = gr.Dropdown(
203
+ label="Select Mode",
204
+ choices=MODE_CHOICES,
205
+ value=MODE_CHOICES[0],
206
+ interactive=True
207
+ )
208
+ scene_dropdown.change(
209
+ fn=lambda scene: [update_scene_display(scene)[0], update_scene_display(scene)[1], get_scene_instruction(scene)],
210
+ inputs=scene_dropdown,
211
+ outputs=[scene_description, scene_preview, prompt_input]
212
+ )
213
+
214
+ submit_btn = gr.Button("Start Navigation Simulation", variant="primary")
215
+ with gr.Column(elem_id="result-panel"):
216
+ gr.Markdown("### Latest Simulation Result")
217
+ video_output = gr.Video(
218
+ label="Live",
219
+ interactive=False,
220
+ format="mp4",
221
+ autoplay=True,
222
+ streaming=True
223
+ )
224
+ with gr.Column() as history_container:
225
+ gr.Markdown("### History")
226
+ gr.Markdown("#### History will be reset after refresh")
227
+ history_slots = []
228
+ for i in range(10):
229
+ with gr.Column(visible=False) as slot:
230
+ with gr.Accordion(visible=False, open=False) as accordion:
231
+ video = gr.Video(interactive=False)
232
+ detail_md = gr.Markdown()
233
+ history_slots.append((slot, accordion, video, detail_md))
234
+ gr.Examples(
235
+ examples=[
236
+ ["demo1", "rdp", "vlnPE", "Walk past the left side of the bed and stop in the doorway."],
237
+ ["demo2", "rdp", "vlnPE", "Walk through the bathroom, past the sink and toilet. Stop in front of the counter with the two suitcase."],
238
+ ["demo3", "rdp", "vlnPE", "Do a U-turn. Walk forward through the kitchen, heading to the black door. Walk out of the door and take a right onto the deck. Walk out on to the deck and stop."],
239
+ ["demo4", "rdp", "vlnPE", "Walk out of bathroom and stand on white bath mat."],
240
+ ["demo5", "rdp", "vlnPE", "Walk straight through the double wood doors, follow the red carpet straight to the next doorway and stop where the carpet splits off."]
241
+ ],
242
+ inputs=[scene_dropdown, model_dropdown, mode_dropdown, prompt_input],
243
+ label="Navigation Task Examples"
244
+ )
245
+ submit_btn.click(
246
+ fn=run_simulation,
247
+ inputs=[scene_dropdown, model_dropdown, mode_dropdown, prompt_input, history_state],
248
+ outputs=[video_output, history_state],
249
+ queue=True,
250
+ api_name="run_simulation"
251
+ ).then(
252
+ fn=update_history_display,
253
+ inputs=history_state,
254
+ outputs=[comp for slot in history_slots for comp in slot],
255
+ queue=True
256
+ )
257
+ demo.load(
258
+ fn=lambda: update_scene_display("demo1"),
259
+ outputs=[scene_description, scene_preview]
260
+ )
261
+ demo.load(
262
+ fn=record_access,
263
+ inputs=None,
264
+ outputs=None,
265
+ queue=False
266
+ )
267
+ demo.queue(default_concurrency_limit=8)
268
+ demo.unload(fn=cleanup_session)
269
+
270
+ if __name__ == "__main__":
271
+ demo.launch(
272
+ server_name="0.0.0.0",
273
+ server_port=7860, # Hugging Face Space默认端口
274
+ share=False,
275
+ debug=False, # 生产环境建议关闭debug
276
+ allowed_paths=["./assets", "./logs", "./tmp"] # 添加临时目录到允许路径
277
+ )
config.py CHANGED
@@ -14,33 +14,53 @@ SCENE_CONFIGS = {
14
  "description": "Demo 1",
15
  "objects": ["bedroom", "kitchen", "living room", ""],
16
  "preview_image": "./assets/scene_1.png",
 
17
  "default_instruction": "Walk past the left side of the bed and stop in the doorway."
18
  },
19
  "demo2": {
20
  "description": "Demo 2",
21
  "objects": ["office", "meeting room", "corridor"],
22
  "preview_image": "./assets/scene_2.png",
 
23
  "default_instruction": "Walk through the bathroom, past the sink and toilet. Stop in front of the counter with the two suitcase."
24
  },
25
  "demo3": {
26
  "description": "Demo 3",
27
  "objects": ["garage", "workshop", "storage"],
28
  "preview_image": "./assets/scene_3.png",
 
29
  "default_instruction": "Do a U-turn. Walk forward through the kitchen, heading to the black door. Walk out of the door and take a right onto the deck. Walk out on to the deck and stop."
30
  },
31
  "demo4": {
32
  "description": "Demo 4",
33
  "objects": ["garden", "patio", "pool"],
34
  "preview_image": "./assets/scene_4.png",
 
35
  "default_instruction": "Walk out of bathroom and stand on white bath mat."
36
  },
37
  "demo5": {
38
  "description": "Demo 5",
39
  "objects": ["library", "hall", "lounge"],
40
  "preview_image": "./assets/scene_5.png",
 
41
  "default_instruction": "Walk straight through the double wood doors, follow the red carpet straight to the next doorway and stop where the carpet splits off."
42
  },
43
  }
44
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
45
  MODEL_CHOICES = ["rdp", "cma"]
46
  MODE_CHOICES = ["vlnPE", "vlnCE"]
 
14
  "description": "Demo 1",
15
  "objects": ["bedroom", "kitchen", "living room", ""],
16
  "preview_image": "./assets/scene_1.png",
17
+ "glb_path": "scene_assets/demo1_no_ceiling.glb",
18
  "default_instruction": "Walk past the left side of the bed and stop in the doorway."
19
  },
20
  "demo2": {
21
  "description": "Demo 2",
22
  "objects": ["office", "meeting room", "corridor"],
23
  "preview_image": "./assets/scene_2.png",
24
+ "glb_path": "scene_assets/demo2_no_ceiling.glb",
25
  "default_instruction": "Walk through the bathroom, past the sink and toilet. Stop in front of the counter with the two suitcase."
26
  },
27
  "demo3": {
28
  "description": "Demo 3",
29
  "objects": ["garage", "workshop", "storage"],
30
  "preview_image": "./assets/scene_3.png",
31
+ "glb_path": "scene_assets/demo3_no_ceiling.glb",
32
  "default_instruction": "Do a U-turn. Walk forward through the kitchen, heading to the black door. Walk out of the door and take a right onto the deck. Walk out on to the deck and stop."
33
  },
34
  "demo4": {
35
  "description": "Demo 4",
36
  "objects": ["garden", "patio", "pool"],
37
  "preview_image": "./assets/scene_4.png",
38
+ "glb_path": "scene_assets/demo4_no_ceiling.glb",
39
  "default_instruction": "Walk out of bathroom and stand on white bath mat."
40
  },
41
  "demo5": {
42
  "description": "Demo 5",
43
  "objects": ["library", "hall", "lounge"],
44
  "preview_image": "./assets/scene_5.png",
45
+ "glb_path": "scene_assets/demo5_no_ceiling.glb",
46
  "default_instruction": "Walk straight through the double wood doors, follow the red carpet straight to the next doorway and stop where the carpet splits off."
47
  },
48
  }
49
 
50
+ EPISODE_CONFIGS = {
51
+ "episode_1": {
52
+ "description": "1",
53
+ },
54
+ "episode_2": {
55
+ "description": "2",
56
+ },
57
+ "episode_3": {
58
+ "description": "3",
59
+ },
60
+ "episode_4": {
61
+ "description": "4",
62
+ }
63
+ }
64
+
65
  MODEL_CHOICES = ["rdp", "cma"]
66
  MODE_CHOICES = ["vlnPE", "vlnCE"]
navigation_ui.py ADDED
@@ -0,0 +1,575 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import base64
2
+ import json
3
+ import logging
4
+ import os
5
+ import subprocess
6
+ import sys
7
+ import threading
8
+ import time
9
+ import uuid
10
+ from datetime import datetime, timedelta
11
+ from typing import Dict, List, Optional
12
+
13
+ import gradio as gr
14
+ import numpy as np
15
+ import open3d as o3d
16
+ import plotly.graph_objects as go
17
+ import requests
18
+ from fastapi import APIRouter, FastAPI, HTTPException, status, BackgroundTasks, Response
19
+ from pydantic import BaseModel
20
+
21
+ import asyncio
22
+ import uvicorn
23
+ from collections import defaultdict
24
+
25
+ BACKEND_URL = os.getenv("BACKEND_URL", "http://localhost:8001") # fastapi server
26
+ API_ENDPOINTS = {
27
+ "submit_task": f"{BACKEND_URL}/predict/video",
28
+ "query_status": f"{BACKEND_URL}/predict/task",
29
+ "get_result": f"{BACKEND_URL}/predict"
30
+ }
31
+
32
+
33
+ SCENE_CONFIGS = {
34
+ "scene_1": {
35
+ "description": "Modern Apartment",
36
+ "name": "17DRP5sb8fy",
37
+ "glb_path": "scene_assets/scene1_no_ceiling.glb" # PLY file path
38
+ },
39
+ "scene_2": {
40
+ "description": "Office Building",
41
+ "name": "r1Q1Z4BcV1o",
42
+ "glb_path": "scene_assets/scene2_no_ceiling.glb"
43
+ },
44
+ "scene_3": {
45
+ "description": "University Campus",
46
+ "name": "dhjEzFoUFzH",
47
+ "glb_path": "scene_assets/scene3_no_ceiling.glb"
48
+ },
49
+ }
50
+
51
+ EPISODE_CONFIGS = {
52
+ "episode_1": {
53
+ "description": "1",
54
+ },
55
+ "episode_2": {
56
+ "description": "2",
57
+ },
58
+ "episode_3": {
59
+ "description": "3",
60
+ },
61
+ "episode_4": {
62
+ "description": "4",
63
+ }
64
+ }
65
+
66
+ MODEL_CHOICES = []
67
+
68
+
69
+ ###############################################################################
70
+
71
+ SESSION_TASKS = {}
72
+ IP_REQUEST_RECORDS = defaultdict(list)
73
+ IP_LIMIT = 5
74
+
75
+ def is_request_allowed(ip: str) -> bool:
76
+ now = datetime.now()
77
+ IP_REQUEST_RECORDS[ip] = [t for t in IP_REQUEST_RECORDS[ip] if now - t < timedelta(minutes=1)]
78
+ if len(IP_REQUEST_RECORDS[ip]) < IP_LIMIT:
79
+ IP_REQUEST_RECORDS[ip].append(now)
80
+ return True
81
+ return False
82
+
83
+ ###############################################################################
84
+
85
+
86
+ # Log directory path
87
+ LOG_DIR = "~/logs"
88
+ os.makedirs(LOG_DIR, exist_ok=True)
89
+ ACCESS_LOG = os.path.join(LOG_DIR, "access.log")
90
+ SUBMISSION_LOG = os.path.join(LOG_DIR, "submissions.log")
91
+
92
+ def log_access(user_ip: str = None, user_agent: str = None):
93
+ timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
94
+ log_entry = {
95
+ "timestamp": timestamp,
96
+ "type": "access",
97
+ "user_ip": user_ip or "unknown",
98
+ "user_agent": user_agent or "unknown"
99
+ }
100
+
101
+ with open(ACCESS_LOG, "a") as f:
102
+ f.write(json.dumps(log_entry) + "\n")
103
+
104
+ def log_submission(scene: str, prompt: str, model: str, user: str = "anonymous", res: str = "unknown"):
105
+ timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
106
+ log_entry = {
107
+ "timestamp": timestamp,
108
+ "type": "submission",
109
+ "user": user,
110
+ "scene": scene,
111
+ "prompt": prompt,
112
+ "model": model,
113
+ #"max_step": str(max_step),
114
+ "res": res
115
+ }
116
+
117
+ with open(SUBMISSION_LOG, "a") as f:
118
+ f.write(json.dumps(log_entry) + "\n")
119
+
120
+ def read_logs(log_type: str = "all", max_entries: int = 50) -> list:
121
+ logs = []
122
+
123
+ if log_type in ["all", "access"]:
124
+ try:
125
+ with open(ACCESS_LOG, "r") as f:
126
+ for line in f:
127
+ logs.append(json.loads(line.strip()))
128
+ except FileNotFoundError:
129
+ pass
130
+
131
+ if log_type in ["all", "submission"]:
132
+ try:
133
+ with open(SUBMISSION_LOG, "r") as f:
134
+ for line in f:
135
+ logs.append(json.loads(line.strip()))
136
+ except FileNotFoundError:
137
+ pass
138
+
139
+ # Sorted by timestemp
140
+ logs.sort(key=lambda x: x["timestamp"], reverse=True)
141
+ return logs[:max_entries]
142
+
143
+ def format_logs_for_display(logs: list) -> str:
144
+ if not logs:
145
+ return "No log record"
146
+
147
+ markdown = "### System Access Log\n\n"
148
+ markdown += "| Time | Type | User/IP | Details |\n"
149
+ markdown += "|------|------|---------|----------|\n"
150
+
151
+ for log in logs:
152
+ timestamp = log.get("timestamp", "unknown")
153
+ log_type = "Access" if log.get("type") == "access" else "Submission"
154
+
155
+ if log_type == "Access":
156
+ user = log.get("user_ip", "unknown")
157
+ details = f"User-Agent: {log.get('user_agent', 'unknown')}"
158
+ else:
159
+ user = log.get("user", "anonymous")
160
+ result = log.get('res', 'unknown')
161
+ if result != "success":
162
+ if len(result) > 40: # Adjust this threshold as needed
163
+ result = f"{result[:20]}...{result[-20:]}"
164
+ details = f"Scene: {log.get('scene', 'unknown')}, Prompt: {log.get('prompt', '')}, Model: {log.get('model', 'unknown')}, result: {result}"
165
+
166
+ markdown += f"| {timestamp} | {log_type} | {user} | {details} |\n"
167
+
168
+ return markdown
169
+
170
+
171
+ def submit_to_backend(
172
+ scene: str,
173
+ prompt: str,
174
+ episode: str,
175
+ user: str = "Gradio-user",
176
+ ) -> dict:
177
+ job_id = str(uuid.uuid4())
178
+
179
+ scene_index = scene.split("_")[-1]
180
+ episode_index = episode.split("_")[-1]
181
+
182
+ data = {
183
+ "task_type": "vln_eval", # Identify task type
184
+ "instruction": prompt,
185
+ "scene_index": scene_index,
186
+ "episode_index": episode_index,
187
+ }
188
+
189
+ payload = {
190
+ "user": user,
191
+ "task": "robot_navigation",
192
+ "job_id": job_id,
193
+ "data": json.dumps(data)
194
+ }
195
+
196
+ try:
197
+ headers = {"Content-Type": "application/json"}
198
+ response = requests.post(
199
+ API_ENDPOINTS["submit_task"],
200
+ json=payload,
201
+ headers=headers,
202
+ timeout=600
203
+ )
204
+ return response.json()
205
+ except Exception as e:
206
+ return {"status": "error", "message": str(e)}
207
+
208
+ def get_task_status(task_id: str) -> dict:
209
+ try:
210
+ response = requests.get(f"{API_ENDPOINTS['query_status']}/{task_id}", timeout=600)
211
+ try:
212
+ return response.json()
213
+ except json.JSONDecodeError:
214
+ return {"status": "error", "message": response.text}
215
+ except Exception as e:
216
+ return {"status": "error", "message": str(e)}
217
+
218
+
219
+ def get_task_result(task_id: str) -> Optional[dict]:
220
+ try:
221
+ response = requests.get(
222
+ f"{API_ENDPOINTS['get_result']}/{task_id}",
223
+ timeout=5
224
+ )
225
+ return response.json()
226
+ except Exception as e:
227
+ print(f"Error fetching result: {e}")
228
+ return None
229
+
230
+ def run_simulation(
231
+ scene: str,
232
+ prompt: str,
233
+ episode: str,
234
+ history: list,
235
+ request: gr.Request
236
+ ) -> dict:
237
+ model = "InternNav-VLA"
238
+
239
+ timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
240
+ scene_desc = SCENE_CONFIGS.get(scene, {}).get("description", scene)
241
+
242
+ user_ip = request.client.host if request else "unknown"
243
+ session_id = request.session_hash
244
+
245
+ if not is_request_allowed(user_ip):
246
+ log_submission(scene, prompt, model, user_ip, "IP blocked temporarily")
247
+ raise gr.Error("Too many requests from this IP. Please wait and try again one minute later.")
248
+
249
+ submission_result = submit_to_backend(scene, prompt, episode)
250
+ print("submission_result: ", submission_result)
251
+
252
+ if submission_result.get("status") != "pending":
253
+ log_submission(scene, prompt, model, user_ip, "Submission failed")
254
+ raise gr.Error(f"Submission failed: {submission_result.get('message', 'unknown issue')}")
255
+
256
+ try:
257
+ task_id = submission_result["task_id"]
258
+ SESSION_TASKS[session_id] = task_id
259
+
260
+ gr.Info(f"Simulation started, task_id: {task_id}")
261
+ time.sleep(5)
262
+ # Get Task Status
263
+ status = get_task_status(task_id)
264
+ print("first status: ", status)
265
+ result_folder = status.get("result", "")
266
+ except Exception as e:
267
+ log_submission(scene, prompt, model, user_ip, str(e))
268
+ raise gr.Error(f"error occurred when parsing submission result from backend: {str(e)}")
269
+
270
+ while True:
271
+ status = get_task_status(task_id)
272
+ if status.get("status") == "completed":
273
+ break
274
+ elif status.get("status") == "failed":
275
+ break
276
+ time.sleep(1)
277
+ if status.get("status") == "completed":
278
+ import base64
279
+ video_bytes = base64.b64decode(status.get("video"))
280
+ receive_time = time.time()
281
+ with open(f"received_video_{receive_time}.mp4", "wb") as f:
282
+ f.write(video_bytes)
283
+ video_path = f"received_video_{receive_time}.mp4"
284
+ new_entry = {
285
+ "timestamp": timestamp,
286
+ "scene": scene,
287
+ "model": model,
288
+ "prompt": prompt,
289
+ "video_path": video_path
290
+ }
291
+
292
+ updated_history = history + [new_entry]
293
+
294
+ if len(updated_history) > 10:
295
+ updated_history = updated_history[:10]
296
+
297
+ print("updated_history:", updated_history)
298
+ log_submission(scene, prompt, model, user_ip, "success")
299
+ gr.Info("Simulation completed successfully!")
300
+ yield video_path, updated_history
301
+
302
+ elif status.get("status") == "failed":
303
+ log_submission(scene, prompt, model, user_ip, status.get('result', 'backend error'))
304
+ raise gr.Error(f"task execution fails: {status.get('result', 'backend error')}")
305
+ yield None, history
306
+
307
+ elif status.get("status") == "terminated":
308
+ log_submission(scene, prompt, model, user_ip, "terminated")
309
+ video_path = os.path.join(result_folder, "output.mp4")
310
+ if os.path.exists(video_path):
311
+ return f" task {task_id} terminated with some results", video_path, history
312
+ else:
313
+ return f" task {task_id} terminated without any results", None, history
314
+
315
+ else:
316
+ log_submission(scene, prompt, model, user_ip, "missing task's status from backend")
317
+ yield None, history
318
+
319
+ ###################################################################################################################
320
+ def update_history_display(history: list) -> list:
321
+ print("update_history_display")
322
+ updates = []
323
+
324
+ for i in range(10):
325
+ if i < len(history):
326
+ entry = history[i]
327
+ updates.extend([
328
+ gr.update(visible=True),
329
+ gr.update(visible=True, label=f"Simulation {i+1} scene: {entry['scene']}, prompt: {entry['prompt']}", open=False),
330
+ gr.update(value=entry['video_path'], visible=True),
331
+ gr.update(value=f"{entry['timestamp']}")
332
+ ])
333
+ print(f'update video')
334
+ print(entry['video_path'])
335
+ else:
336
+ updates.extend([
337
+ gr.update(visible=False),
338
+ gr.update(visible=False),
339
+ gr.update(value=None, visible=False),
340
+ gr.update(value="")
341
+ ])
342
+ print("update_history_display end!!")
343
+ return updates
344
+
345
+ def update_scene_display(scene: str):
346
+ print(f"update_scene_display {scene}")
347
+ config = SCENE_CONFIGS.get(scene, {})
348
+ glb_path = config.get("glb_path", "")
349
+
350
+ # Validate if file path exists
351
+ if not os.path.exists(glb_path):
352
+ return None, None
353
+
354
+ return None, glb_path
355
+
356
+ def update_episode_display(scene: str, episode: str):
357
+ print(f"update_episode_display {scene} {episode}")
358
+ config = SCENE_CONFIGS.get(scene, {})
359
+ scene_name = config.get("name", "")
360
+ episode_id = int(episode[-1])
361
+ image_path = os.path.join("scene_assets", f"{scene_name}_{episode_id-1}.jpg")
362
+ print(f"image_path {image_path}")
363
+ # vaild if file path exists
364
+ if not os.path.exists(image_path):
365
+ return None
366
+
367
+ return image_path
368
+ def update_log_display():
369
+ logs = read_logs()
370
+ return format_logs_for_display(logs)
371
+ ##############################################################################
372
+
373
+
374
+ def cleanup_session(request: gr.Request):
375
+ session_id = request.session_hash
376
+ task_id = SESSION_TASKS.pop(session_id, None)
377
+ if task_id:
378
+ try:
379
+ requests.post(f"{BACKEND_URL}/predict/terminate/{task_id}", timeout=3)
380
+ print(f"Task Terminated: {task_id}")
381
+ except Exception as e:
382
+ print(f"Task Termination Failed: {task_id}: {e}")
383
+
384
+
385
+
386
+ ###############################################################################
387
+
388
+ custom_css = """
389
+ #simulation-panel {
390
+ border-radius: 8px;
391
+ padding: 20px;
392
+ background: #f9f9f9;
393
+ box-shadow: 0 2px 4px rgba(0,0,0,0.1);
394
+ }
395
+ #result-panel {
396
+ border-radius: 8px;
397
+ padding: 20px;
398
+ background: #f0f8ff;
399
+ }
400
+ .dark #simulation-panel { background: #2a2a2a; }
401
+ .dark #result-panel { background: #1a2a3a; }
402
+
403
+ .history-container {
404
+ max-height: 600px;
405
+ overflow-y: auto;
406
+ margin-top: 20px;
407
+ }
408
+
409
+ .history-accordion {
410
+ margin-bottom: 10px;
411
+ }
412
+
413
+ .scene-preview {
414
+ height: 400px;
415
+ border: 1px solid #ddd;
416
+ border-radius: 8px;
417
+ overflow: hidden;
418
+ }
419
+ """
420
+
421
+ with gr.Blocks(title="Robot Navigation Inference", css=custom_css) as demo:
422
+ gr.Markdown("""
423
+ # 🧭 Habitat Robot Navigation Demo
424
+ ### Simulation Test Based on Habitat Framework
425
+ """)
426
+
427
+ history_state = gr.State([])
428
+
429
+ with gr.Row():
430
+ with gr.Column(elem_id="simulation-panel"):
431
+ gr.Markdown("### Simulation Task Configuration")
432
+ with gr.Row():
433
+ scene_dropdown = gr.Dropdown(
434
+ label="Select Scene",
435
+ choices=list(SCENE_CONFIGS.keys()),
436
+ value="scene_1",
437
+ interactive=True,
438
+ )
439
+ episode_dropdown = gr.Dropdown(
440
+ label="Select Start Position",
441
+ choices=list(EPISODE_CONFIGS.keys()),
442
+ value="episode_1",
443
+ interactive=True,
444
+ )
445
+
446
+ with gr.Row():
447
+ scene_preview = gr.Model3D(elem_classes=["scene-preview"],
448
+ camera_position=(90.0, 120, 20000.0),
449
+ #display_mode="solid"
450
+ )
451
+ fps_preview = gr.Image(label="FPS Preview")
452
+
453
+ scene_description = gr.Markdown("### Scene preview")
454
+
455
+ prompt_input = gr.Textbox(
456
+ label="Navigation Instruction",
457
+ value="Exit the bedroom and turn left. Walk straight passing the gray couch and stop near the rug.",
458
+ placeholder="e.g.: 'Exit the bedroom and turn left. Walk straight passing the gray couch and stop near the rug.'",
459
+ lines=2,
460
+ max_lines=4
461
+ )
462
+
463
+ scene_dropdown.change(
464
+ update_scene_display,
465
+ inputs=scene_dropdown,
466
+ outputs=[scene_description, scene_preview]
467
+ ).then(
468
+ update_episode_display,
469
+ inputs=[scene_dropdown, episode_dropdown],
470
+ outputs=[fps_preview]
471
+ )
472
+
473
+ episode_dropdown.change(
474
+ update_episode_display,
475
+ inputs=[scene_dropdown, episode_dropdown],
476
+ outputs=[fps_preview]
477
+ )
478
+
479
+ submit_btn = gr.Button("Start Navigation Simulation", variant="primary")
480
+
481
+
482
+ with gr.Column(elem_id="result-panel"):
483
+ gr.Markdown("### Latest Simulation Result")
484
+
485
+ # Video Output
486
+ video_output = gr.Video(
487
+ label="Live",
488
+ interactive=False,
489
+ format="mp4",
490
+ autoplay=True,
491
+ # streaming=True
492
+ )
493
+
494
+ with gr.Column() as history_container:
495
+ gr.Markdown("### History")
496
+ gr.Markdown("#### History will be reset after refresh")
497
+
498
+ history_slots = []
499
+ for i in range(10):
500
+ with gr.Column(visible=False) as slot:
501
+ with gr.Accordion(visible=False, open=False) as accordion:
502
+ video = gr.Video(interactive=False)
503
+ detail_md = gr.Markdown()
504
+ history_slots.append((slot, accordion, video, detail_md))
505
+
506
+ with gr.Accordion("View System Log (DEV ONLY)", open=False):
507
+ logs_display = gr.Markdown()
508
+ refresh_logs_btn = gr.Button("Refresh Log", variant="secondary")
509
+
510
+ refresh_logs_btn.click(
511
+ update_log_display,
512
+ outputs=logs_display
513
+ )
514
+
515
+ gr.Examples(
516
+ examples=[
517
+ ["scene_1", "Exit the bedroom and turn left. Walk straight passing the gray couch and stop near the rug.", "episode_0"],
518
+ ["scene_2", "Go from reception to conference room passing the water cooler.", "episode_1"],
519
+ ["scene_3", "From the classroom, go to the library via the main hall.", "episode_2"],
520
+ ["scene_4", "From emergency room to pharmacy passing nurse station.", "episode_3"]
521
+ ],
522
+ inputs=[scene_dropdown, prompt_input, episode_dropdown],
523
+ label="Navigation Task Example"
524
+ )
525
+
526
+ submit_btn.click(
527
+ fn=run_simulation,
528
+ inputs=[scene_dropdown, prompt_input, episode_dropdown, history_state],
529
+ outputs=[video_output, history_state],
530
+ queue=True,
531
+ api_name="run_simulation"
532
+ ).then(
533
+ fn=update_history_display,
534
+ inputs=history_state,
535
+ outputs=[comp for slot in history_slots for comp in slot],
536
+ queue=True
537
+ ).then(
538
+ fn=update_log_display,
539
+ outputs=logs_display,
540
+ )
541
+
542
+
543
+ demo.load(
544
+ fn=lambda: update_scene_display("scene_1"),
545
+ outputs=[scene_description, scene_preview]
546
+ ).then(
547
+ fn=update_log_display,
548
+ outputs=logs_display
549
+ )
550
+ demo.load(
551
+ fn=lambda: update_episode_display("scene_1", "episode_1"),
552
+ outputs=[fps_preview]
553
+ )
554
+
555
+ def record_access(request: gr.Request):
556
+ user_ip = request.client.host if request else "unknown"
557
+ user_agent = request.headers.get("user-agent", "unknown")
558
+ log_access(user_ip, user_agent)
559
+ return update_log_display()
560
+
561
+ demo.load(
562
+ fn=record_access,
563
+ inputs=None,
564
+ outputs=logs_display,
565
+ queue=False
566
+ )
567
+
568
+ demo.queue(default_concurrency_limit=8)
569
+
570
+ demo.unload(fn=cleanup_session)
571
+
572
+ if __name__ == "__main__":
573
+ demo.launch(server_name="0.0.0.0", server_port=5750, debug=True, share = True, allowed_paths=["/mnt"])
574
+
575
+
ui_components.py CHANGED
@@ -1,7 +1,8 @@
1
  # ui_components.py
2
  # Gradio界面相关和辅助函数
3
  import gradio as gr
4
- from config import SCENE_CONFIGS
 
5
  from logging_utils import read_logs, format_logs_for_display
6
 
7
  def update_history_display(history: list) -> list:
@@ -29,9 +30,26 @@ def update_scene_display(scene: str):
29
  config = SCENE_CONFIGS.get(scene, {})
30
  desc = config.get("description", "No Description")
31
  objects = "、".join(config.get("objects", []))
32
- image = config.get("preview_image", None)
33
  markdown = f"**{desc}** \nPlaces Included: {objects}"
34
- return markdown, image
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
35
 
36
  def get_scene_instruction(scene: str):
37
  """根据场景获取默认指令"""
 
1
  # ui_components.py
2
  # Gradio界面相关和辅助函数
3
  import gradio as gr
4
+ import os
5
+ from config import SCENE_CONFIGS, EPISODE_CONFIGS
6
  from logging_utils import read_logs, format_logs_for_display
7
 
8
  def update_history_display(history: list) -> list:
 
30
  config = SCENE_CONFIGS.get(scene, {})
31
  desc = config.get("description", "No Description")
32
  objects = "、".join(config.get("objects", []))
33
+ glb_path = config.get("glb_path", "")
34
  markdown = f"**{desc}** \nPlaces Included: {objects}"
35
+
36
+ # Validate if file path exists
37
+ if not os.path.exists(glb_path):
38
+ return markdown, None
39
+
40
+ return markdown, glb_path
41
+
42
+ def update_episode_display(scene: str, episode: str):
43
+ config = SCENE_CONFIGS.get(scene, {})
44
+ scene_name = scene # 使用demo1, demo2等作为scene_name
45
+ episode_id = int(episode[-1])
46
+ image_path = os.path.join("scene_assets", f"{scene_name}_{episode_id-1}.jpg")
47
+
48
+ # Validate if file path exists
49
+ if not os.path.exists(image_path):
50
+ return None
51
+
52
+ return image_path
53
 
54
  def get_scene_instruction(scene: str):
55
  """根据场景获取默认指令"""