Hwandji commited on
Commit
97c7aec
·
1 Parent(s): 70efae9

fix: resolve CORS and API routing issues for Hugging Face

Browse files

- Add environment-aware API base URL detection
- Fix CORS configuration with proper origin handling
- Update nginx reverse proxy for /api routes
- Fix import statements (default vs named exports)
- Enable production deployment on Hugging Face

Fixes agent display issues in production environment.
Resolves ERR_FAILED and 404 errors from API calls.

backend/config/settings.py CHANGED
@@ -189,9 +189,9 @@ class SecuritySettings(BaseSettings):
189
  rate_limit_requests: int = Field(default=1000, env="RATE_LIMIT_REQUESTS")
190
  rate_limit_window: int = Field(default=3600, env="RATE_LIMIT_WINDOW") # 1 hour
191
 
192
- # CORS settings - Updated for network access
193
  allowed_origins: str = Field(
194
- default="http://localhost:5173,http://localhost:8080,http://localhost:3000,http://100.64.0.45:5173,http://100.64.0.45:8080,http://100.64.0.45:3000,http://100.64.0.45:8000",
195
  env="ALLOWED_ORIGINS"
196
  )
197
 
@@ -479,4 +479,4 @@ if __name__ == "__main__":
479
  # Test agent model configs
480
  for agent in ['jane_alesi', 'john_alesi', 'lara_alesi']:
481
  config = settings.get_openrouter_config_for_agent(agent)
482
- logger.info(f" {agent}: {config['model']} (${config['cost_per_1m']}/1M tokens)")
 
189
  rate_limit_requests: int = Field(default=1000, env="RATE_LIMIT_REQUESTS")
190
  rate_limit_window: int = Field(default=3600, env="RATE_LIMIT_WINDOW") # 1 hour
191
 
192
+ # CORS settings - Updated for network access + HuggingFace Spaces
193
  allowed_origins: str = Field(
194
+ default="http://localhost:5173,http://localhost:8080,http://localhost:3000,http://100.64.0.45:5173,http://100.64.0.45:8080,http://100.64.0.45:3000,http://100.64.0.45:8000,https://*.hf.space,https://huggingface.co",
195
  env="ALLOWED_ORIGINS"
196
  )
197
 
 
479
  # Test agent model configs
480
  for agent in ['jane_alesi', 'john_alesi', 'lara_alesi']:
481
  config = settings.get_openrouter_config_for_agent(agent)
482
+ logger.info(f" {agent}: {config['model']} (${config['cost_per_1m']}/1M tokens)")
backend/main.py CHANGED
@@ -182,10 +182,11 @@ app = FastAPI(
182
  lifespan=lifespan
183
  )
184
 
185
- # CORS middleware for Vue.js frontend - using settings
186
  app.add_middleware(
187
  CORSMiddleware,
188
  allow_origins=settings.get_cors_origins(),
 
189
  allow_credentials=True,
190
  allow_methods=["*"],
191
  allow_headers=["*"],
 
182
  lifespan=lifespan
183
  )
184
 
