wu981526092 commited on
Commit
041fce6
·
1 Parent(s): ba6eacd
backend/app.py CHANGED
@@ -177,11 +177,11 @@ async def shutdown_event():
177
  # scheduler_service.stop() # This line is now commented out
178
 
179
 
180
- # Root redirect to React app (no authentication required - frontend handles login modal)
181
  @app.get("/")
182
- async def root(request: Request):
183
- # Allow all users to access the main interface
184
- # Authentication will be handled by frontend login modal
185
  return RedirectResponse(url="/agentgraph")
186
 
187
 
 
177
  # scheduler_service.stop() # This line is now commented out
178
 
179
 
180
+ # Root redirect to React app (requires authentication)
181
  @app.get("/")
182
+ async def root(request: Request, auth_check = Depends(require_auth_in_hf_spaces)):
183
+ # This endpoint is protected by dependency injection
184
+ # If user reaches here, they are authenticated (or in local dev)
185
  return RedirectResponse(url="/agentgraph")
186
 
187
 
backend/dependencies.py CHANGED
@@ -115,7 +115,7 @@ def get_current_user_optional(request: Request) -> Optional[Dict[str, Any]]:
115
  try:
116
  user = request.session.get("user")
117
  if user:
118
- logger.info(f"🔓 Found authenticated user in session: {user.get('username', 'unknown')}")
119
  return user
120
  else:
121
  # Add detailed debugging for session contents
@@ -134,20 +134,6 @@ def get_current_user_optional(request: Request) -> Optional[Dict[str, Any]]:
134
  session_data = dict(request.session) if hasattr(request.session, 'keys') else {}
135
  logger.warning(f"🔍 HF Session data: {session_data}")
136
 
137
- # FALLBACK: Check for localStorage backup data via special header
138
- # Frontend can send this as a custom header when localStorage is available
139
- localStorage_user = request.headers.get("X-HF-User-Backup")
140
- if localStorage_user:
141
- try:
142
- import json
143
- backup_user = json.loads(localStorage_user)
144
- logger.info(f"🔄 Using localStorage backup user: {backup_user.get('username', 'unknown')}")
145
- # Store back in session for next requests
146
- request.session["user"] = backup_user
147
- return backup_user
148
- except (json.JSONDecodeError, ValueError) as e:
149
- logger.warning(f"Could not parse localStorage backup user: {e}")
150
-
151
  # Check request headers that might affect session
