Ananthakr1shnan commited on
Commit
eed2f95
·
1 Parent(s): e6443d9

Updated files

Browse files
.gitignore CHANGED
@@ -1,3 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
  # Byte-compiled / optimized / DLL files
2
  __pycache__/
3
  *.py[cod]
 
1
+ # Ignore temp, cache, and binary files
2
+ tmp/
3
+ chroma_db/
4
+ logs/
5
+ data/
6
+ __pycache__/
7
+ *.sqlite3
8
+ *.db
9
+ *.pyc
10
+ *.pyo
11
+ *.pyd
12
+ *.log
13
  # Byte-compiled / optimized / DLL files
14
  __pycache__/
15
  *.py[cod]
data/active_sessions.json CHANGED
@@ -1,12 +1,7 @@
1
  {
2
  "admin_user": {
3
- "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjoiYWRtaW5fdXNlciIsInVzZXJuYW1lIjoiYWRtaW4iLCJleHAiOjE3NTI0MjY2NDV9.Csfsds7stWuRB_NcJKMZQB40PFBqUpg6X2EFmjoAmUE",
4
- "created_at": "2025-07-13T14:40:45.815582",
5
- "last_activity": "2025-07-13T14:49:54.108574"
6
- },
7
- "user_3": {
8
- "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjoidXNlcl8zIiwidXNlcm5hbWUiOiJhbmFudGh1IiwiZXhwIjoxNzUyNDI3MzMyfQ.GSrJR06gLnNW5whgYok7_gV1YJSbfd0Lpia7Z8z6jak",
9
- "created_at": "2025-07-13T14:52:12.892422",
10
- "last_activity": "2025-07-13T14:52:23.475883"
11
  }
12
  }
 
1
  {
2
  "admin_user": {
3
+ "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjoiYWRtaW5fdXNlciIsInVzZXJuYW1lIjoiYWRtaW4iLCJleHAiOjE3NTI1NDc2NjR9.SZ56Rw7mAUJgfScxu1ElGfI5dfWuUxTwUX6xi3d_7a8",
4
+ "created_at": "2025-07-15T00:17:44.795086",
5
+ "last_activity": "2025-07-15T00:50:22.820489"
 
 
 
 
 
6
  }
7
  }
main.py CHANGED
@@ -1,3 +1,22 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  import os
2
  import sys
3
  import json
@@ -69,19 +88,27 @@ async def initialize_research_mate():
69
  print("🚀 Starting ResearchMate background initialization...")
70
 
71
  try:
 
 
 
 
 
 
 
 
72
  # Run initialization in thread pool to avoid blocking
73
  import concurrent.futures
74
  with concurrent.futures.ThreadPoolExecutor() as executor:
75
  loop = asyncio.get_event_loop()
76
-
77
  print("📊 Initializing Citation Network Analyzer...")
78
  citation_analyzer = await loop.run_in_executor(executor, CitationNetworkAnalyzer)
79
  print("✅ Citation Network Analyzer initialized!")
80
-
81
- print("🧠 Initializing ResearchMate core...")
82
  research_mate = await loop.run_in_executor(executor, ResearchMate)
83
  print("✅ ResearchMate core initialized!")
84
-
85
  research_mate_initialized = True
86
  print("🎉 All components initialized successfully!")
87
  except Exception as e:
