blanchon commited on
Commit
1120045
·
1 Parent(s): f1df768
src/inference_server/simple_integrated.py CHANGED
@@ -4,7 +4,9 @@ import time
4
 
5
  import gradio as gr
6
  import uvicorn
 
7
  from fastapi.middleware.cors import CORSMiddleware
 
8
 
9
  # Import our existing components
10
  from inference_server.main import app as fastapi_app
@@ -30,165 +32,23 @@ def start_api_server_thread(port: int = 8001):
30
 
31
  def run_server():
32
  global server_started
33
- from inference_server.main import app
34
-
35
  try:
36
- uvicorn.run(app, host="localhost", port=port, log_level="warning")
 
 
 
 
37
  except Exception as e:
38
- logger.exception(f"API server error: {e}")
39
  finally:
40
  server_started = False
41
 
42
  server_thread = threading.Thread(target=run_server, daemon=True)
43
  server_thread.start()
 
44
 
45
  # Wait a moment for server to start
46
  time.sleep(2)
47
- server_started = True
48
-
49
-
50
- class SimpleServerManager:
51
- """Direct access to session manager without HTTP calls."""
52
-
53
- def __init__(self):
54
- self.session_manager = session_manager
55
-
56
- async def create_and_start_session(
57
- self,
58
- session_id: str,
59
- policy_path: str,
60
- camera_names: str,
61
- arena_server_url: str,
62
- ) -> str:
63
- """Create and start a session directly."""
64
- try:
65
- cameras = [name.strip() for name in camera_names.split(",") if name.strip()]
66
- if not cameras:
67
- cameras = ["front"]
68
-
69
- # Create session directly
70
- room_info = await self.session_manager.create_session(
71
- session_id=session_id,
72
- policy_path=policy_path,
73
- camera_names=cameras,
74
- arena_server_url=arena_server_url,
75
- )
76
-
77
- # Start the session
78
- await self.session_manager.start_inference(session_id)
79
-
80
- # Format camera rooms more clearly
81
- camera_info = []
82
- for camera_name, room_id in room_info["camera_room_ids"].items():
83
- camera_info.append(f" 📹 **{camera_name.title()}**: `{room_id}`")
84
-
85
- return f"""## ✅ Session Created Successfully!
86
-
87
- **Session ID**: `{session_id}`
88
- **Status**: 🟢 **RUNNING** (inference active)
89
-
90
- ### 📡 Arena Connection Details:
91
- **Workspace ID**: `{room_info["workspace_id"]}`
92
-
93
- **Camera Rooms**:
94
- {chr(10).join(camera_info)}
95
-
96
- **Joint Communication**:
97
- 📥 **Input**: `{room_info["joint_input_room_id"]}`
98
- 📤 **Output**: `{room_info["joint_output_room_id"]}`
99
-
100
- ---
101
- ## 🤖 Ready for Robot Control!
102
- Your robot can now connect to these rooms to start AI-powered control."""
103
-
104
- except Exception as e:
105
- return f"❌ Error: {e!s}"
106
-
107
- async def control_session(self, session_id: str, action: str) -> str:
108
- """Control a session directly."""
109
- if not session_id.strip():
110
- return "⚠️ No session ID provided"
111
-
112
- try:
113
- # Check current status
114
- if session_id not in self.session_manager.sessions:
115
- return f"❌ Session '{session_id}' not found"
116
-
117
- session = self.session_manager.sessions[session_id]
118
- current_status = session.status
119
-
120
- # Smart action handling
121
- if action == "start" and current_status == "running":
122
- return f"ℹ️ Session '{session_id}' is already running"
123
- if action == "stop" and current_status in {"stopped", "ready"}:
124
- return f"ℹ️ Session '{session_id}' is already stopped"
125
-
126
- # Perform action
127
- if action == "start":
128
- await self.session_manager.start_inference(session_id)
129
- return f"✅ Inference started for session {session_id}"
130
- if action == "stop":
131
- await self.session_manager.stop_inference(session_id)
132
- return f"✅ Inference stopped for session {session_id}"
133
- if action == "restart":
134
- await self.session_manager.restart_inference(session_id)
135
- return f"✅ Inference restarted for session {session_id}"
136
- return f"❌ Unknown action: {action}"
137
-
138
- except Exception as e:
139
- return f"❌ Error: {e!s}"
140
-
141
- async def get_session_status(self, session_id: str) -> str:
142
- """Get session status directly."""
143
- if not session_id.strip():
144
- return "⚠️ No session ID provided"
145
-
146
- try:
147
- if session_id not in self.session_manager.sessions:
148
- return f"❌ Session '{session_id}' not found"
149
-
150
- session_data = await self.session_manager.get_session_status(session_id)
151
- session = session_data
152
-
153
- status_emoji = {
154
- "running": "🟢",
155
- "ready": "🟡",
156
- "stopped": "🔴",
157
- "initializing": "🟠",
158
- }.get(session["status"], "⚪")
159
-
160
- # Smart suggestions
161
- suggestions = ""
162
- if session["status"] == "running":
163
- suggestions = "\n\n### 💡 Smart Suggestion:\n**Session is active!** Use the '⏸️ Stop' button to pause inference."
164
- elif session["status"] in {"ready", "stopped"}:
165
- suggestions = "\n\n### 💡 Smart Suggestion:\n**Session is ready!** Use the '▶️ Start' button to begin inference."
166
-
167
- # Format camera names nicely
168
- camera_list = [f"**{cam.title()}**" for cam in session["camera_names"]]
169
-
170
- return f"""## {status_emoji} Session Status
171
-
172
- **Session ID**: `{session_id}`
173
- **Status**: {status_emoji} **{session["status"].upper()}**
174
- **Model**: `{session["policy_path"]}`
175
- **Cameras**: {", ".join(camera_list)}
176
-
177
- ### 📊 Performance Metrics:
178
- | Metric | Value |
179
- |--------|-------|
180
- | 🧠 **Inferences** | {session["stats"]["inference_count"]} |
181
- | 📤 **Commands Sent** | {session["stats"]["commands_sent"]} |
182
- | 📋 **Queue Length** | {session["stats"]["actions_in_queue"]} actions |
183
- | ❌ **Errors** | {session["stats"]["errors"]} |
184
-
185
- ### 🔧 Data Flow:
186
- - 📹 **Images Received**: {session["stats"]["images_received"]}
187
- - 🤖 **Joint States Received**: {session["stats"]["joints_received"]}
188
- {suggestions}"""
189
-
190
- except Exception as e:
191
- return f"❌ Error: {e!s}"
192
 