185
+ # CORS middleware for Vue.js frontend - using settings + HuggingFace Spaces regex
186
  app.add_middleware(
187
  CORSMiddleware,
188
  allow_origins=settings.get_cors_origins(),
189
+ allow_origin_regex=r"https://.*\.hf\.space", # Allow all HuggingFace Spaces
190
  allow_credentials=True,
191
  allow_methods=["*"],
192
  allow_headers=["*"],
frontend/src/App.vue CHANGED
@@ -48,6 +48,7 @@
48
  <script>
49
  import { ref, computed, onMounted } from 'vue'
50
  import SaapDashboard from './components/SaapDashboard.vue'
 
51
 
52
  export default {
53
  name: 'SaapApp',
@@ -74,18 +75,22 @@ export default {
74
  new Date().toLocaleDateString('de-DE')
75
  )
76
 
77
- // Load agents data from API
78
  const loadAgentsStats = async () => {
79
  try {
80
- const response = await fetch('http://localhost:8000/api/v1/agents')
81
- if (response.ok) {
82
- const data = await response.json()
83
- // Backend returns { "agents": [...] } format
84
- const agents = data.agents || data
85
- totalAgents.value = agents.length
86
- activeAgents.value = agents.filter(agent => agent.status === 'active').length
 
87
  isConnected.value = true
88
  console.log(`✅ Loaded ${totalAgents.value} agents (${activeAgents.value} active)`)
 
 
 
89
  }
90
  } catch (error) {
91
  console.error('❌ Failed to load agents stats:', error)
 
48
  <script>
49
  import { ref, computed, onMounted } from 'vue'
50
  import SaapDashboard from './components/SaapDashboard.vue'
51
+ import saapApi from './services/saapApi.js'
52
 
53
  export default {
54
  name: 'SaapApp',
 
75
  new Date().toLocaleDateString('de-DE')
76
  )
77
 
78
+ // Load agents data from API using saapApi service
79
  const loadAgentsStats = async () => {
80
  try {
81
+ console.log('🔄 Loading agents stats via API service...')
82
+ const response = await saapApi.getAgents()
83
+ console.log('📦 API Response:', response)
84
+
85
+ // Backend returns {agents: [...], total: N, active: N}
86
+ if (response && response.agents) {
87
+ totalAgents.value = response.total || response.agents.length
88
+ activeAgents.value = response.active || response.agents.filter(a => a.status === 'active').length
89
  isConnected.value = true
90
  console.log(`✅ Loaded ${totalAgents.value} agents (${activeAgents.value} active)`)
91
+ } else {
92
+ console.warn('⚠️ Unexpected API response format:', response)
93
+ isConnected.value = false
94
  }
95
  } catch (error) {
96
  console.error('❌ Failed to load agents stats:', error)
frontend/src/services/saapApi.js CHANGED
@@ -1,270 +1,251 @@
1
- import axios from 'axios'
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2
 
3
- // 🔧 BACK TO WORKING BACKEND: Port 8000 (we'll add OpenRouter to existing backend)
4
- const API_BASE_URL = 'http://localhost:8000/api/v1'
5
 
6
- const saapApi = axios.create({
 
 
 
 
 
7
  baseURL: API_BASE_URL,
 
8
  headers: {
9
- 'Content-Type': 'application/json'
10
  },
11
- timeout: 120000 // Increased from 30s to 120s for multi-agent coordination
12
- })
13
-
14
- // Request interceptor for debugging
15
- saapApi.interceptors.request.use(
16
- config => {
17
- console.log(`🔄 API Request: ${config.method?.toUpperCase()} ${config.url}`)
18
- return config
 
19
  },
20
- error => {
21
- console.error('❌ API Request Error:', error)
22
- return Promise.reject(error)
23
  }
24
- )
25
-
26
- // Response interceptor for enhanced logging with provider info
27
- saapApi.interceptors.response.use(
28
- response => {
29
- const { status, config } = response
30
- const provider = response.data?.provider || response.data?.response?.provider
31
- const responseTime = response.data?.response_time || response.data?.performance?.response_time
32
- const cost = response.data?.cost_usd || response.data?.performance?.cost_usd
33
-
34
- let logMsg = `✅ API Response: ${config.method?.toUpperCase()} ${config.url} (${status})`
35
- if (provider) logMsg += ` via ${provider}`
36
- if (responseTime) logMsg += ` in ${responseTime.toFixed(2)}s`
37
- if (cost) logMsg += ` ($${cost.toFixed(6)})`
38
-
39
- console.log(logMsg)
40
-
41
- return response
42
  },
43
- error => {
44
- const { config, response } = error
45
- console.error(`❌ API Error: ${config?.method?.toUpperCase()} ${config?.url}`, {
46
- status: response?.status,
47
- data: response?.data,
48
- message: error.message
49
- })
50
- return Promise.reject(error)
 
 
 
 
 
51
  }
52
- )
53
-
54
- // Core API methods
55
- export const api = {
56
- // Health check
57
- async getHealth() {
58
- const response = await saapApi.get('/health')
59
- return response.data
60
- },
61
 
62
- // Agent management
 
 
63
  async getAgents() {
64
- const response = await saapApi.get('/agents')
65
- return response.data
66
  },
67
 
68
  async getAgent(agentId) {
69
- const response = await saapApi.get(`/agents/${agentId}`)
70
- return response.data
71
  },
72
 
73
  async createAgent(agentData) {
74
- const response = await saapApi.post('/agents', agentData)
75
- return response.data
76
  },
77
 
 
 
 
 
 
 
 
 
 
 
 
78
  async startAgent(agentId) {
79
- const response = await saapApi.post(`/agents/${agentId}/start`)
80
- return response.data
81
  },
82
 
83
  async stopAgent(agentId) {
84
- const response = await saapApi.post(`/agents/${agentId}/stop`)
85
- return response.data
86
  },
87
 
88
- // Enhanced chat with provider support (will be added to existing backend)
89
- async chatWithAgent(agentId, message, provider = null) {
90
- const requestData = { message }
91
- if (provider) {
92
- requestData.provider = provider // For future OpenRouter integration
93
- }
94
-
95
- const response = await saapApi.post(`/agents/${agentId}/chat`, requestData)
96
- return response.data
97
  },
98
 
99
- // 🚀 NEW: Multi-Agent Communication with extended timeout
100
- async multiAgentChat(requestData) {
101
- console.log('🤖 Multi-Agent Chat Request:', requestData)
102
- const response = await saapApi.post('/multi-agent/chat', {
103
- user_message: requestData.user_message,
104
- user_context: requestData.user_context || {},
105
- preferred_agent: requestData.preferred_agent || null,
106
- task_priority: requestData.task_priority || 'normal'
107
- }, {
108
- timeout: 240000 // 4 minutes (240s) for multi-agent coordination (Jane → Specialist → Jane = ~200s)
109
- })
110
- console.log('✅ Multi-Agent Chat Response:', response.data)
111
- return response.data
112
  },
113
 
114
- // Multi-Agent status and capabilities
115
  async getMultiAgentStatus() {
116
- const response = await saapApi.get('/multi-agent/status')
117
- return response.data
118
  },
119
 
120
- async getAgentCapabilities() {
121
- const response = await saapApi.get('/multi-agent/capabilities')
122
- return response.data
123
- },
124
-
125
- // Agent templates
126
  async getAgentTemplates() {
127
- const response = await saapApi.get('/templates/agents')
128
- return response.data
129
  },
130
 
131
  async createAgentFromTemplate(templateName) {
132
- const response = await saapApi.post(`/templates/agents/${templateName}`)
133
- return response.data
134
  },
135
 
136
- // System info
137
- async getSystemInfo() {
138
- try {
139
- const response = await saapApi.get('/health')
140
- return response.data
141
- } catch (error) {
142
- console.error('Failed to get system info:', error)
143
- return null
144
- }
145
- }
146
- }
147
-
148
- // 🚀 FIX: Add all API methods directly to saapApi for Frontend compatibility
149
- // This allows both saapApi.getAgents() and api.getAgents() to work
150
- Object.assign(saapApi, api)
151
-
152
- // WebSocket connection for real-time updates (back to working port)
153
- export const createWebSocketConnection = () => {
154
- return new WebSocket('ws://localhost:8000/ws')
155
- }
156
 
157
- // 🚀 NEW: SAAP WebSocket Manager Class (to fix import error in agents.js)
158
- class SaapWebSocketManager {
159
  constructor() {
160
- this.ws = null
161
- this.listeners = new Map()
162
- this.connected = false
163
- this.reconnectAttempts = 0
164
- this.maxReconnectAttempts = 5
165
- this.reconnectInterval = 3000
166
  }
167
 
168
  connect() {
169
- console.log('🔌 Connecting to SAAP WebSocket...')
170
- try {
171
- this.ws = new WebSocket('ws://localhost:8000/ws')
172
-
173
- this.ws.onopen = () => {
174
- console.log('✅ WebSocket connected')
175
- this.connected = true
176
- this.reconnectAttempts = 0
177
- this.emit('connected')
178
- }
179
-
180
- this.ws.onmessage = (event) => {
181
- try {
182
- const data = JSON.parse(event.data)
183
- console.log('📨 WebSocket message received:', data)
184
- this.emit('message', data)
185
- } catch (error) {
186
- console.log('📨 WebSocket raw message:', event.data)
187
- this.emit('message', event.data)
188
- }
 
 
 
189
  }
190
-
191
- this.ws.onclose = () => {
192
- console.log('🔌 WebSocket disconnected')
193
- this.connected = false
194
- this.emit('disconnected')
195
-
196
- // Auto-reconnect
197
- if (this.reconnectAttempts < this.maxReconnectAttempts) {
198
- setTimeout(() => {
199
- this.reconnectAttempts++
200
- console.log(`🔄 Reconnecting... (${this.reconnectAttempts}/${this.maxReconnectAttempts})`)
201
- this.connect()
202
- }, this.reconnectInterval)
203
- }
204
- }
205
-
206
- this.ws.onerror = (error) => {
207
- console.error('❌ WebSocket error:', error)
208
- this.emit('error', error)
209
- }
210
-
211
- } catch (error) {
212
- console.error('❌ WebSocket connection failed:', error)
213
- this.emit('error', error)
214
- }
215
  }
216
 
217
- disconnect() {
218
- console.log('🔌 Disconnecting WebSocket...')
219
- if (this.ws) {
220
- this.connected = false
221
- this.ws.close()
222
- this.ws = null
 
 
223
  }
224
  }
225
 
226
  on(event, callback) {
227
  if (!this.listeners.has(event)) {
228
- this.listeners.set(event, [])
229
  }
230
- this.listeners.get(event).push(callback)
231
  }
232
 
233
- off(event, callback) {
234
  if (this.listeners.has(event)) {
235
- const callbacks = this.listeners.get(event)
236
- const index = callbacks.indexOf(callback)
237
- if (index > -1) {
238
- callbacks.splice(index, 1)
239
- }
240
  }
241
  }
242
 
243
- emit(event, data = null) {
244
- if (this.listeners.has(event)) {
245
- this.listeners.get(event).forEach(callback => {
246
- try {
247
- callback(data)
248
- } catch (error) {
249
- console.error(`❌ WebSocket event handler error for '${event}':`, error)
250
- }
251
- })
252
  }
253
  }
254
 
255
- send(data) {
256
- if (this.connected && this.ws) {
257
- this.ws.send(typeof data === 'string' ? data : JSON.stringify(data))
258
- } else {
259
- console.warn('⚠️ Cannot send data: WebSocket not connected')
260
  }
261
  }
262
  }
263
 
264
- // Create singleton instance
265
- export const saapWebSocket = new SaapWebSocketManager()
266
-
267
- // 🚀 FIX: Add named export for saapApi to resolve import error
268
- export { saapApi }
269
 
270
- export default saapApi
 
 
1
+ /**
2
+ * SAAP API Client - HuggingFace Deployment Compatible
3
+ * Automatically detects environment and uses correct base URL
4
+ */
5
+ import axios from 'axios';
6
+
7
+ // 🌐 Environment-aware base URL detection
8
+ const getApiBaseUrl = () => {
9
+ // Production HuggingFace Spaces deployment
10
+ if (window.location.hostname.includes('hf.space')) {
11
+ return '/api/v1'; // Relative path - nginx routes to backend
12
+ }
13
+
14
+ // Local development
15
+ if (window.location.hostname === 'localhost' || window.location.hostname === '127.0.0.1') {
16
+ return 'http://localhost:8000/api/v1';
17
+ }
18
+
19
+ // Network development (IP-based)
20
+ if (window.location.hostname.match(/^\d+\.\d+\.\d+\.\d+$/)) {
21
+ return `http://${window.location.hostname}:8000/api/v1`;
22
+ }
23
+
24
+ // Fallback: relative path
25
+ return '/api/v1';
26
+ };
27
 
28
+ const API_BASE_URL = getApiBaseUrl();
 
29
 
30
+ console.log('🔧 SAAP API Configuration:');
31
+ console.log(` Environment: ${window.location.hostname}`);
32
+ console.log(` API Base URL: ${API_BASE_URL}`);
33
+
34
+ // Create axios instance with base configuration
35
+ const apiClient = axios.create({
36
  baseURL: API_BASE_URL,
37
+ timeout: 30000,
38
  headers: {
39
+ 'Content-Type': 'application/json',
40
  },
41
+ withCredentials: false, // Changed from true for HuggingFace
42
+ });
43
+
44
+ // Request interceptor for logging
45
+ apiClient.interceptors.request.use(
46
+ (config) => {
47
+ console.log(`🔄 API Request: ${config.method?.toUpperCase()} ${config.url}`);
48
+ console.log(`🔄 Full URL: ${config.baseURL}${config.url}`);
49
+ return config;
50
  },
51
+ (error) => {
52
+ console.error('❌ Request Error:', error);
53
+ return Promise.reject(error);
54
  }
55
+ );
56
+
57
+ // Response interceptor for error handling
58
+ apiClient.interceptors.response.use(
59
+ (response) => {
60
+ console.log(`✅ API Response: ${response.status} ${response.config.url}`);
61
+ return response;
 
 
 
 
 
 
 
 
 
 
 
62
  },
63
+ (error) => {
64
+ if (error.response) {
65
+ console.error('❌ API Error:', {
66
+ status: error.response.status,
67
+ data: error.response.data,
68
+ message: error.message
69
+ });
70
+ } else if (error.request) {
71
+ console.error('❌ Network Error: No response received', error.message);
72
+ } else {
73
+ console.error('❌ Request Setup Error:', error.message);
74
+ }
75
+ return Promise.reject(error);
76
  }
77
+ );
 
 
 
 
 
 
 
 
78
 
79
+ // API Methods
80
+ const saapApi = {
81
+ // Agent Management
82
  async getAgents() {
83
+ const response = await apiClient.get('/agents');
84
+ return response.data;
85
  },
86
 
87
  async getAgent(agentId) {
88
+ const response = await apiClient.get(`/agents/${agentId}`);
89
+ return response.data;
90
  },
91
 
92
  async createAgent(agentData) {
93
+ const response = await apiClient.post('/agents', agentData);
94
+ return response.data;
95
  },
96
 
97
+ async updateAgent(agentId, agentData) {
98
+ const response = await apiClient.put(`/agents/${agentId}`, agentData);
99
+ return response.data;
100
+ },
101
+
102
+ async deleteAgent(agentId) {
103
+ const response = await apiClient.delete(`/agents/${agentId}`);
104
+ return response.data;
105
+ },
106
+
107
+ // Agent Lifecycle
108
  async startAgent(agentId) {
109
+ const response = await apiClient.post(`/agents/${agentId}/start`);
110
+ return response.data;
111
  },
112
 
113
  async stopAgent(agentId) {
114
+ const response = await apiClient.post(`/agents/${agentId}/stop`);
115
+ return response.data;
116
  },
117
 
118
+ // Agent Communication
119
+ async sendMessage(agentId, message) {
120
+ const response = await apiClient.post(`/agents/${agentId}/chat`, {
121
+ message: message,
122
+ timestamp: Date.now()
123
+ });
124
+ return response.data;
 
 
125
  },
126
 
127
+ // Multi-Agent System
128
+ async multiAgentChat(message, options = {}) {
129
+ const response = await apiClient.post('/multi-agent/chat', {
130
+ user_message: message,
131
+ ...options
132
+ });
133
+ return response.data;
 
 
 
 
 
 
134
  },
135
 
 
136
  async getMultiAgentStatus() {
137
+ const response = await apiClient.get('/multi-agent/status');
138
+ return response.data;
139
  },
140
 
141
+ // Templates
 
 
 
 
 
142
  async getAgentTemplates() {
143
+ const response = await apiClient.get('/templates/agents');
144
+ return response.data;
145
  },
146
 
147
  async createAgentFromTemplate(templateName) {
148
+ const response = await apiClient.post(`/templates/agents/${templateName}`);
149
+ return response.data;
150
  },
151
 
152
+ // Health Check
153
+ async healthCheck() {
154
+ const response = await apiClient.get('/health');
155
+ return response.data;
156
+ },
157
+ };
 
 
 
 
 
 
 
 
 
 
 
 
 
 
158
 
159
+ // WebSocket connection for real-time updates
160
+ class SaapWebSocket {
161
  constructor() {
162
+ this.ws = null;
163
+ this.reconnectAttempts = 0;
164
+ this.maxReconnectAttempts = 5;
165
+ this.reconnectDelay = 1000;
166
+ this.listeners = new Map();
 
167
  }
168
 
169
  connect() {
170
+ const wsProtocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
171
+ const wsHost = window.location.hostname.includes('hf.space')
172
+ ? window.location.host
173
+ : 'localhost:8000';
174
+ const wsUrl = `${wsProtocol}//${wsHost}/ws`;
175
+
176
+ console.log(`🔌 Connecting to WebSocket: ${wsUrl}`);
177
+
178
+ this.ws = new WebSocket(wsUrl);
179
+
180
+ this.ws.onopen = () => {
181
+ console.log('✅ WebSocket connected');
182
+ this.reconnectAttempts = 0;
183
+ this.emit('connected', {});
184
+ };
185
+
186
+ this.ws.onmessage = (event) => {
187
+ try {
188
+ const data = JSON.parse(event.data);
189
+ console.log('📨 WebSocket message:', data);
190
+ this.emit('message', data);
191
+ } catch (error) {
192
+ console.error('❌ WebSocket message parse error:', error);
193
  }
194
+ };
195
+
196
+ this.ws.onerror = (error) => {
197
+ console.error('❌ WebSocket error:', error);
198
+ this.emit('error', error);
199
+ };
200
+
201
+ this.ws.onclose = () => {
202
+ console.log('❌ WebSocket disconnected');
203
+ this.emit('disconnected', {});
204
+ this.attemptReconnect();
205
+ };
 
 
 
 
 
 
 
 
 
 
 
 
 
206
  }
207
 
208
+ attemptReconnect() {
209
+ if (this.reconnectAttempts < this.maxReconnectAttempts) {
210
+ this.reconnectAttempts++;
211
+ const delay = this.reconnectDelay * this.reconnectAttempts;
212
+ console.log(`🔄 Reconnecting in ${delay}ms (attempt ${this.reconnectAttempts}/${this.maxReconnectAttempts})`);
213
+ setTimeout(() => this.connect(), delay);
214
+ } else {
215
+ console.error('❌ Max reconnection attempts reached');
216
  }
217
  }
218
 
219
  on(event, callback) {
220
  if (!this.listeners.has(event)) {
221
+ this.listeners.set(event, []);
222
  }
223
+ this.listeners.get(event).push(callback);
224
  }
225
 
226
+ emit(event, data) {
227
  if (this.listeners.has(event)) {
228
+ this.listeners.get(event).forEach(callback => callback(data));
 
 
 
 
229
  }
230
  }
231
 
232
+ send(data) {
233
+ if (this.ws && this.ws.readyState === WebSocket.OPEN) {
234
+ this.ws.send(JSON.stringify(data));
235
+ } else {
236
+ console.error('❌ WebSocket not connected');
 
 
 
 
237
  }
238
  }
239
 
240
+ disconnect() {
241
+ if (this.ws) {
242
+ this.ws.close();
243
+ this.ws = null;
 
244
  }
245
  }
246
  }
247
 
248
+ const saapWebSocket = new SaapWebSocket();
 
 
 
 
249
 
250
+ export default saapApi;
251
+ export { saapWebSocket };
frontend/src/stores/agents.js CHANGED
@@ -5,7 +5,7 @@
5
 
6
  import { defineStore } from 'pinia'
7
  import { ref, computed } from 'vue'
8
- import { saapApi, saapWebSocket } from '@/services/saapApi'
9
 
10
  export const useAgentStore = defineStore('agents', () => {
11
  // ==========================================
@@ -426,4 +426,4 @@ export const useAgentStore = defineStore('agents', () => {
426
  getChatMessages,
427
  clearError
428
  }
429
- })
 
5
 
6
  import { defineStore } from 'pinia'
7
  import { ref, computed } from 'vue'
8
+ import saapApi, { saapWebSocket } from '@/services/saapApi'
9
 
10
  export const useAgentStore = defineStore('agents', () => {
11
  // ==========================================
 
426
  getChatMessages,
427
  clearError
428
  }
429
+ })
huggingface/nginx.conf CHANGED
@@ -1,8 +1,9 @@
1
- # Note: HuggingFace Spaces may run as root
2
- user root;
 
3
  worker_processes auto;
4
  error_log /var/log/nginx/error.log warn;
5
- pid /var/run/nginx.pid;
6
 
7
  events {
8
  worker_connections 1024;
@@ -11,51 +12,40 @@ events {
11
  http {
12
  include /etc/nginx/mime.types;
13
  default_type application/octet-stream;
14
-
15
- log_format main '$remote_addr - $remote_user [$time_local] "$request" '
16
- '$status $body_bytes_sent "$http_referer" '
17
- '"$http_user_agent" "$http_x_forwarded_for"';
18
-
19
- access_log /var/log/nginx/access.log main;
20
-
21
  sendfile on;
22
  tcp_nopush on;
23
  tcp_nodelay on;
24
  keepalive_timeout 65;
25
  types_hash_max_size 2048;
26
-
27
- # Gzip compression
 
28
  gzip on;
29
  gzip_vary on;
30
  gzip_proxied any;
31
- gzip_comp_level 6;
32
- gzip_types text/plain text/css text/xml text/javascript
33
- application/json application/javascript application/xml+rss
34
- application/rss+xml font/truetype font/opentype
35
- application/vnd.ms-fontobject image/svg+xml;
36
-
 
37
  server {
38
  listen 7860;
39
- listen [::]:7860;
40
  server_name _;
41
 
42
- root /app/backend/static;
 
43
  index index.html;
44
-
45
- # Security headers
46
- add_header X-Frame-Options "SAMEORIGIN" always;
47
- add_header X-Content-Type-Options "nosniff" always;
48
- add_header X-XSS-Protection "1; mode=block" always;
49
- add_header Referrer-Policy "no-referrer-when-downgrade" always;
50
-
51
- # Vue.js SPA routing
52
- location / {
53
- try_files $uri $uri/ /index.html;
54
- }
55
-
56
- # API proxy to backend (localhost for HuggingFace Spaces)
57
  location /api/ {
58
- proxy_pass http://localhost:8000/api/;
59
  proxy_http_version 1.1;
60
  proxy_set_header Upgrade $http_upgrade;
61
  proxy_set_header Connection 'upgrade';
@@ -67,30 +57,39 @@ http {
67
  proxy_read_timeout 300s;
68
  proxy_connect_timeout 75s;
69
  }
70
-
71
- # WebSocket support for real-time communication
72
  location /ws {
73
- proxy_pass http://localhost:8000/ws;
74
  proxy_http_version 1.1;
75
  proxy_set_header Upgrade $http_upgrade;
76
  proxy_set_header Connection "upgrade";
77
  proxy_set_header Host $host;
78
  proxy_set_header X-Real-IP $remote_addr;
79
- proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
80
  proxy_read_timeout 86400;
81
  }
82
-
83
- # Cache static assets
84
- location ~* \.(jpg|jpeg|png|gif|ico|css|js|svg|woff|woff2|ttf|eot)$ {
 
 
 
 
 
 
 
 
 
 
 
85
  expires 1y;
86
  add_header Cache-Control "public, immutable";
87
  }
88
-
89
- # Disable access to hidden files
90
- location ~ /\. {
91
- deny all;
92
- access_log off;
93
- log_not_found off;
94
  }
95
  }
96
  }
 
1
+ # SAAP Nginx Configuration for HuggingFace Spaces
2
+ # Single-container deployment with Vue.js frontend + FastAPI backend
3
+
4
  worker_processes auto;
5
  error_log /var/log/nginx/error.log warn;
6
+ pid /tmp/nginx.pid;
7
 
8
  events {
9
  worker_connections 1024;
 
12
  http {
13
  include /etc/nginx/mime.types;
14
  default_type application/octet-stream;
15
+
16
+ # Logging
17
+ access_log /var/log/nginx/access.log;
18
+
19
+ # Performance
 
 
20
  sendfile on;
21
  tcp_nopush on;
22
  tcp_nodelay on;
23
  keepalive_timeout 65;
24
  types_hash_max_size 2048;
25
+ client_max_body_size 10M;
26
+
27
+ # Compression
28
  gzip on;
29
  gzip_vary on;
30
  gzip_proxied any;
31
+ gzip_types text/plain text/css application/json application/javascript text/xml application/xml;
32
+
33
+ # Upstream backend
34
+ upstream backend {
35
+ server 127.0.0.1:8000;
36
+ }
37
+
38
  server {
39
  listen 7860;
 
40
  server_name _;
41
 
42
+ # Root directory for static files
43
+ root /app/frontend/dist;
44
  index index.html;
45
+
46
+ # API routes → FastAPI backend
 
 
 
 
 
 
 
 
 
 
 
47
  location /api/ {
48
+ proxy_pass http://backend;
49
  proxy_http_version 1.1;
50
  proxy_set_header Upgrade $http_upgrade;
51
  proxy_set_header Connection 'upgrade';
 
57
  proxy_read_timeout 300s;
58
  proxy_connect_timeout 75s;
59
  }
60
+
61
+ # WebSocket support
62
  location /ws {
63
+ proxy_pass http://backend;
64
  proxy_http_version 1.1;
65
  proxy_set_header Upgrade $http_upgrade;
66
  proxy_set_header Connection "upgrade";
67
  proxy_set_header Host $host;
68
  proxy_set_header X-Real-IP $remote_addr;
 
69
  proxy_read_timeout 86400;
70
  }
71
+
72
+ # Health check
73
+ location /health {
74
+ proxy_pass http://backend;
75
+ }
76
+
77
+ # API docs
78
+ location ~ ^/(docs|redoc) {
79
+ proxy_pass http://backend;
80
+ }
81
+
82
+ # Frontend assets
83
+ location /assets/ {
84
+ try_files $uri =404;
85
  expires 1y;
86
  add_header Cache-Control "public, immutable";
87
  }
88
+
89
+ # Vue.js SPA - catch all routes
90
+ location / {
91
+ try_files $uri $uri/ /index.html;
92
+ add_header Cache-Control "no-cache";
 
93
  }
94
  }
95
  }