File size: 13,363 Bytes
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
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
"""
Redesigned Gradio UI for Inference Server

This module provides a user-friendly, workflow-oriented interface.
"""

import subprocess
import time
from pathlib import Path

import gradio as gr
import httpx

# Configuration
DEFAULT_SERVER_HOST = "localhost"
DEFAULT_SERVER_PORT = 8001
DEFAULT_ARENA_SERVER_URL = "http://localhost:8000"


class AIServerManager:
    """Manages communication with the AI Server."""

    def __init__(
        self, server_url: str = f"http://{DEFAULT_SERVER_HOST}:{DEFAULT_SERVER_PORT}"
    ):
        self.server_url = server_url
        self.server_process: subprocess.Popen | None = None

    async def check_server_health(self) -> tuple[bool, str]:
        """Check if the AI server is running and healthy."""
        try:
            async with httpx.AsyncClient(timeout=5.0) as client:
                response = await client.get(f"{self.server_url}/health")
                if response.status_code == 200:
                    data = response.json()
                    return (
                        True,
                        f"โœ… Server running - {data['active_sessions']} sessions active",
                    )
                return False, f"โŒ Server error: {response.status_code}"
        except Exception as e:
            return False, f"โŒ Server not reachable: {e!s}"

    def start_server(self) -> str:
        """Start the AI server process using uv."""
        if self.server_process and self.server_process.poll() is None:
            return "โš ๏ธ Server is already running"

        try:
            cmd = [
                "uv",
                "run",
                "uvicorn",
                "inference_server.main:app",
                "--host",
                "0.0.0.0",
                "--port",
                str(DEFAULT_SERVER_PORT),
                "--reload",
            ]

            self.server_process = subprocess.Popen(
                cmd,
                cwd=Path(__file__).parent.parent.parent,
                stdout=subprocess.PIPE,
                stderr=subprocess.STDOUT,
                text=True,
            )

            time.sleep(4)

            if self.server_process.poll() is None:
                return f"๐Ÿš€ AI Server started on {self.server_url}"
            return "โŒ Failed to start server - check your model path and dependencies"

        except Exception as e:
            return f"โŒ Error starting server: {e!s}"

    async def create_and_start_session(
        self,
        session_id: str,
        policy_path: str,
        camera_names: str,
        arena_server_url: str,
        workspace_id: str | None = None,
    ) -> str:
        """Create and immediately start an inference session."""
        try:
            # Parse camera names
            cameras = [name.strip() for name in camera_names.split(",") if name.strip()]
            if not cameras:
                cameras = ["front"]

            request_data = {
                "session_id": session_id,
                "policy_path": policy_path,
                "camera_names": cameras,
                "arena_server_url": arena_server_url,
            }

            if workspace_id and workspace_id.strip():
                request_data["workspace_id"] = workspace_id.strip()

            async with httpx.AsyncClient(timeout=30.0) as client:
                # Create session
                response = await client.post(
                    f"{self.server_url}/sessions", json=request_data
                )

                if response.status_code != 200:
                    error_detail = response.json().get("detail", "Unknown error")
                    return f"โŒ Failed to create session: {error_detail}"

                data = response.json()

                # Immediately start inference
                start_response = await client.post(
                    f"{self.server_url}/sessions/{session_id}/start"
                )

                if start_response.status_code != 200:
                    error_detail = start_response.json().get("detail", "Unknown error")
                    return f"โš ๏ธ Session created but failed to start: {error_detail}"

                return f"""โœ… Session '{session_id}' created and started!

๐Ÿ“ก Connection Details:
โ€ข Workspace: {data["workspace_id"]}
โ€ข Camera rooms: {", ".join(f"{k}:{v}" for k, v in data["camera_room_ids"].items())}
โ€ข Joint input room: {data["joint_input_room_id"]}
โ€ข Joint output room: {data["joint_output_room_id"]}

๐Ÿค– Ready for robot control!"""

        except Exception as e:
            return f"โŒ Error: {e!s}"


# Initialize the server manager
server_manager = AIServerManager()


