liuyiyang commited on
Commit
5fb57c2
·
verified ·
1 Parent(s): 33d0abb

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +14 -779
app.py CHANGED
@@ -1,784 +1,19 @@
1
  import gradio as gr
2
- import requests
3
- import json
4
- import os
5
- import subprocess
6
- import uuid
7
- import time
8
- import cv2
9
- from typing import Optional, List
10
- import numpy as np
11
- from datetime import datetime, timedelta
12
- from collections import defaultdict
13
 
14
- os.environ["SPACES_QUEUE_ENABLED"] = "true"
15
-
16
- # 后端API配置(可配置化)
17
- BACKEND_URL = os.getenv("BACKEND_URL", "http://47.95.6.204:51001/")
18
- API_ENDPOINTS = {
19
- "submit_task": f"{BACKEND_URL}/predict/video",
20
- "query_status": f"{BACKEND_URL}/predict/task",
21
- "terminate_task": f"{BACKEND_URL}/predict/terminate"
22
- }
23
-
24
- # 模拟场景配置
25
- SCENE_CONFIGS = {
26
- "scene_1": {
27
- "description": "scene_1",
28
- "objects": ["milk carton", "ceramic bowl", "mug"],
29
- "preview_image": "assets/scene_1.png"
30
- },
31
- }
32
-
33
- # 可用模型列表
34
- MODEL_CHOICES = [
35
- "gr1",
36
- # "GR00T-N1",
37
- # "GR00T-1.5",
38
- # "Pi0",
39
- # "DP+CLIP",
40
- # "AcT+CLIP"
41
- ]
42
-
43
- ###############################################################################
44
-
45
- SESSION_TASKS = {}
46
- IP_REQUEST_RECORDS = defaultdict(list)
47
- IP_LIMIT = 5 # 每分钟最多请求次数
48
-
49
- def is_request_allowed(ip: str) -> bool:
50
- now = datetime.now()
51
- IP_REQUEST_RECORDS[ip] = [t for t in IP_REQUEST_RECORDS[ip] if now - t < timedelta(minutes=1)]
52
- if len(IP_REQUEST_RECORDS[ip]) < IP_LIMIT:
53
- IP_REQUEST_RECORDS[ip].append(now)
54
- return True
55
- return False
56
- ###############################################################################
57
-
58
-
59
- # 日志文件路径
60
- LOG_DIR = "/opt/gradio-frontend/logs"
61
- os.makedirs(LOG_DIR, exist_ok=True)
62
- ACCESS_LOG = os.path.join(LOG_DIR, "access.log")
63
- SUBMISSION_LOG = os.path.join(LOG_DIR, "submissions.log")
64
-
65
- def log_access(user_ip: str = None, user_agent: str = None):
66
- """记录用户访问日志"""
67
- timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
68
- log_entry = {
69
- "timestamp": timestamp,
70
- "type": "access",
71
- "user_ip": user_ip or "unknown",
72
- "user_agent": user_agent or "unknown"
73
- }
74
-
75
- with open(ACCESS_LOG, "a") as f:
76
- f.write(json.dumps(log_entry) + "\n")
77
-
78
- def log_submission(scene: str, prompt: str, model: str, max_step: int, user: str = "anonymous", res: str = "unknown"):
79
- """记录用户提交日志"""
80
- timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
81
- log_entry = {
82
- "timestamp": timestamp,
83
- "type": "submission",
84
- "user": user,
85
- "scene": scene,
86
- "prompt": prompt,
87
- "model": model,
88
- "max_step": str(max_step),
89
- "res": res
90
- }
91
-
92
- with open(SUBMISSION_LOG, "a") as f:
93
- f.write(json.dumps(log_entry) + "\n")
94
-
95
- def read_logs(log_type: str = "all", max_entries: int = 50) -> list:
96
- """读取日志文件"""
97
- logs = []
98
-
99
- if log_type in ["all", "access"]:
100
- try:
101
- with open(ACCESS_LOG, "r") as f:
102
- for line in f:
103
- logs.append(json.loads(line.strip()))
104
- except FileNotFoundError:
105
- pass
106
-
107
- if log_type in ["all", "submission"]:
108
- try:
109
- with open(SUBMISSION_LOG, "r") as f:
110
- for line in f:
111
- logs.append(json.loads(line.strip()))
112
- except FileNotFoundError:
113
- pass
114
-
115
- # 按时间戳排序,最新的在前
116
- logs.sort(key=lambda x: x["timestamp"], reverse=True)
117
- return logs[:max_entries]
118
-
119
- def format_logs_for_display(logs: list) -> str:
120
- """格式化日志用于显示"""
121
- if not logs:
122
- return "暂无日志记录"
123
-
124
- markdown = "### 系统访问日志\n\n"
125
- markdown += "| 时间 | 类型 | 用户/IP | 详细信息 |\n"
126
- markdown += "|------|------|---------|----------|\n"
127
-
128
- for log in logs:
129
- timestamp = log.get("timestamp", "unknown")
130
- log_type = "访问" if log.get("type") == "access" else "提交"
131
-
132
- if log_type == "访问":
133
- user = log.get("user_ip", "unknown")
134
- details = f"User-Agent: {log.get('user_agent', 'unknown')}"
135
- else:
136
- user = log.get("user", "anonymous")
137
- result = log.get('res', 'unknown')
138
- if result != "success":
139
- if len(result) > 40: # Adjust this threshold as needed
140
- result = f"{result[:20]}...{result[-20:]}"
141
- details = f"场景: {log.get('scene', 'unknown')}, 指令: {log.get('prompt', '')}, 模型: {log.get('model', 'unknown')}, max step: {log.get('max_step', '300')}, result: {result}"
142
-
143
- markdown += f"| {timestamp} | {log_type} | {user} | {details} |\n"
144
-
145
- return markdown
146
-
147
-
148
-
149
- ###############################################################################
150
-
151
-
152
- def stream_simulation_results(result_folder: str, task_id: str, fps: int = 30):
153
- """
154
- 流式输出仿真结果,同时监控图片文件夹和后端任务状态
155
-
156
- 参数:
157
- result_folder: 包含生成图片的文件夹路径
158
- task_id: 后端任务ID���于状态查询
159
- fps: 输出视频的帧率
160
-
161
- 生成:
162
- 生成的视频文件路径 (分段输出)
163
- """
164
- # 初始化变量
165
- result_folder = os.path.join(result_folder, "image")
166
- os.makedirs(result_folder, exist_ok=True)
167
- frame_buffer: List[np.ndarray] = []
168
- frames_per_segment = fps * 2 # 每2秒60帧
169
- processed_files = set()
170
- width, height = 0, 0
171
- last_status_check = 0
172
- status_check_interval = 5 # 每5秒检查一次后端状态
173
- max_time = 240
174
-
175
- while max_time > 0:
176
- max_time -= 1
177
- current_time = time.time()
178
-
179
- # 定期检查后端状态
180
- if current_time - last_status_check > status_check_interval:
181
- status = get_task_status(task_id)
182
- print("status: ", status)
183
- if status.get("status") == "completed":
184
- # 确保处理完所有已生成的图片
185
- process_remaining_images(result_folder, processed_files, frame_buffer)
186
- if frame_buffer:
187
- yield create_video_segment(frame_buffer, fps, width, height)
188
- break
189
- elif status.get("status") == "failed":
190
- raise gr.Error(f"任务执行失败: {status.get('result', '未知错误')}")
191
- elif status.get("status") == "terminated":
192
- break
193
- last_status_check = current_time
194
-
195
- # 处理新生成的图片
196
- current_files = sorted(
197
- [f for f in os.listdir(result_folder)
198
- if f.lower().endswith(('.png', '.jpg', '.jpeg'))],
199
- key=lambda x: os.path.splitext(x)[0] # 按文件名排序
200
- )
201
-
202
- new_files = [f for f in current_files if f not in processed_files]
203
- has_new_frames = False
204
-
205
- for filename in new_files:
206
- try:
207
- img_path = os.path.join(result_folder, filename)
208
- frame = cv2.imread(img_path)
209
- if frame is not None:
210
- if width == 0: # 第一次获取图像尺寸
211
- height, width = frame.shape[:2]
212
-
213
- frame_buffer.append(frame)
214
- processed_files.add(filename)
215
- has_new_frames = True
216
- except Exception as e:
217
- print(f"Error processing {filename}: {e}")
218
-
219
- # 如果有新帧且积累够60帧,输出视频片段
220
- if has_new_frames and len(frame_buffer) >= frames_per_segment:
221
- segment_frames = frame_buffer[:frames_per_segment]
222
- frame_buffer = frame_buffer[frames_per_segment:]
223
- yield create_video_segment(segment_frames, fps, width, height)
224
-
225
- time.sleep(1) # 避免过于频繁检查
226
-
227
- if max_time <= 0:
228
- raise gr.Error("timeout 240s")
229
-
230
- def create_video_segment(frames: List[np.ndarray], fps: int, width: int, height: int) -> str:
231
- """创建视频片段"""
232
- os.makedirs("/opt/gradio_demo/tasks/video_chunk", exist_ok=True)
233
- segment_name = f"/opt/gradio_demo/tasks/video_chunk/output_{uuid.uuid4()}.mp4"
234
- fourcc = cv2.VideoWriter_fourcc(*'mp4v')
235
- out = cv2.VideoWriter(segment_name, fourcc, fps, (width, height))
236
-
237
- for frame in frames:
238
- out.write(frame)
239
- out.release()
240
-
241
- return segment_name
242
-
243
- def process_remaining_images(result_folder: str, processed_files: set, frame_buffer: List[np.ndarray]):
244
- """处理剩余的图片"""
245
- current_files = sorted(
246
- [f for f in os.listdir(result_folder)
247
- if f.lower().endswith(('.png', '.jpg', '.jpeg'))],
248
- key=lambda x: os.path.splitext(x)[0]
249
- )
250
-
251
- new_files = [f for f in current_files if f not in processed_files]
252
-
253
- for filename in new_files:
254
- try:
255
- img_path = os.path.join(result_folder, filename)
256
- frame = cv2.imread(img_path)
257
- if frame is not None:
258
- frame_buffer.append(frame)
259
- processed_files.add(filename)
260
- except Exception as e:
261
- print(f"Error processing remaining {filename}: {e}")
262
-
263
-
264
-
265
-
266
- ###############################################################################
267
-
268
-
269
-
270
- def submit_to_backend(
271
- scene: str,
272
- prompt: str,
273
- model: str,
274
- max_step: int,
275
- user: str = "Gradio-user",
276
- ) -> dict:
277
- job_id = str(uuid.uuid4())
278
-
279
- data = {
280
- "scene_type": scene,
281
- "instruction": prompt,
282
- "model_type": model,
283
- "max_step": str(max_step)
284
- }
285
-
286
- payload = {
287
- "user": user,
288
- "task": "robot_manipulation",
289
- "job_id": job_id,
290
- "data": json.dumps(data)
291
- }
292
-
293
- try:
294
- headers = {"Content-Type": "application/json"}
295
- response = requests.post(
296
- API_ENDPOINTS["submit_task"],
297
- json=payload,
298
- headers=headers,
299
- timeout=10
300
- )
301
- return response.json()
302
- except Exception as e:
303
- return {"status": "error", "message": str(e)}
304
-
305
- def get_task_status(task_id: str) -> dict:
306
- """
307
- 查询任务状态
308
- """
309
- try:
310
- response = requests.get(
311
- f"{API_ENDPOINTS['query_status']}/{task_id}",
312
- timeout=5
313
- )
314
- return response.json()
315
- except Exception as e:
316
- return {"status": "error", "message": str(e)}
317
-
318
- def terminate_task(task_id: str) -> Optional[dict]:
319
- """
320
- 终止任务
321
- """
322
- try:
323
- response = requests.post(
324
- f"{API_ENDPOINTS['terminate_task']}/{task_id}",
325
- timeout=3
326
- )
327
- return response.json()
328
- except Exception as e:
329
- print(f"Error terminate task: {e}")
330
- return None
331
-
332
- def convert_to_h264(video_path):
333
  """
334
- 将视频转换为 H.264 编码的 MP4 格式
335
- 生成新文件路径在原路径基础上添加 _h264 后缀)
336
- """
337
- base, ext = os.path.splitext(video_path)
338
- video_path_h264 = f"{base}_h264.mp4"
339
-
340
- try:
341
- # 构建 FFmpeg 命令
342
- ffmpeg_cmd = [
343
- "ffmpeg",
344
- "-i", video_path,
345
- "-c:v", "libx264",
346
- "-preset", "slow",
347
- "-crf", "23",
348
- "-c:a", "aac",
349
- "-movflags", "+faststart",
350
- video_path_h264
351
- ]
352
-
353
- # 执行 FFmpeg 命令
354
- subprocess.run(ffmpeg_cmd, check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
355
-
356
- # 检查输出文件是否存在
357
- if not os.path.exists(video_path_h264):
358
- raise FileNotFoundError(f"H.264 编码文件未生成: {video_path_h264}")
359
-
360
- return video_path_h264
361
-
362
- except subprocess.CalledProcessError as e:
363
- raise gr.Error(f"FFmpeg 转换失败: {e.stderr}")
364
- except Exception as e:
365
- raise gr.Error(f"转换过程中发生错误: {str(e)}")
366
-
367
- def run_simulation(
368
- scene: str,
369
- prompt: str,
370
- model: str,
371
- max_step: int,
372
- history: list,
373
- request: gr.Request
374
- ) -> dict:
375
- """运行仿真并更新历史记录"""
376
- # 获取当前时间
377
- timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
378
- scene_desc = SCENE_CONFIGS.get(scene, {}).get("description", scene)
379
-
380
- # 记录用户提交
381
- user_ip = request.client.host if request else "unknown"
382
- session_id = request.session_hash
383
-
384
- if not is_request_allowed(user_ip):
385
- log_submission(scene, prompt, model, max_step, user_ip, "IP blocked temporarily")
386
- raise gr.Error("Too many requests from this IP. Please wait and try again one minute later.")
387
-
388
- # 提交任务到后端
389
- submission_result = submit_to_backend(scene, prompt, model, max_step, user_ip)
390
- print("submission_result: ", submission_result)
391
-
392
- if submission_result.get("status") != "pending":
393
- log_submission(scene, prompt, model, max_step, user_ip, "Submission failed")
394
- raise gr.Error(f"Submission failed: {submission_result.get('message', 'unknown issue')}")
395
-
396
- try:
397
- task_id = submission_result["task_id"]
398
- SESSION_TASKS[session_id] = task_id
399
-
400
- gr.Info(f"Simulation started, task_id: {task_id}")
401
- time.sleep(5)
402
- # 获取任务状态
403
- status = get_task_status(task_id)
404
- print("first status: ", status)
405
- result_folder = status.get("result", "")
406
- except Exception as e:
407
- log_submission(scene, prompt, model, max_step, user_ip, str(e))
408
- raise gr.Error(f"error occurred when parsing submission result from backend: {str(e)}")
409
-
410
-
411
- if not os.path.exists(result_folder):
412
- log_submission(scene, prompt, model, max_step, user_ip, "Result folder provided by backend doesn't exist")
413
- raise gr.Error(f"Result folder provided by backend doesn't exist: <PATH>{result_folder}")
414
-
415
-
416
- # 流式输出视频片段
417
- try:
418
- for video_path in stream_simulation_results(result_folder, task_id):
419
- if video_path:
420
- yield video_path, history
421
- except Exception as e:
422
- log_submission(scene, prompt, model, max_step, user_ip, str(e))
423
- raise gr.Error(f"Error while streaming: {str(e)}")
424
-
425
- # 获取任务状态
426
- status = get_task_status(task_id)
427
- print("status: ", status)
428
- if status.get("status") == "completed":
429
- video_path = os.path.join(status.get("result"), "manipulation.mp4")
430
- print("video_path: ", video_path)
431
- video_path = convert_to_h264(video_path)
432
-
433
- # 创建新的历史记录条目
434
- new_entry = {
435
- "timestamp": timestamp,
436
- "scene": scene,
437
- "model": model,
438
- "prompt": prompt,
439
- "max_step": max_step,
440
- "video_path": video_path,
441
- "task_id": task_id
442
- }
443
-
444
- # 将新条目添加到历史记录顶部
445
- updated_history = history + [new_entry]
446
-
447
- # 限制历史记录数量,避免内存问题
448
- if len(updated_history) > 10:
449
- updated_history = updated_history[:10]
450
-
451
- print("updated_history:", updated_history)
452
- log_submission(scene, prompt, model, max_step, user_ip, "success")
453
- gr.Info("Simulation completed successfully!")
454
- yield None, updated_history
455
-
456
- elif status.get("status") == "failed":
457
- log_submission(scene, prompt, model, max_step, user_ip, status.get('result', 'backend error'))
458
- raise gr.Error(f"Task execution failed: {status.get('result', 'backend unknown issue')}")
459
- yield None, history
460
-
461
- elif status.get("status") == "terminated":
462
- log_submission(scene, prompt, model, max_step, user_ip, "user end terminated")
463
- yield None, history
464
-
465
- else:
466
- log_submission(scene, prompt, model, max_step, user_ip, "missing task's status from backend (Pending?)")
467
- raise gr.Error("missing task's status from backend (Pending?)")
468
- yield None, history
469
-
470
-
471
- ###############################################################################
472
-
473
-
474
- def update_history_display(history: list) -> list:
475
- """更新历史记录显示"""
476
- print("更新历史记录显示")
477
- updates = []
478
-
479
- for i in range(10):
480
- if i < len(history): # 如果有历史记录,更新对应槽位
481
- entry = history[i]
482
- updates.extend([
483
- gr.update(visible=True), # 更新 Column 可见性
484
- gr.update(visible=True, label=f"# {i+1} | {entry['scene']} | {entry['model']} | {entry['prompt']}", open=(i+1==len(history))), # 更新 Accordion
485
- gr.update(value=entry['video_path'], visible=True, autoplay=False), # 更新 Video
486
- gr.update(value=f"{entry['timestamp']}") # 更新详细 Markdown
487
- ])
488
- else: # 如果没有历史记录,隐藏槽位
489
- updates.extend([
490
- gr.update(visible=False), # 隐藏 Column
491
- gr.update(visible=False), # 隐藏 Accordion
492
- gr.update(value=None, visible=False), # 清空 Video
493
- gr.update(value="") # 清空详细 Markdown
494
- ])
495
- print("更新完成!")
496
- return updates
497
-
498
- def update_scene_display(scene: str) -> tuple[str, Optional[str]]:
499
- """更新场景描述和预览图"""
500
- config = SCENE_CONFIGS.get(scene, {})
501
- desc = config.get("description", "No description")
502
- objects = ", ".join(config.get("objects", []))
503
- image = config.get("preview_image", None)
504
-
505
- markdown = f"**{desc}** \nObjects in this scene: {objects}"
506
- return markdown, image
507
-
508
- def update_log_display():
509
- """更新日志显示"""
510
- logs = read_logs()
511
- return format_logs_for_display(logs)
512
-
513
- ###############################################################################
514
-
515
-
516
- def cleanup_session(request: gr.Request):
517
- session_id = request.session_hash
518
- task_id = SESSION_TASKS.pop(session_id, None)
519
-
520
- if task_id:
521
- try:
522
- status = get_task_status(task_id)
523
- print("clean up check status: ", status)
524
- if status.get("status") == "pending":
525
- res = terminate_task(task_id)
526
- if res.get("status") == "success":
527
- print(f"已终止任务 {task_id}")
528
- else:
529
- print(f"终止任务失败 {task_id}: {res.get('status', 'unknown issue')}")
530
- except Exception as e:
531
- print(f"终止任务失败 {task_id}: {e}")
532
-
533
-
534
-
535
- ###############################################################################
536
-
537
- header_html = """
538
- <div style="display: flex; justify-content: space-between; align-items: center; width: 100%; margin-bottom: 20px; padding: 20px; background: linear-gradient(135deg, #528bdb 0%, #a7b5d0 100%); border-radius: 8px; box-shadow: 0 2px 8px rgba(0,0,0,0.1);">
539
- <div style="display: flex; align-items: center;">
540
- <img src="https://www.shlab.org.cn/static/img/index_14.685f6559.png" alt="Institution Logo" style="height: 60px; margin-right: 20px;">
541
- <div>
542
- <h1 style="margin: 0; color: #2c3e50; font-weight: 600;">🤖 InternManip Model Inference Demo</h1>
543
- <p style="margin: 4px 0 0 0; color: #5d6d7e; font-size: 0.9em;">Model trained on InternManip framework</p>
544
- </div>
545
- </div>
546
- <div style="display: flex; gap: 15px; align-items: center;">
547
- <a href="https://github.com/InternRobotics" target="_blank" style="text-decoration: none; transition: transform 0.2s;" onmouseover="this.style.transform='scale(1.1)'" onmouseout="this.style.transform='scale(1)'">
548
- <img src="https://github.githubassets.com/images/modules/logos_page/GitHub-Mark.png" alt="GitHub" style="height: 30px;">
549
- </a>
550
- <a href="https://huggingface.co/InternRobotics" target="_blank" style="text-decoration: none; transition: transform 0.2s;" onmouseover="this.style.transform='scale(1.1)'" onmouseout="this.style.transform='scale(1)'">
551
- <img src="https://huggingface.co/front/assets/huggingface_logo-noborder.svg" alt="HuggingFace" style="height: 30px;">
552
- </a>
553
- <a href="https://huggingface.co/spaces/OpenRobotLab/InternNav-eval-demo" target="_blank">
554
- <button style="padding: 8px 15px; background: #3498db; color: white; border: none; border-radius: 4px; cursor: pointer; font-weight: 500; transition: all 0.2s;"
555
- onmouseover="this.style.backgroundColor='#2980b9'; this.style.transform='scale(1.05)'"
556
- onmouseout="this.style.backgroundColor='#3498db'; this.style.transform='scale(1)'">
557
- Go to InternNav Demo
558
- </button>
559
- </a>
560
- </div>
561
- </div>
562
- """
563
-
564
- ###############################################################################
565
-
566
-
567
- # 自定义CSS样式
568
- custom_css = """
569
- #simulation-panel {
570
- border-radius: 8px;
571
- padding: 20px;
572
- background: #f9f9f9;
573
- box-shadow: 0 2px 4px rgba(0,0,0,0.1);
574
- }
575
- #result-panel {
576
- border-radius: 8px;
577
- padding: 20px;
578
- background: #f0f8ff;
579
- }
580
- .dark #simulation-panel { background: #2a2a2a; }
581
- .dark #result-panel { background: #1a2a3a; }
582
-
583
- .history-container {
584
- max-height: 600px;
585
- overflow-y: auto;
586
- margin-top: 20px;
587
- }
588
-
589
- .history-accordion {
590
- margin-bottom: 10px;
591
- }
592
-
593
- .logs-container {
594
- max-height: 500px;
595
- overflow-y: auto;
596
- margin-top: 20px;
597
- padding: 15px;
598
- background: #f5f5f5;
599
- border-radius: 8px;
600
- }
601
-
602
- .dark .logs-container {
603
- background: #2a2a2a;
604
- }
605
-
606
- .log-table {
607
- width: 100%;
608
- border-collapse: collapse;
609
- }
610
-
611
- .log-table th, .log-table td {
612
- padding: 8px 12px;
613
- border: 1px solid #ddd;
614
- text-align: left;
615
- }
616
-
617
- .dark .log-table th, .dark .log-table td {
618
- border-color: #444;
619
- }
620
- """
621
-
622
- # 创建Gradio界面
623
- with gr.Blocks(title="InternManip Model Inference Demo", css=custom_css) as demo:
624
- gr.HTML(header_html)
625
-
626
- # # 标题和描述
627
- # gr.Markdown("""
628
- # # 🤖 InternManip Model Inference Demo
629
- # ### Model trained on InternManip framework
630
- # """)
631
-
632
- # 存储历史记录的组件变量
633
- history_state = gr.State([])
634
-
635
- with gr.Row():
636
- # 左侧控制面板
637
- with gr.Column(elem_id="simulation-panel"):
638
- gr.Markdown("### Simulation Settings")
639
-
640
- # 场景选择
641
- scene_dropdown = gr.Dropdown(
642
- label="Choose a scene",
643
- choices=list(SCENE_CONFIGS.keys()),
644
- value="scene_1",
645
- interactive=True
646
- )
647
-
648
- # 场景描述预览
649
- scene_description = gr.Markdown("")
650
- scene_preview = gr.Image(
651
- label="Scene Preview",
652
- elem_classes=["scene-preview"],
653
- interactive=False
654
- )
655
-
656
- scene_dropdown.change(
657
- update_scene_display,
658
- inputs=scene_dropdown,
659
- outputs=[scene_description, scene_preview]
660
- )
661
-
662
- # 操作指令输入
663
- prompt_input = gr.Textbox(
664
- label="Manipulation Prompt",
665
- value="Move the milk carton to the top of the ceramic bowl.",
666
- placeholder="Example: 'Move the milk carton to the top of the ceramic bowl.'",
667
- lines=2,
668
- max_lines=4
669
- )
670
-
671
- # 模型选择
672
- model_dropdown = gr.Dropdown(
673
- label="Chose a pretrained model",
674
- choices=MODEL_CHOICES,
675
- value=MODEL_CHOICES[0]
676
- )
677
-
678
- with gr.Accordion("Advance Settings", open=False):
679
- max_steps = gr.Slider(
680
- minimum=50,
681
- maximum=500,
682
- value=300,
683
- step=10,
684
- label="Max Steps"
685
- )
686
-
687
- # 提交按钮
688
- submit_btn = gr.Button("Apply and Start Simulation", variant="primary")
689
-
690
- # 右侧结果面板
691
- with gr.Column(elem_id="result-panel"):
692
- gr.Markdown("### Result")
693
-
694
- # progress_instruction = gr.Markdown("### Please click the botton on the left column to start.")
695
-
696
- # 视频输出
697
- video_output = gr.Video(
698
- label="Live",
699
- interactive=False,
700
- format="mp4",
701
- autoplay=True,
702
- streaming=True
703
- )
704
-
705
- # 历史记录显示区域
706
- with gr.Column() as history_container:
707
- gr.Markdown("### History")
708
- gr.Markdown("#### History will be reset after refresh")
709
-
710
- # 预创建10个历史记录槽位
711
- history_slots = []
712
- for i in range(10):
713
- with gr.Column(visible=False) as slot:
714
- with gr.Accordion(visible=False, open=False) as accordion:
715
- video = gr.Video(interactive=False) # 用于播放视频
716
- detail_md = gr.Markdown() # 用于显示详细信息
717
- history_slots.append((slot, accordion, video, detail_md)) # 存储所有相关组件
718
-
719
- # 添加日志显示区域
720
- with gr.Accordion("查看系统访问日志(DEV ONLY)", open=False):
721
- logs_display = gr.Markdown()
722
- refresh_logs_btn = gr.Button("刷新日志", variant="secondary")
723
-
724
- refresh_logs_btn.click(
725
- update_log_display,
726
- outputs=logs_display
727
- )
728
-
729
- # 示例
730
- gr.Examples(
731
- examples=[
732
- ["scene_1", "Move the milk carton to the top of the ceramic bowl.", "gr1", 300],
733
- ],
734
- inputs=[scene_dropdown, prompt_input, model_dropdown, max_steps],
735
- label="Examples"
736
- )
737
-
738
- # 提交处理
739
- submit_btn.click(
740
- fn=run_simulation,
741
- inputs=[scene_dropdown, prompt_input, model_dropdown, max_steps, history_state],
742
- outputs=[video_output, history_state],
743
- queue=True
744
- ).then(
745
- fn=update_history_display,
746
- inputs=history_state,
747
- outputs=[comp for slot in history_slots for comp in slot],
748
- queue=True
749
- ).then(
750
- fn=update_log_display,
751
- outputs=logs_display
752
- )
753
-
754
- # 初始化场景描述和日志
755
- demo.load(
756
- fn=lambda: update_scene_display("scene_1"),
757
- outputs=[scene_description, scene_preview]
758
- ).then(
759
- fn=update_log_display,
760
- outputs=logs_display
761
- )
762
-
763
- # 记录访问
764
- def record_access(request: gr.Request):
765
- user_ip = request.client.host if request else "unknown"
766
- user_agent = request.headers.get("user-agent", "unknown")
767
- log_access(user_ip, user_agent)
768
- return update_log_display()
769
-
770
- demo.load(
771
- fn=record_access,
772
- inputs=None,
773
- outputs=logs_display,
774
- queue=False
775
- )
776
-
777
- demo.queue(default_concurrency_limit=8)
778
-
779
- demo.unload(fn=cleanup_session)
780
 
 
 
781
 
782
- # 启动应用
783
- if __name__ == "__main__":
784
- demo.launch()
 
1
  import gradio as gr
 
 
 
 
 
 
 
 
 
 
 
2
 
3
+ def show_external_website():
4
+ # 使用iframe嵌入目标网页
5
+ iframe_html = """
6
+ <iframe
7
+ src="http://47.95.6.204:51000"
8
+ width="100%"
9
+ height="500px"
10
+ frameborder="0"
11
+ allow="fullscreen"
12
+ ></iframe>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
13
  """
14
+ return iframe_html
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
15
 
16
+ with gr.Blocks() as demo:
17
+ gr.HTML(show_external_website())
18
 
19
+ demo.launch()