Spaces:
Running
Running
Fix HF Spaces
Browse files- Dockerfile +3 -0
- src-python/src/main.py +24 -5
- src/lib/components/panel/RobotControlPanel.svelte +14 -7
- src/lib/utils/config.ts +96 -0
Dockerfile
CHANGED
|
@@ -62,6 +62,9 @@ cd $HOME/app/static-frontend && python -m http.server 7860 --bind 0.0.0.0 &\n\
|
|
| 62 |
echo "✅ Both services started!"\n\
|
| 63 |
echo "Backend: http://localhost:8080"\n\
|
| 64 |
echo "Frontend: http://localhost:7860"\n\
|
|
|
|
|
|
|
|
|
|
| 65 |
wait' > start_services.sh && chmod +x start_services.sh
|
| 66 |
|
| 67 |
# Expose both ports (7860 is default for HF Spaces)
|
|
|
|
| 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)
|
src-python/src/main.py
CHANGED
|
@@ -1,5 +1,6 @@
|
|
| 1 |
import asyncio
|
| 2 |
import logging
|
|
|
|
| 3 |
import uuid
|
| 4 |
from datetime import UTC, datetime
|
| 5 |
|
|
@@ -24,14 +25,32 @@ app = FastAPI(
|
|
| 24 |
version="1.0.0",
|
| 25 |
)
|
| 26 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 27 |
# CORS middleware for web frontend
|
| 28 |
app.add_middleware(
|
| 29 |
CORSMiddleware,
|
| 30 |
-
allow_origins=
|
| 31 |
-
"http://localhost:5173",
|
| 32 |
-
"http://localhost:5174",
|
| 33 |
-
"http://localhost:3000",
|
| 34 |
-
], # Add your frontend URLs
|
| 35 |
allow_credentials=True,
|
| 36 |
allow_methods=["*"],
|
| 37 |
allow_headers=["*"],
|
|
|
|
| 1 |
import asyncio
|
| 2 |
import logging
|
| 3 |
+
import os
|
| 4 |
import uuid
|
| 5 |
from datetime import UTC, datetime
|
| 6 |
|
|
|
|
| 25 |
version="1.0.0",
|
| 26 |
)
|
| 27 |
|
| 28 |
+
# Dynamic CORS origins based on environment
|
| 29 |
+
cors_origins = [
|
| 30 |
+
"http://localhost:5173",
|
| 31 |
+
"http://localhost:5174",
|
| 32 |
+
"http://localhost:7860",
|
| 33 |
+
"http://localhost:3000",
|
| 34 |
+
"https://*.hf.space",
|
| 35 |
+
"https://hf.space",
|
| 36 |
+
"https://huggingface.co",
|
| 37 |
+
]
|
| 38 |
+
|
| 39 |
+
# Add SPACE_HOST if running in Hugging Face Spaces
|
| 40 |
+
space_host = os.environ.get("SPACE_HOST")
|
| 41 |
+
if space_host:
|
| 42 |
+
cors_origins.extend([
|
| 43 |
+
f"https://{space_host}",
|
| 44 |
+
f"http://{space_host}",
|
| 45 |
+
])
|
| 46 |
+
logger.info(f"🌐 Running in Hugging Face Spaces: {space_host}")
|
| 47 |
+
|
| 48 |
+
logger.info(f"🔒 CORS origins: {cors_origins}")
|
| 49 |
+
|
| 50 |
# CORS middleware for web frontend
|
| 51 |
app.add_middleware(
|
| 52 |
CORSMiddleware,
|
| 53 |
+
allow_origins=cors_origins,
|
|
|
|
|
|
|
|
|
|
|
|
|
| 54 |
allow_credentials=True,
|
| 55 |
allow_methods=["*"],
|
| 56 |
allow_headers=["*"],
|
src/lib/components/panel/RobotControlPanel.svelte
CHANGED
|
@@ -2,6 +2,7 @@
|
|
| 2 |
import { robotManager } from "$lib/robot/RobotManager.svelte";
|
| 3 |
import { robotUrdfConfigMap } from "$lib/configs/robotUrdfConfig";
|
| 4 |
import type { Robot } from "$lib/robot/Robot.svelte";
|
|
|
|
| 5 |
|
| 6 |
interface Props {}
|
| 7 |
|
|
@@ -23,6 +24,13 @@
|
|
| 23 |
const robotsWithSlaves = $derived(robotManager.robotsWithSlaves.length);
|
| 24 |
const robotsWithMaster = $derived(robotManager.robotsWithMaster.length);
|
| 25 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 26 |
async function createRobot() {
|
| 27 |
if (isCreating) return;
|
| 28 |
|
|
@@ -50,9 +58,9 @@
|
|
| 50 |
}
|
| 51 |
|
| 52 |
// Master connection functions
|
| 53 |
-
async function
|
| 54 |
try {
|
| 55 |
-
await robotManager.connectDemoSequences(robot.id
|
| 56 |
} catch (err) {
|
| 57 |
error = `Failed to connect demo sequences: ${err}`;
|
| 58 |
console.error(err);
|
|
@@ -63,7 +71,7 @@
|
|
| 63 |
try {
|
| 64 |
const config: import('$lib/types/robotDriver').MasterDriverConfig = {
|
| 65 |
type: "remote-server",
|
| 66 |
-
url:
|
| 67 |
apiKey: undefined,
|
| 68 |
pollInterval: 100
|
| 69 |
};
|
|
@@ -105,8 +113,7 @@
|
|
| 105 |
async function connectRemoteServerSlave(robot: Robot) {
|
| 106 |
try {
|
| 107 |
// First, fetch available robots from the server
|
| 108 |
-
const
|
| 109 |
-
const response = await fetch(`${serverUrl}/api/robots`);
|
| 110 |
|
| 111 |
if (!response.ok) {
|
| 112 |
throw new Error(`Server responded with ${response.status}: ${response.statusText}`);
|
|
@@ -137,7 +144,7 @@
|
|
| 137 |
try {
|
| 138 |
await robotManager.connectRemoteServerSlave(
|
| 139 |
pendingLocalRobot.id,
|
| 140 |
-
|
| 141 |
undefined,
|
| 142 |
selectedServerRobotId
|
| 143 |
);
|
|
@@ -332,7 +339,7 @@
|
|
| 332 |
<div class="flex flex-wrap gap-2">
|
| 333 |
<button
|
| 334 |
class="px-3 py-1.5 bg-orange-500 text-white rounded text-sm hover:bg-orange-600 transition-colors disabled:bg-slate-600 disabled:cursor-not-allowed"
|
| 335 |
-
onclick={() =>
|
| 336 |
disabled={robot.controlState.hasActiveMaster}
|
| 337 |
>
|
| 338 |
Demo Sequences
|
|
|
|
| 2 |
import { robotManager } from "$lib/robot/RobotManager.svelte";
|
| 3 |
import { robotUrdfConfigMap } from "$lib/configs/robotUrdfConfig";
|
| 4 |
import type { Robot } from "$lib/robot/Robot.svelte";
|
| 5 |
+
import { getApiBaseUrl, getWebSocketBaseUrl, getEnvironmentInfo } from "$lib/utils/config";
|
| 6 |
|
| 7 |
interface Props {}
|
| 8 |
|
|
|
|
| 24 |
const robotsWithSlaves = $derived(robotManager.robotsWithSlaves.length);
|
| 25 |
const robotsWithMaster = $derived(robotManager.robotsWithMaster.length);
|
| 26 |
|
| 27 |
+
// Get URLs from configuration
|
| 28 |
+
const apiBaseUrl = getApiBaseUrl();
|
| 29 |
+
const wsBaseUrl = getWebSocketBaseUrl();
|
| 30 |
+
|
| 31 |
+
// Log environment info for debugging
|
| 32 |
+
console.log('Environment info:', getEnvironmentInfo());
|
| 33 |
+
|
| 34 |
async function createRobot() {
|
| 35 |
if (isCreating) return;
|
| 36 |
|
|
|
|
| 58 |
}
|
| 59 |
|
| 60 |
// Master connection functions
|
| 61 |
+
async function connectDemoSequences(robot: Robot) {
|
| 62 |
try {
|
| 63 |
+
await robotManager.connectDemoSequences(robot.id);
|
| 64 |
} catch (err) {
|
| 65 |
error = `Failed to connect demo sequences: ${err}`;
|
| 66 |
console.error(err);
|
|
|
|
| 71 |
try {
|
| 72 |
const config: import('$lib/types/robotDriver').MasterDriverConfig = {
|
| 73 |
type: "remote-server",
|
| 74 |
+
url: wsBaseUrl,
|
| 75 |
apiKey: undefined,
|
| 76 |
pollInterval: 100
|
| 77 |
};
|
|
|
|
| 113 |
async function connectRemoteServerSlave(robot: Robot) {
|
| 114 |
try {
|
| 115 |
// First, fetch available robots from the server
|
| 116 |
+
const response = await fetch(`${apiBaseUrl}/api/robots`);
|
|
|
|
| 117 |
|
| 118 |
if (!response.ok) {
|
| 119 |
throw new Error(`Server responded with ${response.status}: ${response.statusText}`);
|
|
|
|
| 144 |
try {
|
| 145 |
await robotManager.connectRemoteServerSlave(
|
| 146 |
pendingLocalRobot.id,
|
| 147 |
+
wsBaseUrl,
|
| 148 |
undefined,
|
| 149 |
selectedServerRobotId
|
| 150 |
);
|
|
|
|
| 339 |
<div class="flex flex-wrap gap-2">
|
| 340 |
<button
|
| 341 |
class="px-3 py-1.5 bg-orange-500 text-white rounded text-sm hover:bg-orange-600 transition-colors disabled:bg-slate-600 disabled:cursor-not-allowed"
|
| 342 |
+
onclick={() => connectDemoSequences(robot)}
|
| 343 |
disabled={robot.controlState.hasActiveMaster}
|
| 344 |
>
|
| 345 |
Demo Sequences
|
src/lib/utils/config.ts
ADDED
|
@@ -0,0 +1,96 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
/**
|
| 2 |
+
* Configuration utilities for environment-specific URLs
|
| 3 |
+
*/
|
| 4 |
+
|
| 5 |
+
// Check if we're running in browser
|
| 6 |
+
const isBrowser = typeof window !== 'undefined';
|
| 7 |
+
|
| 8 |
+
/**
|
| 9 |
+
* Get the SPACE_HOST from various sources
|
| 10 |
+
*/
|
| 11 |
+
function getSpaceHost(): string | undefined {
|
| 12 |
+
if (!isBrowser) return undefined;
|
| 13 |
+
|
| 14 |
+
// Check window.SPACE_HOST (injected by container)
|
| 15 |
+
if ((window as unknown as { SPACE_HOST?: string }).SPACE_HOST) {
|
| 16 |
+
return (window as unknown as { SPACE_HOST: string }).SPACE_HOST;
|
| 17 |
+
}
|
| 18 |
+
|
| 19 |
+
// Check if current hostname looks like HF Spaces
|
| 20 |
+
const hostname = window.location.hostname;
|
| 21 |
+
if (hostname.includes('hf.space') || hostname.includes('huggingface.co')) {
|
| 22 |
+
return hostname;
|
| 23 |
+
}
|
| 24 |
+
|
| 25 |
+
return undefined;
|
| 26 |
+
}
|
| 27 |
+
|
| 28 |
+
/**
|
| 29 |
+
* Get the base URL for API requests
|
| 30 |
+
*/
|
| 31 |
+
export function getApiBaseUrl(): string {
|
| 32 |
+
if (!isBrowser) return 'http://localhost:8080';
|
| 33 |
+
|
| 34 |
+
// Check for Hugging Face Spaces
|
| 35 |
+
const spaceHost = getSpaceHost();
|
| 36 |
+
if (spaceHost) {
|
| 37 |
+
return `https://${spaceHost}`;
|
| 38 |
+
}
|
| 39 |
+
|
| 40 |
+
// In browser, check current location
|
| 41 |
+
const { protocol, hostname } = window.location;
|
| 42 |
+
|
| 43 |
+
// If we're on localhost or serving from file, use localhost:8080
|
| 44 |
+
if (hostname === 'localhost' || hostname === '127.0.0.1' || protocol === 'file:') {
|
| 45 |
+
return 'http://localhost:8080';
|
| 46 |
+
}
|
| 47 |
+
|
| 48 |
+
// Default fallback - same origin but port 8080
|
| 49 |
+
return `${protocol}//${hostname}:8080`;
|
| 50 |
+
}
|
| 51 |
+
|
| 52 |
+
/**
|
| 53 |
+
* Get the WebSocket URL for real-time connections
|
| 54 |
+
*/
|
| 55 |
+
export function getWebSocketBaseUrl(): string {
|
| 56 |
+
if (!isBrowser) return 'ws://localhost:8080';
|
| 57 |
+
|
| 58 |
+
// Check for Hugging Face Spaces
|
| 59 |
+
const spaceHost = getSpaceHost();
|
| 60 |
+
if (spaceHost) {
|
| 61 |
+
return `wss://${spaceHost}`;
|
| 62 |
+
}
|
| 63 |
+
|
| 64 |
+
const { protocol, hostname } = window.location;
|
| 65 |
+
|
| 66 |
+
// If we're on localhost, use ws://localhost:8080
|
| 67 |
+
if (hostname === 'localhost' || hostname === '127.0.0.1') {
|
| 68 |
+
return 'ws://localhost:8080';
|
| 69 |
+
}
|
| 70 |
+
|
| 71 |
+
// For HTTPS sites, use WSS; for HTTP sites, use WS
|
| 72 |
+
const wsProtocol = protocol === 'https:' ? 'wss:' : 'ws:';
|
| 73 |
+
|
| 74 |
+
// Default fallback - same origin but port 8080
|
| 75 |
+
return `${wsProtocol}//${hostname}:8080`;
|
| 76 |
+
}
|
| 77 |
+
|
| 78 |
+
/**
|
| 79 |
+
* Get environment info for debugging
|
| 80 |
+
*/
|
| 81 |
+
export function getEnvironmentInfo() {
|
| 82 |
+
if (!isBrowser) return { env: 'server', hostname: 'unknown' };
|
| 83 |
+
|
| 84 |
+
const { protocol, hostname, port } = window.location;
|
| 85 |
+
const spaceHost = getSpaceHost();
|
| 86 |
+
|
| 87 |
+
return {
|
| 88 |
+
env: spaceHost ? 'huggingface-spaces' : hostname === 'localhost' ? 'local' : 'production',
|
| 89 |
+
hostname,
|
| 90 |
+
port,
|
| 91 |
+
protocol,
|
| 92 |
+
spaceHost,
|
| 93 |
+
apiBaseUrl: getApiBaseUrl(),
|
| 94 |
+
wsBaseUrl: getWebSocketBaseUrl()
|
| 95 |
+
};
|
| 96 |
+
}
|