152
  relevant_headers = {
153
  'host': request.headers.get('host'),
 
115
  try:
116
  user = request.session.get("user")
117
  if user:
118
+ logger.info(f"🔓 Found authenticated user: {user.get('username', 'unknown')}")
119
  return user
120
  else:
121
  # Add detailed debugging for session contents
 
134
  session_data = dict(request.session) if hasattr(request.session, 'keys') else {}
135
  logger.warning(f"🔍 HF Session data: {session_data}")
136
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
137
  # Check request headers that might affect session
138
  relevant_headers = {
139
  'host': request.headers.get('host'),
backend/middleware/usage_tracker.py CHANGED
@@ -45,13 +45,8 @@ class UsageTrackingMiddleware(BaseHTTPMiddleware):
45
  """Track API usage and log user activity."""
46
  start_time = time.time()
47
 
48
- # Get user info from request state (set by auth middleware) or session
49
  user = getattr(request.state, "user", None)
50
- if not user:
51
- try:
52
- user = request.session.get("user")
53
- except (AttributeError, AssertionError):
54
- user = None
55
  user_id = user.get("username", "anonymous") if user else "anonymous"
56
  user_auth_method = user.get("auth_method", "none") if user else "none"
57
 
 
45
  """Track API usage and log user activity."""
46
  start_time = time.time()
47
 
48
+ # Get user info from request state (set by auth middleware)
49
  user = getattr(request.state, "user", None)
 
 
 
 
 
50
  user_id = user.get("username", "anonymous") if user else "anonymous"
51
  user_auth_method = user.get("auth_method", "none") if user else "none"
52
 
backend/routers/auth.py CHANGED
@@ -7,7 +7,6 @@ These routes are only active when running in HF Spaces environment.
7
  import os
8
  import logging
9
  import secrets
10
- import json
11
  from typing import Optional
12
  from pathlib import Path
13
  from fastapi import APIRouter, Request, Response, HTTPException
@@ -30,14 +29,10 @@ async def auth_status(request: Request):
30
  """Get authentication status and configuration."""
31
  config = get_oauth_config()
32
  user = getattr(request.state, "user", None)
33
- logger.info(f"🔍 Auth status check - user from request.state: {user}")
34
-
35
  if not user:
36
  try:
37
  user = request.session.get("user")
38
- logger.info(f"🔍 Auth status check - user from session: {user}")
39
- except (AttributeError, AssertionError) as e:
40
- logger.warning(f"🔍 Auth status check - session access failed: {e}")
41
  user = None
42
 
43
  return {
@@ -47,12 +42,8 @@ async def auth_status(request: Request):
47
  "login_required": True, # Mandatory for OpenAI API protection
48
  "user_authenticated": bool(user),
49
  "user_info": {
50
- "id": user.get("id") if user else None,
51
- "username": user.get("username") if user else None,
52
- "name": user.get("name") if user else None,
53
- "email": user.get("email") if user else None,
54
- "avatar_url": user.get("avatar_url") if user else None,
55
  "auth_method": user.get("auth_method") if user else None,
 
56
  } if user else None,
57
  "hf_sign_detected": bool(request.query_params.get("__sign")) if is_huggingface_space() else False,
58
  }
@@ -254,97 +245,8 @@ async def oauth_callback(request: Request, code: str, state: str):
254
 
255
  logger.info(f"User logged in: {user_info.get('name')} ({user_info.get('login')})")
256
 
257
- # For HF Spaces, show a success page that notifies parent window and closes
258
- if is_huggingface_space():
259
- # Get user data to pass to parent window
260
- user_data = request.session.get("user", {})
261
- user_json = json.dumps(user_data)
262
-
263
- success_html = f"""
264
- <!DOCTYPE html>
265
- <html>
266
- <head>
267
- <title>Login Successful</title>
268
- <style>
269
- body {{
270
- font-family: Arial, sans-serif;
271
- text-align: center;
272
- padding: 50px;
273
- background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
274
- color: white;
275
- margin: 0;
276
- min-height: 100vh;
277
- display: flex;
278
- align-items: center;
279
- justify-content: center;
280
- flex-direction: column;
281
- }}
282
- .success-card {{
283
- background: rgba(255,255,255,0.1);
284
- padding: 40px;
285
- border-radius: 20px;
286
- backdrop-filter: blur(10px);
287
- border: 1px solid rgba(255,255,255,0.2);
288
- }}
289
- .checkmark {{
290
- font-size: 60px;
291
- margin-bottom: 20px;
292
- color: #4CAF50;
293
- }}
294
- </style>
295
- </head>
296
- <body>
297
- <div class="success-card">
298
- <div class="checkmark">✅</div>
299
- <h1>Login Successful!</h1>
300
- <p>Welcome, {user_data.get('name', 'User')}!</p>
301
- <p>You have successfully logged in with your Hugging Face account.</p>
302
- <p>This tab will close automatically...</p>
303
- </div>
304
-
305
- <script>
306
- // Store user data in localStorage as backup (for iframe issues)
307
- const userData = {user_json};
308
- try {{
309
- localStorage.setItem('hf_user', JSON.stringify(userData));
310
- localStorage.setItem('hf_login_timestamp', Date.now().toString());
311
- console.log('✅ Stored user data in localStorage as backup');
312
- }} catch (e) {{
313
- console.log('Could not store in localStorage:', e);
314
- }}
315
-
316
- // Try to send message to parent window with user data
317
- try {{
318
- if (window.opener) {{
319
- window.opener.postMessage({{
320
- type: 'HF_LOGIN_SUCCESS',
321
- userData: userData,
322
- timestamp: Date.now()
323
- }}, window.location.origin);
324
- console.log('✅ Sent login success message to parent window');
325
- }}
326
- }} catch (e) {{
327
- console.log('Could not send message to parent:', e);
328
- }}
329
-
330
- // Close this tab after a short delay
331
- setTimeout(() => {{
332
- try {{
333
- window.close();
334
- }} catch (e) {{
335
- console.log('Could not close window:', e);
336
- // Redirect to main app if we can't close
337
- window.location.href = '/';
338
- }}
339
- }}, 2000);
340
- </script>
341
- </body>
342
- </html>
343
- """
344
- return HTMLResponse(content=success_html)
345
- else:
346
- # For local development, redirect normally
347
- return RedirectResponse(url="/", status_code=302)
348
 
349
 
350
  @router.get("/logout")
 
7
  import os
8
  import logging
9
  import secrets
 
10
  from typing import Optional
11
  from pathlib import Path
12
  from fastapi import APIRouter, Request, Response, HTTPException
 
29
  """Get authentication status and configuration."""
30
  config = get_oauth_config()
31
  user = getattr(request.state, "user", None)
 
 
32
  if not user:
33
  try:
34
  user = request.session.get("user")
35
+ except (AttributeError, AssertionError):
 
 
36
  user = None
37
 
38
  return {
 
42
  "login_required": True, # Mandatory for OpenAI API protection
43
  "user_authenticated": bool(user),
44
  "user_info": {
 
 
 
 
 
45
  "auth_method": user.get("auth_method") if user else None,
46
+ "username": user.get("username") if user else None,
47
  } if user else None,
48
  "hf_sign_detected": bool(request.query_params.get("__sign")) if is_huggingface_space() else False,
49
  }
 
245
 
246
  logger.info(f"User logged in: {user_info.get('name')} ({user_info.get('login')})")
247
 
248
+ # Redirect to main application
249
+ return RedirectResponse(url="/", status_code=302)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
250
 
251
 
252
  @router.get("/logout")
backend/routers/knowledge_graphs.py CHANGED
@@ -23,7 +23,7 @@ import math
23
  # Add the project root to the Python path for proper imports
24
  sys.path.append(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))))