def create_main_interface() -> gr.Blocks:
    """Create the main user-friendly interface."""
    with gr.Blocks(title="๐Ÿค– Robot AI Control Center", theme=gr.themes.Soft()) as demo:
        gr.Markdown("""
        # ๐Ÿค– Robot AI Control Center

        **Control your robot with AI using ACT (Action Chunking Transformer) models**

        Follow the steps below to set up real-time AI control for your robot.
        """)

        # Step 1: Server Status
        with gr.Group():
            gr.Markdown("## ๐Ÿ“ก Step 1: AI Server")

            with gr.Row():
                with gr.Column(scale=2):
                    server_status_display = gr.Textbox(
                        label="Server Status",
                        value="Checking server...",
                        interactive=False,
                        lines=2,
                    )

                with gr.Column(scale=1):
                    start_server_btn = gr.Button("๐Ÿš€ Start Server", variant="primary")
                    check_health_btn = gr.Button("๐Ÿ” Check Status", variant="secondary")

        # Step 2: Robot Setup
        with gr.Group():
            gr.Markdown("## ๐Ÿค– Step 2: Set Up Robot AI")

            with gr.Row():
                with gr.Column():
                    session_id_input = gr.Textbox(
                        label="Session Name",
                        placeholder="my-robot-session",
                        value="my-robot-01",
                    )

                    policy_path_input = gr.Textbox(
                        label="AI Model Path",
                        placeholder="./checkpoints/act_so101_beyond",
                        value="./checkpoints/act_so101_beyond",
                    )

                    camera_names_input = gr.Textbox(
                        label="Camera Names (comma-separated)",
                        placeholder="front, wrist, overhead",
                        value="front",
                    )

                    arena_server_url_input = gr.Textbox(
                        label="Arena Server URL",
                        placeholder="http://localhost:8000",
                        value=DEFAULT_ARENA_SERVER_URL,
                    )

                    create_start_btn = gr.Button(
                        "๐ŸŽฏ Create & Start AI Control", variant="primary"
                    )

                with gr.Column():
                    setup_result = gr.Textbox(
                        label="Setup Result",
                        lines=10,
                        interactive=False,
                        placeholder="Click 'Create & Start AI Control' to begin...",
                    )

        # Control buttons
        with gr.Group():
            gr.Markdown("## ๐ŸŽฎ Step 3: Control Session")

            with gr.Row():
                current_session_input = gr.Textbox(
                    label="Session ID", placeholder="Enter session ID"
                )

                start_btn = gr.Button("โ–ถ๏ธ Start", variant="primary")
                stop_btn = gr.Button("โธ๏ธ Stop", variant="secondary")
                status_btn = gr.Button("๐Ÿ“Š Status", variant="secondary")

            session_status_display = gr.Textbox(
                label="Session Status", lines=8, interactive=False
            )

        # Event Handlers
        def start_server_click():
            return server_manager.start_server()

        async def check_health_click():
            _is_healthy, message = await server_manager.check_server_health()
            return message

        async def create_start_session_click(
            session_id, policy_path, camera_names, arena_server_url
        ):
            result = await server_manager.create_and_start_session(
                session_id, policy_path, camera_names, arena_server_url
            )
            return result, session_id

        async def control_session(session_id, action):
            """Control a session (start/stop)."""
            if not session_id.strip():
                return "โš ๏ธ No session ID provided"

            try:
                async with httpx.AsyncClient(timeout=30.0) as client:
                    endpoint = f"/sessions/{session_id}/{action}"
                    response = await client.post(
                        f"{server_manager.server_url}{endpoint}"
                    )

                    if response.status_code == 200:
                        result = response.json()
                        return f"โœ… {result['message']}"
                    error_detail = response.json().get("detail", "Unknown error")
                    return f"โŒ Failed to {action}: {error_detail}"
            except Exception as e:
                return f"โŒ Error: {e!s}"

        async def get_session_status(session_id):
            """Get session status."""
            if not session_id.strip():
                return "โš ๏ธ No session ID provided"

            try:
                async with httpx.AsyncClient(timeout=10.0) as client:
                    response = await client.get(
                        f"{server_manager.server_url}/sessions/{session_id}"
                    )

                    if response.status_code == 200:
                        session = response.json()

                        status_emoji = {
                            "running": "๐ŸŸข",
                            "ready": "๐ŸŸก",
                            "stopped": "๐Ÿ”ด",
                            "initializing": "๐ŸŸ ",
                        }.get(session["status"], "โšช")

                        return f"""{status_emoji} Session: {session_id}
Status: {session["status"].upper()}
Model: {session["policy_path"]}
Cameras: {", ".join(session["camera_names"])}

๐Ÿ“Š Performance:
โ€ข Inferences: {session["stats"]["inference_count"]}
โ€ข Commands sent: {session["stats"]["commands_sent"]}
โ€ข Queue: {session["stats"]["actions_in_queue"]} actions
โ€ข Errors: {session["stats"]["errors"]}

๐Ÿ”ง Data flow:
โ€ข Images received: {session["stats"]["images_received"]}
โ€ข Joint states received: {session["stats"]["joints_received"]}"""
                    return f"โŒ Session not found or error: {response.status_code}"
            except Exception as e:
                return f"โŒ Error: {e!s}"

        # Connect events
        start_server_btn.click(start_server_click, outputs=[server_status_display])
        check_health_btn.click(check_health_click, outputs=[server_status_display])

        create_start_btn.click(
            create_start_session_click,
            inputs=[
                session_id_input,
                policy_path_input,
                camera_names_input,
                arena_server_url_input,
            ],
            outputs=[setup_result, current_session_input],
        )

        # Session control buttons - create proper async wrappers
        async def start_session_click(session_id):
            return await control_session(session_id, "start")

        async def stop_session_click(session_id):
            return await control_session(session_id, "stop")

        start_btn.click(
            start_session_click,
            inputs=[current_session_input],
            outputs=[session_status_display],
        )

        stop_btn.click(
            stop_session_click,
            inputs=[current_session_input],
            outputs=[session_status_display],
        )

        status_btn.click(
            get_session_status,
            inputs=[current_session_input],
            outputs=[session_status_display],
        )

        # Auto-refresh on load
        demo.load(check_health_click, outputs=[server_status_display])

        # Add helpful instructions
        gr.Markdown("""
        ---
        ### ๐Ÿ“– Quick Guide:
        1. **Start the Server**: Ensure the AI server is running (Step 1)
        2. **Configure Your Robot**: Enter your model path and camera setup (Step 2)
        3. **Create Session**: Click "Create & Start AI Control" to begin
        4. **Monitor & Control**: Use Step 3 to start/stop and monitor your session

        ๐Ÿ’ก **Tips**:
        - Make sure your ACT model path exists before creating a session
        - Camera names should match your robot's camera configuration
        - Session will automatically start after creation
        """)

    return demo


def launch_ui(
    server_name: str = "localhost", server_port: int = 7860, share: bool = False
) -> None:
    """Launch the redesigned UI."""
    demo = create_main_interface()
    demo.launch(
        server_name=server_name, server_port=server_port, share=share, show_error=True
    )


if __name__ == "__main__":
    launch_ui()