blanchon commited on
Commit
de6f5a0
·
1 Parent(s): 0d92723

Fix HF Spaces

Browse files
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 connectMockSequenceMaster(robot: Robot) {
54
  try {
55
- await robotManager.connectDemoSequences(robot.id, true);
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: "ws://localhost:8080",
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 serverUrl = "http://localhost:8080";
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
- "ws://localhost:8080",
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={() => connectMockSequenceMaster(robot)}
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
+ }