193
 
194
  def create_simple_gradio_interface() -> gr.Blocks:
@@ -201,130 +61,170 @@ def create_simple_gradio_interface() -> gr.Blocks:
201
  css=".gradio-container { max-width: 1200px !important; }",
202
  fill_height=True,
203
  ) as demo:
204
- gr.Markdown("""
205
- # 🤖 Robot AI Control Center
206
- **Control your robot with AI using ACT models**
207
-
208
- *Integrated mode - FastAPI available at `/api/docs` for direct API access*
209
- """)
210
-
211
- # Server status
212
- with gr.Group():
213
- gr.Markdown("## 📡 API Status")
214
- gr.Markdown(
215
- value="""
216
- ✅ **FastAPI Server**: Available at `/api`
217
- 📖 **API Documentation**: Available at `/api/docs`
218
- 🔄 **Health Check**: Available at `/api/health`
219
- """,
220
- show_copy_button=True,
221
- )
222
-
223
- # Setup
224
- with gr.Group():
225
- gr.Markdown("## 🤖 Set Up Robot AI")
226
-
227
- with gr.Row():
228
- with gr.Column():
229
- session_id_input = gr.Textbox(
230
- label="Session Name", value="my-robot-01"
231
  )
232
- policy_path_input = gr.Textbox(
233
- label="AI Model Path", value="./checkpoints/act_so101_beyond"
 
 
234
  )
235
- camera_names_input = gr.Textbox(label="Camera Names", value="front")
236
- arena_server_url_input = gr.Textbox(
237
- label="Arena Server URL",
238
- value=DEFAULT_ARENA_SERVER_URL,
239
- placeholder="http://localhost:8000",
240
  )
