Download src/lib/api.ts from kimaan28/CytoSight: direct link, hf CLI and curl.
- Browser
- Download file 9.51 kB
-
https://huggingface.co/spaces/kimaan28/CytoSight/resolve/main/src/lib/api.ts
- Command line
-
hf download hf://spaces/kimaan28/CytoSight/src/lib/api.ts
-
curl -L -o api.ts https://huggingface.co/spaces/kimaan28/CytoSight/resolve/main/src/lib/api.ts
9.51 kB
| // ===== ENVIRONMENT-AWARE API URL DETECTION ===== | |
| function getBaseUrl(): string { | |
| // Priority 1: Check localStorage for user-set ngrok URL | |
| const storedBackendUrl = localStorage.getItem('backend_url'); | |
| if (storedBackendUrl) { | |
| console.log('✅ Using stored backend URL:', storedBackendUrl); | |
| return storedBackendUrl; | |
| } | |
| // Priority 2: Check environment variable | |
| if (typeof import.meta.env.VITE_BACKEND_URL !== 'undefined' && import.meta.env.VITE_BACKEND_URL) { | |
| const envUrl = import.meta.env.VITE_BACKEND_URL; | |
| // Add /api suffix if not present | |
| const finalUrl = envUrl.endsWith('/api') ? envUrl : `${envUrl.replace(/\/$/, '')}/api`; | |
| console.log('✅ Using VITE_BACKEND_URL:', finalUrl); | |
| return finalUrl; | |
| } | |
| // Priority 3: Check if running on local development | |
| const hostname = window.location.hostname; | |
| const isLocalhost = hostname === 'localhost' || hostname === '127.0.0.1'; | |
| const isLocalDomain = hostname.includes('local') || hostname.includes('10.7.'); | |
| if (isLocalhost || isLocalDomain) { | |
| console.log('✅ Running on local, using localhost:8000/api'); | |
| return 'http://localhost:8000/api'; | |
| } | |
| // Priority 4: Production fallback | |
| console.log('☁️ Running on production domain - using Hugging Face backend'); | |
| return 'https://kimaan28-cytosight.hf.space/api'; | |
| } | |
| function getCurrentBaseUrl(): string { | |
| return getBaseUrl(); | |
| } | |
| export function setBackendUrl(url: string) { | |
| localStorage.setItem('backend_url', url); | |
| console.log('✅ Backend URL updated to:', url); | |
| console.log('🔄 Refresh page or make API call for changes to take effect'); | |
| } | |
| type HttpMethod = "GET" | "POST" | "PUT" | "PATCH" | "DELETE"; | |
| export async function apiCall<T = unknown>( | |
| method: HttpMethod, | |
| endpoint: string, | |
| body?: unknown, | |
| token?: string | |
| ): Promise<T> { | |
| const authToken = token ?? getToken(); | |
| const headers: Record<string, string> = { | |
| "Content-Type": "application/json", | |
| }; | |
| if (authToken) { | |
| headers.Authorization = `Bearer ${authToken}`; | |
| } | |
| const baseUrl = getCurrentBaseUrl(); | |
| const url = `${baseUrl}${endpoint}`; | |
| try { | |
| console.log(`[API Request] ${method} ${url}`); | |
| const res = await fetch(url, { | |
| method, | |
| headers, | |
| credentials: 'include', | |
| body: body === undefined ? undefined : JSON.stringify(body), | |
| }); | |
| console.log(`[API Response] ${method} ${url} - Status ${res.status}`); | |
| if (!res.ok) { | |
| let errorMessage = `HTTP ${res.status}`; | |
| try { | |
| const error = await res.json(); | |
| errorMessage = error.detail || errorMessage; | |
| } catch { | |
| errorMessage = res.statusText || errorMessage; | |
| } | |
| if (res.status === 401) { | |
| errorMessage = "Unauthorized - Please login again"; | |
| } else if (res.status === 403) { | |
| errorMessage = "Forbidden - You don't have permission"; | |
| } else if (res.status === 404) { | |
| errorMessage = "Resource not found"; | |
| } else if (res.status === 500) { | |
| errorMessage = "Server error - Backend may not be running"; | |
| } | |
| console.error(`[API Error] ${errorMessage}`); | |
| throw new Error(errorMessage); | |
| } | |
| if (res.status === 204) { | |
| return undefined as T; | |
| } | |
| const data = await res.json(); | |
| console.log(`[API Success] ${method} ${url}`); | |
| return data; | |
| } catch (error) { | |
| if (error instanceof TypeError) { | |
| if (error.message.includes('Failed to fetch')) { | |
| const helpMsg = 'Failed to reach backend. Make sure:\n1. Backend is running\n2. ngrok tunnel is active\n3. ngrok URL is correct in localStorage'; | |
| console.error(`[CORS/Network Error] ${helpMsg}`); | |
| throw new Error(helpMsg); | |
| } | |
| } | |
| console.error(`[API Error] ${method} ${url}`, error); | |
| throw error; | |
| } | |
| } | |
| // ✅ FIXED: signup and login now use proper apiCall with credentials | |
| export async function signup({ | |
| fullName, | |
| email, | |
| password | |
| }: { | |
| fullName: string; | |
| email: string; | |
| password: string; | |
| }) { | |
| const cleanEmail = email.trim(); | |
| const cleanPassword = password.trim(); | |
| const baseUrl = getCurrentBaseUrl(); | |
| try { | |
| console.log('[SIGNUP] Attempting signup...'); | |
| const res = await fetch(`${baseUrl}/auth/signup`, { | |
| method: "POST", | |
| headers: { | |
| "Content-Type": "application/json", | |
| }, | |
| credentials: 'include', // ✅ Critical for CORS | |
| body: JSON.stringify({ | |
| full_name: fullName.trim(), | |
| email: cleanEmail, | |
| password: cleanPassword, | |
| }), | |
| }); | |
| if (!res.ok) { | |
| const error = await res.json().catch(() => ({})); | |
| throw new Error(error.detail || "Signup failed"); | |
| } | |
| const data = await res.json(); | |
| console.log('[SIGNUP] Success'); | |
| if (data.access_token) { | |
| localStorage.setItem('access_token', data.access_token); | |
| localStorage.setItem('refresh_token', data.refresh_token); | |
| localStorage.setItem('user', JSON.stringify(data.user)); | |
| } | |
| return data; | |
| } catch (err) { | |
| console.error('[SIGNUP] Error:', err); | |
| throw err; | |
| } | |
| } | |
| export async function login({ | |
| email, | |
| password | |
| }: { | |
| email: string; | |
| password: string; | |
| }) { | |
| const cleanEmail = email.trim(); | |
| const cleanPassword = password.trim(); | |
| const baseUrl = getCurrentBaseUrl(); | |
| try { | |
| console.log('[LOGIN] Attempting login...'); | |
| const res = await fetch(`${baseUrl}/auth/login`, { | |
| method: "POST", | |
| headers: { | |
| "Content-Type": "application/json", | |
| }, | |
| credentials: 'include', // ✅ Critical for CORS | |
| body: JSON.stringify({ | |
| email: cleanEmail, | |
| password: cleanPassword, | |
| }), | |
| }); | |
| if (!res.ok) { | |
| const error = await res.json().catch(() => ({})); | |
| throw new Error(error.detail || "Login failed"); | |
| } | |
| const data = await res.json(); | |
| console.log('[LOGIN] Success'); | |
| if (data.access_token) { | |
| localStorage.setItem('access_token', data.access_token); | |
| localStorage.setItem('refresh_token', data.refresh_token); | |
| localStorage.setItem('user', JSON.stringify(data.user)); | |
| } | |
| return data; | |
| } catch (err) { | |
| console.error('[LOGIN] Error:', err); | |
| throw err; | |
| } | |
| } | |
| export async function uploadImage({ | |
| file, | |
| token | |
| }: { | |
| file: File; | |
| token: string; | |
| }) { | |
| const formData = new FormData(); | |
| formData.append("file", file); | |
| const res = await fetch(`${getCurrentBaseUrl()}/upload/image`, { | |
| method: "POST", | |
| headers: { | |
| Authorization: `Bearer ${token}`, | |
| }, | |
| credentials: 'include', | |
| body: formData, | |
| }); | |
| if (!res.ok) { | |
| const error = await res.json().catch(() => ({})); | |
| throw new Error(error.detail || "Image upload failed"); | |
| } | |
| return res.json(); | |
| } | |
| export async function runDiagnosis({ | |
| imagePath, | |
| imageUrl, | |
| token | |
| }: { | |
| imagePath?: string; | |
| imageUrl?: string; | |
| token: string; | |
| }) { | |
| const res = await fetch(`${getCurrentBaseUrl()}/diagnosis/predict`, { | |
| method: "POST", | |
| headers: { | |
| "Content-Type": "application/json", | |
| Authorization: `Bearer ${token}`, | |
| }, | |
| credentials: 'include', | |
| body: JSON.stringify({ | |
| image_file_path: imagePath, | |
| image_url: imageUrl, | |
| }), | |
| }); | |
| if (!res.ok) { | |
| const error = await res.json().catch(() => ({})); | |
| throw new Error(error.detail || "Diagnosis failed"); | |
| } | |
| return res.json(); | |
| } | |
| export async function runSegmentation({ | |
| imagePath, | |
| imageUrl, | |
| token, | |
| }: { | |
| imagePath?: string; | |
| imageUrl?: string; | |
| token: string; | |
| }) { | |
| const res = await fetch(`${getCurrentBaseUrl()}/segmentation/predict`, { | |
| method: "POST", | |
| headers: { | |
| "Content-Type": "application/json", | |
| Authorization: `Bearer ${token}`, | |
| }, | |
| credentials: "include", | |
| body: JSON.stringify({ | |
| image_file_path: imagePath, | |
| image_url: imageUrl, | |
| }), | |
| }); | |
| if (!res.ok) { | |
| const error = await res.json().catch(() => ({})); | |
| throw new Error(error.detail || "Segmentation failed"); | |
| } | |
| return res.json(); | |
| } | |
| export async function fetchExplainability({ | |
| imageUrl, | |
| diagnosisData, | |
| token, | |
| }: { | |
| imageUrl: string; | |
| diagnosisData: any; | |
| token?: string; | |
| }) { | |
| return apiCall<{ | |
| attention_heatmap_base64: string; | |
| attention_bbox_base64: string; | |
| highest_attention_crop_base64: string; | |
| zone_reference_base64: string; | |
| gpt_statement: string; | |
| }>("POST", "/explain", { | |
| image_url: imageUrl, | |
| diagnosis_data: diagnosisData, | |
| }, token); | |
| } | |
| // Token helpers | |
| export function getToken(): string | null { | |
| return localStorage.getItem('access_token'); | |
| } | |
| export function getRefreshToken(): string | null { | |
| return localStorage.getItem('refresh_token'); | |
| } | |
| export function getCurrentUser() { | |
| const userStr = localStorage.getItem('user'); | |
| return userStr ? JSON.parse(userStr) : null; | |
| } | |
| export function isLoggedIn(): boolean { | |
| return !!localStorage.getItem('access_token'); | |
| } | |
| export function logout() { | |
| localStorage.removeItem('access_token'); | |
| localStorage.removeItem('refresh_token'); | |
| localStorage.removeItem('user'); | |
| console.log('[LOGOUT] User logged out'); | |
| } | |
| export async function clearAllHistory(token: string) { | |
| const res = await fetch(`${getCurrentBaseUrl()}/diagnosis/history`, { | |
| method: "DELETE", | |
| headers: { | |
| Authorization: `Bearer ${token}`, | |
| }, | |
| credentials: "include", | |
| }); | |
| if (!res.ok) { | |
| const error = await res.json().catch(() => ({})); | |
| throw new Error(error.detail || "Failed to clear history"); | |
| } | |
| return res.json(); | |
| } |