25
 
26
- from backend.dependencies import get_db_session, get_current_user_optional
27
  from backend.services import KnowledgeGraphService
28
  from backend.models import KnowledgeGraphResponse, PlatformStatsResponse
29
  from backend.database import get_db
 
23
  # Add the project root to the Python path for proper imports
24
  sys.path.append(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))))
25
 
26
+ from backend.dependencies import get_db_session
27
  from backend.services import KnowledgeGraphService
28
  from backend.models import KnowledgeGraphResponse, PlatformStatsResponse
29
  from backend.database import get_db
frontend/src/App.tsx CHANGED
@@ -5,29 +5,14 @@ import { NotificationProvider } from "./context/NotificationContext";
5
  import { ModalProvider, useModal } from "./context/ModalContext";
6
  import { NavigationProvider } from "./context/NavigationContext";
7
  import { KGDisplayModeProvider } from "./context/KGDisplayModeContext";
8
- import { AuthProvider, useAuth } from "./context/AuthContext";
9
  import { MainLayout } from "./components/layout/MainLayout";
10
  import { ModalSystem } from "./components/shared/ModalSystem";
11
- import { LoginModal } from "./components/auth/LoginModal";
12
  import { Toaster } from "./components/ui/toaster";
13
  import { ErrorBoundary } from "./components/shared/ErrorBoundary";
14
  import "./styles/globals.css";
15
 