241
- create_btn = gr.Button(
242
- "🎯 Create & Start AI Control", variant="primary"
243
- )
244
-
245
- with gr.Column():
246
- setup_result = gr.Markdown(
247
- value="Ready to create your robot AI session...",
248
- show_copy_button=True,
249
- container=True,
250
- height=300,
251
- )
252
-
253
- # Control
254
- with gr.Group():
255
- gr.Markdown("## 🎮 Control Session")
256
 
257
- with gr.Row():
258
- current_session_input = gr.Textbox(label="Session ID")
259
- start_btn = gr.Button("▶️ Start", variant="primary")
260
- stop_btn = gr.Button("⏸️ Stop", variant="secondary")
261
- status_btn = gr.Button("📊 Status", variant="secondary")
262
 
263
- session_status_display = gr.Markdown(
264
- value="Click '📊 Status' to check session information...",
265
- show_copy_button=True,
266
- container=True,
267
- height=400,
268
- )
269
 
270
- # Event handlers
271
- async def create_session(
272
- session_id, policy_path, camera_names, arena_server_url
273
- ):
274
- result = await server_manager.create_and_start_session(
275
- session_id, policy_path, camera_names, arena_server_url
276
- )
277
- return result, session_id
278
-
279
- async def start_session(session_id):
280
- return await server_manager.control_session(session_id, "start")
281
 
282
- async def stop_session(session_id):
283
- return await server_manager.control_session(session_id, "stop")
 
 
284
 
285
- async def get_status(session_id):
286
- return await server_manager.get_session_status(session_id)
287
 
288
- # Connect events
289
  create_btn.click(
290
- create_session,
291
- inputs=[
292
- session_id_input,
293
- policy_path_input,
294
- camera_names_input,
295
- arena_server_url_input,
296
- ],
297
- outputs=[setup_result, current_session_input],
298
  )
299
 
300
  start_btn.click(
301
- start_session,
302
- inputs=[current_session_input],
303
- outputs=[session_status_display],
304
  )
 
305
  stop_btn.click(
306
- stop_session,
307
- inputs=[current_session_input],
308
- outputs=[session_status_display],
309
  )
 
310
  status_btn.click(
311
- get_status, inputs=[current_session_input], outputs=[session_status_display]
 
 
312
  )
313
 
314
- gr.Markdown("""
315
- ### 💡 Tips:
316
- - 🟢 **RUNNING**: Session actively doing inference
317
- - 🟡 **READY**: Session created but not started
318
- - 🔴 **STOPPED**: Session paused
319
 
320
- ### 🚀 API Access:
321
- - **FastAPI Docs**: Visit `/api/docs` for interactive API documentation
322
- - **Direct API**: Use `/api/sessions` endpoints for programmatic access
323
- - **Health Check**: `/api/health` shows server status
324
- - **OpenAPI Schema**: Available at `/api/openapi.json`
325
- """)
326
 
327
- return demo
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
328
 
329
 
