Spaces:
Sleeping
Sleeping
| import logging | |
| import threading | |
| import time | |
| import gradio as gr | |
| import uvicorn | |
| from fastapi import FastAPI | |
| from fastapi.middleware.cors import CORSMiddleware | |
| from fastapi.responses import RedirectResponse | |
| # Import our existing components | |
| from inference_server.main import app as fastapi_app | |
| from inference_server.main import session_manager | |
| logger = logging.getLogger(__name__) | |
| # Configuration | |
| DEFAULT_PORT = 7860 | |
| DEFAULT_ARENA_SERVER_URL = "http://localhost:8000" | |
| # Global server thread | |
| server_thread = None | |
| server_started = False | |
| def start_api_server_thread(port: int = 8001): | |
| """Start the API server in a background thread.""" | |
| global server_thread, server_started | |
| if server_thread and server_thread.is_alive(): | |
| return | |
| def run_server(): | |
| global server_started | |
| try: | |
| # Import here to avoid circular imports | |
| from inference_server.main import app | |
| logger.info(f"Starting AI server on port {port}") | |
| uvicorn.run(app, host="0.0.0.0", port=port, log_level="warning") | |
| except Exception as e: | |
| logger.exception(f"Failed to start AI server: {e}") | |
| finally: | |
| server_started = False | |
| server_thread = threading.Thread(target=run_server, daemon=True) | |
| server_thread.start() | |
| server_started = True | |
| # Wait a moment for server to start | |
| time.sleep(2) | |
| def create_simple_gradio_interface() -> gr.Blocks: | |
| """Create a simple Gradio interface with direct session management.""" | |
| server_manager = SimpleServerManager() | |
| with gr.Blocks( | |
| title="π€ Robot AI Control Center", | |
| theme=gr.themes.Soft(), | |
| css=".gradio-container { max-width: 1200px !important; }", | |
| fill_height=True, | |
| ) as demo: | |
| gr.Markdown("# π€ Robot AI Control Center") | |
| gr.Markdown("*Real-time ACT Model Inference for Robot Control*") | |
| with gr.Row(): | |
| with gr.Column(scale=2): | |
| gr.Markdown("## π Set Up Robot AI") | |
| with gr.Group(): | |
| session_name = gr.Textbox( | |
| label="Session Name", | |
| placeholder="my-robot-01", | |
| value="default-session", | |
| ) | |
| model_path = gr.Textbox( | |
| label="AI Model Path", | |
| placeholder="./checkpoints/act_so101_beyond", | |
| value="./checkpoints/act_so101_beyond", | |
| ) | |
| camera_names = gr.Textbox( | |
| label="Camera Names (comma-separated)", | |
| placeholder="front,wrist,overhead", | |
| value="front,wrist", | |
| ) | |
| create_btn = gr.Button( | |
| "π― Create & Start AI Control", variant="primary" | |
| ) | |
| with gr.Column(scale=1): | |
| gr.Markdown("## π Control Session") | |
| session_id_input = gr.Textbox( | |
| label="Session ID", | |
| placeholder="Will be auto-filled", | |
| interactive=False, | |
| ) | |
| with gr.Row(): | |
| start_btn = gr.Button("βΆοΈ Start", variant="secondary") | |
| stop_btn = gr.Button("βΉοΈ Stop", variant="secondary") | |
| status_btn = gr.Button("π Status", variant="secondary") | |
| with gr.Row(): | |
| output_display = gr.Markdown("### Ready to create AI session...") | |
| # Event handlers | |
| create_btn.click( | |
| fn=server_manager.create_and_start_session, | |
| inputs=[session_name, model_path, camera_names], | |
| outputs=[session_id_input, output_display], | |
| ) | |
| start_btn.click( | |
| fn=server_manager.start_session, | |
| inputs=[session_id_input], | |
| outputs=[output_display], | |
| ) | |
| stop_btn.click( | |
| fn=server_manager.stop_session, | |
| inputs=[session_id_input], | |
| outputs=[output_display], | |
| ) | |
| status_btn.click( | |
| fn=server_manager.get_session_status, | |
| inputs=[session_id_input], | |
| outputs=[output_display], | |
| ) | |
| return demo | |
| class SimpleServerManager: | |
| """Direct session management without HTTP API calls.""" | |
| def create_and_start_session(self, name: str, model_path: str, camera_names: str): | |
| """Create and start a new session directly.""" | |
| try: | |
| # Parse camera names | |
| cameras = [c.strip() for c in camera_names.split(",") if c.strip()] | |
| # Create session directly using session_manager | |
| session_data = { | |
| "name": name, | |
| "model_path": model_path, | |
| "arena_server_url": DEFAULT_ARENA_SERVER_URL, | |
| "workspace_id": "default_workspace", | |
| "room_id": f"room_{name}", | |
| "camera_names": cameras, | |
| } | |
| session_id = session_manager.create_session(session_data) | |
| session_manager.start_session(session_id) | |
| success_msg = f""" | |
| ### β Success! | |
| **Session ID:** `{session_id}` | |
| **Status:** Running | |
| **Model:** {model_path} | |
| **Cameras:** {", ".join(cameras)} | |
| π AI control is now active! | |
| """ | |
| return session_id, success_msg | |
| except Exception as e: | |
| error_msg = f""" | |
| ### β Error | |
| Failed to create session: {e!s} | |
| Please check your model path and try again. | |
| """ | |
| return "", error_msg | |
| def start_session(self, session_id: str): | |
| """Start an existing session.""" | |
| if not session_id: | |
| return "β οΈ Please provide a session ID" | |
| try: | |
| session_manager.start_session(session_id) | |
| return f"β Session `{session_id}` started successfully!" | |
| except Exception as e: | |
| return f"β Failed to start session: {e!s}" | |
| def stop_session(self, session_id: str): | |
| """Stop an existing session.""" | |
| if not session_id: | |
| return "β οΈ Please provide a session ID" | |
| try: | |
| session_manager.stop_session(session_id) | |
| return f"βΉοΈ Session `{session_id}` stopped successfully!" | |
| except Exception as e: | |
| return f"β Failed to stop session: {e!s}" | |
| def get_session_status(self, session_id: str): | |
| """Get detailed session status.""" | |
| if not session_id: | |
| return "β οΈ Please provide a session ID" | |
| try: | |
| status = session_manager.get_session_status(session_id) | |
| if not status: | |
| return f"β Session `{session_id}` not found" | |
| status_msg = f""" | |
| ### π Session Status: `{session_id}` | |
| **State:** {status.get("state", "Unknown")} | |
| **Model:** {status.get("model_path", "N/A")} | |
| **Inferences:** {status.get("total_inferences", 0)} | |
| **Commands Sent:** {status.get("commands_sent", 0)} | |
| **Errors:** {status.get("errors", 0)} | |
| **Performance:** | |
| - Queue Length: {status.get("queue_length", 0)} | |
| - Last Update: {status.get("last_update", "Never")} | |
| """ | |
| return status_msg | |
| except Exception as e: | |
| return f"β Failed to get status: {e!s}" | |
| def launch_simple_integrated_app( | |
| host: str = "localhost", port: int = DEFAULT_PORT, share: bool = False | |
| ): | |
| """Launch the integrated application with both FastAPI and Gradio.""" | |
| print(f"π Starting integrated app on {host}:{port}") | |
| print(f"π¨ Gradio UI: http://{host}:{port}/") | |
| print(f"π FastAPI Docs: http://{host}:{port}/api/docs") | |
| print(f"π Health Check: http://{host}:{port}/api/health") | |
| print("π§ Direct session management + API access!") | |
| # Create Gradio demo first | |
| demo = create_simple_gradio_interface() | |
| # Create main FastAPI app | |
| app = FastAPI( | |
| title="π€ Robot AI Control Center", | |
| description="Integrated ACT Model Inference Server with Web Interface", | |
| version="1.0.0", | |
| ) | |
| # Add CORS middleware | |
| app.add_middleware( | |
| CORSMiddleware, | |
| allow_origins=["*"], | |
| allow_credentials=True, | |
| allow_methods=["*"], | |
| allow_headers=["*"], | |
| ) | |
| # Mount the FastAPI AI server under /api FIRST | |
| app.mount("/api", fastapi_app) | |
| # Mount Gradio at a subpath to avoid the root redirect issue | |
| app = gr.mount_gradio_app(app, demo, path="/gradio") | |
| # Add custom root endpoint that redirects to /gradio/ (with trailing slash) | |
| def root_redirect(): | |
| return RedirectResponse(url="/gradio/", status_code=302) | |
| # Launch with uvicorn | |
| uvicorn.run( | |
| app, | |
| host=host, | |
| port=port, | |
| log_level="info", | |
| ) | |
| if __name__ == "__main__": | |
| launch_simple_integrated_app() | |