16
  function AppContent() {
17
  const { modalState, closeModal } = useModal();
18
- const { showLoginModal, setShowLoginModal, isLoading } = useAuth();
19
-
20
- // Show loading screen while checking auth status
21
- if (isLoading) {
22
- return (
23
- <div className="h-screen bg-background text-foreground flex items-center justify-center">
24
- <div className="text-center">
25
- <div className="animate-spin rounded-full h-8 w-8 border-b-2 border-primary mx-auto mb-4"></div>
26
- <p className="text-muted-foreground">Loading AgentGraph...</p>
27
- </div>
28
- </div>
29
- );
30
- }
31
 
32
  return (
33
  <div className="h-screen bg-background text-foreground flex flex-col">
@@ -36,10 +21,6 @@ function AppContent() {
36
  </ErrorBoundary>
37
  <Toaster />
38
  <ModalSystem modalState={modalState} onClose={closeModal} />
39
- <LoginModal
40
- isOpen={showLoginModal}
41
- onClose={() => setShowLoginModal(false)}
42
- />
43
  </div>
44
  );
45
  }
@@ -48,19 +29,17 @@ function App() {
48
  return (
49
  <ErrorBoundary>
50
  <ThemeProvider>
51
- <AuthProvider>
52
- <NotificationProvider>
53
- <NavigationProvider>
54
- <ModalProvider>
55
- <KGDisplayModeProvider>
56
- <AgentGraphProvider>
57
- <AppContent />
58
- </AgentGraphProvider>
59
- </KGDisplayModeProvider>
60
- </ModalProvider>
61
- </NavigationProvider>
62
- </NotificationProvider>
63
- </AuthProvider>
64
  </ThemeProvider>
65
  </ErrorBoundary>
66
  );
 
5
  import { ModalProvider, useModal } from "./context/ModalContext";
6
  import { NavigationProvider } from "./context/NavigationContext";
7
  import { KGDisplayModeProvider } from "./context/KGDisplayModeContext";
 
8
  import { MainLayout } from "./components/layout/MainLayout";
9
  import { ModalSystem } from "./components/shared/ModalSystem";
 
10
  import { Toaster } from "./components/ui/toaster";
11
  import { ErrorBoundary } from "./components/shared/ErrorBoundary";
12
  import "./styles/globals.css";
13
 
14
  function AppContent() {
15
  const { modalState, closeModal } = useModal();
 
 
 
 
 
 
 
 
 
 
 
 
 
16
 
17
  return (
18
  <div className="h-screen bg-background text-foreground flex flex-col">
 
21
  </ErrorBoundary>
22
  <Toaster />
23
  <ModalSystem modalState={modalState} onClose={closeModal} />
 
 
 
 
24
  </div>
25
  );
26
  }
 
29
  return (
30
  <ErrorBoundary>
31
  <ThemeProvider>
32
+ <NotificationProvider>
33
+ <NavigationProvider>
34
+ <ModalProvider>
35
+ <KGDisplayModeProvider>
36
+ <AgentGraphProvider>
37
+ <AppContent />
38
+ </AgentGraphProvider>
39
+ </KGDisplayModeProvider>
40
+ </ModalProvider>
41
+ </NavigationProvider>
42
+ </NotificationProvider>
 
 
43
  </ThemeProvider>
44
  </ErrorBoundary>
45
  );
frontend/src/components/auth/LoginModal.tsx DELETED
@@ -1,141 +0,0 @@
1
- import React from "react";
2
- import { X, FileText, ExternalLink } from "lucide-react";
3
- import { useAuth } from "../../context/AuthContext";
4
-
5
- interface LoginModalProps {
6
- isOpen: boolean;
7
- onClose: () => void;
8
- }
9
-
10
- export function LoginModal({ isOpen, onClose }: LoginModalProps) {
11
- const { login, isHFSpaces } = useAuth();
12
-
13
- if (!isOpen) return null;
14
-
15
- const handleLogin = () => {
16
- login();
17
- };
18
-
19
- const handleOverlayClick = (e: React.MouseEvent) => {
20
- if (e.target === e.currentTarget) {
21
- onClose();
22
- }
23
- };
24
-
25
- return (
26
- <div
27
- className="fixed inset-0 bg-black/50 backdrop-blur-sm z-50 flex items-center justify-center p-4"
28
- onClick={handleOverlayClick}
29
- >
30
- <div className="bg-card border border-border rounded-2xl shadow-2xl max-w-4xl w-full max-h-[90vh] overflow-hidden">
31
- {/* Header */}
32
- <div className="flex justify-between items-center p-6 border-b border-border">
33
- <h2 className="text-2xl font-bold text-foreground">
34
- Welcome to AgentGraph
35
- </h2>
36
- <button
37
- onClick={onClose}
38
- className="p-2 hover:bg-muted rounded-lg transition-colors"
39
- >
40
- <X className="w-5 h-5" />
41
- </button>
42
- </div>
43
-
44
- <div className="grid lg:grid-cols-2 gap-8 p-6">
45
- {/* Content Section */}
46
- <div className="space-y-6">
47
- {/* Title */}
48
- <div className="space-y-4">
49
- <h1 className="text-3xl lg:text-4xl font-bold bg-gradient-to-r from-foreground to-primary bg-clip-text text-transparent">
50
- AgentGraph
51
- </h1>
52
- <p className="text-lg text-muted-foreground">
53
- Trace-to-Graph Platform for Interactive Analysis and Robustness
54
- Testing in Agentic AI Systems
55
- </p>
56
- </div>
57
-
58
- {/* Description */}
59
- <p className="text-muted-foreground">
60
- Convert execution logs into interactive knowledge graphs with
61
- actionable insights for AI system analysis and robustness testing.
62
- </p>
63
-
64
- {/* CTA Section */}
65
- <div className="space-y-4">
66
- {isHFSpaces ? (
67
- <a
68
- href="/auth/login"
69
- target="_blank"
70
- rel="noopener noreferrer"
71
- className="w-full bg-primary hover:bg-primary/90 text-primary-foreground font-semibold py-3 px-6 rounded-lg transition-colors flex items-center justify-center gap-2"
72
- >
73
- Login with Hugging Face
74
- <ExternalLink className="w-4 h-4" />
75
- </a>
76
- ) : (
77
- <button
78
- onClick={handleLogin}
79
- className="w-full bg-primary hover:bg-primary/90 text-primary-foreground font-semibold py-3 px-6 rounded-lg transition-colors flex items-center justify-center gap-2"
80
- >
81
- Continue to Platform
82
- </button>
83
- )}
84
-
85
- <div className="bg-muted/30 p-4 rounded-lg">
86
- <p className="text-sm text-muted-foreground">
87
- {isHFSpaces
88
- ? "Authentication required for responsible AI resource usage"
89
- : "Local development mode - no authentication required"}
90
- </p>
91
- </div>
92
- </div>
93
- </div>
94
-
95
- {/* Video and Paper Section */}
96
- <div className="space-y-6">
97
- {/* Demo Video */}
98
- <div className="bg-muted/20 rounded-xl overflow-hidden">
99
- <div className="relative aspect-video">
100
- <iframe
101
- src="https://www.youtube.com/embed/btrS9pfDYJY?si=dDX4tIs-oS2O2d2p"
102
- title="AgentGraph: Interactive Analysis Platform Demo"
103
- allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
104
- allowFullScreen
105
- className="w-full h-full rounded-xl"
106
- />
107
- </div>
108
- </div>
109
-
110
- {/* Research Paper */}
111
- <div className="bg-muted/20 p-4 rounded-xl">
112
- <div className="flex items-center space-x-3">
113
- <div className="flex-shrink-0">
114
- <FileText className="w-6 h-6 text-primary" />
115
- </div>
116
- <div className="flex-1">
117
- <h3 className="font-semibold text-foreground mb-1">
118
- Research Paper
119
- </h3>
120
- <p className="text-sm text-muted-foreground mb-2">
121
- AgentGraph: Trace-to-Graph Platform for Interactive Analysis
122
- and Robustness Testing in Agentic AI Systems
123
- </p>
124
- <a
125
- href="/static/papers/agentgraph_paper.pdf"
126
- target="_blank"
127
- rel="noopener noreferrer"
128
- className="inline-flex items-center text-primary hover:text-primary/80 transition-colors text-sm"
129
- >
130
- <FileText className="w-4 h-4 mr-1" />
131
- Download PDF
132
- </a>
133
- </div>
134
- </div>
135
- </div>
136
- </div>
137
- </div>
138
- </div>
139
- </div>
140
- );
141
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
frontend/src/context/AuthContext.tsx DELETED
@@ -1,229 +0,0 @@
1
- import React, {
2
- createContext,
3
- useContext,
4
- useState,
5
- useEffect,
6
- ReactNode,
7
- } from "react";
8
-
9
- export interface User {
10
- id: string;
11
- username: string;
12
- name: string;
13
- email?: string;
14
- avatar_url?: string;
15
- auth_method: string;
16
- }
17
-
18
- interface AuthState {
19
- user: User | null;
20
- isAuthenticated: boolean;
21
- isLoading: boolean;
22
- isHFSpaces: boolean;
23
- authRequired: boolean;
24
- }
25
-
26
- interface AuthContextType extends AuthState {
27
- login: () => void;
28
- logout: () => void;
29
- checkAuthStatus: () => Promise<void>;
30
- showLoginModal: boolean;
31
- setShowLoginModal: (show: boolean) => void;
32
- }
33
-
34
- const AuthContext = createContext<AuthContextType | undefined>(undefined);
35
-
36
- export function useAuth() {
37
- const context = useContext(AuthContext);
38
- if (context === undefined) {
39
- throw new Error("useAuth must be used within an AuthProvider");
40
- }
41
- return context;
42
- }
43
-
44
- interface AuthProviderProps {
45
- children: ReactNode;
46
- }
47
-
48
- export function AuthProvider({ children }: AuthProviderProps) {
49
- const [user, setUser] = useState<User | null>(null);
50
- const [isLoading, setIsLoading] = useState(true);
51
- const [isHFSpaces, setIsHFSpaces] = useState(false);
52
- const [authRequired, setAuthRequired] = useState(false);
53
- const [showLoginModal, setShowLoginModal] = useState(false);
54
-
55
- const isAuthenticated = user !== null;
56
-
57
- const checkAuthStatus = async () => {
58
- try {
59
- setIsLoading(true);
60
- console.log("🔍 Checking authentication status...");
61
-
62
- // Prepare headers with localStorage backup if available
63
- const headers: Record<string, string> = {};
64
- try {
65
- const localUser = localStorage.getItem('hf_user');
66
- if (localUser) {
67
- // Send localStorage backup as header for HF Spaces iframe issues
68
- headers['X-HF-User-Backup'] = localUser;
69
- console.log("📦 Sending localStorage backup with auth check");
70
- }
71
- } catch (e) {
72
- console.warn("Could not access localStorage:", e);
73
- }
74
-
75
- const response = await fetch("/auth/status", { headers });
76
- const data = await response.json();
77
-
78
- console.log("📊 Auth status response:", data);
79
-
80
- setIsHFSpaces(data.environment === "huggingface_spaces");
81
- setAuthRequired(
82
- data.login_required && data.environment === "huggingface_spaces"
83
- );
84
-
85
- if (data.user_authenticated && data.user_info) {
86
- const userData = {
87
- id: data.user_info.id || "unknown",
88
- username: data.user_info.username || "Unknown User",
89
- name:
90
- data.user_info.name || data.user_info.username || "Unknown User",
91
- email: data.user_info.email,
92
- avatar_url: data.user_info.avatar_url,
93
- auth_method: data.user_info.auth_method || "unknown",
94
- };
95
-
96
- setUser(userData);
97
- setShowLoginModal(false);
98
- console.log("✅ User authenticated successfully:", userData.username);
99
- } else {
100
- setUser(null);
101
- console.log(
102
- "❌ User not authenticated, environment:",
103
- data.environment,
104
- "auth required:",
105
- data.login_required
106
- );
107
-
108
- // Only show login modal in HF Spaces when auth is required
109
- if (data.environment === "huggingface_spaces" && data.login_required) {
110
- console.log("🔐 Showing login modal for HF Spaces");
111
- setShowLoginModal(true);
112
- } else {
113
- console.log("🏠 Local development - no login required");
114
- setShowLoginModal(false);
115
- }
116
- }
117
- } catch (error) {
118
- console.error("❌ Failed to check auth status:", error);
119
- setUser(null);
120
- // Assume local development if fetch fails
121
- setIsHFSpaces(false);
122
- setAuthRequired(false);
123
- setShowLoginModal(false);
124
- } finally {
125
- setIsLoading(false);
126
- }
127
- };
128
-
129
- const login = () => {
130
- if (isHFSpaces) {
131
- // For HF Spaces, redirect to OAuth login
132
- window.location.href = "/auth/login";
133
- } else {
134
- // For local development, just close the modal
135
- setShowLoginModal(false);
136
- setUser({
137
- id: "local_dev",
138
- username: "local_user",
139
- name: "Local Development User",
140
- auth_method: "local_dev",
141
- });
142
- }
143
- };
144
-
145
- const logout = async () => {
146
- try {
147
- if (isHFSpaces) {
148
- await fetch("/auth/logout");
149
- window.location.reload();
150
- } else {
151
- setUser(null);
152
- setShowLoginModal(false);
153
- }
154
- } catch (error) {
155
- console.error("Failed to logout:", error);
156
- }
157
- };
158
-
159
- useEffect(() => {
160
- // Initial auth check
161
- checkAuthStatus();
162
-
163
- // Listen for auth-required events from API calls
164
- const handleAuthRequired = () => {
165
- console.log("🔐 Auth required event received");
166
- if (authRequired && !isAuthenticated) {
167
- setShowLoginModal(true);
168
- }
169
- };
170
-
171
- // Listen for messages from login popup/tab
172
- const handleMessage = (event: MessageEvent) => {
173
- if (event.origin !== window.location.origin) {
174
- return; // Only accept messages from same origin
175
- }
176
-
177
- if (event.data.type === "HF_LOGIN_SUCCESS") {
178
- console.log("✅ Login success message received from popup");
179
-
180
- // If user data is provided, store it locally as backup
181
- if (event.data.userData) {
182
- try {
183
- localStorage.setItem('hf_user', JSON.stringify(event.data.userData));
184
- localStorage.setItem('hf_login_timestamp', Date.now().toString());
185
- console.log("📦 Stored user data in localStorage as backup");
186
- } catch (e) {
187
- console.warn("Could not store user data in localStorage:", e);
188
- }
189
- }
190
-
191
- setShowLoginModal(false);
192
- // Refresh auth status after successful login
193
- setTimeout(() => checkAuthStatus(), 1000);
194
- }
195
- };
196
-
197
- // Periodically check auth status while login modal is open (for new tab flow)
198
- const interval = showLoginModal
199
- ? setInterval(() => {
200
- console.log("🔄 Periodic auth check while login modal is open");
201
- checkAuthStatus();
202
- }, 2000)
203
- : null;
204
-
205
- window.addEventListener("auth-required", handleAuthRequired);
206
- window.addEventListener("message", handleMessage);
207
-
208
- return () => {
209
- window.removeEventListener("auth-required", handleAuthRequired);
210
- window.removeEventListener("message", handleMessage);
211
- if (interval) clearInterval(interval);
212
- };
213
- }, [showLoginModal]);
214
-
215
- const value: AuthContextType = {
216
- user,
217
- isAuthenticated,
218
- isLoading,
219
- isHFSpaces,
220
- authRequired,
221
- login,
222
- logout,
223
- checkAuthStatus,
224
- showLoginModal,
225
- setShowLoginModal,
226
- };
227
-
228
- return <AuthContext.Provider value={value}>{children}</AuthContext.Provider>;
229
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
frontend/src/lib/api.ts CHANGED
@@ -33,22 +33,6 @@ async function fetchApi<T>(
33
  });
34
 
35
  if (!response.ok) {
36
- // Handle 401 (Unauthorized) - trigger login modal
37
- if (response.status === 401) {
38
- // Check if running in browser environment
39
- if (typeof window !== "undefined") {
40
- // Dispatch a custom event to trigger login modal
41
- window.dispatchEvent(
42
- new CustomEvent("auth-required", {
43
- detail: {
44
- message: "Authentication required to access this feature",
45
- },
46
- })
47
- );
48
- }
49
- throw new ApiError(response.status, "Authentication required");
50
- }
51
-
52
  // Handle 429 (Too Many Requests) with exponential backoff
53
  if (response.status === 429 && retryCount < 3) {
54
  const backoffDelay = Math.pow(2, retryCount) * 1000; // 1s, 2s, 4s
 
33
  });
34
 
35
  if (!response.ok) {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
36
  // Handle 429 (Too Many Requests) with exponential backoff
37
  if (response.status === 429 && retryCount < 3) {
38
  const backoffDelay = Math.pow(2, retryCount) * 1000; // 1s, 2s, 4s