Spaces:
Sleeping
Sleeping
Update
Browse files
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 |
-
|
|
|
|
|
|
|
|
|
37 |
except Exception as e:
|
38 |
-
logger.exception(f"
|
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 |
-
|
206 |
-
|
207 |
-
|
208 |
-
|
209 |
-
|
210 |
-
|
211 |
-
|
212 |
-
|
213 |
-
|
214 |
-
|
215 |
-
|
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 |
-
|
233 |
-
label="AI Model Path",
|
|
|
|
|
234 |
)
|
235 |
-
|
236 |
-
|
237 |
-
|
238 |
-
value=
|
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 |
-
|
258 |
-
|
259 |
-
|
260 |
-
stop_btn = gr.Button("⏸️ Stop", variant="secondary")
|
261 |
-
status_btn = gr.Button("📊 Status", variant="secondary")
|
262 |
|
263 |
-
|
264 |
-
|
265 |
-
show_copy_button=True,
|
266 |
-
container=True,
|
267 |
-
height=400,
|
268 |
-
)
|
269 |
|
270 |
-
|
271 |
-
|
272 |
-
|
273 |
-
|
274 |
-
|
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 |
-
|
283 |
-
|
|
|
|
|
284 |
|
285 |
-
|
286 |
-
|
287 |
|
288 |
-
#
|
289 |
create_btn.click(
|
290 |
-
|
291 |
-
inputs=[
|
292 |
-
|
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=[
|
303 |
-
outputs=[
|
304 |
)
|
|
|
305 |
stop_btn.click(
|
306 |
-
stop_session,
|
307 |
-
inputs=[
|
308 |
-
outputs=[
|
309 |
)
|
|
|
310 |
status_btn.click(
|
311 |
-
|
|
|
|
|
312 |
)
|
313 |
|
314 |
-
|
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 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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
|
344 |
-
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 |
-
#
|
359 |
-
|
360 |
-
|
361 |
-
|
362 |
-
|
363 |
-
|
364 |
-
|
365 |
-
|
366 |
-
|
|
|
|
|
|
|
|
|
|
|
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 |
|