fix
Browse files- frontend/src/app/page.tsx +42 -12
- 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
|
| 69 |
-
|
|
|
|
| 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 |
-
//
|
| 116 |
-
|
| 117 |
-
|
| 118 |
-
|
| 119 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 |
-
|
| 81 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 82 |
}
|
| 83 |
|
| 84 |
async getLanguages(): Promise<{ languages: Language[] }> {
|
| 85 |
-
|
| 86 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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> {
|