akhaliq HF Staff commited on
Commit
e6104d9
·
1 Parent(s): aa0bba7
Files changed (2) hide show
  1. frontend/src/app/page.tsx +42 -12
  2. frontend/src/lib/api.ts +46 -4
frontend/src/app/page.tsx CHANGED
@@ -57,6 +57,9 @@ export default function Home() {
57
  }
58
  }, [messages]);
59
 
 
 
 
60
  // Check auth on mount and handle OAuth callback
61
  useEffect(() => {
62
  checkAuth();
@@ -65,15 +68,19 @@ export default function Home() {
65
  // initializeOAuth already handles this, but we call checkAuth to sync state
66
  const urlParams = new URLSearchParams(window.location.search);
67
  if (urlParams.get('session')) {
68
- // OAuth callback - check auth after a brief delay to let initializeOAuth complete
69
- setTimeout(() => checkAuth(), 100);
 
70
  }
71
  }, []); // Only run once on mount
72
 
73
  // Listen for storage changes (e.g., logout from another tab)
 
74
  useEffect(() => {
75
  const handleStorageChange = (e: StorageEvent) => {
76
  if (e.key === 'hf_oauth_token' || e.key === 'hf_user_info') {
 
 
77
  checkAuth();
78
  }
79
  };
@@ -85,6 +92,8 @@ export default function Home() {
85
  // Listen for window focus (user returns to tab after OAuth redirect)
86
  useEffect(() => {
87
  const handleFocus = () => {
 
 
88
  checkAuth();
89
  };
90
 
@@ -96,15 +105,16 @@ export default function Home() {
96
  const authenticated = checkIsAuthenticated();
97
  setIsAuthenticated(authenticated);
98
 
99
- // Make sure API client has the token
100
  if (authenticated) {
101
  const token = getStoredToken();
102
  if (token) {
103
  apiClient.setToken(token);
104
 
105
- // Get username from auth status (only if we don't have it yet)
106
- // This is a one-time fetch, not polling
107
- if (!username) {
 
108
  try {
109
  const authStatus = await apiClient.getAuthStatus();
110
  if (authStatus.username) {
@@ -112,21 +122,40 @@ export default function Home() {
112
  }
113
  } catch (error: any) {
114
  // Silently handle connection errors - don't spam console
115
- // Only log non-connection errors
116
- if (error.code !== 'ECONNABORTED' &&
117
- error.code !== 'ECONNRESET' &&
118
- !error.message?.includes('socket hang up') &&
119
- !error.message?.includes('timeout')) {
 
 
 
 
 
 
 
 
120
  console.error('Failed to get username:', error);
121
  }
 
 
122
  }
123
  }
 
 
 
 
 
 
 
124
  }
125
  } else {
126
- // Not authenticated - clear username
 
127
  if (username) {
128
  setUsername(null);
129
  }
 
130
  }
131
  };
132
 
@@ -730,3 +759,4 @@ export default function Home() {
730
  );
731
  }
732
 
 
 
57
  }
58
  }, [messages]);
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(() => {
65
  checkAuth();
 
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
76
 
77
  // Listen for storage changes (e.g., logout from another tab)
78
+ // Note: storage events only fire in OTHER tabs, not the current one
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
  };
 
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
 
 
105
  const authenticated = checkIsAuthenticated();
106
  setIsAuthenticated(authenticated);
107
 
108
+ // Make sure API client has the token or clears it
109
  if (authenticated) {
110
  const token = getStoredToken();
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) {
 
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' ||
129
+ error.code === 'ECONNREFUSED' ||
130
+ error.message?.includes('socket hang up') ||
131
+ error.message?.includes('timeout') ||
132
+ error.message?.includes('Network Error') ||
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 {
145
+ // Token missing but authenticated flag is true - clear state
146
+ setIsAuthenticated(false);
147
+ if (username) {
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
 
 
759
  );
760
  }
761
 
762
+
frontend/src/lib/api.ts CHANGED
@@ -77,13 +77,55 @@ class ApiClient {
77
  }
78
 
79
  async getModels(): Promise<Model[]> {
80
- const response = await this.client.get<Model[]>('/api/models');
81
- return response.data;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
82
  }
83
 
84
  async getLanguages(): Promise<{ languages: Language[] }> {
85
- const response = await this.client.get<{ languages: Language[] }>('/api/languages');
86
- return response.data;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
87
  }
88
 
89
  async getAuthStatus(): Promise<AuthStatus> {
 
77
  }
78
 
79
  async getModels(): Promise<Model[]> {
80
+ try {
81
+ const response = await this.client.get<Model[]>('/api/models');
82
+ return response.data;
83
+ } catch (error: any) {
84
+ // Handle connection errors gracefully
85
+ const isConnectionError =
86
+ error.code === 'ECONNABORTED' ||
87
+ error.code === 'ECONNRESET' ||
88
+ error.code === 'ECONNREFUSED' ||
89
+ error.message?.includes('socket hang up') ||
90
+ error.message?.includes('timeout') ||
91
+ error.message?.includes('Network Error') ||
92
+ error.response?.status === 503 ||
93
+ error.response?.status === 502;
94
+
95
+ if (isConnectionError) {
96
+ // Backend is not available - return empty array instead of throwing
97
+ console.warn('Backend not available, cannot load models');
98
+ return [];
99
+ }
100
+ // Re-throw other errors
101
+ throw error;
102
+ }
103
  }
104
 
105
  async getLanguages(): Promise<{ languages: Language[] }> {
106
+ try {
107
+ const response = await this.client.get<{ languages: Language[] }>('/api/languages');
108
+ return response.data;
109
+ } catch (error: any) {
110
+ // Handle connection errors gracefully
111
+ const isConnectionError =
112
+ error.code === 'ECONNABORTED' ||
113
+ error.code === 'ECONNRESET' ||
114
+ error.code === 'ECONNREFUSED' ||
115
+ error.message?.includes('socket hang up') ||
116
+ error.message?.includes('timeout') ||
117
+ error.message?.includes('Network Error') ||
118
+ error.response?.status === 503 ||
119
+ error.response?.status === 502;
120
+
121
+ if (isConnectionError) {
122
+ // Backend is not available - return default languages instead of throwing
123
+ console.warn('Backend not available, using default languages');
124
+ return { languages: ['html', 'gradio', 'transformers.js', 'streamlit', 'comfyui', 'react'] };
125
+ }
126
+ // Re-throw other errors
127
+ throw error;
128
+ }
129
  }
130
 
131
  async getAuthStatus(): Promise<AuthStatus> {