@@ -296,9 +323,9 @@ async def login(request: LoginRequest):
296
  response.set_cookie(
297
  key="authToken",
298
  value=result["token"],
299
- httponly=False, # Allow JavaScript access for debugging
300
- secure=False, # Don't require HTTPS for internal communication
301
- samesite="lax", # CSRF protection while allowing normal navigation
302
  max_age=24*60*60, # 24 hours
303
  path="/",
304
  domain=None # Let browser determine domain
@@ -496,27 +523,28 @@ async def upload_pdf(file: UploadFile = File(...), current_user: dict = Depends(
496
  raise HTTPException(status_code=400, detail="Only PDF files are supported")
497
 
498
  try:
499
- # Save uploaded file
500
- upload_dir = Path("uploads")
501
- upload_dir.mkdir(exist_ok=True)
 
502
  file_path = upload_dir / file.filename
503
-
504
  with open(file_path, "wb") as buffer:
505
  content = await file.read()
506
  buffer.write(content)
507
-
508
  # Process PDF
509
  result = research_mate.upload_pdf(str(file_path))
510
-
511
  # Clean up file
512
  file_path.unlink()
513
-
514
  if not result.get("success"):
515
  raise HTTPException(status_code=400, detail=result.get("error", "PDF analysis failed"))
516
-
517
  return result
518
  except Exception as e:
519
- raise HTTPException(status_code=500, detail=str(e))
520
 
521
  @app.post("/api/projects")
522
  async def create_project(project: ProjectCreate, current_user: dict = Depends(get_current_user_dependency)):
@@ -526,37 +554,9 @@ async def create_project(project: ProjectCreate, current_user: dict = Depends(ge
526
  try:
527
  user_id = current_user.get("user_id")
528
  result = research_mate.create_project(project.name, project.research_question, project.keywords, user_id)
529
- if not result.get("success"):
530
- raise HTTPException(status_code=400, detail=result.get("error", "Project creation failed"))
531
- return result
532
- except Exception as e:
533
- raise HTTPException(status_code=500, detail=str(e))
534
-
535
- @app.get("/api/projects")
536
- async def list_projects(current_user: dict = Depends(get_current_user_dependency)):
537
- if research_mate is None:
538
- raise HTTPException(status_code=503, detail="ResearchMate not initialized")
539
-
540
- try:
541
- user_id = current_user.get("user_id")
542
- result = research_mate.list_projects(user_id)
543
- if not result.get("success"):
544
- raise HTTPException(status_code=400, detail=result.get("error", "Failed to list projects"))
545
- return result
546
- except Exception as e:
547
- raise HTTPException(status_code=500, detail=str(e))
548
-
549
- @app.get("/api/projects/{project_id}")
550
- async def get_project(project_id: str, current_user: dict = Depends(get_current_user_dependency)):
551
- if research_mate is None:
552
- raise HTTPException(status_code=503, detail="ResearchMate not initialized")
553
-
554
- try:
555
- user_id = current_user.get("user_id")
556
- result = research_mate.get_project(project_id, user_id)
557
- if not result.get("success"):
558
- raise HTTPException(status_code=404, detail=result.get("error", "Project not found"))
559
- return result
560
  except Exception as e:
561
  raise HTTPException(status_code=500, detail=str(e))
562
 
@@ -827,7 +827,7 @@ if __name__ == "__main__":
827
 
828
  # Hugging Face Spaces uses port 7860
829
  port = int(os.environ.get('PORT', 7860))
830
- host = "0.0.0.0"
831
 
832
  print("Starting ResearchMate on Hugging Face Spaces...")
833
  print(f"Web Interface: http://0.0.0.0:{port}")
 
1
+ import shutil
2
+ # ...existing code...
3
+ import os
4
+ import sys
5
+ import json
6
+ import asyncio
7
+ from typing import Dict, List, Optional, Any
8
+ from datetime import datetime
9
+ # ...existing code...
10
+
11
+ # Place this after app and get_current_user_dependency are defined
12
+ # (see lines ~161 and ~231)
13
+
14
+ from fastapi import UploadFile, File
15
+
16
+ # ...existing code...
17
+
18
+
19
+ # ...existing code...
20
  import os
21
  import sys
22
  import json
 
88
  print("🚀 Starting ResearchMate background initialization...")
89
 
90
  try:
91
+ # Ensure vectorstore/chroma persist directory exists before initializing components
92
+ base_dir = Path(__file__).parent.resolve()
93
+ chroma_dir = base_dir / "tmp" / "researchmate" / "chroma_persist"
94
+ chroma_dir.mkdir(parents=True, exist_ok=True)
95
+
96
+ # Set environment variable for ChromaDB persist directory (if needed by your code)
97
+ os.environ["CHROMA_PERSIST_DIR"] = str(chroma_dir)
98
+
99
  # Run initialization in thread pool to avoid blocking
100
  import concurrent.futures
101
  with concurrent.futures.ThreadPoolExecutor() as executor:
102
  loop = asyncio.get_event_loop()
103
+
104
  print("📊 Initializing Citation Network Analyzer...")
105
  citation_analyzer = await loop.run_in_executor(executor, CitationNetworkAnalyzer)
106
  print("✅ Citation Network Analyzer initialized!")
107
+
108
+ print(f"🧠 Initializing ResearchMate core (vectorstore at: {chroma_dir})")
109
  research_mate = await loop.run_in_executor(executor, ResearchMate)
110
  print("✅ ResearchMate core initialized!")
111
+
112
  research_mate_initialized = True
113
  print("🎉 All components initialized successfully!")
114
  except Exception as e:
 
323
  response.set_cookie(
324
  key="authToken",
325
  value=result["token"],
326
+ httponly=True, # HttpOnly for security
327
+ secure=True, # Secure for HTTPS
328
+ samesite="lax", # CSRF protection while allowing normal navigation
329
  max_age=24*60*60, # 24 hours
330
  path="/",
331
  domain=None # Let browser determine domain
 
523
  raise HTTPException(status_code=400, detail="Only PDF files are supported")
524
 
525
  try:
526
+ # Use a cross-platform upload directory relative to the project root
527
+ base_dir = Path(__file__).parent.resolve()
528
+ upload_dir = base_dir / "uploads"
529
+ upload_dir.mkdir(parents=True, exist_ok=True)
530
  file_path = upload_dir / file.filename
531
+
532
  with open(file_path, "wb") as buffer:
533
  content = await file.read()
534
  buffer.write(content)
535
+
536
  # Process PDF
537
  result = research_mate.upload_pdf(str(file_path))
538
+
539
  # Clean up file
540
  file_path.unlink()
541
+
542
  if not result.get("success"):
543
  raise HTTPException(status_code=400, detail=result.get("error", "PDF analysis failed"))
544
+
545
  return result
546
  except Exception as e:
547
+ raise HTTPException(status_code=500, detail=f"Failed to upload/process file: {e}")
548
 
549
  @app.post("/api/projects")
550
  async def create_project(project: ProjectCreate, current_user: dict = Depends(get_current_user_dependency)):
 
554
  try:
555
  user_id = current_user.get("user_id")
556
  result = research_mate.create_project(project.name, project.research_question, project.keywords, user_id)
557
+ if result["success"]:
558
+ # Project creation successful, return result
559
+ return result
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
560
  except Exception as e:
561
  raise HTTPException(status_code=500, detail=str(e))
562
 
 
827
 
828
  # Hugging Face Spaces uses port 7860
829
  port = int(os.environ.get('PORT', 7860))
830
+ host = "127.0.0.1"
831
 
832
  print("Starting ResearchMate on Hugging Face Spaces...")
833
  print(f"Web Interface: http://0.0.0.0:{port}")
projects.json CHANGED
The diff for this file is too large to render. See raw diff
 
src/components/__pycache__/__init__.cpython-311.pyc CHANGED
Binary files a/src/components/__pycache__/__init__.cpython-311.pyc and b/src/components/__pycache__/__init__.cpython-311.pyc differ
 
src/components/__pycache__/auth.cpython-311.pyc CHANGED
Binary files a/src/components/__pycache__/auth.cpython-311.pyc and b/src/components/__pycache__/auth.cpython-311.pyc differ
 
src/components/__pycache__/citation_network.cpython-311.pyc CHANGED
Binary files a/src/components/__pycache__/citation_network.cpython-311.pyc and b/src/components/__pycache__/citation_network.cpython-311.pyc differ
 
src/components/__pycache__/config.cpython-311.pyc CHANGED
Binary files a/src/components/__pycache__/config.cpython-311.pyc and b/src/components/__pycache__/config.cpython-311.pyc differ
 
src/components/__pycache__/groq_processor.cpython-311.pyc CHANGED
Binary files a/src/components/__pycache__/groq_processor.cpython-311.pyc and b/src/components/__pycache__/groq_processor.cpython-311.pyc differ
 
src/components/__pycache__/pdf_processor.cpython-311.pyc CHANGED
Binary files a/src/components/__pycache__/pdf_processor.cpython-311.pyc and b/src/components/__pycache__/pdf_processor.cpython-311.pyc differ
 
src/components/__pycache__/rag_system.cpython-311.pyc CHANGED
Binary files a/src/components/__pycache__/rag_system.cpython-311.pyc and b/src/components/__pycache__/rag_system.cpython-311.pyc differ
 
src/components/__pycache__/research_assistant.cpython-311.pyc CHANGED
Binary files a/src/components/__pycache__/research_assistant.cpython-311.pyc and b/src/components/__pycache__/research_assistant.cpython-311.pyc differ
 
src/components/__pycache__/trend_monitor.cpython-311.pyc CHANGED
Binary files a/src/components/__pycache__/trend_monitor.cpython-311.pyc and b/src/components/__pycache__/trend_monitor.cpython-311.pyc differ
 
src/components/__pycache__/unified_fetcher.cpython-311.pyc CHANGED
Binary files a/src/components/__pycache__/unified_fetcher.cpython-311.pyc and b/src/components/__pycache__/unified_fetcher.cpython-311.pyc differ
 
src/static/js/main.js CHANGED
@@ -3,182 +3,62 @@
3
  // Global variables
4
  let currentToast = null;
5
 
6
- // Authentication utilities with enhanced security
7
- let sessionTimeout = null;
8
- let lastActivityTime = Date.now();
9
- const SESSION_TIMEOUT_MINUTES = 480; // 8 hours for prototype (less aggressive)
10
- const ACTIVITY_CHECK_INTERVAL = 300000; // Check every 5 minutes
11
-
12
- function getAuthToken() {
13
- // Check both sessionStorage (preferred) and localStorage (fallback)
14
- return sessionStorage.getItem('authToken') || localStorage.getItem('authToken');
15
- }
16
 
17
- function setAuthToken(token) {
18
- // Store in sessionStorage for better security (clears on browser close)
19
- sessionStorage.setItem('authToken', token);
20
- // Also store in localStorage for compatibility, but with shorter expiry
21
- localStorage.setItem('authToken', token);
22
- localStorage.setItem('tokenTimestamp', Date.now().toString());
23
-
24
- // Set cookie with HttpOnly equivalent behavior
25
- document.cookie = `authToken=${token}; path=/; SameSite=Strict; Secure=${location.protocol === 'https:'}`;
26
-
27
- // Reset activity tracking
28
- lastActivityTime = Date.now();
29
- startSessionTimeout();
30
- }
31
-
32
- function clearAuthToken() {
33
- sessionStorage.removeItem('authToken');
34
- sessionStorage.removeItem('userId');
35
- localStorage.removeItem('authToken');
36
- localStorage.removeItem('userId');
37
- localStorage.removeItem('tokenTimestamp');
38
-
39
- // Clear cookie
40
- document.cookie = 'authToken=; path=/; expires=Thu, 01 Jan 1970 00:00:00 GMT; SameSite=Strict';
41
-
42
- clearTimeout(sessionTimeout);
43
- }
44
-
45
- function isTokenExpired() {
46
- const timestamp = localStorage.getItem('tokenTimestamp');
47
- if (!timestamp) return true;
48
-
49
- const tokenAge = Date.now() - parseInt(timestamp);
50
- const maxAge = 24 * 60 * 60 * 1000; // 24 hours
51
-
52
- return tokenAge > maxAge;
53
- }
54
 
55
- function startSessionTimeout() {
56
- clearTimeout(sessionTimeout);
57
- sessionTimeout = setTimeout(() => {
58
- const inactivityTime = Date.now() - lastActivityTime;
59
- if (inactivityTime >= SESSION_TIMEOUT_MINUTES * 60 * 1000) {
60
- // Session expired due to inactivity
61
- showToast('Session expired due to inactivity. Please log in again.', 'warning');
62
- logout();
 
 
63
  } else {
64
- // Still active, reset timer
65
- startSessionTimeout();
 
 
 
 
 
 
 
 
66
  }
67
- }, ACTIVITY_CHECK_INTERVAL);
68
- }
69
-
70
- function trackActivity() {
71
- lastActivityTime = Date.now();
72
- }
73
-
74
- function setAuthHeaders(headers = {}) {
75
- const token = getAuthToken();
76
- if (token && !isTokenExpired()) {
77
- headers['Authorization'] = `Bearer ${token}`;
78
- }
79
- return headers;
80
- }
81
-
82
- function makeAuthenticatedRequest(url, options = {}) {
83
- const headers = setAuthHeaders(options.headers || {});
84
- return fetch(url, {
85
- ...options,
86
- headers: headers
87
  });
88
- }
89
-
90
- // Check if user is authenticated
91
- function isAuthenticated() {
92
- const token = getAuthToken();
93
- return !!(token && !isTokenExpired());
94
- }
95
 
96
- // Redirect to login if not authenticated
97
- function requireAuth() {
98
- if (!isAuthenticated()) {
99
- clearAuthToken();
100
- window.location.href = '/login';
101
- return false;
102
- }
103
- return true;
104
- }
105
 
106
- // Document ready with enhanced security
107
- document.addEventListener('DOMContentLoaded', function() {
108
- // Check authentication on protected pages
109
- if (window.location.pathname !== '/login' && !isAuthenticated()) {
110
- clearAuthToken();
111
- window.location.href = '/login';
112
- return;
113
- }
114
-
115
- // Start session timeout if authenticated
116
- if (isAuthenticated()) {
117
- startSessionTimeout();
118
- }
119
-
120
- // Track user activity for session timeout
121
- document.addEventListener('click', trackActivity);
122
- document.addEventListener('keypress', trackActivity);
123
- document.addEventListener('scroll', trackActivity);
124
- document.addEventListener('mousemove', trackActivity);
125
-
126
  // Initialize tooltips
127
  initializeTooltips();
128
-
129
- // Handle page visibility changes (user switches tabs or minimizes browser)
130
- document.addEventListener('visibilitychange', function() {
131
- if (document.hidden) {
132
- // Page is hidden, reduce activity tracking
133
- clearTimeout(sessionTimeout);
134
- } else {
135
- // Page is visible again, resume activity tracking
136
- if (isAuthenticated()) {
137
- trackActivity();
138
- startSessionTimeout();
139
- }
140
- }
141
- });
142
-
143
  // Handle beforeunload event (browser/tab closing)
144
  window.addEventListener('beforeunload', function() {
145
  // Clear sessionStorage on page unload (but keep localStorage for potential restoration)
146
  sessionStorage.clear();
147
  });
148
-
149
- // Periodically validate token with server (disabled for prototype)
150
- // if (isAuthenticated()) {
151
- // setInterval(async function() {
152
- // try {
153
- // const response = await makeAuthenticatedRequest('/api/user/status');
154
- // if (!response.ok) {
155
- // // Token is invalid or expired
156
- // showToast('Session expired. Please log in again.', 'warning');
157
- // logout();
158
- // }
159
- // } catch (error) {
160
- // console.log('Token validation failed:', error);
161
- // }
162
- // }, 5 * 60 * 1000); // Check every 5 minutes
163
- // }
164
-
165
  // Initialize smooth scrolling
166
  initializeSmoothScrolling();
167
-
168
  // Initialize animations
169
  initializeAnimations();
170
-
171
  // Initialize keyboard shortcuts
172
  initializeKeyboardShortcuts();
173
-
174
- // Theme toggle removed
175
-
176
  // Initialize upload
177
  initializeUpload();
178
-
179
  // Initialize search page (if on search page)
180
  initializeSearchPage();
181
-
182
  console.log('ResearchMate initialized successfully!');
183
  });
184
 
@@ -259,35 +139,35 @@ function initializeKeyboardShortcuts() {
259
  // Theme toggle removed: always use dark theme
260
 
261
  // Enhanced Upload functionality
262
- function initializeUpload() {
263
  const uploadArea = document.getElementById('upload-area');
264
  const fileInput = document.getElementById('pdf-file');
265
  const uploadBtn = document.getElementById('upload-btn');
266
-
267
  if (!uploadArea || !fileInput || !uploadBtn) return;
268
-
269
  // Restore previous upload results if they exist
270
- restoreUploadResults();
271
-
272
  // Click to browse files
273
  uploadArea.addEventListener('click', () => {
274
  fileInput.click();
275
  });
276
-
277
  // Drag and drop functionality
278
  uploadArea.addEventListener('dragover', (e) => {
279
  e.preventDefault();
280
  uploadArea.classList.add('dragover');
281
  });
282
-
283
  uploadArea.addEventListener('dragleave', () => {
284
  uploadArea.classList.remove('dragover');
285
  });
286
-
287
  uploadArea.addEventListener('drop', (e) => {
288
  e.preventDefault();
289
  uploadArea.classList.remove('dragover');
290
-
291
  const files = e.dataTransfer.files;
292
  if (files.length > 0 && files[0].type === 'application/pdf') {
293
  fileInput.files = files;
@@ -296,18 +176,18 @@ function initializeUpload() {
296
  showToast('Please select a valid PDF file', 'danger');
297
  }
298
  });
299
-
300
  // File input change
301
  fileInput.addEventListener('change', (e) => {
302
  if (e.target.files.length > 0) {
303
  handleFileSelection(e.target.files[0]);
304
  }
305
  });
306
-
307
  function handleFileSelection(file) {
308
  uploadBtn.disabled = false;
309
  uploadBtn.innerHTML = `<i class="fas fa-upload me-2"></i>Upload "${file.name}"`;
310
-
311
  // Update upload area
312
  uploadArea.innerHTML = `
313
  <i class="fas fa-file-pdf text-danger"></i>
@@ -335,43 +215,36 @@ function toggleUploadArea() {
335
  }
336
 
337
  // Upload result persistence functions
338
- function saveUploadResults(data) {
339
  try {
340
- const currentUser = getCurrentUserId();
341
- const currentSession = getSessionId();
342
-
343
- if (!currentUser || !currentSession) {
344
- console.warn('Cannot save upload results: no user or session');
345
  return;
346
  }
347
-
348
  const dataToSave = {
349
  ...data,
350
  userId: currentUser,
351
- sessionId: currentSession,
352
  savedAt: new Date().toISOString(),
353
  pageUrl: window.location.pathname
354
  };
355
-
356
  saveToLocalStorage('researchmate_upload_results', dataToSave);
357
  } catch (error) {
358
  console.error('Failed to save upload results:', error);
359
  }
360
  }
361
 
362
- function restoreUploadResults() {
363
  try {
364
  const resultsContainer = document.getElementById('results-container');
365
  if (!resultsContainer) return;
366
-
367
- // Get current user from session/token
368
- const currentUser = getCurrentUserId(); // You'll need to implement this
369
  if (!currentUser) {
370
  // No user logged in, clear any existing results
371
  clearUploadResults();
372
  return;
373
  }
374
-
375
  const savedData = loadFromLocalStorage('researchmate_upload_results');
376
  if (savedData && savedData.pageUrl === window.location.pathname) {
377
  // Check if data belongs to current user
@@ -380,22 +253,12 @@ function restoreUploadResults() {
380
  clearUploadResults();
381
  return;
382
  }
383
-
384
- // Check if data is from current session
385
- const currentSessionId = getSessionId(); // You'll need to implement this
386
- if (savedData.sessionId !== currentSessionId) {
387
- console.log('Upload results from different session, clearing');
388
- clearUploadResults();
389
- return;
390
- }
391
-
392
- // Check if data is recent (within current session, max 1 hour)
393
  const savedTime = new Date(savedData.savedAt);
394
  const now = new Date();
395
  const hoursDiff = (now - savedTime) / (1000 * 60 * 60);
396
-
397
  if (hoursDiff < 1) {
398
- console.log('Restoring upload results from current session');
399
  displayUploadResults(savedData);
400
  showToast('Previous PDF analysis restored', 'info', 3000);
401
  } else {
@@ -408,15 +271,14 @@ function restoreUploadResults() {
408
  }
409
  }
410
 
411
- // Helper function to get current user ID
412
- function getCurrentUserId() {
413
  try {
414
- const token = localStorage.getItem('authToken') || sessionStorage.getItem('authToken');
415
- if (!token) return null;
416
-
417
- // Decode JWT token to get user ID (simple base64 decode)
418
- const payload = JSON.parse(atob(token.split('.')[1]));
419
- return payload.user_id || payload.sub;
420
  } catch (error) {
421
  console.error('Failed to get current user ID:', error);
422
  return null;
@@ -1194,22 +1056,18 @@ function loadFromLocalStorage(key, defaultValue = null) {
1194
 
1195
  // Enhanced logout function with security cleanup
1196
  function logout() {
1197
- // Clear all authentication data
1198
- clearAuthToken();
1199
-
1200
  // Clear all session data
1201
  sessionStorage.clear();
1202
-
1203
- // Clear specific localStorage items but keep non-sensitive data
1204
- const keysToRemove = ['authToken', 'userId', 'tokenTimestamp', 'userSession'];
1205
- keysToRemove.forEach(key => localStorage.removeItem(key));
1206
-
1207
  // Call logout API
1208
  fetch('/api/auth/logout', {
1209
  method: 'POST',
1210
  headers: {
1211
  'Content-Type': 'application/json',
1212
- }
 
1213
  })
1214
  .then(() => {
1215
  // Redirect to login page
@@ -1224,8 +1082,7 @@ function logout() {
1224
  // Make logout function globally available
1225
  window.logout = logout;
1226
 
1227
- // Make makeAuthenticatedRequest globally available
1228
- window.makeAuthenticatedRequest = makeAuthenticatedRequest;
1229
 
1230
  // Export functions for global use
1231
  window.ResearchMate = {
@@ -1247,7 +1104,8 @@ window.ResearchMate = {
1247
  saveUploadResults,
1248
  restoreUploadResults,
1249
  clearUploadResults,
1250
- displayUploadResults
 
1251
  };
1252
 
1253
  // Make clearUploadResults globally available for onclick handlers
 
3
  // Global variables
4
  let currentToast = null;
5
 
6
+ // Authentication/session handled by backend HttpOnly cookie and /api/user/status
7
+ // All API calls should use apiRequest with credentials: 'include'.
8
+ // No token storage or Authorization header needed.
9
+ // Session timeout and activity tracking handled server-side.
 
 
 
 
 
 
10
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
11
 
12
+ // Document ready
13
+ document.addEventListener('DOMContentLoaded', function() {
14
+ // Always verify authentication with backend on page load
15
+ fetch('/api/user/status', {
16
+ credentials: 'include'
17
+ })
18
+ .then(response => {
19
+ if (response.ok) {
20
+ // User is authenticated, continue
21
+ // Optionally, start session timeout or other logic here
22
  } else {
23
+ // Not authenticated, redirect to login if not already there
24
+ if (window.location.pathname !== '/login') {
25
+ window.location.href = '/login';
26
+ }
27
+ }
28
+ })
29
+ .catch((error) => {
30
+ console.error('User status check error:', error);
31
+ if (window.location.pathname !== '/login') {
32
+ window.location.href = '/login';
33
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
34
  });
 
 
 
 
 
 
 
35
 
36
+ // (trackActivity removed: no longer needed)
 
 
 
 
 
 
 
 
37
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
38
  // Initialize tooltips
39
  initializeTooltips();
40
+
 
 
 
 
 
 
 
 
 
 
 
 
 
 
41
  // Handle beforeunload event (browser/tab closing)
42
  window.addEventListener('beforeunload', function() {
43
  // Clear sessionStorage on page unload (but keep localStorage for potential restoration)
44
  sessionStorage.clear();
45
  });
46
+
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
47
  // Initialize smooth scrolling
48
  initializeSmoothScrolling();
49
+
50
  // Initialize animations
51
  initializeAnimations();
52
+
53
  // Initialize keyboard shortcuts
54
  initializeKeyboardShortcuts();
55
+
 
 
56
  // Initialize upload
57
  initializeUpload();
58
+
59
  // Initialize search page (if on search page)
60
  initializeSearchPage();
61
+
62
  console.log('ResearchMate initialized successfully!');
63
  });
64
 
 
139
  // Theme toggle removed: always use dark theme
140
 
141
  // Enhanced Upload functionality
142
+ async function initializeUpload() {
143
  const uploadArea = document.getElementById('upload-area');
144
  const fileInput = document.getElementById('pdf-file');
145
  const uploadBtn = document.getElementById('upload-btn');
146
+
147
  if (!uploadArea || !fileInput || !uploadBtn) return;
148
+
149
  // Restore previous upload results if they exist
150
+ await restoreUploadResults();
151
+
152
  // Click to browse files
153
  uploadArea.addEventListener('click', () => {
154
  fileInput.click();
155
  });
156
+
157
  // Drag and drop functionality
158
  uploadArea.addEventListener('dragover', (e) => {
159
  e.preventDefault();
160
  uploadArea.classList.add('dragover');
161
  });
162
+
163
  uploadArea.addEventListener('dragleave', () => {
164
  uploadArea.classList.remove('dragover');
165
  });
166
+
167
  uploadArea.addEventListener('drop', (e) => {
168
  e.preventDefault();
169
  uploadArea.classList.remove('dragover');
170
+
171
  const files = e.dataTransfer.files;
172
  if (files.length > 0 && files[0].type === 'application/pdf') {
173
  fileInput.files = files;
 
176
  showToast('Please select a valid PDF file', 'danger');
177
  }
178
  });
179
+
180
  // File input change
181
  fileInput.addEventListener('change', (e) => {
182
  if (e.target.files.length > 0) {
183
  handleFileSelection(e.target.files[0]);
184
  }
185
  });
186
+
187
  function handleFileSelection(file) {
188
  uploadBtn.disabled = false;
189
  uploadBtn.innerHTML = `<i class="fas fa-upload me-2"></i>Upload "${file.name}"`;
190
+
191
  // Update upload area
192
  uploadArea.innerHTML = `
193
  <i class="fas fa-file-pdf text-danger"></i>
 
215
  }
216
 
217
  // Upload result persistence functions
218
+ async function saveUploadResults(data) {
219
  try {
220
+ const currentUser = await getCurrentUserId();
221
+ if (!currentUser) {
222
+ console.warn('Cannot save upload results: no user');
 
 
223
  return;
224
  }
 
225
  const dataToSave = {
226
  ...data,
227
  userId: currentUser,
 
228
  savedAt: new Date().toISOString(),
229
  pageUrl: window.location.pathname
230
  };
 
231
  saveToLocalStorage('researchmate_upload_results', dataToSave);
232
  } catch (error) {
233
  console.error('Failed to save upload results:', error);
234
  }
235
  }
236
 
237
+ async function restoreUploadResults() {
238
  try {
239
  const resultsContainer = document.getElementById('results-container');
240
  if (!resultsContainer) return;
241
+ // Get current user from backend
242
+ const currentUser = await getCurrentUserId();
 
243
  if (!currentUser) {
244
  // No user logged in, clear any existing results
245
  clearUploadResults();
246
  return;
247
  }
 
248
  const savedData = loadFromLocalStorage('researchmate_upload_results');
249
  if (savedData && savedData.pageUrl === window.location.pathname) {
250
  // Check if data belongs to current user
 
253
  clearUploadResults();
254
  return;
255
  }
256
+ // Check if data is recent (max 1 hour)
 
 
 
 
 
 
 
 
 
257
  const savedTime = new Date(savedData.savedAt);
258
  const now = new Date();
259
  const hoursDiff = (now - savedTime) / (1000 * 60 * 60);
 
260
  if (hoursDiff < 1) {
261
+ console.log('Restoring upload results for user');
262
  displayUploadResults(savedData);
263
  showToast('Previous PDF analysis restored', 'info', 3000);
264
  } else {
 
271
  }
272
  }
273
 
274
+ // Helper function to get current user ID from backend
275
+ async function getCurrentUserId() {
276
  try {
277
+ const response = await fetch('/api/user/status', { credentials: 'include' });
278
+ if (!response.ok) return null;
279
+ const data = await response.json();
280
+ // Try user_id, id, or username as fallback
281
+ return data.user_id || data.id || data.username || null;
 
282
  } catch (error) {
283
  console.error('Failed to get current user ID:', error);
284
  return null;
 
1056
 
1057
  // Enhanced logout function with security cleanup
1058
  function logout() {
 
 
 
1059
  // Clear all session data
1060
  sessionStorage.clear();
1061
+ // Clear user info from localStorage
1062
+ localStorage.removeItem('userId');
1063
+ localStorage.removeItem('username');
 
 
1064
  // Call logout API
1065
  fetch('/api/auth/logout', {
1066
  method: 'POST',
1067
  headers: {
1068
  'Content-Type': 'application/json',
1069
+ },
1070
+ credentials: 'include'
1071
  })
1072
  .then(() => {
1073
  // Redirect to login page
 
1082
  // Make logout function globally available
1083
  window.logout = logout;
1084
 
1085
+
 
1086
 
1087
  // Export functions for global use
1088
  window.ResearchMate = {
 
1104
  saveUploadResults,
1105
  restoreUploadResults,
1106
  clearUploadResults,
1107
+ displayUploadResults,
1108
+ getCurrentUserId
1109
  };
1110
 
1111
  // Make clearUploadResults globally available for onclick handlers
src/templates/index.html CHANGED
@@ -223,12 +223,10 @@ document.addEventListener('DOMContentLoaded', function() {
223
 
224
  function loadSystemStatus() {
225
  // Load system status (components and general info)
226
- makeAuthenticatedRequest('/api/status')
227
- .then(response => response.json())
228
  .then(systemData => {
229
  // Load user-specific statistics
230
- makeAuthenticatedRequest('/api/user/status')
231
- .then(response => response.json())
232
  .then(userData => {
233
  const statusDiv = document.getElementById('system-status');
234
  if (systemData.success && userData.success) {
 
223
 
224
  function loadSystemStatus() {
225
  // Load system status (components and general info)
226
+ apiRequest('/api/status', { credentials: 'include' })
 
227
  .then(systemData => {
228
  // Load user-specific statistics
229
+ apiRequest('/api/user/status', { credentials: 'include' })
 
230
  .then(userData => {
231
  const statusDiv = document.getElementById('system-status');
232
  if (systemData.success && userData.success) {
src/templates/login.html CHANGED
@@ -86,6 +86,29 @@
86
  {% block extra_js %}
87
  <script>
88
  document.addEventListener('DOMContentLoaded', function() {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
89
  const loginForm = document.getElementById('login-form');
90
  const registerForm = document.getElementById('register-form');
91
  const loginBtn = document.getElementById('login-btn');
@@ -124,10 +147,7 @@ document.addEventListener('DOMContentLoaded', function() {
124
  console.log('Login response:', data); // Debug log
125
 
126
  if (response.ok && data.success) {
127
- // Store token as backup
128
- if (data.token) {
129
- localStorage.setItem('authToken', data.token);
130
- }
131
  if (data.user_id) {
132
  localStorage.setItem('userId', data.user_id);
133
  }
@@ -254,36 +274,26 @@ document.addEventListener('DOMContentLoaded', function() {
254
  }
255
  }
256
 
257
- // Check if user is already logged in - with better error handling
258
- const token = localStorage.getItem('authToken');
259
- if (token) {
260
- // Verify token is still valid
261
- fetch('/api/user/status', {
262
- headers: {
263
- 'Authorization': `Bearer ${token}`
264
- },
265
- credentials: 'include'
266
- })
267
- .then(response => {
268
- if (response.ok) {
269
- // User is already logged in, redirect
270
- console.log('User already logged in, redirecting...');
271
- window.location.href = '/';
272
- } else {
273
- // Token is invalid, clear it
274
- localStorage.removeItem('authToken');
275
- localStorage.removeItem('userId');
276
- localStorage.removeItem('username');
277
- }
278
- })
279
- .catch((error) => {
280
- console.error('Token verification error:', error);
281
- // Token is invalid, clear it
282
- localStorage.removeItem('authToken');
283
  localStorage.removeItem('userId');
284
  localStorage.removeItem('username');
285
- });
286
- }
 
 
 
 
 
287
  });
288
  </script>
289
  {% endblock %}
 
86
  {% block extra_js %}
87
  <script>
88
  document.addEventListener('DOMContentLoaded', function() {
89
+ // Always verify authentication with backend on page load
90
+ fetch('/api/user/status', {
91
+ credentials: 'include'
92
+ })
93
+ .then(response => {
94
+ if (response.ok) {
95
+ // User is authenticated, redirect to home if not already there
96
+ if (window.location.pathname === '/login') {
97
+ window.location.href = '/';
98
+ }
99
+ } else {
100
+ // Not authenticated, redirect to login if not already there
101
+ if (window.location.pathname !== '/login') {
102
+ window.location.href = '/login';
103
+ }
104
+ }
105
+ })
106
+ .catch((error) => {
107
+ console.error('User status check error:', error);
108
+ if (window.location.pathname !== '/login') {
109
+ window.location.href = '/login';
110
+ }
111
+ });
112
  const loginForm = document.getElementById('login-form');
113
  const registerForm = document.getElementById('register-form');
114
  const loginBtn = document.getElementById('login-btn');
 
147
  console.log('Login response:', data); // Debug log
148
 
149
  if (response.ok && data.success) {
150
+ // Store user info for UI only
 
 
 
151
  if (data.user_id) {
152
  localStorage.setItem('userId', data.user_id);
153
  }
 
274
  }
275
  }
276
 
277
+ // Check if user is already logged in (cookie-based)
278
+ fetch('/api/user/status', {
279
+ credentials: 'include'
280
+ })
281
+ .then(response => {
282
+ if (response.ok) {
283
+ // User is already logged in, redirect
284
+ console.log('User already logged in, redirecting...');
285
+ window.location.href = '/';
286
+ } else {
287
+ // Not logged in, clear any UI user info
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
288
  localStorage.removeItem('userId');
289
  localStorage.removeItem('username');
290
+ }
291
+ })
292
+ .catch((error) => {
293
+ console.error('User status check error:', error);
294
+ localStorage.removeItem('userId');
295
+ localStorage.removeItem('username');
296
+ });
297
  });
298
  </script>
299
  {% endblock %}
src/templates/projects.html CHANGED
@@ -113,13 +113,7 @@ document.addEventListener('DOMContentLoaded', function() {
113
  });
114
 
115
  function loadProjects() {
116
- makeAuthenticatedRequest('/api/projects')
117
- .then(response => {
118
- if (!response.ok) {
119
- throw new Error(`HTTP ${response.status}: ${response.statusText}`);
120
- }
121
- return response.json();
122
- })
123
  .then(data => {
124
  console.log('Projects API response:', data); // Debug log
125
  if (data.success) {
@@ -145,19 +139,18 @@ document.addEventListener('DOMContentLoaded', function() {
145
  submitBtn.disabled = true;
146
  }
147
 
148
- fetch('/api/projects', {
149
  method: 'POST',
150
  headers: {
151
- 'Content-Type': 'application/json',
152
- 'Authorization': `Bearer ${localStorage.getItem('authToken')}`
153
  },
 
154
  body: JSON.stringify({
155
  name: name,
156
  research_question: researchQuestion,
157
  keywords: keywords
158
  })
159
  })
160
- .then(response => response.json())
161
  .then(data => {
162
  if (data.success) {
163
  // Close modal and reset form
@@ -278,12 +271,7 @@ projectsContainer.innerHTML = html;
278
 
279
  // Global functions for project actions
280
  window.viewProject = function(projectId) {
281
- fetch(`/api/projects/${projectId}`, {
282
- headers: {
283
- 'Authorization': `Bearer ${localStorage.getItem('authToken')}`
284
- }
285
- })
286
- .then(response => response.json())
287
  .then(data => {
288
  if (data.success) {
289
  displayProjectDetails(data.project);
@@ -311,14 +299,13 @@ projectsContainer.innerHTML = html;
311
  // Show persistent loading message (no auto-dismiss)
312
  showAlert('info', 'Literature search in progress... This may take a few minutes.', 0); // 0 = no auto-dismiss
313
 
314
- fetch(`/api/projects/${projectId}/search`, {
315
  method: 'POST',
316
  headers: {
317
- 'Content-Type': 'application/json',
318
- 'Authorization': `Bearer ${localStorage.getItem('authToken')}`
319
- }
320
  })
321
- .then(response => response.json())
322
  .then(data => {
323
  // Remove loading alert if present
324
  if (window.currentActiveAlert && window.currentActiveAlert.parentNode) {
@@ -411,14 +398,13 @@ projectsContainer.innerHTML = html;
411
 
412
  showAlert('info', 'Analyzing project data... This may take a few minutes.', 0);
413
 
414
- fetch(`/api/projects/${projectId}/analyze`, {
415
  method: 'POST',
416
  headers: {
417
- 'Content-Type': 'application/json',
418
- 'Authorization': `Bearer ${localStorage.getItem('authToken')}`
419
- }
420
  })
421
- .then(response => response.json())
422
  .then(data => {
423
  // Remove loading alert if present
424
  if (window.currentActiveAlert && window.currentActiveAlert.parentNode) {
@@ -463,14 +449,13 @@ projectsContainer.innerHTML = html;
463
 
464
  showAlert('info', 'Generating literature review... This may take several minutes.', 0); // 0 = no auto-dismiss
465
 
466
- fetch(`/api/projects/${projectId}/review`, {
467
  method: 'POST',
468
  headers: {
469
- 'Content-Type': 'application/json',
470
- 'Authorization': `Bearer ${localStorage.getItem('authToken')}`
471
- }
472
  })
473
- .then(response => response.json())
474
  .then(data => {
475
  console.log('Review response:', data); // Debug log
476
  // Remove loading alert if present
 
113
  });
114
 
115
  function loadProjects() {
116
+ apiRequest('/api/projects', { credentials: 'include' })
 
 
 
 
 
 
117
  .then(data => {
118
  console.log('Projects API response:', data); // Debug log
119
  if (data.success) {
 
139
  submitBtn.disabled = true;
140
  }
141
 
142
+ apiRequest('/api/projects', {
143
  method: 'POST',
144
  headers: {
145
+ 'Content-Type': 'application/json'
 
146
  },
147
+ credentials: 'include',
148
  body: JSON.stringify({
149
  name: name,
150
  research_question: researchQuestion,
151
  keywords: keywords
152
  })
153
  })
 
154
  .then(data => {
155
  if (data.success) {
156
  // Close modal and reset form
 
271
 
272
  // Global functions for project actions
273
  window.viewProject = function(projectId) {
274
+ apiRequest(`/api/projects/${projectId}`, { credentials: 'include' })
 
 
 
 
 
275
  .then(data => {
276
  if (data.success) {
277
  displayProjectDetails(data.project);
 
299
  // Show persistent loading message (no auto-dismiss)
300
  showAlert('info', 'Literature search in progress... This may take a few minutes.', 0); // 0 = no auto-dismiss
301
 
302
+ apiRequest(`/api/projects/${projectId}/search`, {
303
  method: 'POST',
304
  headers: {
305
+ 'Content-Type': 'application/json'
306
+ },
307
+ credentials: 'include'
308
  })
 
309
  .then(data => {
310
  // Remove loading alert if present
311
  if (window.currentActiveAlert && window.currentActiveAlert.parentNode) {
 
398
 
399
  showAlert('info', 'Analyzing project data... This may take a few minutes.', 0);
400
 
401
+ apiRequest(`/api/projects/${projectId}/analyze`, {
402
  method: 'POST',
403
  headers: {
404
+ 'Content-Type': 'application/json'
405
+ },
406
+ credentials: 'include'
407
  })
 
408
  .then(data => {
409
  // Remove loading alert if present
410
  if (window.currentActiveAlert && window.currentActiveAlert.parentNode) {
 
449
 
450
  showAlert('info', 'Generating literature review... This may take several minutes.', 0); // 0 = no auto-dismiss
451
 
452
+ apiRequest(`/api/projects/${projectId}/review`, {
453
  method: 'POST',
454
  headers: {
455
+ 'Content-Type': 'application/json'
456
+ },
457
+ credentials: 'include'
458
  })
 
459
  .then(data => {
460
  console.log('Review response:', data); // Debug log
461
  // Remove loading alert if present