wu981526092 commited on
Commit
aad2775
·
1 Parent(s): 3b65325

Implement popup login modal system

Browse files

✨ Major UX Improvement:
- Replace full-page login with overlay modal
- Users can now see the platform in background before login
- Created AuthContext for centralized auth state management
- Added LoginModal component with embedded video and paper
- Modified API to trigger login modal on 401 errors
- Removed forced auth redirect from root route

🎨 Design Features:
- Glass-morphism modal design consistent with platform
- Embedded demo video and research paper download
- Responsive layout with proper backdrop blur
- Smooth loading states and transitions

🔧 Technical Changes:
- New AuthContext with HF Spaces detection
- Custom auth-required events from API layer
- Conditional authentication based on environment
- Proper error handling for unauthorized requests

This provides a much better user experience where users can
explore the interface before deciding to authenticate.

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 (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
 
 
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
 
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
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, get_current_user_optional
27
  from backend.services import KnowledgeGraphService
28
  from backend.models import KnowledgeGraphResponse, PlatformStatsResponse
29
  from backend.database import get_db
backend/templates/login.html CHANGED
@@ -210,9 +210,17 @@
210
  <div class="glass-card p-6 rounded-2xl">
211
  <div class="flex items-center space-x-4">
212
  <div class="flex-shrink-0">
213
- <svg class="w-8 h-8 text-primary" fill="currentColor" viewBox="0 0 20 20">
214
- <path fill-rule="evenodd" d="M4 4a2 2 0 012-2h8a2 2 0 012 2v12a2 2 0 01-2 2H6a2 2 0 01-2-2V4zm2 0v12h8V4H6z" clip-rule="evenodd"/>
215
- <path d="M8 6h4v1H8V6zM8 8h4v1H8V8zM8 10h2v1H8v-1z"/>
 
 
 
 
 
 
 
 
216
  </svg>
217
  </div>
218
  <div class="flex-1">
@@ -220,15 +228,26 @@
220
  Research Paper
221
  </h3>
222
  <p class="text-sm text-muted-foreground mb-3">
223
- AgentGraph: Trace-to-Graph Platform for Interactive Analysis and Robustness Testing in Agentic AI Systems
 
224
  </p>
225
  <a
226
  href="/static/papers/agentgraph_paper.pdf"
227
  target="_blank"
228
  class="inline-flex items-center text-primary hover:text-primary/80 transition-colors"
229
  >
230
- <svg class="w-4 h-4 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
231
- <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 10v6m0 0l-3-3m3 3l3-3m2 8H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"/>
 
 
 
 
 
 
 
 
 
 
232
  </svg>
233
  Download PDF
234
  </a>
 
210
  <div class="glass-card p-6 rounded-2xl">
211
  <div class="flex items-center space-x-4">
212
  <div class="flex-shrink-0">
213
+ <svg
214
+ class="w-8 h-8 text-primary"
215
+ fill="currentColor"
216
+ viewBox="0 0 20 20"
217
+ >
218
+ <path
219
+ fill-rule="evenodd"
220
+ d="M4 4a2 2 0 012-2h8a2 2 0 012 2v12a2 2 0 01-2 2H6a2 2 0 01-2-2V4zm2 0v12h8V4H6z"
221
+ clip-rule="evenodd"
222
+ />
223
+ <path d="M8 6h4v1H8V6zM8 8h4v1H8V8zM8 10h2v1H8v-1z" />
224
  </svg>
225
  </div>
226
  <div class="flex-1">
 
228
  Research Paper
229
  </h3>
230
  <p class="text-sm text-muted-foreground mb-3">
231
+ AgentGraph: Trace-to-Graph Platform for Interactive Analysis
232
+ and Robustness Testing in Agentic AI Systems
233
  </p>
234
  <a
235
  href="/static/papers/agentgraph_paper.pdf"
236
  target="_blank"
237
  class="inline-flex items-center text-primary hover:text-primary/80 transition-colors"
238
  >
239
+ <svg
240
+ class="w-4 h-4 mr-2"
241
+ fill="none"
242
+ stroke="currentColor"
243
+ viewBox="0 0 24 24"
244
+ >
245
+ <path
246
+ stroke-linecap="round"
247
+ stroke-linejoin="round"
248
+ stroke-width="2"
249
+ d="M12 10v6m0 0l-3-3m3 3l3-3m2 8H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"
250
+ />
251
  </svg>
252
  Download PDF
253
  </a>
frontend/src/App.tsx CHANGED
@@ -5,14 +5,29 @@ 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 { 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,6 +36,10 @@ function AppContent() {
21
  </ErrorBoundary>
22
  <Toaster />
23
  <ModalSystem modalState={modalState} onClose={closeModal} />
 
 
 
 
24
  </div>
25
  );
26
  }
@@ -29,17 +48,19 @@ function App() {
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
  );
 
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
  </ErrorBoundary>
37
  <Toaster />
38
  <ModalSystem modalState={modalState} onClose={closeModal} />
39
+ <LoginModal
40
+ isOpen={showLoginModal}
41
+ onClose={() => setShowLoginModal(false)}
42
+ />
43
  </div>
44
  );
