Spaces:
Sleeping
Sleeping
Commit
·
eed2f95
1
Parent(s):
e6443d9
Updated files
Browse files- .gitignore +12 -0
- data/active_sessions.json +3 -8
- main.py +48 -48
- projects.json +0 -0
- src/components/__pycache__/__init__.cpython-311.pyc +0 -0
- src/components/__pycache__/auth.cpython-311.pyc +0 -0
- src/components/__pycache__/citation_network.cpython-311.pyc +0 -0
- src/components/__pycache__/config.cpython-311.pyc +0 -0
- src/components/__pycache__/groq_processor.cpython-311.pyc +0 -0
- src/components/__pycache__/pdf_processor.cpython-311.pyc +0 -0
- src/components/__pycache__/rag_system.cpython-311.pyc +0 -0
- src/components/__pycache__/research_assistant.cpython-311.pyc +0 -0
- src/components/__pycache__/trend_monitor.cpython-311.pyc +0 -0
- src/components/__pycache__/unified_fetcher.cpython-311.pyc +0 -0
- src/static/js/main.js +68 -210
- src/templates/index.html +2 -4
- src/templates/login.html +42 -32
- src/templates/projects.html +17 -32
.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.
|
4 |
-
"created_at": "2025-07-
|
5 |
-
"last_activity": "2025-07-
|
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=
|
300 |
-
secure=
|
301 |
-
samesite="lax",
|
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 |
-
#
|
500 |
-
|
501 |
-
upload_dir
|
|
|
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=
|
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
|
530 |
-
|
531 |
-
|
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 = "
|
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
|
7 |
-
|
8 |
-
|
9 |
-
|
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 |
-
|
56 |
-
|
57 |
-
|
58 |
-
|
59 |
-
|
60 |
-
|
61 |
-
|
62 |
-
|
|
|
|
|
63 |
} else {
|
64 |
-
//
|
65 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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 |
-
//
|
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 |
-
|
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 |
-
|
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
|
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
|
415 |
-
if (!
|
416 |
-
|
417 |
-
//
|
418 |
-
|
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 |
-
|
1204 |
-
|
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 |
-
|
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 |
-
|
227 |
-
.then(response => response.json())
|
228 |
.then(systemData => {
|
229 |
// Load user-specific statistics
|
230 |
-
|
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
|
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 -
|
258 |
-
|
259 |
-
|
260 |
-
|
261 |
-
|
262 |
-
|
263 |
-
|
264 |
-
|
265 |
-
|
266 |
-
}
|
267 |
-
|
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 |
-
|
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 |
-
|
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 |
-
|
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 |
-
|
315 |
method: 'POST',
|
316 |
headers: {
|
317 |
-
'Content-Type': 'application/json'
|
318 |
-
|
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 |
-
|
415 |
method: 'POST',
|
416 |
headers: {
|
417 |
-
'Content-Type': 'application/json'
|
418 |
-
|
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 |
-
|
467 |
method: 'POST',
|
468 |
headers: {
|
469 |
-
'Content-Type': 'application/json'
|
470 |
-
|
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
|