330
  def launch_simple_integrated_app(
@@ -337,11 +237,15 @@ def launch_simple_integrated_app(
337
  print(f"🔄 Health Check: http://{host}:{port}/api/health")
338
  print("🔧 Direct session management + API access!")
339
 
340
- # Create Gradio demo
341
  demo = create_simple_gradio_interface()
342
 
343
- # Create custom FastAPI app from the Gradio demo's built-in app
344
- app = demo.app
 
 
 
 
345
 
346
  # Add CORS middleware
347
  app.add_middleware(
@@ -352,18 +256,23 @@ def launch_simple_integrated_app(
352
  allow_headers=["*"],
353
  )
354
 
355
- # Mount the FastAPI AI server under /api
356
  app.mount("/api", fastapi_app)
357
 
358
- # Launch using Gradio's queue and launch methods
359
- demo.queue()
360
- demo.launch(
361
- server_name=host,
362
- server_port=port,
363
- share=share,
364
- show_error=True,
365
- show_api=False, # Hide Gradio's default API docs since we have our own
366
- prevent_thread_lock=False,
 
 
 
 
 
367
  )
368
 
369
 
 
4
 
5
  import gradio as gr
6
  import uvicorn
7
+ from fastapi import FastAPI
8
  from fastapi.middleware.cors import CORSMiddleware
9
+ from fastapi.responses import RedirectResponse
10
 
11
  # Import our existing components
12
  from inference_server.main import app as fastapi_app
 
32
 
33
  def run_server():
34
  global server_started
 
 
35
  try:
36
+ # Import here to avoid circular imports
37
+ from inference_server.main import app
38
+
39
+ logger.info(f"Starting AI server on port {port}")
40
+ uvicorn.run(app, host="0.0.0.0", port=port, log_level="warning")
41
  except Exception as e:
42
+ logger.exception(f"Failed to start AI server: {e}")
43
  finally:
44
  server_started = False
45
 
46
  server_thread = threading.Thread(target=run_server, daemon=True)
47
  server_thread.start()
48
+ server_started = True
49
 
50
  # Wait a moment for server to start
51
  time.sleep(2)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
52
 
53
 
54
  def create_simple_gradio_interface() -> gr.Blocks:
 
61
  css=".gradio-container { max-width: 1200px !important; }",
62
  fill_height=True,
63
  ) as demo:
64
+ gr.Markdown("# 🤖 Robot AI Control Center")
65
+ gr.Markdown("*Real-time ACT Model Inference for Robot Control*")
66
+
67
+ with gr.Row():
68
+ with gr.Column(scale=2):
69
+ gr.Markdown("## 🚀 Set Up Robot AI")
70
+
71
+ with gr.Group():
72
+ session_name = gr.Textbox(
73
+ label="Session Name",
74
+ placeholder="my-robot-01",
75
+ value="default-session",
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
76
  )
77
+ model_path = gr.Textbox(
78
+ label="AI Model Path",
79
+ placeholder="./checkpoints/act_so101_beyond",
80
+ value="./checkpoints/act_so101_beyond",
81
  )
82
+ camera_names = gr.Textbox(
83
+ label="Camera Names (comma-separated)",
84
+ placeholder="front,wrist,overhead",
85
+ value="front,wrist",
 
86
  )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
87
 
88
+ create_btn = gr.Button(
89
+ "🎯 Create & Start AI Control", variant="primary"
90
+ )
 
 
91
 
92
+ with gr.Column(scale=1):
93
+ gr.Markdown("## 📊 Control Session")
 
 
 
 
94
 
95
+ session_id_input = gr.Textbox(
96
+ label="Session ID",
97
+ placeholder="Will be auto-filled",
98
+ interactive=False,
99
+ )
 
 
 
 
 
 
100
 
101
+ with gr.Row():
102
+ start_btn = gr.Button("▶️ Start", variant="secondary")
103
+ stop_btn = gr.Button("⏹️ Stop", variant="secondary")
104
+ status_btn = gr.Button("📈 Status", variant="secondary")
105
 
106
+ with gr.Row():
107
+ output_display = gr.Markdown("### Ready to create AI session...")
108
 
109
+ # Event handlers
110
  create_btn.click(
111
+ fn=server_manager.create_and_start_session,
112
+ inputs=[session_name, model_path, camera_names],
113
+ outputs=[session_id_input, output_display],
 
 
 
 
 
114
  )
115
 
116
  start_btn.click(
117
+ fn=server_manager.start_session,
118
+ inputs=[session_id_input],
119
+ outputs=[output_display],
120
  )
121
+
122
  stop_btn.click(
123
+ fn=server_manager.stop_session,
124
+ inputs=[session_id_input],
125
+ outputs=[output_display],
126
  )
127
+
128
  status_btn.click(
129
+ fn=server_manager.get_session_status,
130
+ inputs=[session_id_input],
131
+ outputs=[output_display],
132
  )
133
 
134
+ return demo
 
 
 
 
135
 
 
 
 
 
 
 
136
 
137
+ class SimpleServerManager:
138
+ """Direct session management without HTTP API calls."""
139
+
140
+ def create_and_start_session(self, name: str, model_path: str, camera_names: str):
141
+ """Create and start a new session directly."""
142
+ try:
143
+ # Parse camera names
144
+ cameras = [c.strip() for c in camera_names.split(",") if c.strip()]
145
+
146
+ # Create session directly using session_manager
147
+ session_data = {
148
+ "name": name,
149
+ "model_path": model_path,
150
+ "arena_server_url": DEFAULT_ARENA_SERVER_URL,
151
+ "workspace_id": "default_workspace",
152
+ "room_id": f"room_{name}",
153
+ "camera_names": cameras,
154
+ }
155
+
156
+ session_id = session_manager.create_session(session_data)
157
+ session_manager.start_session(session_id)
158
+
159
+ success_msg = f"""
160
+ ### ✅ Success!
161
+ **Session ID:** `{session_id}`
162
+ **Status:** Running
163
+ **Model:** {model_path}
164
+ **Cameras:** {", ".join(cameras)}
165
+
166
+ 🎉 AI control is now active!
167
+ """
168
+ return session_id, success_msg
169
+
170
+ except Exception as e:
171
+ error_msg = f"""
172
+ ### ❌ Error
173
+ Failed to create session: {e!s}
174
+
175
+ Please check your model path and try again.
176
+ """
177
+ return "", error_msg
178
+
179
+ def start_session(self, session_id: str):
180
+ """Start an existing session."""
181
+ if not session_id:
182
+ return "⚠️ Please provide a session ID"
183
+
184
+ try:
185
+ session_manager.start_session(session_id)
186
+ return f"✅ Session `{session_id}` started successfully!"
187
+ except Exception as e:
188
+ return f"❌ Failed to start session: {e!s}"
189
+
190
+ def stop_session(self, session_id: str):
191
+ """Stop an existing session."""
192
+ if not session_id:
193
+ return "⚠️ Please provide a session ID"
194
+
195
+ try:
196
+ session_manager.stop_session(session_id)
197
+ return f"⏹️ Session `{session_id}` stopped successfully!"
198
+ except Exception as e:
199
+ return f"❌ Failed to stop session: {e!s}"
200
+
201
+ def get_session_status(self, session_id: str):
202
+ """Get detailed session status."""
203
+ if not session_id:
204
+ return "⚠️ Please provide a session ID"
205
+
206
+ try:
207
+ status = session_manager.get_session_status(session_id)
208
+ if not status:
209
+ return f"❌ Session `{session_id}` not found"
210
+
211
+ status_msg = f"""
212
+ ### 📊 Session Status: `{session_id}`
213
+
214
+ **State:** {status.get("state", "Unknown")}
215
+ **Model:** {status.get("model_path", "N/A")}
216
+ **Inferences:** {status.get("total_inferences", 0)}
217
+ **Commands Sent:** {status.get("commands_sent", 0)}
218
+ **Errors:** {status.get("errors", 0)}
219
+
220
+ **Performance:**
221
+ - Queue Length: {status.get("queue_length", 0)}
222
+ - Last Update: {status.get("last_update", "Never")}
223
+ """
224
+ return status_msg
225
+
226
+ except Exception as e:
227
+ return f"❌ Failed to get status: {e!s}"
228
 
229
 
230
  def launch_simple_integrated_app(
 
237
  print(f"🔄 Health Check: http://{host}:{port}/api/health")
238
  print("🔧 Direct session management + API access!")
239
 
240
+ # Create Gradio demo first
241
  demo = create_simple_gradio_interface()
242
 
243
+ # Create main FastAPI app
244
+ app = FastAPI(
245
+ title="🤖 Robot AI Control Center",
246
+ description="Integrated ACT Model Inference Server with Web Interface",
247
+ version="1.0.0",
248
+ )
249
 
250
  # Add CORS middleware
251
  app.add_middleware(
 
256
  allow_headers=["*"],
257
  )
258
 
259
+ # Mount the FastAPI AI server under /api FIRST
260
  app.mount("/api", fastapi_app)
261
 
262
+ # Mount Gradio at a subpath to avoid the root redirect issue
263
+ app = gr.mount_gradio_app(app, demo, path="/gradio")
264
+
265
+ # Add custom root endpoint that redirects to /gradio/ (with trailing slash)
266
+ @app.get("/")
267
+ async def root():
268
+ return RedirectResponse(url="/gradio/", status_code=302)
269
+
270
+ # Launch with uvicorn
271
+ uvicorn.run(
272
+ app,
273
+ host=host,
274
+ port=port,
275
+ log_level="info",
276
  )
277
 
278