Spaces:
Running
Running
Commit
·
041fce6
1
Parent(s):
ba6eacd
add
Browse files- backend/app.py +4 -4
- backend/dependencies.py +1 -15
- backend/middleware/usage_tracker.py +1 -6
- backend/routers/auth.py +4 -102
- backend/routers/knowledge_graphs.py +1 -1
- frontend/src/App.tsx +11 -32
- frontend/src/components/auth/LoginModal.tsx +0 -141
- frontend/src/context/AuthContext.tsx +0 -229
- frontend/src/lib/api.ts +0 -16
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 (
|
| 181 |
@app.get("/")
|
| 182 |
-
async def root(request: Request):
|
| 183 |
-
#
|
| 184 |
-
#
|
| 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
|
| 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)
|
| 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 |
-
|
| 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 |
-
#
|
| 258 |
-
|
| 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
|
| 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 |
-
<
|
| 52 |
-
<
|
| 53 |
-
<
|
| 54 |
-
<
|
| 55 |
-
<
|
| 56 |
-
<
|
| 57 |
-
|
| 58 |
-
|
| 59 |
-
|
| 60 |
-
|
| 61 |
-
|
| 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
|