File size: 8,793 Bytes
63ed3a7
 
 
 
 
 
1120045
63ed3a7
1120045
63ed3a7
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1120045
 
 
 
 
63ed3a7
1120045
63ed3a7
 
 
 
 
1120045
63ed3a7
 
 
 
 
 
 
 
 
 
 
 
 
f1df768
63ed3a7
1120045
 
 
 
 
 
 
 
 
 
 
 
63ed3a7
1120045
 
 
 
63ed3a7
1120045
 
 
 
63ed3a7
 
1120045
 
 
63ed3a7
1120045
 
63ed3a7
1120045
 
 
 
 
63ed3a7
1120045
 
 
 
63ed3a7
1120045
 
63ed3a7
1120045
63ed3a7
1120045
 
 
63ed3a7
 
 
1120045
 
 
63ed3a7
1120045
63ed3a7
1120045
 
 
63ed3a7
1120045
63ed3a7
1120045
 
 
63ed3a7
 
1120045
63ed3a7
 
1120045
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
63ed3a7
 
 
 
 
 
 
 
 
 
 
 
1120045
f1df768
 
1120045
 
 
 
 
 
63ed3a7
f1df768
 
 
 
 
 
 
 
 
1120045
f1df768
 
1120045
 
 
 
 
f9fa7dd
1120045
 
 
 
 
 
 
 
63ed3a7
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
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)
    @app.get("/")
    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()