45
  }
 
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
  );
frontend/src/components/auth/LoginModal.tsx ADDED
@@ -0,0 +1,136 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+ <button
67
+ onClick={handleLogin}
68
+ 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"
69
+ >
70
+ {isHFSpaces ? (
71
+ <>
72
+ Login with Hugging Face
73
+ <ExternalLink className="w-4 h-4" />
74
+ </>
75
+ ) : (
76
+ "Continue to Platform"
77
+ )}
78
+ </button>
79
+
80
+ <div className="bg-muted/30 p-4 rounded-lg">
81
+ <p className="text-sm text-muted-foreground">
82
+ {isHFSpaces
83
+ ? "Authentication required for responsible AI resource usage"
84
+ : "Local development mode - no authentication required"}
85
+ </p>
86
+ </div>
87
+ </div>
88
+ </div>
89
+
90
+ {/* Video and Paper Section */}
91
+ <div className="space-y-6">
92
+ {/* Demo Video */}
93
+ <div className="bg-muted/20 rounded-xl overflow-hidden">
94
+ <div className="relative aspect-video">
95
+ <iframe
96
+ src="https://www.youtube.com/embed/btrS9pfDYJY?si=dDX4tIs-oS2O2d2p"
97
+ title="AgentGraph: Interactive Analysis Platform Demo"
98
+ allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
99
+ allowFullScreen
100
+ className="w-full h-full rounded-xl"
101
+ />
102
+ </div>
103
+ </div>
104
+
105
+ {/* Research Paper */}
106
+ <div className="bg-muted/20 p-4 rounded-xl">
107
+ <div className="flex items-center space-x-3">
108
+ <div className="flex-shrink-0">
109
+ <FileText className="w-6 h-6 text-primary" />
110
+ </div>
111
+ <div className="flex-1">
112
+ <h3 className="font-semibold text-foreground mb-1">
113
+ Research Paper
114
+ </h3>
115
+ <p className="text-sm text-muted-foreground mb-2">
116
+ AgentGraph: Trace-to-Graph Platform for Interactive Analysis
117
+ and Robustness Testing in Agentic AI Systems
118
+ </p>
119
+ <a
120
+ href="/static/papers/agentgraph_paper.pdf"
121
+ target="_blank"
122
+ rel="noopener noreferrer"
123
+ className="inline-flex items-center text-primary hover:text-primary/80 transition-colors text-sm"
124
+ >
125
+ <FileText className="w-4 h-4 mr-1" />
126
+ Download PDF
127
+ </a>
128
+ </div>
129
+ </div>
130
+ </div>
131
+ </div>
132
+ </div>
133
+ </div>
134
+ </div>
135
+ );
136
+ }
frontend/src/context/AuthContext.tsx ADDED
@@ -0,0 +1,158 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+ const response = await fetch("/auth/status");
61
+ const data = await response.json();
62
+
63
+ setIsHFSpaces(data.environment === "huggingface_spaces");
64
+ setAuthRequired(
65
+ data.login_required && data.environment === "huggingface_spaces"
66
+ );
67
+
68
+ if (data.user_authenticated && data.user_info) {
69
+ setUser({
70
+ id: data.user_info.user_id || "unknown",
71
+ username: data.user_info.username || "Unknown User",
72
+ name:
73
+ data.user_info.name || data.user_info.username || "Unknown User",
74
+ email: data.user_info.email,
75
+ avatar_url: data.user_info.avatar_url,
76
+ auth_method: data.user_info.auth_method || "unknown",
77
+ });
78
+ setShowLoginModal(false);
79
+ } else {
80
+ setUser(null);
81
+ // Show login modal if in HF Spaces and auth is required
82
+ if (data.environment === "huggingface_spaces" && data.login_required) {
83
+ setShowLoginModal(true);
84
+ }
85
+ }
86
+ } catch (error) {
87
+ console.error("Failed to check auth status:", error);
88
+ setUser(null);
89
+ // Assume local development if fetch fails
90
+ setIsHFSpaces(false);
91
+ setAuthRequired(false);
92
+ } finally {
93
+ setIsLoading(false);
94
+ }
95
+ };
96
+
97
+ const login = () => {
98
+ if (isHFSpaces) {
99
+ // For HF Spaces, redirect to OAuth login
100
+ window.location.href = "/auth/login";
101
+ } else {
102
+ // For local development, just close the modal
103
+ setShowLoginModal(false);
104
+ setUser({
105
+ id: "local_dev",
106
+ username: "local_user",
107
+ name: "Local Development User",
108
+ auth_method: "local_dev",
109
+ });
110
+ }
111
+ };
112
+
113
+ const logout = async () => {
114
+ try {
115
+ if (isHFSpaces) {
116
+ await fetch("/auth/logout");
117
+ window.location.reload();
118
+ } else {
119
+ setUser(null);
120
+ setShowLoginModal(false);
121
+ }
122
+ } catch (error) {
123
+ console.error("Failed to logout:", error);
124
+ }
125
+ };
126
+
127
+ useEffect(() => {
128
+ checkAuthStatus();
129
+
130
+ // Listen for auth-required events from API calls
131
+ const handleAuthRequired = () => {
132
+ if (authRequired && !isAuthenticated) {
133
+ setShowLoginModal(true);
134
+ }
135
+ };
136
+
137
+ window.addEventListener('auth-required', handleAuthRequired);
138
+
139
+ return () => {
140
+ window.removeEventListener('auth-required', handleAuthRequired);
141
+ };
142
+ }, [authRequired, isAuthenticated]);
143
+
144
+ const value: AuthContextType = {
145
+ user,
146
+ isAuthenticated,
147
+ isLoading,
148
+ isHFSpaces,
149
+ authRequired,
150
+ login,
151
+ logout,
152
+ checkAuthStatus,
153
+ showLoginModal,
154
+ setShowLoginModal,
155
+ };
156
+
157
+ return <AuthContext.Provider value={value}>{children}</AuthContext.Provider>;
158
+ }
frontend/src/lib/api.ts CHANGED
@@ -33,6 +33,18 @@ async function fetchApi<T>(
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
 
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(new CustomEvent('auth-required', {
42
+ detail: { message: 'Authentication required to access this feature' }
43
+ }));
44
+ }
45
+ throw new ApiError(response.status, 'Authentication required');
46
+ }
47
+
48
  // Handle 429 (Too Many Requests) with exponential backoff
49
  if (response.status === 429 && retryCount < 3) {
50
  const backoffDelay = Math.pow(2, retryCount) * 1000; // 1s, 2s, 4s