Spaces:
Running
Running
Fix hf by merging both service
Browse files- DOCKER_README.md +63 -15
- Dockerfile +7 -20
- docker-compose.yml +6 -6
- src-python/src/main.py +101 -4
- src-python/start_server.py +13 -10
- src/lib/utils/config.ts +24 -13
DOCKER_README.md
CHANGED
|
@@ -2,35 +2,83 @@
|
|
| 2 |
|
| 3 |
This Docker setup provides a containerized environment that runs both the Python backend and Svelte frontend for LeRobot Arena using modern tools like Bun, uv, and box-packager.
|
| 4 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 5 |
## π Quick Start
|
| 6 |
|
| 7 |
-
###
|
| 8 |
|
| 9 |
```bash
|
| 10 |
-
# Build
|
| 11 |
-
docker-
|
|
|
|
|
|
|
|
|
|
| 12 |
|
| 13 |
-
#
|
| 14 |
-
|
| 15 |
```
|
| 16 |
|
| 17 |
-
### Using Docker
|
| 18 |
|
| 19 |
```bash
|
| 20 |
-
#
|
| 21 |
-
docker
|
| 22 |
|
| 23 |
-
#
|
| 24 |
-
|
| 25 |
```
|
| 26 |
|
| 27 |
-
## π
|
|
|
|
|
|
|
|
|
|
|
|
|
| 28 |
|
| 29 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 30 |
|
| 31 |
-
|
| 32 |
-
- **Backend (Python/FastAPI)**: http://localhost:8080
|
| 33 |
-
- **Backend API Docs**: http://localhost:8080/docs
|
| 34 |
|
| 35 |
## π What's Included
|
| 36 |
|
|
|
|
| 2 |
|
| 3 |
This Docker setup provides a containerized environment that runs both the Python backend and Svelte frontend for LeRobot Arena using modern tools like Bun, uv, and box-packager.
|
| 4 |
|
| 5 |
+
## ποΈ Architecture
|
| 6 |
+
|
| 7 |
+
The application consists of:
|
| 8 |
+
- **Svelte Frontend**: Built as static files and served by FastAPI
|
| 9 |
+
- **FastAPI Backend**: WebSocket server with HTTP API for robot control
|
| 10 |
+
- **Single Port**: Both frontend and backend served on port 7860
|
| 11 |
+
|
| 12 |
## π Quick Start
|
| 13 |
|
| 14 |
+
### Build and Run with Docker
|
| 15 |
|
| 16 |
```bash
|
| 17 |
+
# Build the image
|
| 18 |
+
docker build -t lerobot-arena .
|
| 19 |
+
|
| 20 |
+
# Run the container
|
| 21 |
+
docker run -p 7860:7860 lerobot-arena
|
| 22 |
|
| 23 |
+
# Access the application
|
| 24 |
+
open http://localhost:7860
|
| 25 |
```
|
| 26 |
|
| 27 |
+
### Using Docker Compose
|
| 28 |
|
| 29 |
```bash
|
| 30 |
+
# Start the application
|
| 31 |
+
docker-compose up
|
| 32 |
|
| 33 |
+
# Access the application
|
| 34 |
+
open http://localhost:7860
|
| 35 |
```
|
| 36 |
|
| 37 |
+
## π Deployment
|
| 38 |
+
|
| 39 |
+
### Hugging Face Spaces
|
| 40 |
+
|
| 41 |
+
This project is configured for deployment on Hugging Face Spaces:
|
| 42 |
|
| 43 |
+
1. Push to a Hugging Face Space repository
|
| 44 |
+
2. The Dockerfile will automatically build and serve the application
|
| 45 |
+
3. Both frontend and backend run on port 7860 (HF Spaces default)
|
| 46 |
+
4. Environment detection automatically configures URLs for HF Spaces
|
| 47 |
+
|
| 48 |
+
### Local Development vs Production
|
| 49 |
+
|
| 50 |
+
- **Local Development**: Frontend dev server on 5173, backend on 7860
|
| 51 |
+
- **Docker/Production**: Both frontend and backend served by FastAPI on 7860
|
| 52 |
+
- **HF Spaces**: Single FastAPI server on 7860 with automatic HTTPS and domain detection
|
| 53 |
+
|
| 54 |
+
## π§ Environment Variables
|
| 55 |
+
|
| 56 |
+
- `SPACE_HOST`: Set automatically by Hugging Face Spaces for proper URL configuration
|
| 57 |
+
- `PORT`: Override the default port (7860)
|
| 58 |
+
|
| 59 |
+
## π‘ API Endpoints
|
| 60 |
+
|
| 61 |
+
- **Frontend**: `http://localhost:7860/` (Served by FastAPI)
|
| 62 |
+
- **API**: `http://localhost:7860/api/*`
|
| 63 |
+
- **WebSocket**: `ws://localhost:7860/ws/*`
|
| 64 |
+
- **Status**: `http://localhost:7860/status`
|
| 65 |
+
|
| 66 |
+
## π οΈ Development
|
| 67 |
+
|
| 68 |
+
For local development without Docker:
|
| 69 |
+
|
| 70 |
+
```bash
|
| 71 |
+
# Start backend
|
| 72 |
+
cd src-python
|
| 73 |
+
uv sync
|
| 74 |
+
uv run python start_server.py
|
| 75 |
+
|
| 76 |
+
# Start frontend (in another terminal)
|
| 77 |
+
bun install
|
| 78 |
+
bun run dev
|
| 79 |
+
```
|
| 80 |
|
| 81 |
+
The frontend dev server (port 5173) will automatically proxy API requests to the backend (port 7860).
|
|
|
|
|
|
|
| 82 |
|
| 83 |
## π What's Included
|
| 84 |
|
Dockerfile
CHANGED
|
@@ -51,24 +51,11 @@ RUN uv sync
|
|
| 51 |
# Copy built frontend from previous stage with proper ownership
|
| 52 |
COPY --chown=user --from=frontend-builder /app/build $HOME/app/static-frontend
|
| 53 |
|
| 54 |
-
#
|
| 55 |
WORKDIR $HOME/app
|
| 56 |
-
|
| 57 |
-
|
| 58 |
-
|
| 59 |
-
|
| 60 |
-
|
| 61 |
-
|
| 62 |
-
echo "β
Both services started!"\n\
|
| 63 |
-
echo "Backend: http://localhost:8080"\n\
|
| 64 |
-
echo "Frontend: http://localhost:7860"\n\
|
| 65 |
-
if [ ! -z "$SPACE_HOST" ]; then\n\
|
| 66 |
-
echo "π Hugging Face Space: https://$SPACE_HOST"\n\
|
| 67 |
-
fi\n\
|
| 68 |
-
wait' > start_services.sh && chmod +x start_services.sh
|
| 69 |
-
|
| 70 |
-
# Expose both ports (7860 is default for HF Spaces)
|
| 71 |
-
EXPOSE 7860 8080
|
| 72 |
-
|
| 73 |
-
# Start both services
|
| 74 |
-
CMD ["./start_services.sh"]
|
|
|
|
| 51 |
# Copy built frontend from previous stage with proper ownership
|
| 52 |
COPY --chown=user --from=frontend-builder /app/build $HOME/app/static-frontend
|
| 53 |
|
| 54 |
+
# Set working directory back to app root
|
| 55 |
WORKDIR $HOME/app
|
| 56 |
+
|
| 57 |
+
# Expose port 7860 (HF Spaces default)
|
| 58 |
+
EXPOSE 7860
|
| 59 |
+
|
| 60 |
+
# Start the FastAPI server (serves both frontend and backend)
|
| 61 |
+
CMD ["sh", "-c", "cd src-python && uv run python start_server.py"]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
docker-compose.yml
CHANGED
|
@@ -2,13 +2,13 @@ version: '3.8'
|
|
| 2 |
|
| 3 |
services:
|
| 4 |
lerobot-arena:
|
| 5 |
-
build:
|
| 6 |
-
context: .
|
| 7 |
-
dockerfile: Dockerfile
|
| 8 |
ports:
|
| 9 |
-
- "
|
| 10 |
-
- "7860:7860" # Svelte frontend (HF Spaces default)
|
| 11 |
environment:
|
| 12 |
- NODE_ENV=production
|
| 13 |
-
|
|
|
|
|
|
|
|
|
|
| 14 |
restart: unless-stopped
|
|
|
|
| 2 |
|
| 3 |
services:
|
| 4 |
lerobot-arena:
|
| 5 |
+
build: .
|
|
|
|
|
|
|
| 6 |
ports:
|
| 7 |
+
- "7860:7860" # FastAPI server serving both frontend and backend
|
|
|
|
| 8 |
environment:
|
| 9 |
- NODE_ENV=production
|
| 10 |
+
# volumes:
|
| 11 |
+
# # Optional: Mount local development files for debugging
|
| 12 |
+
# - ./src-python:/home/user/app/src-python
|
| 13 |
+
# - ./static-frontend:/home/user/app/static-frontend
|
| 14 |
restart: unless-stopped
|
src-python/src/main.py
CHANGED
|
@@ -6,6 +6,8 @@ from datetime import UTC, datetime
|
|
| 6 |
|
| 7 |
from fastapi import FastAPI, HTTPException, WebSocket, WebSocketDisconnect
|
| 8 |
from fastapi.middleware.cors import CORSMiddleware
|
|
|
|
|
|
|
| 9 |
|
| 10 |
from .connection_manager import ConnectionManager
|
| 11 |
from .models import (
|
|
@@ -60,6 +62,51 @@ app.add_middleware(
|
|
| 60 |
connection_manager = ConnectionManager()
|
| 61 |
robot_manager = RobotManager()
|
| 62 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 63 |
|
| 64 |
@app.on_event("startup")
|
| 65 |
async def startup_event():
|
|
@@ -73,15 +120,65 @@ async def startup_event():
|
|
| 73 |
|
| 74 |
|
| 75 |
@app.get("/")
|
| 76 |
-
async def
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 77 |
return {
|
| 78 |
"message": "LeRobot Arena Server",
|
| 79 |
"version": "1.0.0",
|
| 80 |
"robots_connected": len(robot_manager.robots),
|
| 81 |
"active_connections": connection_manager.get_connection_count(),
|
|
|
|
| 82 |
}
|
| 83 |
|
| 84 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 85 |
# ============= ROBOT MANAGEMENT API =============
|
| 86 |
|
| 87 |
|
|
@@ -563,11 +660,11 @@ if __name__ == "__main__":
|
|
| 563 |
logger = logging.getLogger("lerobot-arena")
|
| 564 |
logger.info("π Starting LeRobot Arena WebSocket Server...")
|
| 565 |
|
| 566 |
-
# Start the server
|
| 567 |
uvicorn.run(
|
| 568 |
app,
|
| 569 |
host="0.0.0.0",
|
| 570 |
-
port=
|
| 571 |
log_level="info",
|
| 572 |
-
reload=False,
|
| 573 |
)
|
|
|
|
| 6 |
|
| 7 |
from fastapi import FastAPI, HTTPException, WebSocket, WebSocketDisconnect
|
| 8 |
from fastapi.middleware.cors import CORSMiddleware
|
| 9 |
+
from fastapi.responses import FileResponse
|
| 10 |
+
from fastapi.staticfiles import StaticFiles
|
| 11 |
|
| 12 |
from .connection_manager import ConnectionManager
|
| 13 |
from .models import (
|
|
|
|
| 62 |
connection_manager = ConnectionManager()
|
| 63 |
robot_manager = RobotManager()
|
| 64 |
|
| 65 |
+
# Mount static files for the frontend
|
| 66 |
+
# Try different possible locations for the static frontend
|
| 67 |
+
static_dir_candidates = [
|
| 68 |
+
os.path.join(
|
| 69 |
+
os.path.dirname(os.path.dirname(__file__)), "..", "static-frontend"
|
| 70 |
+
), # From src-python/src
|
| 71 |
+
os.path.join(
|
| 72 |
+
os.path.dirname(os.path.dirname(os.path.dirname(__file__))), "static-frontend"
|
| 73 |
+
), # From src-python
|
| 74 |
+
"/home/user/app/static-frontend", # HF Spaces absolute path
|
| 75 |
+
"static-frontend", # Relative to working directory
|
| 76 |
+
]
|
| 77 |
+
|
| 78 |
+
static_dir = None
|
| 79 |
+
for candidate in static_dir_candidates:
|
| 80 |
+
if os.path.exists(candidate):
|
| 81 |
+
static_dir = candidate
|
| 82 |
+
break
|
| 83 |
+
|
| 84 |
+
if static_dir:
|
| 85 |
+
app.mount("/static", StaticFiles(directory=static_dir), name="static")
|
| 86 |
+
logger.info(f"π Serving static files from: {static_dir}")
|
| 87 |
+
else:
|
| 88 |
+
logger.warning(f"β οΈ Static directory not found in any of: {static_dir_candidates}")
|
| 89 |
+
|
| 90 |
+
|
| 91 |
+
def get_static_dir() -> str | None:
|
| 92 |
+
"""Get the static directory path"""
|
| 93 |
+
static_dir_candidates = [
|
| 94 |
+
os.path.join(
|
| 95 |
+
os.path.dirname(os.path.dirname(__file__)), "..", "static-frontend"
|
| 96 |
+
), # From src-python/src
|
| 97 |
+
os.path.join(
|
| 98 |
+
os.path.dirname(os.path.dirname(os.path.dirname(__file__))),
|
| 99 |
+
"static-frontend",
|
| 100 |
+
), # From src-python
|
| 101 |
+
"/home/user/app/static-frontend", # HF Spaces absolute path
|
| 102 |
+
"static-frontend", # Relative to working directory
|
| 103 |
+
]
|
| 104 |
+
|
| 105 |
+
for candidate in static_dir_candidates:
|
| 106 |
+
if os.path.exists(candidate):
|
| 107 |
+
return candidate
|
| 108 |
+
return None
|
| 109 |
+
|
| 110 |
|
| 111 |
@app.on_event("startup")
|
| 112 |
async def startup_event():
|
|
|
|
| 120 |
|
| 121 |
|
| 122 |
@app.get("/")
|
| 123 |
+
async def serve_frontend():
|
| 124 |
+
"""Serve the main frontend page"""
|
| 125 |
+
static_dir = get_static_dir()
|
| 126 |
+
if not static_dir:
|
| 127 |
+
return {
|
| 128 |
+
"message": "Frontend not built. Run 'bun run build' to build the frontend."
|
| 129 |
+
}
|
| 130 |
+
|
| 131 |
+
index_file = os.path.join(static_dir, "index.html")
|
| 132 |
+
if os.path.exists(index_file):
|
| 133 |
+
return FileResponse(index_file)
|
| 134 |
+
return {"message": "Frontend not built. Run 'bun run build' to build the frontend."}
|
| 135 |
+
|
| 136 |
+
|
| 137 |
+
@app.get("/status")
|
| 138 |
+
async def get_status():
|
| 139 |
+
"""Get server status for health checks"""
|
| 140 |
return {
|
| 141 |
"message": "LeRobot Arena Server",
|
| 142 |
"version": "1.0.0",
|
| 143 |
"robots_connected": len(robot_manager.robots),
|
| 144 |
"active_connections": connection_manager.get_connection_count(),
|
| 145 |
+
"status": "healthy",
|
| 146 |
}
|
| 147 |
|
| 148 |
|
| 149 |
+
# Serve static assets from the _app directory
|
| 150 |
+
@app.get("/_app/{path:path}")
|
| 151 |
+
async def serve_app_assets(path: str):
|
| 152 |
+
"""Serve Svelte app assets"""
|
| 153 |
+
static_dir = get_static_dir()
|
| 154 |
+
if not static_dir:
|
| 155 |
+
raise HTTPException(status_code=404, detail="Frontend not found")
|
| 156 |
+
|
| 157 |
+
file_path = os.path.join(static_dir, "_app", path)
|
| 158 |
+
if os.path.exists(file_path) and os.path.isfile(file_path):
|
| 159 |
+
return FileResponse(file_path)
|
| 160 |
+
raise HTTPException(status_code=404, detail="File not found")
|
| 161 |
+
|
| 162 |
+
|
| 163 |
+
# Catch-all route for client-side routing (SPA fallback)
|
| 164 |
+
@app.get("/{path:path}")
|
| 165 |
+
async def serve_spa_fallback(path: str):
|
| 166 |
+
"""Serve the frontend for client-side routing"""
|
| 167 |
+
# If it's an API or WebSocket path, let it pass through to other handlers
|
| 168 |
+
if path.startswith(("api/", "ws/")):
|
| 169 |
+
raise HTTPException(status_code=404, detail="Not found")
|
| 170 |
+
|
| 171 |
+
# For all other paths, serve the index.html (SPA)
|
| 172 |
+
static_dir = get_static_dir()
|
| 173 |
+
if not static_dir:
|
| 174 |
+
raise HTTPException(status_code=404, detail="Frontend not found")
|
| 175 |
+
|
| 176 |
+
index_file = os.path.join(static_dir, "index.html")
|
| 177 |
+
if os.path.exists(index_file):
|
| 178 |
+
return FileResponse(index_file)
|
| 179 |
+
raise HTTPException(status_code=404, detail="Frontend not found")
|
| 180 |
+
|
| 181 |
+
|
| 182 |
# ============= ROBOT MANAGEMENT API =============
|
| 183 |
|
| 184 |
|
|
|
|
| 660 |
logger = logging.getLogger("lerobot-arena")
|
| 661 |
logger.info("π Starting LeRobot Arena WebSocket Server...")
|
| 662 |
|
| 663 |
+
# Start the server on port 7860 for HF Spaces compatibility
|
| 664 |
uvicorn.run(
|
| 665 |
app,
|
| 666 |
host="0.0.0.0",
|
| 667 |
+
port=7860,
|
| 668 |
log_level="info",
|
| 669 |
+
reload=False,
|
| 670 |
)
|
src-python/start_server.py
CHANGED
|
@@ -5,34 +5,37 @@ LeRobot Arena WebSocket Server
|
|
| 5 |
Run with: python start_server.py
|
| 6 |
"""
|
| 7 |
|
| 8 |
-
import uvicorn
|
| 9 |
import logging
|
| 10 |
import sys
|
| 11 |
from pathlib import Path
|
| 12 |
|
|
|
|
|
|
|
| 13 |
# Add src to path
|
| 14 |
sys.path.insert(0, str(Path(__file__).parent / "src"))
|
| 15 |
|
| 16 |
from src.main import app
|
| 17 |
|
|
|
|
| 18 |
def main():
|
| 19 |
"""Start the LeRobot Arena server"""
|
| 20 |
logging.basicConfig(
|
| 21 |
level=logging.INFO,
|
| 22 |
-
format="%(asctime)s - %(name)s - %(levelname)s - %(message)s"
|
| 23 |
)
|
| 24 |
-
|
| 25 |
logger = logging.getLogger("lerobot-arena")
|
| 26 |
logger.info("π Starting LeRobot Arena WebSocket Server...")
|
| 27 |
-
|
| 28 |
-
# Start the server
|
| 29 |
uvicorn.run(
|
| 30 |
-
app,
|
| 31 |
-
host="0.0.0.0",
|
| 32 |
-
port=
|
| 33 |
log_level="info",
|
| 34 |
-
reload=False # Auto-reload on code changes
|
| 35 |
)
|
| 36 |
|
|
|
|
| 37 |
if __name__ == "__main__":
|
| 38 |
-
main()
|
|
|
|
| 5 |
Run with: python start_server.py
|
| 6 |
"""
|
| 7 |
|
|
|
|
| 8 |
import logging
|
| 9 |
import sys
|
| 10 |
from pathlib import Path
|
| 11 |
|
| 12 |
+
import uvicorn
|
| 13 |
+
|
| 14 |
# Add src to path
|
| 15 |
sys.path.insert(0, str(Path(__file__).parent / "src"))
|
| 16 |
|
| 17 |
from src.main import app
|
| 18 |
|
| 19 |
+
|
| 20 |
def main():
|
| 21 |
"""Start the LeRobot Arena server"""
|
| 22 |
logging.basicConfig(
|
| 23 |
level=logging.INFO,
|
| 24 |
+
format="%(asctime)s - %(name)s - %(levelname)s - %(message)s",
|
| 25 |
)
|
| 26 |
+
|
| 27 |
logger = logging.getLogger("lerobot-arena")
|
| 28 |
logger.info("π Starting LeRobot Arena WebSocket Server...")
|
| 29 |
+
|
| 30 |
+
# Start the server on port 7860 for HF Spaces compatibility
|
| 31 |
uvicorn.run(
|
| 32 |
+
app,
|
| 33 |
+
host="0.0.0.0",
|
| 34 |
+
port=7860,
|
| 35 |
log_level="info",
|
| 36 |
+
reload=False, # Auto-reload on code changes
|
| 37 |
)
|
| 38 |
|
| 39 |
+
|
| 40 |
if __name__ == "__main__":
|
| 41 |
+
main()
|
src/lib/utils/config.ts
CHANGED
|
@@ -29,7 +29,7 @@ function getSpaceHost(): string | undefined {
|
|
| 29 |
* Get the base URL for API requests
|
| 30 |
*/
|
| 31 |
export function getApiBaseUrl(): string {
|
| 32 |
-
if (!isBrowser) return 'http://localhost:
|
| 33 |
|
| 34 |
// Check for Hugging Face Spaces
|
| 35 |
const spaceHost = getSpaceHost();
|
|
@@ -38,22 +38,27 @@ export function getApiBaseUrl(): string {
|
|
| 38 |
}
|
| 39 |
|
| 40 |
// In browser, check current location
|
| 41 |
-
const { protocol, hostname } = window.location;
|
| 42 |
|
| 43 |
-
// If we're on
|
| 44 |
-
if (hostname === 'localhost' || hostname === '127.0.0.1'
|
| 45 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 46 |
}
|
| 47 |
|
| 48 |
-
//
|
| 49 |
-
return `${protocol}//${hostname}:
|
| 50 |
}
|
| 51 |
|
| 52 |
/**
|
| 53 |
* Get the WebSocket URL for real-time connections
|
| 54 |
*/
|
| 55 |
export function getWebSocketBaseUrl(): string {
|
| 56 |
-
if (!isBrowser) return 'ws://localhost:
|
| 57 |
|
| 58 |
// Check for Hugging Face Spaces
|
| 59 |
const spaceHost = getSpaceHost();
|
|
@@ -61,18 +66,24 @@ export function getWebSocketBaseUrl(): string {
|
|
| 61 |
return `wss://${spaceHost}`;
|
| 62 |
}
|
| 63 |
|
| 64 |
-
const { protocol, hostname } = window.location;
|
| 65 |
|
| 66 |
-
// If we're on localhost
|
| 67 |
if (hostname === 'localhost' || hostname === '127.0.0.1') {
|
| 68 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 69 |
}
|
| 70 |
|
| 71 |
// For HTTPS sites, use WSS; for HTTP sites, use WS
|
| 72 |
const wsProtocol = protocol === 'https:' ? 'wss:' : 'ws:';
|
| 73 |
|
| 74 |
-
//
|
| 75 |
-
return `${wsProtocol}//${hostname}:
|
| 76 |
}
|
| 77 |
|
| 78 |
/**
|
|
|
|
| 29 |
* Get the base URL for API requests
|
| 30 |
*/
|
| 31 |
export function getApiBaseUrl(): string {
|
| 32 |
+
if (!isBrowser) return 'http://localhost:7860';
|
| 33 |
|
| 34 |
// Check for Hugging Face Spaces
|
| 35 |
const spaceHost = getSpaceHost();
|
|
|
|
| 38 |
}
|
| 39 |
|
| 40 |
// In browser, check current location
|
| 41 |
+
const { protocol, hostname, port } = window.location;
|
| 42 |
|
| 43 |
+
// If we're on the same host and port, use same origin (both frontend and backend on same server)
|
| 44 |
+
if (hostname === 'localhost' || hostname === '127.0.0.1') {
|
| 45 |
+
// In development, frontend might be on 5173 and backend on 7860
|
| 46 |
+
if (port === '5173') {
|
| 47 |
+
return 'http://localhost:7860';
|
| 48 |
+
}
|
| 49 |
+
// If frontend is served from backend (port 7860), use same origin
|
| 50 |
+
return `${protocol}//${hostname}:${port}`;
|
| 51 |
}
|
| 52 |
|
| 53 |
+
// For production, use same origin (both served from same FastAPI server)
|
| 54 |
+
return `${protocol}//${hostname}${port ? `:${port}` : ''}`;
|
| 55 |
}
|
| 56 |
|
| 57 |
/**
|
| 58 |
* Get the WebSocket URL for real-time connections
|
| 59 |
*/
|
| 60 |
export function getWebSocketBaseUrl(): string {
|
| 61 |
+
if (!isBrowser) return 'ws://localhost:7860';
|
| 62 |
|
| 63 |
// Check for Hugging Face Spaces
|
| 64 |
const spaceHost = getSpaceHost();
|
|
|
|
| 66 |
return `wss://${spaceHost}`;
|
| 67 |
}
|
| 68 |
|
| 69 |
+
const { protocol, hostname, port } = window.location;
|
| 70 |
|
| 71 |
+
// If we're on localhost
|
| 72 |
if (hostname === 'localhost' || hostname === '127.0.0.1') {
|
| 73 |
+
// In development, frontend might be on 5173 and backend on 7860
|
| 74 |
+
if (port === '5173') {
|
| 75 |
+
return 'ws://localhost:7860';
|
| 76 |
+
}
|
| 77 |
+
// If frontend is served from backend (port 7860), use same origin
|
| 78 |
+
const wsProtocol = protocol === 'https:' ? 'wss:' : 'ws:';
|
| 79 |
+
return `${wsProtocol}//${hostname}:${port}`;
|
| 80 |
}
|
| 81 |
|
| 82 |
// For HTTPS sites, use WSS; for HTTP sites, use WS
|
| 83 |
const wsProtocol = protocol === 'https:' ? 'wss:' : 'ws:';
|
| 84 |
|
| 85 |
+
// For production, use same origin (both served from same FastAPI server)
|
| 86 |
+
return `${wsProtocol}//${hostname}${port ? `:${port}` : ''}`;
|
| 87 |
}
|
| 88 |
|
| 89 |
/**
|