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 |
+
}
|