akhaliq HF Staff commited on
Commit
3c247ba
·
1 Parent(s): e6104d9
backend_api.py CHANGED
@@ -90,7 +90,7 @@ def get_cached_client(model_id: str, provider: str = "auto"):
90
  # Define models and languages here to avoid importing Gradio UI
91
  AVAILABLE_MODELS = [
92
  {"name": "Gemini 3.0 Pro", "id": "gemini-3.0-pro", "description": "Google Gemini 3.0 Pro via Poe with advanced reasoning"},
93
- {"name": "Grok 4.1 Fast", "id": "x-ai/grok-4.1-fast", "description": "Grok 4.1 Fast model via OpenRouter"},
94
  {"name": "MiniMax M2", "id": "MiniMaxAI/MiniMax-M2", "description": "MiniMax M2 model via HuggingFace InferenceClient with Novita provider"},
95
  {"name": "DeepSeek V3.2-Exp", "id": "deepseek-ai/DeepSeek-V3.2-Exp", "description": "DeepSeek V3.2 Experimental via HuggingFace"},
96
  {"name": "DeepSeek R1", "id": "deepseek-ai/DeepSeek-R1-0528", "description": "DeepSeek R1 model for code generation"},
@@ -490,16 +490,69 @@ async def generate_code(
490
  yield f"data: {completion_data}\n\n"
491
 
492
  except Exception as e:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
493
  error_data = json.dumps({
494
  "type": "error",
495
- "message": str(e)
496
  })
497
  yield f"data: {error_data}\n\n"
498
 
499
  except Exception as e:
 
 
 
 
 
 
 
 
 
500
  error_data = json.dumps({
501
  "type": "error",
502
- "message": f"Generation error: {str(e)}"
503
  })
504
  yield f"data: {error_data}\n\n"
505
 
 
90
  # Define models and languages here to avoid importing Gradio UI
91
  AVAILABLE_MODELS = [
92
  {"name": "Gemini 3.0 Pro", "id": "gemini-3.0-pro", "description": "Google Gemini 3.0 Pro via Poe with advanced reasoning"},
93
+ {"name": "Grok 4.1 Fast", "id": "x-ai/grok-4.1-fast", "description": "Grok 4.1 Fast model via OpenRouter (20 req/min on free tier)"},
94
  {"name": "MiniMax M2", "id": "MiniMaxAI/MiniMax-M2", "description": "MiniMax M2 model via HuggingFace InferenceClient with Novita provider"},
95
  {"name": "DeepSeek V3.2-Exp", "id": "deepseek-ai/DeepSeek-V3.2-Exp", "description": "DeepSeek V3.2 Experimental via HuggingFace"},
96
  {"name": "DeepSeek R1", "id": "deepseek-ai/DeepSeek-R1-0528", "description": "DeepSeek R1 model for code generation"},
 
490
  yield f"data: {completion_data}\n\n"
491
 
492
  except Exception as e:
493
+ # Handle rate limiting and other API errors
494
+ error_message = str(e)
495
+ is_rate_limit = False
496
+ error_type = type(e).__name__
497
+
498
+ # Check for OpenAI SDK rate limit errors
499
+ if error_type == "RateLimitError" or "rate_limit" in error_type.lower():
500
+ is_rate_limit = True
501
+ # Check if this is a rate limit error (429 status code)
502
+ elif hasattr(e, 'status_code') and e.status_code == 429:
503
+ is_rate_limit = True
504
+ # Check error message for rate limit indicators
505
+ elif "429" in error_message or "rate limit" in error_message.lower() or "too many requests" in error_message.lower():
506
+ is_rate_limit = True
507
+
508
+ if is_rate_limit:
509
+ # Try to extract retry-after header or message
510
+ retry_after = None
511
+ if hasattr(e, 'response') and e.response:
512
+ retry_after = e.response.headers.get('Retry-After') or e.response.headers.get('retry-after')
513
+ # Also check if the error object has retry_after
514
+ elif hasattr(e, 'retry_after'):
515
+ retry_after = str(e.retry_after)
516
+
517
+ if selected_model_id == "x-ai/grok-4.1-fast" or selected_model_id.startswith("openrouter/"):
518
+ error_message = "⏱️ Rate limit exceeded for OpenRouter model"
519
+ if retry_after:
520
+ error_message += f". Please wait {retry_after} seconds before trying again."
521
+ else:
522
+ error_message += ". Free tier allows up to 20 requests per minute. Please wait a moment and try again."
523
+ else:
524
+ error_message = f"⏱️ Rate limit exceeded. Please wait before trying again."
525
+ if retry_after:
526
+ error_message += f" Retry after {retry_after} seconds."
527
+
528
+ # Check for other common API errors
529
+ elif hasattr(e, 'status_code'):
530
+ if e.status_code == 401:
531
+ error_message = "❌ Authentication failed. Please check your API key."
532
+ elif e.status_code == 403:
533
+ error_message = "❌ Access forbidden. Please check your API key permissions."
534
+ elif e.status_code == 500 or e.status_code == 502 or e.status_code == 503:
535
+ error_message = "❌ Service temporarily unavailable. Please try again later."
536
+
537
  error_data = json.dumps({
538
  "type": "error",
539
+ "message": error_message
540
  })
541
  yield f"data: {error_data}\n\n"
542
 
543
  except Exception as e:
544
+ # Fallback error handling
545
+ error_message = str(e)
546
+ # Check if it's a rate limit error in the exception message
547
+ if "429" in error_message or "rate limit" in error_message.lower() or "too many requests" in error_message.lower():
548
+ if selected_model_id == "x-ai/grok-4.1-fast" or selected_model_id.startswith("openrouter/"):
549
+ error_message = "⏱️ Rate limit exceeded for OpenRouter model. Free tier allows up to 20 requests per minute. Please wait a moment and try again."
550
+ else:
551
+ error_message = "⏱️ Rate limit exceeded. Please wait before trying again."
552
+
553
  error_data = json.dumps({
554
  "type": "error",
555
+ "message": f"Generation error: {error_message}"
556
  })
557
  yield f"data: {error_data}\n\n"
558
 
frontend/src/app/page.tsx CHANGED
@@ -59,6 +59,8 @@ export default function Home() {
59
 
60
  // Track if we've attempted to fetch username to avoid repeated failures
61
  const usernameFetchAttemptedRef = useRef(false);
 
 
62
 
63
  // Check auth on mount and handle OAuth callback
64
  useEffect(() => {
@@ -68,8 +70,9 @@ export default function Home() {
68
  // initializeOAuth already handles this, but we call checkAuth to sync state
69
  const urlParams = new URLSearchParams(window.location.search);
70
  if (urlParams.get('session')) {
71
- // OAuth callback - reset username fetch attempt and check auth after a brief delay
72
  usernameFetchAttemptedRef.current = false;
 
73
  setTimeout(() => checkAuth(), 200);
74
  }
75
  }, []); // Only run once on mount
@@ -79,8 +82,11 @@ export default function Home() {
79
  useEffect(() => {
80
  const handleStorageChange = (e: StorageEvent) => {
81
  if (e.key === 'hf_oauth_token' || e.key === 'hf_user_info') {
82
- // Reset username fetch attempt when storage changes
83
- usernameFetchAttemptedRef.current = false;
 
 
 
84
  checkAuth();
85
  }
86
  };
@@ -90,10 +96,16 @@ export default function Home() {
90
  }, []);
91
 
92
  // Listen for window focus (user returns to tab after OAuth redirect)
 
93
  useEffect(() => {
94
  const handleFocus = () => {
95
- // Reset username fetch attempt on focus (user might have logged in elsewhere)
96
- usernameFetchAttemptedRef.current = false;
 
 
 
 
 
97
  checkAuth();
98
  };
99
 
@@ -111,18 +123,18 @@ export default function Home() {
111
  if (token) {
112
  apiClient.setToken(token);
113
 
114
- // Get username from auth status (only if we don't have it yet and haven't failed)
115
- // This is a one-time fetch per session, not polling
116
- if (!username && !usernameFetchAttemptedRef.current) {
117
  usernameFetchAttemptedRef.current = true;
118
  try {
119
  const authStatus = await apiClient.getAuthStatus();
120
  if (authStatus.username) {
121
  setUsername(authStatus.username);
 
122
  }
123
  } catch (error: any) {
124
- // Silently handle connection errors - don't spam console
125
- // Connection errors mean backend isn't available, which is OK for client-side auth
126
  const isConnectionError =
127
  error.code === 'ECONNABORTED' ||
128
  error.code === 'ECONNRESET' ||
@@ -133,12 +145,16 @@ export default function Home() {
133
  error.response?.status === 503 ||
134
  error.response?.status === 502;
135
 
136
- if (!isConnectionError) {
137
- // Only log non-connection errors
 
 
 
 
 
138
  console.error('Failed to get username:', error);
 
139
  }
140
- // Reset attempt flag so we can try again later (e.g., when backend comes up)
141
- usernameFetchAttemptedRef.current = false;
142
  }
143
  }
144
  } else {
@@ -148,14 +164,16 @@ export default function Home() {
148
  setUsername(null);
149
  }
150
  usernameFetchAttemptedRef.current = false;
 
151
  }
152
  } else {
153
- // Not authenticated - clear username and reset fetch attempt
154
  apiClient.setToken(null);
155
  if (username) {
156
  setUsername(null);
157
  }
158
  usernameFetchAttemptedRef.current = false;
 
159
  }
160
  };
161
 
 
59
 
60
  // Track if we've attempted to fetch username to avoid repeated failures
61
  const usernameFetchAttemptedRef = useRef(false);
62
+ // Track if backend appears to be unavailable (to avoid repeated failed requests)
63
+ const backendUnavailableRef = useRef(false);
64
 
65
  // Check auth on mount and handle OAuth callback
66
  useEffect(() => {
 
70
  // initializeOAuth already handles this, but we call checkAuth to sync state
71
  const urlParams = new URLSearchParams(window.location.search);
72
  if (urlParams.get('session')) {
73
+ // OAuth callback - reset both flags and check auth after a brief delay
74
  usernameFetchAttemptedRef.current = false;
75
+ backendUnavailableRef.current = false; // Reset backend status on OAuth callback
76
  setTimeout(() => checkAuth(), 200);
77
  }
78
  }, []); // Only run once on mount
 
82
  useEffect(() => {
83
  const handleStorageChange = (e: StorageEvent) => {
84
  if (e.key === 'hf_oauth_token' || e.key === 'hf_user_info') {
85
+ // Only reset username fetch if we have a token (might be logging in)
86
+ if (e.newValue) {
87
+ usernameFetchAttemptedRef.current = false;
88
+ backendUnavailableRef.current = false; // Reset backend status on login
89
+ }
90
  checkAuth();
91
  }
92
  };
 
96
  }, []);
97
 
98
  // Listen for window focus (user returns to tab after OAuth redirect)
99
+ // Only check if backend was available before or if we're authenticated with token
100
  useEffect(() => {
101
  const handleFocus = () => {
102
+ // Only reset and check if we're authenticated (might have logged in elsewhere)
103
+ // Don't reset if backend is known to be unavailable and we're not authenticated
104
+ const authenticated = checkIsAuthenticated();
105
+ if (authenticated) {
106
+ usernameFetchAttemptedRef.current = false;
107
+ backendUnavailableRef.current = false; // Reset backend status - might be back up
108
+ }
109
  checkAuth();
110
  };
111
 
 
123
  if (token) {
124
  apiClient.setToken(token);
125
 
126
+ // Get username from auth status (only if we don't have it yet and backend is available)
127
+ // Skip if backend is known to be unavailable to avoid repeated failed requests
128
+ if (!username && !usernameFetchAttemptedRef.current && !backendUnavailableRef.current) {
129
  usernameFetchAttemptedRef.current = true;
130
  try {
131
  const authStatus = await apiClient.getAuthStatus();
132
  if (authStatus.username) {
133
  setUsername(authStatus.username);
134
+ backendUnavailableRef.current = false; // Backend is working
135
  }
136
  } catch (error: any) {
137
+ // Check if this is a connection error
 
138
  const isConnectionError =
139
  error.code === 'ECONNABORTED' ||
140
  error.code === 'ECONNRESET' ||
 
145
  error.response?.status === 503 ||
146
  error.response?.status === 502;
147
 
148
+ if (isConnectionError) {
149
+ // Mark backend as unavailable to avoid repeated requests
150
+ backendUnavailableRef.current = true;
151
+ // Don't reset attempt flag - keep it true so we don't retry until explicitly reset
152
+ // This prevents repeated failed requests when backend is down
153
+ } else {
154
+ // Non-connection error - log it and reset attempt flag
155
  console.error('Failed to get username:', error);
156
+ usernameFetchAttemptedRef.current = false;
157
  }
 
 
158
  }
159
  }
160
  } else {
 
164
  setUsername(null);
165
  }
166
  usernameFetchAttemptedRef.current = false;
167
+ backendUnavailableRef.current = false;
168
  }
169
  } else {
170
+ // Not authenticated - clear username and reset flags
171
  apiClient.setToken(null);
172
  if (username) {
173
  setUsername(null);
174
  }
175
  usernameFetchAttemptedRef.current = false;
176
+ // Keep backendUnavailableRef as is - it's useful information even when not authenticated
177
  }
178
  };
179
 
frontend/src/lib/api.ts CHANGED
@@ -177,6 +177,12 @@ class ApiClient {
177
  signal: abortController.signal,
178
  })
179
  .then(async (response) => {
 
 
 
 
 
 
180
  if (!response.ok) {
181
  throw new Error(`HTTP error! status: ${response.status}`);
182
  }
 
177
  signal: abortController.signal,
178
  })
179
  .then(async (response) => {
180
+ // Handle rate limit errors before parsing response
181
+ if (response.status === 429) {
182
+ onError('⏱️ Rate limit exceeded. Free tier allows up to 20 requests per minute. Please wait a moment and try again.');
183
+ return;
184
+ }
185
+
186
  if (!response.ok) {
187
  throw new Error(`HTTP error! status: ${response.status}`);
188
  }