VinitT commited on
Commit
4d5c005
·
1 Parent(s): 28e9f5e

add huggingface space

Browse files
.gitignore ADDED
@@ -0,0 +1,31 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Environment variables
2
+ .env
3
+
4
+ # Firebase credentials
5
+ *serviceAccountKey.json
6
+
7
+ # Python
8
+ __pycache__/
9
+ *.py[cod]
10
+ *$py.class
11
+ *.so
12
+ .Python
13
+ env/
14
+ build/
15
+ develop-eggs/
16
+ dist/
17
+ downloads/
18
+ eggs/
19
+ .eggs/
20
+ lib/
21
+ lib64/
22
+ parts/
23
+ sdist/
24
+ var/
25
+ *.egg-info/
26
+ .installed.cfg
27
+ *.egg
28
+
29
+ # Virtual Environment
30
+ venv/
31
+ ENV/
Dockerfile ADDED
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ FROM python:3.9-slim
2
+
3
+ WORKDIR /code
4
+
5
+ COPY ./requirements.txt /code/requirements.txt
6
+ COPY . /code
7
+
8
+ RUN pip install --no-cache-dir --upgrade -r /code/requirements.txt
9
+
10
+ EXPOSE 7860
11
+
12
+ CMD ["python", "app.py"]
app.py ADDED
@@ -0,0 +1,421 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from flask import Flask, render_template, request, jsonify, redirect, url_for, flash, session
2
+ from flask_login import LoginManager, UserMixin, login_user, login_required, logout_user, current_user
3
+ from flask_wtf.csrf import CSRFProtect
4
+ from flask_wtf import FlaskForm
5
+ from wtforms import StringField, PasswordField, SubmitField
6
+ from wtforms.validators import DataRequired
7
+ from werkzeug.security import generate_password_hash, check_password_hash
8
+ import arxiv
9
+ import requests
10
+ import PyPDF2
11
+ from io import BytesIO
12
+ from langchain.text_splitter import RecursiveCharacterTextSplitter
13
+ from langchain_groq import ChatGroq
14
+ from langchain.memory import ConversationBufferMemory
15
+ from langchain_community.embeddings import HuggingFaceEmbeddings
16
+ import numpy as np
17
+ from concurrent.futures import ThreadPoolExecutor, TimeoutError
18
+ from functools import lru_cache
19
+ import time
20
+ import os
21
+ from dotenv import load_dotenv
22
+ import json
23
+ from datetime import datetime
24
+ import firebase_admin
25
+ from firebase_admin import credentials, auth
26
+
27
+ # Load environment variables
28
+ load_dotenv()
29
+
30
+ app = Flask(__name__)
31
+ app.secret_key = os.getenv('FLASK_SECRET_KEY')
32
+
33
+ # Initialize CSRF protection
34
+ csrf = CSRFProtect()
35
+ csrf.init_app(app)
36
+
37
+ # Initialize Flask-Login
38
+ login_manager = LoginManager()
39
+ login_manager.init_app(app)
40
+ login_manager.login_view = 'login'
41
+
42
+ # Initialize Groq
43
+ groq_api_key = os.getenv('GROQ_API_KEY')
44
+ llm = ChatGroq(
45
+ temperature=0.3,
46
+ groq_api_key=groq_api_key,
47
+ model_name="mixtral-8x7b-32768"
48
+ )
49
+
50
+ # Initialize embeddings
51
+ embeddings_model = HuggingFaceEmbeddings(
52
+ model_name="sentence-transformers/all-MiniLM-L6-v2"
53
+ )
54
+
55
+ # Constants
56
+ MAX_CHUNKS = 50
57
+ MAX_RESPONSE_LENGTH = 6000
58
+ CACHE_DURATION = 3600 # 1 hour in seconds
59
+
60
+ # Form Classes
61
+ class LoginForm(FlaskForm):
62
+ username = StringField('Username', validators=[DataRequired()])
63
+ password = PasswordField('Password', validators=[DataRequired()])
64
+ submit = SubmitField('Login')
65
+
66
+ class RegisterForm(FlaskForm):
67
+ username = StringField('Username', validators=[DataRequired()])
68
+ password = PasswordField('Password', validators=[DataRequired()])
69
+ submit = SubmitField('Register')
70
+
71
+ # User class for Flask-Login
72
+ class User(UserMixin):
73
+ def __init__(self, user_id, email):
74
+ self.id = user_id
75
+ self.email = email
76
+
77
+ def generate_analysis(chunks):
78
+ analysis_prompts = {
79
+ 'executive_summary': """
80
+ ## 🧠 Role
81
+ You are an AI assistant that explains research papers in a way that makes reading the original paper unnecessary. Your explanations should be **clear, engaging, and easy to understand**, even for someone who is not deeply familiar with the subject.
82
+
83
+ ## 🎯 Goal
84
+ Given any research paper, provide a **simple breakdown** covering:
85
+
86
+ ### 1️⃣ What problem does this paper solve?
87
+ - Explain the **issue the paper addresses**.
88
+ - Why is this problem **important**?
89
+ - What **challenges** existed before this research?
90
+
91
+ ### 2️⃣ How does it solve the problem?
92
+ - Summarize the **key idea, method, or approach** used in the paper.
93
+ - If applicable, break it down into **steps or components**.
94
+ - Compare it to **previous solutions** and highlight what makes it better.
95
+
96
+ ### 3️⃣ Why does this matter? (Real-world impact & applications)
97
+ - How can this research be **used in practice**?
98
+ - What **industries or fields** benefit from it?
99
+ - Does it improve **efficiency, accuracy, cost, or scalability**?
100
+
101
+ ### 4️⃣ Explain with a simple analogy (if applicable)
102
+ - Use a **real-life example** to explain complex ideas.
103
+ - Keep it **relatable** (e.g., compare it to something like cooking, traveling, or streaming music).
104
+
105
+ ### 5️⃣ Key findings & results
106
+ - Summarize the **main results** in simple terms.
107
+ - If possible, include **numbers, graphs, or comparisons** for clarity.
108
+
109
+ ### 6️⃣ Limitations & Future Work
110
+ - Mention any **weaknesses** or areas for improvement.
111
+ - What are the **next steps** for research in this area?
112
+
113
+ ### 7️⃣ Final Takeaway (One-liner summary)
114
+ - Provide a **quick summary** of the research in a **single sentence**.
115
+
116
+ ---
117
+
118
+ ## 🎨 Tone & Style
119
+ ✔ **Simple & clear language** – Avoid jargon unless necessary.
120
+ ✔ **Step-by-step explanations** – Organize information logically.
121
+ ✔ **Engaging & structured** – Use bullet points, lists, or tables when needed.
122
+ ✔ **Make it feel like a story** – Guide the reader smoothly from problem to solution.
123
+
124
+ ---
125
+
126
+ ## ⚡ How to Use This Prompt
127
+ 1️⃣ Enter the **title, abstract, or full text** of any research paper.
128
+ 2️⃣ AI will generate a **detailed explanation** that makes the paper easy to understand.
129
+ 3️⃣ Use it for **blog posts, study guides, or an AI-powered research assistant**.
130
+
131
+
132
+ Remember: The output should be properly formatted in markdown while providing comprehensive coverage of the paper's content."""
133
+ }
134
+
135
+ analysis_results = {}
136
+
137
+ for aspect, prompt in analysis_prompts.items():
138
+ try:
139
+ # Clean and join the chunks
140
+ context = "\n\n".join(
141
+ chunk.encode('ascii', 'ignore').decode('ascii')
142
+ for chunk in chunks[:3]
143
+ )
144
+ response = llm.invoke(
145
+ f"""Based on the following context from a research paper, {prompt}
146
+
147
+ Context:
148
+ {context}
149
+
150
+ Additional Instructions:
151
+ - Provide specific examples and evidence from the text
152
+ - Use clear, academic language
153
+ - Maintain objectivity
154
+ - Include relevant quotes or data points
155
+ - Structure your response logically
156
+ - Use markdown formatting for clarity
157
+
158
+ Please provide a clear and specific response.""",
159
+ temperature=0.3
160
+ )
161
+ analysis_results[aspect] = response.content[:MAX_RESPONSE_LENGTH]
162
+ except Exception as e:
163
+ analysis_results[aspect] = f"Analysis failed: {str(e)}"
164
+
165
+ return analysis_results
166
+
167
+ def process_pdf(pdf_url):
168
+ try:
169
+ print(f"Starting PDF processing for: {pdf_url}")
170
+
171
+ response = requests.get(pdf_url, timeout=30)
172
+ response.raise_for_status()
173
+ pdf_file = BytesIO(response.content)
174
+
175
+ pdf_reader = PyPDF2.PdfReader(pdf_file)
176
+ # Clean and normalize the text
177
+ text = " ".join(
178
+ page.extract_text().encode('ascii', 'ignore').decode('ascii')
179
+ for page in pdf_reader.pages
180
+ )
181
+
182
+ if not text.strip():
183
+ return {'error': 'No text could be extracted from the PDF'}
184
+
185
+ text_splitter = RecursiveCharacterTextSplitter(
186
+ chunk_size=2000,
187
+ chunk_overlap=200,
188
+ length_function=len,
189
+ separators=["\n\n", "\n", " ", ""]
190
+ )
191
+
192
+ chunks = text_splitter.split_text(text)[:MAX_CHUNKS]
193
+
194
+ analysis = generate_analysis(chunks)
195
+ return {
196
+ 'success': True,
197
+ 'analysis': analysis
198
+ }
199
+
200
+ except Exception as e:
201
+ return {'error': f"PDF processing failed: {str(e)}"}
202
+
203
+
204
+ @login_manager.user_loader
205
+ def load_user(user_id):
206
+ if 'user_data' in session:
207
+ user_data = session['user_data']
208
+ return User(user_data['uid'], user_data['email'])
209
+ return None
210
+
211
+ # User management functions
212
+ def load_users():
213
+ try:
214
+ with open('users.json', 'r') as f:
215
+ return json.load(f)
216
+ except FileNotFoundError:
217
+ return {}
218
+
219
+ def save_users(users):
220
+ with open('users.json', 'w') as f:
221
+ json.dump(users, f)
222
+
223
+ # Routes
224
+ @app.route('/')
225
+ @login_required
226
+ def index():
227
+ return render_template('index.html')
228
+
229
+ @app.route('/login', methods=['GET'])
230
+ def login():
231
+ if current_user.is_authenticated:
232
+ return redirect(url_for('index'))
233
+ return render_template('login.html')
234
+
235
+ @app.route('/register', methods=['GET'])
236
+ def register():
237
+ if current_user.is_authenticated:
238
+ print("User is already authenticated")
239
+ return redirect(url_for('index'))
240
+ return render_template('register.html')
241
+
242
+ @app.route('/verify-token', methods=['POST'])
243
+ def verify_token():
244
+ try:
245
+ data = request.json
246
+ if not data or not data.get('uid') or not data.get('email'):
247
+ return jsonify({'error': 'Missing required data'}), 400
248
+
249
+ # Store user data in session
250
+ session['user_data'] = {
251
+ 'uid': data['uid'],
252
+ 'email': data['email']
253
+ }
254
+
255
+ # Create and login user
256
+ user = User(data['uid'], data['email'])
257
+ login_user(user)
258
+
259
+ return jsonify({'success': True, 'redirect': url_for('index')})
260
+ except Exception as e:
261
+ print(f"Verification error: {str(e)}") # Add logging
262
+ return jsonify({'error': str(e)}), 500
263
+
264
+ @app.route('/logout')
265
+ @login_required
266
+ def logout():
267
+ logout_user()
268
+ session.clear()
269
+ return redirect(url_for('login'))
270
+
271
+ @app.route('/search', methods=['POST'])
272
+ @login_required
273
+ def search():
274
+ try:
275
+ data = request.get_json()
276
+ paper_name = data.get('paper_name')
277
+ sort_by = data.get('sort_by', 'relevance')
278
+ max_results = data.get('max_results', 10)
279
+
280
+ if not paper_name:
281
+ return jsonify({'error': 'No search query provided'}), 400
282
+
283
+ # Map sort_by to arxiv.SortCriterion
284
+ sort_mapping = {
285
+ 'relevance': arxiv.SortCriterion.Relevance,
286
+ 'lastUpdated': arxiv.SortCriterion.LastUpdatedDate,
287
+ 'submitted': arxiv.SortCriterion.SubmittedDate
288
+ }
289
+ sort_criterion = sort_mapping.get(sort_by, arxiv.SortCriterion.Relevance)
290
+
291
+ # Perform the search
292
+ search = arxiv.Search(
293
+ query=paper_name,
294
+ max_results=max_results,
295
+ sort_by=sort_criterion
296
+ )
297
+
298
+ results = []
299
+ for paper in search.results():
300
+ results.append({
301
+ 'title': paper.title,
302
+ 'authors': ', '.join(author.name for author in paper.authors),
303
+ 'abstract': paper.summary,
304
+ 'pdf_link': paper.pdf_url,
305
+ 'arxiv_link': paper.entry_id,
306
+ 'published': paper.published.strftime('%Y-%m-%d'),
307
+ 'category': paper.primary_category,
308
+ 'comment': paper.comment if hasattr(paper, 'comment') else None,
309
+ 'doi': paper.doi if hasattr(paper, 'doi') else None
310
+ })
311
+
312
+ return jsonify(results)
313
+
314
+ except Exception as e:
315
+ print(f"Search error: {str(e)}")
316
+ return jsonify({'error': f'Failed to search papers: {str(e)}'}), 500
317
+
318
+ @app.route('/perform-rag', methods=['POST'])
319
+ @login_required
320
+ def perform_rag():
321
+ try:
322
+ pdf_url = request.json.get('pdf_url')
323
+ if not pdf_url:
324
+ return jsonify({'error': 'PDF URL is required'}), 400
325
+
326
+ result = process_pdf(pdf_url)
327
+
328
+ if 'error' in result:
329
+ return jsonify({'error': result['error']}), 500
330
+
331
+ return jsonify(result)
332
+
333
+ except Exception as e:
334
+ return jsonify({'error': str(e)}), 500
335
+
336
+ @app.route('/chat-with-paper', methods=['POST'])
337
+ @login_required
338
+ def chat_with_paper():
339
+ try:
340
+ pdf_url = request.json.get('pdf_url')
341
+ question = request.json.get('question')
342
+
343
+ if not pdf_url or not question:
344
+ return jsonify({'error': 'PDF URL and question are required'}), 400
345
+
346
+ # Get PDF text and create chunks
347
+ response = requests.get(pdf_url, timeout=30)
348
+ response.raise_for_status()
349
+ pdf_file = BytesIO(response.content)
350
+
351
+ pdf_reader = PyPDF2.PdfReader(pdf_file)
352
+ text = " ".join(page.extract_text() for page in pdf_reader.pages)
353
+
354
+ if not text.strip():
355
+ return jsonify({'error': 'No text could be extracted from the PDF'})
356
+
357
+ # Create text chunks
358
+ text_splitter = RecursiveCharacterTextSplitter(
359
+ chunk_size=2000,
360
+ chunk_overlap=200,
361
+ length_function=len
362
+ )
363
+ chunks = text_splitter.split_text(text)[:MAX_CHUNKS]
364
+
365
+ # Generate embeddings for chunks
366
+ chunk_embeddings = embeddings_model.embed_documents(chunks)
367
+
368
+ # Generate embedding for the question
369
+ question_embedding = embeddings_model.embed_query(question)
370
+
371
+ # Find most relevant chunks using cosine similarity
372
+ similarities = []
373
+ for chunk_embedding in chunk_embeddings:
374
+ similarity = np.dot(question_embedding, chunk_embedding) / (
375
+ np.linalg.norm(question_embedding) * np.linalg.norm(chunk_embedding)
376
+ )
377
+ similarities.append(similarity)
378
+
379
+ # Get top 3 most relevant chunks
380
+ top_chunk_indices = np.argsort(similarities)[-3:][::-1]
381
+ relevant_chunks = [chunks[i] for i in top_chunk_indices]
382
+
383
+ # Construct prompt with relevant context
384
+ context = "\n\n".join(relevant_chunks)
385
+ prompt = f"""Based on the following relevant excerpts from the research paper, please answer this question: {question}
386
+
387
+ Context from paper:
388
+ {context}
389
+
390
+ Please provide a clear, specific, and accurate response based solely on the information provided in these excerpts. If the answer cannot be fully determined from the given context, please indicate this in your response."""
391
+
392
+ # Generate response using Groq
393
+ response = llm.invoke(prompt)
394
+
395
+ # Format and return response
396
+ formatted_response = response.content.strip()
397
+
398
+ # Add source citations
399
+ source_info = "\n\nThis response is based on specific sections from the paper."
400
+
401
+ return jsonify({
402
+ 'response': formatted_response + source_info,
403
+ 'relevance_scores': [float(similarities[i]) for i in top_chunk_indices]
404
+ })
405
+
406
+ except Exception as e:
407
+ print(f"Chat error: {str(e)}")
408
+ return jsonify({'error': f'Failed to process request: {str(e)}'}), 500
409
+
410
+ @app.route('/api/data', methods=['GET'])
411
+ def get_data():
412
+ try:
413
+ # Example: Get documents from a collection
414
+ docs = load_users()
415
+ data = [{doc_id: doc_data} for doc_id, doc_data in docs.items()]
416
+ return jsonify(data), 200
417
+ except Exception as e:
418
+ return jsonify({"error": str(e)}), 500
419
+
420
+ if __name__ == '__main__':
421
+ app.run(host='0.0.0.0', port=7860)
auth_utils.py ADDED
@@ -0,0 +1,56 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from firebase_admin import auth
2
+ from typing import Dict, Optional
3
+
4
+ def create_user(email: str, password: str) -> Dict:
5
+ """Create a new user with email and password"""
6
+ try:
7
+ user = auth.create_user(
8
+ email=email,
9
+ password=password,
10
+ email_verified=False
11
+ )
12
+ return {
13
+ "success": True,
14
+ "user_id": user.uid,
15
+ "email": user.email
16
+ }
17
+ except auth.EmailAlreadyExistsError:
18
+ return {
19
+ "success": False,
20
+ "error": "Email already exists"
21
+ }
22
+ except Exception as e:
23
+ return {
24
+ "success": False,
25
+ "error": str(e)
26
+ }
27
+
28
+ def verify_token(id_token: str) -> Optional[Dict]:
29
+ """Verify Firebase ID token"""
30
+ try:
31
+ decoded_token = auth.verify_id_token(id_token)
32
+ return decoded_token
33
+ except Exception as e:
34
+ return None
35
+
36
+ def get_user_by_email(email: str) -> Optional[Dict]:
37
+ """Get user by email"""
38
+ try:
39
+ user = auth.get_user_by_email(email)
40
+ return {
41
+ "user_id": user.uid,
42
+ "email": user.email,
43
+ "email_verified": user.email_verified
44
+ }
45
+ except auth.UserNotFoundError:
46
+ return None
47
+ except Exception:
48
+ return None
49
+
50
+ def delete_user(uid: str) -> bool:
51
+ """Delete a user by UID"""
52
+ try:
53
+ auth.delete_user(uid)
54
+ return True
55
+ except Exception:
56
+ return False
db_utils.py ADDED
@@ -0,0 +1,36 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from firebase_admin import firestore
2
+ from typing import Dict, List, Optional
3
+
4
+ def add_user_data(db: firestore.Client, user_id: str, data: Dict) -> bool:
5
+ """Add user data to Firestore"""
6
+ try:
7
+ db.collection('users').document(user_id).set(data)
8
+ return True
9
+ except Exception:
10
+ return False
11
+
12
+ def get_user_data(db: firestore.Client, user_id: str) -> Optional[Dict]:
13
+ """Get user data from Firestore"""
14
+ try:
15
+ doc = db.collection('users').document(user_id).get()
16
+ if doc.exists:
17
+ return doc.to_dict()
18
+ return None
19
+ except Exception:
20
+ return None
21
+
22
+ def update_user_data(db: firestore.Client, user_id: str, data: Dict) -> bool:
23
+ """Update user data in Firestore"""
24
+ try:
25
+ db.collection('users').document(user_id).update(data)
26
+ return True
27
+ except Exception:
28
+ return False
29
+
30
+ def delete_user_data(db: firestore.Client, user_id: str) -> bool:
31
+ """Delete user data from Firestore"""
32
+ try:
33
+ db.collection('users').document(user_id).delete()
34
+ return True
35
+ except Exception:
36
+ return False
firebase_config.py ADDED
@@ -0,0 +1,14 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import firebase_admin
2
+ from firebase_admin import credentials, firestore, auth
3
+
4
+ def initialize_firebase():
5
+ # Initialize Firebase Admin SDK
6
+ cred = credentials.Certificate('researchradarai-firebase-adminsdk-fbsvc-281fee7dee.json')
7
+ firebase_admin.initialize_app(cred, {
8
+ 'projectId': 'researchradarai',
9
+ 'storageBucket': 'researchradarai.firebasestorage.app'
10
+ })
11
+
12
+ # Initialize Firestore client
13
+ db = firestore.client()
14
+ return db
firestore.rules ADDED
@@ -0,0 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ rules_version = '2';
2
+ service cloud.firestore {
3
+ match /databases/{database}/documents {
4
+ // User profiles
5
+ match /users/{userId} {
6
+ allow read: if request.auth != null && request.auth.uid == userId;
7
+ allow write: if request.auth != null && request.auth.uid == userId;
8
+ }
9
+
10
+ // Default deny
11
+ match /{document=**} {
12
+ allow read, write: if false;
13
+ }
14
+ }
15
+ }
init_db.py ADDED
@@ -0,0 +1 @@
 
 
1
+
requirements.txt ADDED
@@ -0,0 +1,16 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ flask
2
+ arxiv
3
+ requests
4
+ PyPDF2
5
+ langchain
6
+ langchain-groq
7
+ langchain-community
8
+ python-dotenv
9
+ numpy
10
+ sentence-transformers
11
+ gradio
12
+ firebase-admin
13
+ google-cloud-firestore
14
+ google-auth
15
+ google-auth-oauthlib
16
+ google-auth-httplib2
researchradarai-firebase-adminsdk-fbsvc-281fee7dee.json ADDED
@@ -0,0 +1,13 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "type": "service_account",
3
+ "project_id": "researchradarai",
4
+ "private_key_id": "281fee7dee23aa4df991aeffb650e418e51d08d0",
5
+ "private_key": "-----BEGIN PRIVATE KEY-----\nMIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCfvkAA+uP5qyW3\nBPd9l5767K73DdRV60NBzygv9O9PjSDHCTIqpVdOMXwWSO8E4+9O5R013Dg8bVTm\ncMAZB+HnrBEWoYHFu7Jqzh0KI+WVejFlZ0rkYcekOfZ0sG43meIq9pFm8ehsIz/k\nC2Ow7FFeEd0b493oqvV3wF9c8u9l5jZVgbU7J8nyo3K638v+V22D+BPmgHEksbk2\nEiyHsypcDZmSV6woo8SCrh2vs2vSCHvqx05X6f0MxRBcmtZS6WcJJ+vvntktilpc\n+Q3hoPYLmMXwaZCaaujU12uKHYudGCrZf5PXVsPplSd63ztW2smw1oEDgYwuoxON\nmbqJwnZ9AgMBAAECggEAFI7qiSBriJfdzGESQj0/fkb0TuKeah79/TveOIB1mq+s\nq+Y4vkOgfyAKj9+mYI2+daaHjNqg+NM6nZxId1rhWEHNF97Z5wsR74GZO9MvhSjr\nBIQ3n47J9Q5p883k/A43jOnwqpFaz9f1grFzN14igVBxPAP7wimGWnlijIF4t+H6\n4HVxXCmhzz3IZcZYUKX//jg6r5lpFIVxZK5cIWDhmf9sKlD6GUR+3gKr7MQpzRJx\nsOfYbBe+KbHN0azSMNyKFNUvsDXUmGVTpjqun7Zuw9XUe6GyCM4WC9mN8+IPTZqm\n6AjnSgp9JUwZ+tJjuUo1ZSFblS2iNflUpioLsMbIDQKBgQDecLqA1vxY6+VW4v8M\n3+TuNEUe96LZA/ypLoBi07GhncxN9g2LuWnRpDyiZmbey3UQmFk/QrUem/j0UNzY\nYfLcfuE5UmXFUeySS+jJfjqvXp+996zYfSYVbOEba1QJjBPcOFsaXlk2AP0cWv36\nFRWF/Iug4kbX+JGyK+LvGQEaHwKBgQC31/rmW5m7XNA61gtbtK5Fg6KKBJB/27YL\n7aLSOEUmMYgfRiLB666y+4jdZDA3EkjKX88ddiVuoVyZuDGTsr0/ifG8hGOoSw0u\nvbykMBezPLPRkQyD1TBiErxBXa++WyNaHLEJFymCOCja7Xvxc141U8pSnKUGmUr1\nr/sxTCgT4wKBgF4It78poyoQJzaQ5ZNSvxu3+xR5SV2GovP+VYXxyiDxbWHzx4wu\nyL44OO3Kbmh3CDkIvonQsHKNKzRVTtcmqR1vgdTSsXU2CdVaw8ESXMqwLMWJA4fN\neCEMkykdOTyD/A1XwaOnCP2cc2PeT4m+CghHV9uebKZ2TVlN1jSPlHivAoGBAI14\nppqd4q9LvPGJxTPM1k6n/Ww4FvpNGMoVFDaxFoVNmHJ4hka0Fruk1K2Ja5D1gQ46\nrCb/w85eXePs2jnOUdOTU3K+bfITzxEo8QFoANTs4XNjKz5Hz/OodzXV4meZupqd\neZ6FNGwAy2+tULN9FAH1eLwZzuRFEmn+Ak7tS+oBAoGANo1QB4ZTX6NNBaz8zdGX\ngClsWRCY1wKg7bK87bnwv9u7nSyh+7ud7pjA9Km06kFSUrgEKiFS8IensxRwaE3T\ndteGp/+bOwkCIQW3w8d7bG7SiZuJ1UWoaJC7vN+eHaUSDM9+OzaK9cjWZDf0O/6D\nsxlQDuwbqOFEa/MGqZr8ZqU=\n-----END PRIVATE KEY-----\n",
6
+ "client_email": "[email protected]",
7
+ "client_id": "114293117382677247598",
8
+ "auth_uri": "https://accounts.google.com/o/oauth2/auth",
9
+ "token_uri": "https://oauth2.googleapis.com/token",
10
+ "auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
11
+ "client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/firebase-adminsdk-fbsvc%40researchradarai.iam.gserviceaccount.com",
12
+ "universe_domain": "googleapis.com"
13
+ }
static/analysis.css ADDED
@@ -0,0 +1,246 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /* Dark theme variables */
2
+ :root {
3
+ --bg-primary: #1a1a1a;
4
+ --bg-secondary: #2a2a2a;
5
+ --text-primary: #ffffff;
6
+ --text-secondary: #cccccc;
7
+ --accent-color: #6c63ff;
8
+ --error-color: #f44336;
9
+ --success-color: #4CAF50;
10
+ }
11
+
12
+ /* Loading Overlay */
13
+ .loading-overlay {
14
+ position: fixed;
15
+ top: 0;
16
+ left: 0;
17
+ width: 100%;
18
+ height: 100%;
19
+ background: rgba(0, 0, 0, 0.9);
20
+ display: flex;
21
+ justify-content: center;
22
+ align-items: center;
23
+ z-index: 1000;
24
+ }
25
+
26
+ .loading-content {
27
+ text-align: center;
28
+ color: var(--text-primary);
29
+ }
30
+
31
+ .loading-spinner {
32
+ width: 50px;
33
+ height: 50px;
34
+ border: 3px solid var(--text-primary);
35
+ border-radius: 50%;
36
+ border-top-color: transparent;
37
+ animation: spin 1s linear infinite;
38
+ margin: 0 auto 20px;
39
+ }
40
+
41
+ /* Analysis Modal */
42
+ .analysis-modal {
43
+ position: fixed;
44
+ top: 0;
45
+ left: 0;
46
+ width: 100%;
47
+ height: 100%;
48
+ background: rgba(0, 0, 0, 0.8);
49
+ display: flex;
50
+ justify-content: center;
51
+ align-items: center;
52
+ z-index: 1000;
53
+ }
54
+
55
+ .modal-content {
56
+ background: var(--bg-primary);
57
+ width: 90%;
58
+ max-width: 800px;
59
+ max-height: 90vh;
60
+ border-radius: 8px;
61
+ overflow: hidden;
62
+ }
63
+
64
+ .modal-header {
65
+ padding: 20px;
66
+ background: var(--bg-secondary);
67
+ display: flex;
68
+ justify-content: space-between;
69
+ align-items: center;
70
+ border-bottom: 1px solid #333;
71
+ }
72
+
73
+ .modal-header h2 {
74
+ color: var(--text-primary);
75
+ margin: 0;
76
+ }
77
+
78
+ .modal-body {
79
+ padding: 20px;
80
+ overflow-y: auto;
81
+ max-height: calc(90vh - 80px);
82
+ }
83
+
84
+ .analysis-section {
85
+ background: var(--bg-secondary);
86
+ border-radius: 6px;
87
+ padding: 15px;
88
+ margin-bottom: 15px;
89
+ }
90
+
91
+ .analysis-section h3 {
92
+ color: var(--accent-color);
93
+ margin-bottom: 10px;
94
+ }
95
+
96
+ .analysis-section p {
97
+ color: var(--text-secondary);
98
+ line-height: 1.6;
99
+ }
100
+
101
+ /* Buttons */
102
+ .close-btn {
103
+ background: none;
104
+ border: none;
105
+ color: var(--text-primary);
106
+ font-size: 24px;
107
+ cursor: pointer;
108
+ padding: 0;
109
+ width: 30px;
110
+ height: 30px;
111
+ display: flex;
112
+ align-items: center;
113
+ justify-content: center;
114
+ border-radius: 50%;
115
+ transition: background 0.3s;
116
+ }
117
+
118
+ .close-btn:hover {
119
+ background: rgba(255, 255, 255, 0.1);
120
+ }
121
+
122
+ .cancel-btn {
123
+ background: var(--bg-secondary);
124
+ border: none;
125
+ color: var(--text-primary);
126
+ padding: 8px 16px;
127
+ border-radius: 4px;
128
+ cursor: pointer;
129
+ margin-top: 15px;
130
+ transition: background 0.3s;
131
+ }
132
+
133
+ .cancel-btn:hover {
134
+ background: #333;
135
+ }
136
+
137
+ /* Error Message */
138
+ .error-message {
139
+ position: fixed;
140
+ top: 20px;
141
+ right: 20px;
142
+ background: var(--error-color);
143
+ color: var(--text-primary);
144
+ padding: 12px 24px;
145
+ border-radius: 6px;
146
+ z-index: 1001;
147
+ animation: slideIn 0.3s ease-out;
148
+ }
149
+
150
+ /* Animations */
151
+ @keyframes spin {
152
+ to { transform: rotate(360deg); }
153
+ }
154
+
155
+ @keyframes slideIn {
156
+ from { transform: translateX(100%); opacity: 0; }
157
+ to { transform: translateX(0); opacity: 1; }
158
+ }
159
+
160
+ .analysis-container {
161
+ max-width: 800px;
162
+ margin: 2rem auto;
163
+ background-color: var(--card-background);
164
+ border-radius: 15px;
165
+ padding: 2rem;
166
+ box-shadow: 0 4px 15px var(--shadow-color);
167
+ }
168
+
169
+ .analysis-header {
170
+ text-align: center;
171
+ margin-bottom: 2rem;
172
+ padding-bottom: 1rem;
173
+ border-bottom: 2px solid #eee;
174
+ }
175
+
176
+ .analysis-header h2 {
177
+ color: var(--primary-color);
178
+ font-size: 1.8rem;
179
+ margin-bottom: 0.5rem;
180
+ }
181
+
182
+ .analysis-section {
183
+ margin-bottom: 2rem;
184
+ padding: 1.5rem;
185
+ background-color: #f8f9fa;
186
+ border-radius: 10px;
187
+ transition: all 0.3s ease;
188
+ }
189
+
190
+ .analysis-section:hover {
191
+ transform: translateY(-2px);
192
+ box-shadow: 0 4px 15px var(--shadow-color);
193
+ }
194
+
195
+ .analysis-section h3 {
196
+ color: var(--secondary-color);
197
+ margin-bottom: 1rem;
198
+ font-size: 1.3rem;
199
+ display: flex;
200
+ align-items: center;
201
+ gap: 0.5rem;
202
+ }
203
+
204
+ .analysis-section p {
205
+ line-height: 1.8;
206
+ color: #4a4a4a;
207
+ }
208
+
209
+ .back-button {
210
+ display: inline-block;
211
+ padding: 0.8rem 1.5rem;
212
+ background-color: var(--secondary-color);
213
+ color: white;
214
+ text-decoration: none;
215
+ border-radius: 25px;
216
+ margin-top: 2rem;
217
+ transition: all 0.3s ease;
218
+ }
219
+
220
+ .back-button:hover {
221
+ transform: translateY(-2px);
222
+ box-shadow: 0 4px 15px var(--shadow-color);
223
+ }
224
+
225
+ /* Loading Animation */
226
+ .analysis-loading {
227
+ display: flex;
228
+ flex-direction: column;
229
+ align-items: center;
230
+ gap: 1rem;
231
+ padding: 3rem;
232
+ }
233
+
234
+ .analysis-spinner {
235
+ width: 60px;
236
+ height: 60px;
237
+ border: 6px solid #f3f3f3;
238
+ border-top: 6px solid var(--secondary-color);
239
+ border-radius: 50%;
240
+ animation: spin 1s linear infinite;
241
+ }
242
+
243
+ @keyframes spin {
244
+ 0% { transform: rotate(0deg); }
245
+ 100% { transform: rotate(360deg); }
246
+ }
static/auth.css ADDED
@@ -0,0 +1,151 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ :root {
2
+ --primary-color: #00FFA3;
3
+ --background-color: #13111C;
4
+ --card-background: #1E1B2E;
5
+ --text-color: #ffffff;
6
+ --error-color: #ff4444;
7
+ }
8
+
9
+ * {
10
+ margin: 0;
11
+ padding: 0;
12
+ box-sizing: border-box;
13
+ font-family: 'Space Grotesk', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
14
+ }
15
+
16
+ body {
17
+ background: var(--background-color);
18
+ min-height: 100vh;
19
+ display: flex;
20
+ align-items: center;
21
+ justify-content: center;
22
+ }
23
+
24
+ .auth-container {
25
+ width: 100%;
26
+ max-width: 420px;
27
+ padding: 20px;
28
+ }
29
+
30
+ .auth-card {
31
+ background: var(--card-background);
32
+ padding: 40px;
33
+ border-radius: 20px;
34
+ box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3);
35
+ }
36
+
37
+ .auth-title {
38
+ color: var(--primary-color);
39
+ font-size: 2.5rem;
40
+ text-align: center;
41
+ margin-bottom: 10px;
42
+ font-weight: 700;
43
+ }
44
+
45
+ .auth-subtitle {
46
+ color: var(--text-color);
47
+ text-align: center;
48
+ opacity: 0.7;
49
+ margin-bottom: 30px;
50
+ }
51
+
52
+ .auth-form {
53
+ display: flex;
54
+ flex-direction: column;
55
+ gap: 20px;
56
+ }
57
+
58
+ .form-group {
59
+ position: relative;
60
+ }
61
+
62
+ .input-icon {
63
+ position: absolute;
64
+ left: 15px;
65
+ top: 50%;
66
+ transform: translateY(-50%);
67
+ font-size: 1.2rem;
68
+ opacity: 0.7;
69
+ }
70
+
71
+ .auth-input {
72
+ width: 100%;
73
+ padding: 15px 15px 15px 45px;
74
+ border: 1px solid rgba(255, 255, 255, 0.1);
75
+ border-radius: 10px;
76
+ background: rgba(255, 255, 255, 0.05);
77
+ color: var(--text-color);
78
+ font-size: 1rem;
79
+ transition: all 0.3s ease;
80
+ }
81
+
82
+ .auth-input:focus {
83
+ outline: none;
84
+ border-color: var(--primary-color);
85
+ background: rgba(255, 255, 255, 0.1);
86
+ }
87
+
88
+ .auth-input::placeholder {
89
+ color: rgba(255, 255, 255, 0.5);
90
+ }
91
+
92
+ .auth-button {
93
+ background: var(--primary-color);
94
+ color: var(--background-color);
95
+ border: none;
96
+ padding: 15px;
97
+ border-radius: 10px;
98
+ font-size: 1rem;
99
+ font-weight: 600;
100
+ cursor: pointer;
101
+ transition: all 0.3s ease;
102
+ margin-top: 10px;
103
+ }
104
+
105
+ .auth-button:hover {
106
+ transform: translateY(-2px);
107
+ box-shadow: 0 4px 15px rgba(0, 255, 163, 0.4);
108
+ }
109
+
110
+ .auth-footer {
111
+ text-align: center;
112
+ color: var(--text-color);
113
+ margin-top: 30px;
114
+ font-size: 0.9rem;
115
+ opacity: 0.7;
116
+ }
117
+
118
+ .auth-link {
119
+ color: var(--primary-color);
120
+ text-decoration: none;
121
+ font-weight: 600;
122
+ transition: all 0.3s ease;
123
+ }
124
+
125
+ .auth-link:hover {
126
+ opacity: 0.8;
127
+ }
128
+
129
+ .error-messages {
130
+ background: rgba(255, 68, 68, 0.1);
131
+ border: 1px solid var(--error-color);
132
+ border-radius: 10px;
133
+ padding: 15px;
134
+ }
135
+
136
+ .error-message {
137
+ color: var(--error-color);
138
+ font-size: 0.9rem;
139
+ text-align: center;
140
+ }
141
+
142
+ /* Responsive Design */
143
+ @media (max-width: 480px) {
144
+ .auth-card {
145
+ padding: 30px 20px;
146
+ }
147
+
148
+ .auth-title {
149
+ font-size: 2rem;
150
+ }
151
+ }
static/firebase-config.js ADDED
@@ -0,0 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // Your web app's Firebase configuration
2
+ const firebaseConfig = {
3
+ apiKey: "AIzaSyBDRY1Vr8rVUdyBYfX6MwFalqUeJ6Yqjo8",
4
+ authDomain: "researchradarai.firebaseapp.com",
5
+ projectId: "researchradarai",
6
+ storageBucket: "researchradarai.firebasestorage.app",
7
+ messagingSenderId: "230099674724",
8
+ appId: "1:230099674724:web:5f0dd7bcd233ebb19f6171",
9
+ measurementId: "G-SBBNQ6KT58"
10
+ };
11
+
12
+ // Initialize Firebase
13
+ firebase.initializeApp(firebaseConfig);
14
+
15
+ // For Firebase JS SDK v7.20.0 and later, measurementId is optional
static/script.js ADDED
@@ -0,0 +1,80 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ document.addEventListener('DOMContentLoaded', function() {
2
+ const lightMode = document.getElementById('lightMode');
3
+ const darkMode = document.getElementById('darkMode');
4
+ const body = document.body;
5
+
6
+ // Set initial theme based on system preference
7
+ if (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) {
8
+ body.classList.add('dark-mode');
9
+ darkMode.style.display = 'none';
10
+ lightMode.style.display = 'block';
11
+ } else {
12
+ body.classList.remove('dark-mode');
13
+ darkMode.style.display = 'block';
14
+ lightMode.style.display = 'none';
15
+ }
16
+
17
+ // Toggle theme functions
18
+ lightMode.addEventListener('click', () => {
19
+ body.classList.remove('dark-mode');
20
+ darkMode.style.display = 'block';
21
+ lightMode.style.display = 'none';
22
+ localStorage.setItem('theme', 'light');
23
+ });
24
+
25
+ darkMode.addEventListener('click', () => {
26
+ body.classList.add('dark-mode');
27
+ darkMode.style.display = 'none';
28
+ lightMode.style.display = 'block';
29
+ localStorage.setItem('theme', 'dark');
30
+ });
31
+
32
+ // Check for saved theme preference
33
+ const savedTheme = localStorage.getItem('theme');
34
+ if (savedTheme) {
35
+ if (savedTheme === 'dark') {
36
+ body.classList.add('dark-mode');
37
+ darkMode.style.display = 'none';
38
+ lightMode.style.display = 'block';
39
+ } else {
40
+ body.classList.remove('dark-mode');
41
+ darkMode.style.display = 'block';
42
+ lightMode.style.display = 'none';
43
+ }
44
+ }
45
+ });
46
+ function createPaperCard(paper) {
47
+ return `
48
+ <div class="paper-card">
49
+ <h2 class="paper-title">${paper.title}</h2>
50
+ <div class="paper-authors">
51
+ ${paper.authors.split(', ').map(author =>
52
+ `<span class="author-name">${author}</span>`
53
+ ).join('')}
54
+ </div>
55
+ <div class="paper-abstract">
56
+ ${paper.abstract}
57
+ </div>
58
+ <div class="paper-meta">
59
+ <div class="meta-info">
60
+ <span class="meta-item">
61
+ <span class="meta-icon">📅</span>
62
+ ${paper.published}
63
+ </span>
64
+ <span class="meta-item">
65
+ <span class="meta-icon">🏷️</span>
66
+ ${paper.categories.join(', ')}
67
+ </span>
68
+ </div>
69
+ <div class="paper-actions">
70
+ <button onclick="analyzeWithAI('${paper.pdf_link}')" class="action-button analyze-btn">
71
+ <span>🔍</span> Analyze with AI
72
+ </button>
73
+ <button onclick="downloadPaper('${paper.pdf_link}', '${paper.title}')" class="action-button download-btn">
74
+ <span>📥</span> Download PDF
75
+ </button>
76
+ </div>
77
+ </div>
78
+ </div>
79
+ `;
80
+ }
static/search.js ADDED
@@ -0,0 +1,780 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // Global state management
2
+ const state = {
3
+ isLoading: false,
4
+ currentPdfUrl: null
5
+ };
6
+
7
+ // Add state management for the current paper
8
+ let currentPaperState = {
9
+ pdfUrl: null,
10
+ title: null
11
+ };
12
+
13
+ // Add state management for search results
14
+ let searchState = {
15
+ lastResults: null,
16
+ lastQuery: null
17
+ };
18
+
19
+ // Utility Functions
20
+ function getCsrfToken() {
21
+ return document.querySelector('meta[name="csrf-token"]').getAttribute('content');
22
+ }
23
+
24
+ function showLoading() {
25
+ const overlay = document.getElementById('loadingOverlay');
26
+ overlay.classList.remove('hidden');
27
+ state.isLoading = true;
28
+ }
29
+
30
+ function hideLoading() {
31
+ const overlay = document.getElementById('loadingOverlay');
32
+ overlay.classList.add('hidden');
33
+ state.isLoading = false;
34
+ }
35
+
36
+ // Search Functionality
37
+ async function performSearch() {
38
+ const searchQuery = document.getElementById('searchInput').value.trim();
39
+ const sortBy = document.getElementById('sortBy').value;
40
+ const maxResults = parseInt(document.getElementById('maxResults').value);
41
+
42
+ if (!searchQuery) {
43
+ alert('Please enter a search term');
44
+ return;
45
+ }
46
+
47
+ // Show loading overlay
48
+ document.getElementById('loadingOverlay').classList.remove('hidden');
49
+
50
+ try {
51
+ const response = await fetch('/search', {
52
+ method: 'POST',
53
+ headers: {
54
+ 'Content-Type': 'application/json',
55
+ 'X-CSRFToken': getCsrfToken()
56
+ },
57
+ body: JSON.stringify({
58
+ paper_name: searchQuery,
59
+ sort_by: sortBy,
60
+ max_results: maxResults
61
+ })
62
+ });
63
+
64
+ const data = await response.json();
65
+
66
+ // Save the results to the state
67
+ searchState.lastResults = data;
68
+ searchState.lastQuery = searchQuery;
69
+
70
+ // Get results section and clear it
71
+ const resultsSection = document.getElementById('resultsSection');
72
+ resultsSection.innerHTML = '';
73
+
74
+ if (!response.ok) {
75
+ throw new Error(data.error || 'Search failed');
76
+ }
77
+
78
+ // Show results section
79
+ showResults();
80
+
81
+ if (data.length === 0) {
82
+ resultsSection.innerHTML = `
83
+ <div class="no-results">
84
+ <p>No papers found matching your search.</p>
85
+ </div>`;
86
+ return;
87
+ }
88
+
89
+ // Create results container
90
+ const resultsGrid = document.createElement('div');
91
+ resultsGrid.className = 'results-grid';
92
+
93
+ // Add each paper to the grid
94
+ data.forEach(paper => {
95
+ const paperCard = document.createElement('div');
96
+ paperCard.className = 'paper-card';
97
+ paperCard.innerHTML = `
98
+ <h3 class="paper-title">${escapeHtml(paper.title)}</h3>
99
+ <p class="paper-authors">Authors: ${escapeHtml(paper.authors)}</p>
100
+ <p class="paper-category">Category: ${escapeHtml(paper.category)}</p>
101
+ <p class="paper-date">Published: ${escapeHtml(paper.published)}</p>
102
+ <p class="paper-abstract">${escapeHtml(paper.abstract.substring(0, 200))}...</p>
103
+ <div class="paper-actions">
104
+ <a href="${paper.pdf_link}" target="_blank" class="action-button pdf-button">PDF</a>
105
+ <a href="${paper.arxiv_link}" target="_blank" class="action-button arxiv-button">ARXIV</a>
106
+ <button onclick="analyzePaper('${paper.pdf_link}', '${escapeHtml(paper.title)}')" class="action-button analyze">Analyze</button>
107
+ </div>
108
+ `;
109
+ resultsGrid.appendChild(paperCard);
110
+ });
111
+
112
+ resultsSection.appendChild(resultsGrid);
113
+
114
+ } catch (error) {
115
+ console.error('Search error:', error);
116
+ document.getElementById('resultsSection').innerHTML = `
117
+ <div class="error-message">
118
+ <p>Failed to search papers: ${error.message}</p>
119
+ </div>`;
120
+ } finally {
121
+ // Hide loading overlay
122
+ document.getElementById('loadingOverlay').classList.add('hidden');
123
+ }
124
+ }
125
+
126
+ // Helper function to escape HTML and prevent XSS
127
+ function escapeHtml(unsafe) {
128
+ return unsafe
129
+ .replace(/&/g, "&amp;")
130
+ .replace(/</g, "&lt;")
131
+ .replace(/>/g, "&gt;")
132
+ .replace(/"/g, "&quot;")
133
+ .replace(/'/g, "&#039;");
134
+ }
135
+
136
+ // Display Functions
137
+ function displayResults(results) {
138
+ const resultsSection = document.getElementById('resultsSection');
139
+ resultsSection.innerHTML = '';
140
+
141
+ const resultsGrid = document.createElement('div');
142
+ resultsGrid.className = 'results-grid';
143
+
144
+ results.forEach((paper, index) => {
145
+ // Create paper card
146
+ const paperCard = document.createElement('div');
147
+ paperCard.className = 'paper-card';
148
+
149
+ // Build the card HTML - removed save button
150
+ paperCard.innerHTML = `
151
+ <div class="paper-date">${paper.published}</div>
152
+ <h2 class="paper-title">${paper.title}</h2>
153
+ <div class="paper-author">${paper.authors}</div>
154
+ <p class="paper-abstract">${paper.abstract}</p>
155
+ <div class="paper-actions">
156
+ <div class="action-row">
157
+ <button class="action-link view-pdf">VIEW PDF</button>
158
+ <button class="action-link arxiv">ARXIV</button>
159
+ <button class="action-button analyze">ANALYZE</button>
160
+ </div>
161
+ </div>
162
+ `;
163
+
164
+ // Add the paper card to the grid
165
+ resultsGrid.appendChild(paperCard);
166
+
167
+ // Add click handlers for remaining buttons
168
+ const viewPdfButton = paperCard.querySelector('.view-pdf');
169
+ viewPdfButton.addEventListener('click', () => window.open(paper.pdf_link, '_blank'));
170
+
171
+ const arxivButton = paperCard.querySelector('.arxiv');
172
+ arxivButton.addEventListener('click', () => window.open(paper.arxiv_link, '_blank'));
173
+
174
+ const analyzeButton = paperCard.querySelector('.analyze');
175
+ analyzeButton.addEventListener('click', () => analyzePaper(paper.pdf_link, paper.title));
176
+ });
177
+
178
+ resultsSection.appendChild(resultsGrid);
179
+ }
180
+
181
+ // Analysis Functions
182
+ async function analyzePaper(pdfUrl, paperTitle) {
183
+ // Update current paper state
184
+ currentPaperState.pdfUrl = pdfUrl;
185
+ currentPaperState.title = paperTitle;
186
+
187
+ // Show loading overlay
188
+ showLoading();
189
+
190
+ try {
191
+ const response = await fetch('/perform-rag', {
192
+ method: 'POST',
193
+ headers: {
194
+ 'Content-Type': 'application/json',
195
+ 'X-CSRFToken': getCsrfToken()
196
+ },
197
+ body: JSON.stringify({
198
+ pdf_url: pdfUrl,
199
+ paper_title: paperTitle
200
+ })
201
+ });
202
+
203
+ const data = await response.json();
204
+
205
+ if (data.error) {
206
+ throw new Error(data.error);
207
+ }
208
+
209
+ // Show results section
210
+ const resultsSection = document.getElementById('resultsSection');
211
+ resultsSection.innerHTML = `
212
+ <div class="analysis-container">
213
+ <div class="analysis-header">
214
+ <button onclick="backToSearchResults()" class="back-button">
215
+ <span>←</span> Back to Results
216
+ </button>
217
+ <h2 class="current-paper-title">${paperTitle}</h2>
218
+ </div>
219
+
220
+ <div class="analysis-tabs">
221
+ <button id="paperAnalysisTab"
222
+ class="tab-button active"
223
+ onclick="switchPaperTab('analysis')">
224
+ Paper Analysis
225
+ </button>
226
+ <button id="chatWithPaperTab"
227
+ class="tab-button"
228
+ onclick="switchPaperTab('chat')">
229
+ Chat with Paper
230
+ </button>
231
+ </div>
232
+
233
+ <div id="analysisContent" class="tab-content active">
234
+ <div class="analysis-section">
235
+ <h3>Research Paper Analysis</h3>
236
+ <div class="analysis-content">
237
+ ${marked.parse(data.analysis.executive_summary)}
238
+ </div>
239
+ </div>
240
+ </div>
241
+
242
+ <div id="chatContent" class="tab-content">
243
+ <div class="chat-container">
244
+ <div class="quick-questions">
245
+ <button class="quick-question-btn" onclick="handleQuickQuestion('What are the main findings?')">
246
+ What are the main findings?
247
+ </button>
248
+ <button class="quick-question-btn" onclick="handleQuickQuestion('Explain the methodology')">
249
+ Explain the methodology
250
+ </button>
251
+ <button class="quick-question-btn" onclick="handleQuickQuestion('Key contributions?')">
252
+ Key contributions?
253
+ </button>
254
+ <button class="quick-question-btn" onclick="handleQuickQuestion('Future research directions?')">
255
+ Future research directions?
256
+ </button>
257
+ </div>
258
+ <div class="chat-messages" id="chatMessages">
259
+ <div class="message-wrapper">
260
+ <div class="message-avatar ai-avatar">AI</div>
261
+ <div class="message ai-message">
262
+ Hello! I'm here to help you understand this research paper better.
263
+ Feel free to ask any questions about the paper's content, methodology,
264
+ findings, or implications.
265
+ </div>
266
+ </div>
267
+ </div>
268
+ <div class="chat-input-area">
269
+ <input type="text"
270
+ class="chat-input"
271
+ placeholder="Ask a question about the paper..."
272
+ id="chatInput">
273
+ <button class="chat-send-button" onclick="sendMessage()">
274
+ <span>Send</span>
275
+ <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
276
+ <path d="M22 2L11 13M22 2l-7 20-4-9-9-4 20-7z"/>
277
+ </svg>
278
+ </button>
279
+ </div>
280
+ </div>
281
+ </div>
282
+ </div>
283
+ `;
284
+
285
+ } catch (error) {
286
+ console.error('Analysis error:', error);
287
+ document.getElementById('resultsSection').innerHTML = `
288
+ <div class="error-message">
289
+ Failed to analyze paper: ${error.message}
290
+ </div>
291
+ `;
292
+ } finally {
293
+ hideLoading();
294
+ }
295
+ }
296
+
297
+ // Function to switch between paper analysis and chat tabs
298
+ function switchPaperTab(tab) {
299
+ const analysisBtnTab = document.getElementById('paperAnalysisTab');
300
+ const chatBtnTab = document.getElementById('chatWithPaperTab');
301
+ const analysisContent = document.getElementById('analysisContent');
302
+ const chatContent = document.getElementById('chatContent');
303
+
304
+ if (tab === 'analysis') {
305
+ analysisBtnTab.classList.add('active');
306
+ chatBtnTab.classList.remove('active');
307
+ analysisContent.classList.add('active');
308
+ chatContent.classList.remove('active');
309
+ } else {
310
+ chatBtnTab.classList.add('active');
311
+ analysisBtnTab.classList.remove('active');
312
+ chatContent.classList.add('active');
313
+ analysisContent.classList.remove('active');
314
+ }
315
+ }
316
+
317
+ // Function to handle chat messages
318
+ async function sendMessage() {
319
+ const chatInput = document.getElementById('chatInput');
320
+ const question = chatInput.value.trim();
321
+
322
+ if (!question || !currentPaperState.pdfUrl) return;
323
+
324
+ const chatMessages = document.getElementById('chatMessages');
325
+
326
+ // Add user message
327
+ addMessage(question, true);
328
+
329
+ // Add AI thinking message with loading animation
330
+ const thinkingMessageId = 'ai-thinking-' + Date.now();
331
+ addMessage(`${thinkingMessageId} AI is thinking...`, false);
332
+
333
+ chatInput.value = '';
334
+ chatMessages.scrollTop = chatMessages.scrollHeight;
335
+
336
+ try {
337
+ const response = await fetch('/chat-with-paper', {
338
+ method: 'POST',
339
+ headers: {
340
+ 'Content-Type': 'application/json',
341
+ 'X-CSRFToken': getCsrfToken()
342
+ },
343
+ body: JSON.stringify({
344
+ pdf_url: currentPaperState.pdfUrl,
345
+ question: question
346
+ })
347
+ });
348
+
349
+ const data = await response.json();
350
+
351
+ if (data.error) {
352
+ throw new Error(data.error);
353
+ }
354
+
355
+ // Remove thinking message
356
+ const thinkingMessage = document.getElementById(thinkingMessageId);
357
+ if (thinkingMessage) {
358
+ thinkingMessage.remove();
359
+ }
360
+
361
+ // Add AI response
362
+ addMessage(data.response, false);
363
+
364
+ chatMessages.scrollTop = chatMessages.scrollHeight;
365
+
366
+ } catch (error) {
367
+ console.error('Chat error:', error);
368
+ // Remove thinking message
369
+ const thinkingMessage = document.getElementById(thinkingMessageId);
370
+ if (thinkingMessage) {
371
+ thinkingMessage.remove();
372
+ }
373
+
374
+ addMessage(`Error: ${error.message}`, false);
375
+ }
376
+ }
377
+
378
+ // Navigation Functions
379
+ function showHome() {
380
+ hideAllSections();
381
+ document.getElementById('homeSection').classList.add('active');
382
+ document.getElementById('backButton').style.display = 'none';
383
+ }
384
+
385
+ function showResults() {
386
+ hideAllSections();
387
+ document.getElementById('resultsSection').classList.add('active');
388
+ document.getElementById('currentSection').textContent = 'Search Results';
389
+ document.getElementById('backButton').style.display = 'block';
390
+ }
391
+
392
+ function hideAllSections() {
393
+ const sections = ['homeSection', 'historySection', 'savedSection', 'resultsSection'];
394
+ sections.forEach(section => {
395
+ document.getElementById(section).classList.remove('active');
396
+ });
397
+ }
398
+
399
+ function goBack() {
400
+ showHome();
401
+ }
402
+
403
+ // Load search history
404
+ function loadSearchHistory() {
405
+ const historyGrid = document.getElementById('searchHistory');
406
+ try {
407
+ const history = JSON.parse(localStorage.getItem('searchHistory') || '[]');
408
+
409
+ if (history.length === 0) {
410
+ historyGrid.innerHTML = '<p class="no-history">No search history yet</p>';
411
+ return;
412
+ }
413
+
414
+ historyGrid.innerHTML = '';
415
+ history.forEach(item => {
416
+ const historyCard = document.createElement('div');
417
+ historyCard.className = 'history-card';
418
+ historyCard.innerHTML = `
419
+ <h3>${item.query}</h3>
420
+ <p class="timestamp">${new Date(item.timestamp).toLocaleDateString()}</p>
421
+ <button onclick="repeatSearch('${item.query}')" class="action-button">Search Again</button>
422
+ `;
423
+ historyGrid.appendChild(historyCard);
424
+ });
425
+ } catch (error) {
426
+ console.error('Error loading history:', error);
427
+ historyGrid.innerHTML = '<p class="error-message">Error loading history</p>';
428
+ }
429
+ }
430
+
431
+ // Add notification system
432
+ function showNotification(message, type = 'info') {
433
+ const notification = document.createElement('div');
434
+ notification.className = `notification ${type}`;
435
+ notification.textContent = message;
436
+ document.body.appendChild(notification);
437
+
438
+ setTimeout(() => {
439
+ notification.classList.add('show');
440
+ setTimeout(() => {
441
+ notification.classList.remove('show');
442
+ setTimeout(() => notification.remove(), 300);
443
+ }, 2000);
444
+ }, 100);
445
+ }
446
+
447
+ // Add history functionality
448
+ function saveToHistory(query, results) {
449
+ try {
450
+ const history = JSON.parse(localStorage.getItem('searchHistory') || '[]');
451
+ history.unshift({
452
+ query,
453
+ timestamp: new Date().toISOString(),
454
+ results: results.slice(0, 3) // Save only first 3 results to save space
455
+ });
456
+ // Keep only last 10 searches
457
+ localStorage.setItem('searchHistory', JSON.stringify(history.slice(0, 10)));
458
+ } catch (error) {
459
+ console.error('Error saving to history:', error);
460
+ }
461
+ }
462
+
463
+ // Add function to repeat search from history
464
+ function repeatSearch(query) {
465
+ const searchInput = document.getElementById('searchInput');
466
+ searchInput.value = query;
467
+ showHome();
468
+ performSearch();
469
+ }
470
+
471
+ // Add tab switching functionality
472
+ function switchTab(tabName) {
473
+ // Update tab buttons
474
+ document.querySelectorAll('.tab-button').forEach(button => {
475
+ button.classList.remove('active');
476
+ if (button.textContent.toLowerCase().includes(tabName)) {
477
+ button.classList.add('active');
478
+ }
479
+ });
480
+
481
+ // Update tab content
482
+ document.querySelectorAll('.tab-content').forEach(content => {
483
+ content.classList.remove('active');
484
+ });
485
+ document.getElementById(`${tabName}Tab`).classList.add('active');
486
+ }
487
+
488
+ // Add function to return to search results
489
+ function showSearchResults() {
490
+ if (lastSearchResults) {
491
+ displayResults(lastSearchResults);
492
+ // Update navigation state
493
+ document.getElementById('currentSection').textContent = 'Search Results';
494
+ } else {
495
+ showHome();
496
+ }
497
+ }
498
+
499
+ // Add function to handle back to search
500
+ function backToSearchResults() {
501
+ if (searchState.lastResults) {
502
+ const resultsSection = document.getElementById('resultsSection');
503
+ resultsSection.innerHTML = '';
504
+
505
+ // Create results container
506
+ const resultsGrid = document.createElement('div');
507
+ resultsGrid.className = 'results-grid';
508
+
509
+ // Recreate the results from the saved state
510
+ searchState.lastResults.forEach(paper => {
511
+ const paperCard = document.createElement('div');
512
+ paperCard.className = 'paper-card';
513
+ paperCard.innerHTML = `
514
+ <h3 class="paper-title">${escapeHtml(paper.title)}</h3>
515
+ <p class="paper-authors">Authors: ${escapeHtml(paper.authors)}</p>
516
+ <p class="paper-category">Category: ${escapeHtml(paper.category)}</p>
517
+ <p class="paper-date">Published: ${escapeHtml(paper.published)}</p>
518
+ <p class="paper-abstract">${escapeHtml(paper.abstract.substring(0, 200))}...</p>
519
+ <div class="paper-actions">
520
+ <a href="${paper.pdf_link}" target="_blank" class="action-button pdf-button">PDF</a>
521
+ <a href="${paper.arxiv_link}" target="_blank" class="action-button arxiv-button">ARXIV</a>
522
+ <button onclick="analyzePaper('${paper.pdf_link}', '${escapeHtml(paper.title)}')" class="action-button analyze">Analyze</button>
523
+ </div>
524
+ `;
525
+ resultsGrid.appendChild(paperCard);
526
+ });
527
+
528
+ resultsSection.appendChild(resultsGrid);
529
+ } else {
530
+ // If no previous results, redirect to home
531
+ showHome();
532
+ }
533
+ }
534
+
535
+ // Add function to handle search history
536
+ function updateSearchHistory(searchTerm) {
537
+ try {
538
+ let searches = JSON.parse(localStorage.getItem('recentSearches') || '[]');
539
+
540
+ // Add new search with timestamp
541
+ searches.unshift({
542
+ term: searchTerm,
543
+ date: new Date().toISOString()
544
+ });
545
+
546
+ // Keep only the most recent 9 searches
547
+ searches = searches.slice(0, 9);
548
+
549
+ localStorage.setItem('recentSearches', JSON.stringify(searches));
550
+ displaySearchHistory();
551
+ } catch (error) {
552
+ console.error('Error updating search history:', error);
553
+ }
554
+ }
555
+
556
+ // Add function to display search history
557
+ function displaySearchHistory() {
558
+ const historyContainer = document.getElementById('searchHistory');
559
+ if (!historyContainer) return;
560
+
561
+ try {
562
+ const searches = JSON.parse(localStorage.getItem('recentSearches') || '[]');
563
+
564
+ historyContainer.innerHTML = `
565
+ <h2 class="history-title">Recent Searches</h2>
566
+ <div class="search-history-grid">
567
+ ${searches.map(search => `
568
+ <div class="history-item">
569
+ <h3 class="search-term">${search.term}</h3>
570
+ <div class="search-date">
571
+ ${new Date(search.date).toLocaleDateString('en-GB')}
572
+ </div>
573
+ <button
574
+ onclick="searchAgain('${search.term}')"
575
+ class="search-again-btn">
576
+ SEARCH AGAIN
577
+ </button>
578
+ </div>
579
+ `).join('')}
580
+ </div>
581
+ `;
582
+ } catch (error) {
583
+ console.error('Error displaying search history:', error);
584
+ }
585
+ }
586
+
587
+ // Add function to handle search again
588
+ function searchAgain(term) {
589
+ document.getElementById('searchInput').value = term;
590
+ handleSearch(term);
591
+ }
592
+
593
+ // Update the search container HTML in your JavaScript
594
+ function initializeSearchInterface() {
595
+ const searchSection = document.querySelector('.search-section');
596
+ if (searchSection) {
597
+ searchSection.innerHTML = `
598
+ <div class="search-container">
599
+ <input
600
+ type="text"
601
+ id="searchInput"
602
+ class="search-input"
603
+ placeholder="Enter research paper title"
604
+ autocomplete="off"
605
+ >
606
+ <select id="sortBy" class="filter-select">
607
+ <option value="relevance">Sort by Relevance</option>
608
+ <option value="lastUpdated">Sort by Last Updated</option>
609
+ </select>
610
+ <select id="maxResults" class="filter-select">
611
+ <option value="10">10 results</option>
612
+ <option value="25">25 results</option>
613
+ <option value="50">50 results</option>
614
+ </select>
615
+ <button id="searchButton" class="search-button">
616
+ SCAN
617
+ </button>
618
+ </div>
619
+ `;
620
+
621
+ // Add event listeners
622
+ const searchInput = document.getElementById('searchInput');
623
+ const searchButton = document.getElementById('searchButton');
624
+
625
+ searchInput.addEventListener('keypress', (e) => {
626
+ if (e.key === 'Enter') {
627
+ performSearch();
628
+ }
629
+ });
630
+
631
+ searchButton.addEventListener('click', performSearch);
632
+ }
633
+ }
634
+
635
+ function createChatInterface() {
636
+ return `
637
+ <div class="chat-container">
638
+ <div class="quick-questions">
639
+ <button class="quick-question-btn" onclick="handleQuickQuestion('What are the main findings?')">
640
+ What are the main findings?
641
+ </button>
642
+ <button class="quick-question-btn" onclick="handleQuickQuestion('Explain the methodology')">
643
+ Explain the methodology
644
+ </button>
645
+ <button class="quick-question-btn" onclick="handleQuickQuestion('Key contributions?')">
646
+ Key contributions?
647
+ </button>
648
+ <button class="quick-question-btn" onclick="handleQuickQuestion('Future research directions?')">
649
+ Future research directions?
650
+ </button>
651
+ </div>
652
+ <div class="chat-messages" id="chatMessages">
653
+ <div class="message-wrapper">
654
+ <div class="message-avatar ai-avatar">AI</div>
655
+ <div class="message ai-message">
656
+ Hello! I'm here to help you understand this research paper better.
657
+ Feel free to ask any questions about the paper's content, methodology,
658
+ findings, or implications.
659
+ </div>
660
+ </div>
661
+ </div>
662
+ <div class="chat-input-area">
663
+ <input type="text"
664
+ class="chat-input"
665
+ placeholder="Ask a question about the paper..."
666
+ id="chatInput">
667
+ <button class="chat-send-button" onclick="sendMessage()">
668
+ <span>Send</span>
669
+ <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
670
+ <path d="M22 2L11 13M22 2l-7 20-4-9-9-4 20-7z"/>
671
+ </svg>
672
+ </button>
673
+ </div>
674
+ </div>
675
+ `;
676
+ }
677
+
678
+ function addMessage(message, isUser = false) {
679
+ const chatMessages = document.getElementById('chatMessages');
680
+ const messageWrapper = document.createElement('div');
681
+ messageWrapper.className = 'message-wrapper';
682
+
683
+ const avatar = document.createElement('div');
684
+ avatar.className = `message-avatar ${isUser ? 'user-avatar' : 'ai-avatar'}`;
685
+ avatar.textContent = isUser ? 'You' : 'AI';
686
+
687
+ const messageDiv = document.createElement('div');
688
+ messageDiv.className = `message ${isUser ? 'user-message' : 'ai-message'}`;
689
+
690
+ // Convert markdown to HTML if it's an AI message
691
+ if (!isUser) {
692
+ messageDiv.innerHTML = marked.parse(message);
693
+ } else {
694
+ messageDiv.textContent = message;
695
+ }
696
+
697
+ messageWrapper.appendChild(avatar);
698
+ messageWrapper.appendChild(messageDiv);
699
+ chatMessages.appendChild(messageWrapper);
700
+
701
+ // Scroll to bottom
702
+ chatMessages.scrollTop = chatMessages.scrollHeight;
703
+ }
704
+
705
+ // Add this new function to handle quick questions
706
+ async function handleQuickQuestion(question) {
707
+ // Add user's question to chat
708
+ addMessage(question, true);
709
+
710
+ // Show thinking indicator
711
+ const thinkingDiv = addThinkingMessage();
712
+
713
+ try {
714
+ const response = await fetch('/chat-with-paper', {
715
+ method: 'POST',
716
+ headers: {
717
+ 'Content-Type': 'application/json',
718
+ 'X-CSRFToken': getCsrfToken()
719
+ },
720
+ body: JSON.stringify({
721
+ pdf_url: currentPaperState.pdfUrl,
722
+ question: question
723
+ })
724
+ });
725
+
726
+ const data = await response.json();
727
+
728
+ // Remove thinking indicator
729
+ thinkingDiv.remove();
730
+
731
+ if (data.error) {
732
+ addMessage(`Error: ${data.error}`, false);
733
+ return;
734
+ }
735
+
736
+ // Add AI's response to chat
737
+ addMessage(data.response, false);
738
+
739
+ } catch (error) {
740
+ // Remove thinking indicator
741
+ thinkingDiv.remove();
742
+ addMessage(`Error: ${error.message}`, false);
743
+ }
744
+ }
745
+
746
+ // Event Listeners
747
+ document.addEventListener('DOMContentLoaded', () => {
748
+ // Navigation initialization
749
+ showHome();
750
+
751
+ initializeSearchInterface();
752
+
753
+ // Search button click
754
+ const searchButton = document.getElementById('searchButton');
755
+ searchButton.addEventListener('click', performSearch);
756
+
757
+ // Search input enter key
758
+ const searchInput = document.getElementById('searchInput');
759
+ searchInput.addEventListener('keypress', (e) => {
760
+ if (e.key === 'Enter') {
761
+ performSearch();
762
+ }
763
+ });
764
+ });
765
+
766
+ function addThinkingMessage() {
767
+ const chatMessages = document.getElementById('chatMessages');
768
+ const thinkingDiv = document.createElement('div');
769
+ thinkingDiv.className = 'ai-thinking';
770
+ thinkingDiv.innerHTML = `
771
+ <div class="ai-thinking-dots">
772
+ <div class="ai-thinking-dot"></div>
773
+ <div class="ai-thinking-dot"></div>
774
+ <div class="ai-thinking-dot"></div>
775
+ </div>
776
+ `;
777
+ chatMessages.appendChild(thinkingDiv);
778
+ chatMessages.scrollTop = chatMessages.scrollHeight;
779
+ return thinkingDiv;
780
+ }
static/styles.css ADDED
@@ -0,0 +1,2271 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ :root {
2
+ --primary-color: #e91e63;
3
+ --primary-light: #f06292;
4
+ --primary-dark: #c2185b;
5
+ --secondary-color: #9c27b0;
6
+ --background-color: #13111C;
7
+ --card-background: #1E1B2E;
8
+ --surface-light: rgba(30, 27, 46, 0.6);
9
+ --surface-dark: rgba(19, 17, 28, 0.6);
10
+ --text-primary: rgba(255, 255, 255, 0.95);
11
+ --text-secondary: rgba(255, 255, 255, 0.7);
12
+ --border-color: rgba(255, 255, 255, 0.08);
13
+ --error-color: #ff4444;
14
+ --success-color: #00C851;
15
+ }
16
+
17
+ /* Base Styles */
18
+ * {
19
+ margin: 0;
20
+ padding: 0;
21
+ box-sizing: border-box;
22
+ }
23
+
24
+ body {
25
+ background: var(--background-color);
26
+ color: var(--text-primary);
27
+ font-family: 'Space Grotesk', sans-serif;
28
+ min-height: 100vh;
29
+ line-height: 1.6;
30
+ }
31
+
32
+ /* Layout */
33
+ .app-container {
34
+ min-height: 100vh;
35
+ display: flex;
36
+ flex-direction: column;
37
+ }
38
+
39
+ /* Navigation */
40
+ .top-nav {
41
+ display: flex;
42
+ justify-content: space-between;
43
+ align-items: center;
44
+ padding: 1.2rem 2.5rem;
45
+ background: rgba(30, 27, 46, 0.98);
46
+ backdrop-filter: blur(10px);
47
+ }
48
+
49
+ .logo {
50
+ color: var(--primary-color);
51
+ font-size: 1.5rem;
52
+ font-weight: 600;
53
+ letter-spacing: 0.5px;
54
+ }
55
+
56
+ .nav-right {
57
+ display: flex;
58
+ align-items: center;
59
+ gap: 2rem;
60
+ }
61
+
62
+ .user-info {
63
+ color: rgba(255, 255, 255, 0.9);
64
+ font-size: 0.95rem;
65
+ letter-spacing: 0.3px;
66
+ }
67
+
68
+ .logout-button {
69
+ color: var(--primary-color);
70
+ text-decoration: none;
71
+ font-size: 0.95rem;
72
+ font-weight: 500;
73
+ transition: all 0.3s ease;
74
+ }
75
+
76
+ .logout-button:hover {
77
+ color: #ff4081;
78
+ }
79
+
80
+ /* Search Container */
81
+ .search-container {
82
+ width: 90%;
83
+ max-width: 800px;
84
+ margin: 3rem auto;
85
+ display: flex;
86
+ flex-direction: column;
87
+ gap: 1.2rem;
88
+ }
89
+
90
+ /* Search Input Wrapper */
91
+ .search-input-wrapper {
92
+ width: 100%;
93
+ background: rgba(30, 27, 46, 0.95);
94
+ border-radius: 15px;
95
+ padding: 1rem 1.5rem;
96
+ border: 1px solid rgba(255, 255, 255, 0.08);
97
+ transition: all 0.3s ease;
98
+ }
99
+
100
+ .search-input-wrapper:focus-within {
101
+ border-color: rgba(255, 255, 255, 0.15);
102
+ box-shadow: 0 0 20px rgba(233, 30, 99, 0.1);
103
+ }
104
+
105
+ /* Search Input */
106
+ .search-input {
107
+ width: 100%;
108
+ padding: 16px 24px;
109
+ font-size: 1.1rem;
110
+ background: rgba(255, 255, 255, 0.08);
111
+ border: 2px solid rgba(255, 255, 255, 0.1);
112
+ border-radius: 16px;
113
+ color: var(--text-primary);
114
+ transition: all 0.3s ease;
115
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
116
+ }
117
+
118
+ .search-input:focus {
119
+ outline: none;
120
+ border-color: var(--primary-color);
121
+ background: rgba(255, 255, 255, 0.12);
122
+ box-shadow: 0 8px 24px rgba(233, 30, 99, 0.15);
123
+ transform: translateY(-1px);
124
+ }
125
+
126
+ .search-input::placeholder {
127
+ color: rgba(255, 255, 255, 0.5);
128
+ font-weight: 400;
129
+ }
130
+
131
+ /* Filters Row */
132
+ .filters-row {
133
+ display: flex;
134
+ align-items: center;
135
+ gap: 1rem;
136
+ justify-content: space-between;
137
+ }
138
+
139
+ /* Filter Selects */
140
+ .filter-select {
141
+ background: rgba(30, 27, 46, 0.95);
142
+ border: 1px solid rgba(255, 255, 255, 0.08);
143
+ border-radius: 12px;
144
+ color: rgba(255, 255, 255, 0.9);
145
+ padding: 0.8rem 2.5rem 0.8rem 1.2rem;
146
+ font-size: 0.95rem;
147
+ cursor: pointer;
148
+ outline: none;
149
+ flex: 1;
150
+ appearance: none;
151
+ background-image: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='white' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3e%3cpolyline points='6 9 12 15 18 9'%3e%3c/polyline%3e%3c/svg%3e");
152
+ background-repeat: no-repeat;
153
+ background-position: right 1rem center;
154
+ background-size: 1em;
155
+ transition: all 0.3s ease;
156
+ }
157
+
158
+ .filter-select:hover {
159
+ border-color: rgba(255, 255, 255, 0.15);
160
+ background-color: rgba(40, 37, 56, 0.98);
161
+ }
162
+
163
+ /* Search Button */
164
+ .search-button {
165
+ background: linear-gradient(135deg, #e91e63, #f06292);
166
+ color: white;
167
+ border: none;
168
+ padding: 0.8rem 2.5rem;
169
+ border-radius: 12px;
170
+ font-size: 0.95rem;
171
+ font-weight: 500;
172
+ cursor: pointer;
173
+ transition: all 0.3s ease;
174
+ text-transform: uppercase;
175
+ letter-spacing: 1px;
176
+ min-width: 120px;
177
+ box-shadow: 0 4px 15px rgba(233, 30, 99, 0.2);
178
+ }
179
+
180
+ .search-button:hover {
181
+ transform: translateY(-1px);
182
+ box-shadow: 0 6px 20px rgba(233, 30, 99, 0.3);
183
+ background: linear-gradient(135deg, #ec407a, #f48fb1);
184
+ }
185
+
186
+ .search-button:active {
187
+ transform: translateY(0);
188
+ box-shadow: 0 2px 10px rgba(233, 30, 99, 0.2);
189
+ }
190
+
191
+ /* Results Section */
192
+ .results-section {
193
+ margin-top: 2rem;
194
+ padding: 0 2rem;
195
+ }
196
+
197
+ /* Loading Overlay */
198
+ .loading-overlay {
199
+ position: fixed;
200
+ top: 0;
201
+ left: 0;
202
+ right: 0;
203
+ bottom: 0;
204
+ background: rgba(13, 12, 20, 0.8);
205
+ backdrop-filter: blur(5px);
206
+ display: flex;
207
+ justify-content: center;
208
+ align-items: center;
209
+ z-index: 1000;
210
+ }
211
+
212
+ .loading-overlay.hidden {
213
+ display: none;
214
+ }
215
+
216
+ /* Responsive Design */
217
+ @media (max-width: 768px) {
218
+ .top-nav {
219
+ padding: 1rem 1.5rem;
220
+ }
221
+
222
+ .search-container {
223
+ margin: 2rem auto;
224
+ }
225
+
226
+ .filters-row {
227
+ flex-direction: column;
228
+ gap: 0.8rem;
229
+ }
230
+
231
+ .filter-select,
232
+ .search-button {
233
+ width: 100%;
234
+ }
235
+ }
236
+
237
+ /* Main Content */
238
+ .main-content {
239
+ flex: 1;
240
+ max-width: 1200px;
241
+ margin: 0 auto;
242
+ padding: 2rem;
243
+ width: 100%;
244
+ }
245
+
246
+ /* Search Section */
247
+ .search-section {
248
+ margin: 2rem auto;
249
+ max-width: 700px;
250
+ }
251
+
252
+ .results-container {
253
+ padding: 2rem;
254
+ max-width: 1200px;
255
+ margin: 0 auto;
256
+ }
257
+
258
+ .results-count {
259
+ color: var(--text-primary);
260
+ font-size: 1.1rem;
261
+ margin-bottom: 1.5rem;
262
+ opacity: 0.8;
263
+ }
264
+
265
+ .results-grid {
266
+ display: grid;
267
+ grid-template-columns: repeat(auto-fill, minmax(450px, 1fr));
268
+ gap: 1.5rem;
269
+ padding: 2rem;
270
+ max-width: 1400px;
271
+ margin: 0 auto;
272
+ }
273
+
274
+ .paper-card {
275
+ background: var(--card-background);
276
+ border-radius: 15px;
277
+ padding: 1.5rem;
278
+ display: flex;
279
+ flex-direction: column;
280
+ gap: 1rem;
281
+ height: 100%;
282
+ }
283
+
284
+ .paper-card:hover {
285
+ transform: translateY(-2px);
286
+ box-shadow: 0 8px 24px rgba(0, 0, 0, 0.2);
287
+ }
288
+
289
+ .paper-meta {
290
+ display: flex;
291
+ justify-content: space-between;
292
+ align-items: center;
293
+ margin-bottom: 1rem;
294
+ }
295
+
296
+ .paper-date {
297
+ color: var(--primary-color);
298
+ font-size: 0.9rem;
299
+ opacity: 0.9;
300
+ }
301
+
302
+ .paper-title {
303
+ color: var(--text-primary);
304
+ font-size: 1.5rem;
305
+ line-height: 1.3;
306
+ margin: 0;
307
+ font-weight: 600;
308
+ }
309
+
310
+ .paper-author {
311
+ color: var(--text-primary);
312
+ font-size: 1rem;
313
+ opacity: 0.9;
314
+ }
315
+
316
+ .paper-abstract {
317
+ color: var(--text-primary);
318
+ font-size: 0.95rem;
319
+ line-height: 1.5;
320
+ opacity: 0.7;
321
+ margin: 0;
322
+ display: -webkit-box;
323
+ -webkit-line-clamp: 3;
324
+ -webkit-box-orient: vertical;
325
+ overflow: hidden;
326
+ }
327
+
328
+ .paper-actions {
329
+ display: flex;
330
+ gap: 12px;
331
+ justify-content: space-between;
332
+ align-items: center;
333
+ margin-top: auto;
334
+ }
335
+
336
+ .action-button {
337
+ flex: 1;
338
+ padding: 12px 20px;
339
+ border-radius: 12px;
340
+ font-size: 0.95rem;
341
+ font-weight: 600;
342
+ cursor: pointer;
343
+ transition: all 0.3s ease;
344
+ text-transform: uppercase;
345
+ letter-spacing: 0.5px;
346
+ text-align: center;
347
+ text-decoration: none;
348
+ border: none;
349
+ }
350
+
351
+ .action-button.pdf-button {
352
+ background: linear-gradient(135deg, #FF4B2B, #FF416C);
353
+ color: white;
354
+ }
355
+
356
+ .action-button.arxiv-button {
357
+ background: linear-gradient(135deg, #4A00E0, #8E2DE2);
358
+ color: white;
359
+ }
360
+
361
+ .action-button.analyze {
362
+ background: linear-gradient(135deg, #00B4DB, #0083B0);
363
+ color: white;
364
+ }
365
+
366
+ .action-button:hover {
367
+ transform: translateY(-2px);
368
+ box-shadow: 0 8px 20px rgba(0, 0, 0, 0.2);
369
+ filter: brightness(110%);
370
+ }
371
+
372
+ .action-button:active {
373
+ transform: translateY(0);
374
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
375
+ }
376
+
377
+ /* Add subtle glow effect on hover */
378
+ .action-button.pdf-button:hover {
379
+ box-shadow: 0 8px 20px rgba(255, 75, 43, 0.3);
380
+ }
381
+
382
+ .action-button.arxiv-button:hover {
383
+ box-shadow: 0 8px 20px rgba(142, 45, 226, 0.3);
384
+ }
385
+
386
+ .action-button.analyze:hover {
387
+ box-shadow: 0 8px 20px rgba(0, 180, 219, 0.3);
388
+ }
389
+
390
+ @media (max-width: 768px) {
391
+ .paper-actions {
392
+ flex-direction: column;
393
+ gap: 8px;
394
+ }
395
+
396
+ .action-button {
397
+ width: 100%;
398
+ }
399
+ }
400
+
401
+ /* Analysis Section */
402
+ .analysis-container {
403
+ background: var(--card-background);
404
+ border-radius: 10px;
405
+ padding: 20px;
406
+ margin-top: 20px;
407
+ }
408
+
409
+ .analysis-section {
410
+ margin-bottom: 2rem;
411
+ }
412
+
413
+ .analysis-section h3 {
414
+ margin-bottom: 1rem;
415
+ color: var(--primary-color);
416
+ }
417
+
418
+ /* Analysis Section Tabs */
419
+ .analysis-tabs {
420
+ display: flex;
421
+ gap: 0;
422
+ padding: 1.5rem 2rem;
423
+ background: rgba(19, 17, 28, 0.95);
424
+ border-bottom: 1px solid rgba(255, 255, 255, 0.08);
425
+ position: relative;
426
+ }
427
+
428
+ .tab-button {
429
+ background: transparent;
430
+ border: none;
431
+ color: rgba(255, 255, 255, 0.6);
432
+ padding: 1rem 2.5rem;
433
+ font-size: 1rem;
434
+ font-weight: 500;
435
+ cursor: pointer;
436
+ transition: all 0.3s ease;
437
+ position: relative;
438
+ text-transform: uppercase;
439
+ letter-spacing: 1px;
440
+ }
441
+
442
+ .tab-button:hover {
443
+ color: rgba(255, 255, 255, 0.9);
444
+ }
445
+
446
+ .tab-button.active {
447
+ color: #E91E63;
448
+ font-weight: 600;
449
+ }
450
+
451
+ .tab-button.active::after {
452
+ content: '';
453
+ position: absolute;
454
+ bottom: -1px;
455
+ left: 0;
456
+ width: 100%;
457
+ height: 2px;
458
+ background: linear-gradient(90deg, #E91E63, #F06292);
459
+ animation: slideIn 0.3s ease;
460
+ }
461
+
462
+ @keyframes slideIn {
463
+ from {
464
+ transform: scaleX(0);
465
+ }
466
+ to {
467
+ transform: scaleX(1);
468
+ }
469
+ }
470
+
471
+ /* Tab Content Container */
472
+ .tab-content {
473
+ display: none;
474
+ opacity: 0;
475
+ padding: 2rem;
476
+ transition: all 0.3s ease;
477
+ }
478
+
479
+ .tab-content.active {
480
+ display: block;
481
+ opacity: 1;
482
+ animation: fadeIn 0.4s ease;
483
+ }
484
+
485
+ @keyframes fadeIn {
486
+ from {
487
+ opacity: 0;
488
+ transform: translateY(10px);
489
+ }
490
+ to {
491
+ opacity: 1;
492
+ transform: translateY(0);
493
+ }
494
+ }
495
+
496
+ /* Paper Analysis Content */
497
+ .paper-analysis {
498
+ padding: 2rem;
499
+ }
500
+
501
+ .analysis-section {
502
+ margin-bottom: 2.5rem;
503
+ }
504
+
505
+ .analysis-section h3 {
506
+ color: var(--primary-color);
507
+ font-size: 1.3rem;
508
+ margin-bottom: 1rem;
509
+ display: flex;
510
+ align-items: center;
511
+ gap: 0.8rem;
512
+ }
513
+
514
+ .analysis-section p {
515
+ color: rgba(255, 255, 255, 0.9);
516
+ line-height: 1.7;
517
+ font-size: 1rem;
518
+ }
519
+
520
+ /* Chat Interface Styles */
521
+ .chat-container {
522
+ height: 600px;
523
+ display: flex;
524
+ flex-direction: column;
525
+ background: var(--surface-dark);
526
+ border-radius: 20px;
527
+ border: 1px solid var(--border-color);
528
+ box-shadow: 0 8px 32px rgba(0, 0, 0, 0.2);
529
+ margin: 2rem auto;
530
+ overflow: hidden;
531
+ }
532
+
533
+ .chat-messages {
534
+ flex: 1;
535
+ overflow-y: auto;
536
+ padding: 1.5rem;
537
+ display: flex;
538
+ flex-direction: column;
539
+ gap: 1.5rem;
540
+ }
541
+
542
+ .message-wrapper {
543
+ display: flex;
544
+ gap: 1rem;
545
+ align-items: flex-start;
546
+ opacity: 0;
547
+ transform: translateY(10px);
548
+ animation: messageAppear 0.3s ease forwards;
549
+ }
550
+
551
+ @keyframes messageAppear {
552
+ to {
553
+ opacity: 1;
554
+ transform: translateY(0);
555
+ }
556
+ }
557
+
558
+ .message-avatar {
559
+ width: 38px;
560
+ height: 38px;
561
+ border-radius: 12px;
562
+ display: flex;
563
+ align-items: center;
564
+ justify-content: center;
565
+ flex-shrink: 0;
566
+ font-weight: 600;
567
+ font-size: 0.9rem;
568
+ }
569
+
570
+ .user-avatar {
571
+ background: linear-gradient(135deg, var(--primary-color), var(--primary-light));
572
+ color: white;
573
+ }
574
+
575
+ .ai-avatar {
576
+ background: var(--surface-light);
577
+ border: 1px solid var(--border-color);
578
+ color: var(--text-primary);
579
+ }
580
+
581
+ .message {
582
+ max-width: 85%;
583
+ padding: 1.2rem 1.5rem;
584
+ border-radius: 16px;
585
+ line-height: 1.6;
586
+ font-size: 0.95rem;
587
+ letter-spacing: 0.2px;
588
+ }
589
+
590
+ .user-message {
591
+ background: linear-gradient(135deg, var(--primary-color), var(--primary-light));
592
+ color: white;
593
+ margin-left: auto;
594
+ border-bottom-right-radius: 4px;
595
+ box-shadow: 0 4px 15px rgba(233, 30, 99, 0.2);
596
+ }
597
+
598
+ .ai-message {
599
+ background: var(--surface-light);
600
+ color: var(--text-primary);
601
+ border: 1px solid var(--border-color);
602
+ border-bottom-left-radius: 4px;
603
+ }
604
+
605
+ /* Markdown Styles in AI Messages */
606
+ .ai-message h1,
607
+ .ai-message h2,
608
+ .ai-message h3 {
609
+ margin-top: 1.2rem;
610
+ margin-bottom: 0.8rem;
611
+ color: var(--text-primary);
612
+ font-weight: 600;
613
+ }
614
+
615
+ .ai-message h1 { font-size: 1.4rem; }
616
+ .ai-message h2 { font-size: 1.2rem; }
617
+ .ai-message h3 { font-size: 1.1rem; }
618
+
619
+ .ai-message p {
620
+ margin-bottom: 1rem;
621
+ line-height: 1.7;
622
+ }
623
+
624
+ .ai-message ul,
625
+ .ai-message ol {
626
+ margin: 1rem 0;
627
+ padding-left: 1.5rem;
628
+ }
629
+
630
+ .ai-message li {
631
+ margin-bottom: 0.5rem;
632
+ line-height: 1.6;
633
+ }
634
+
635
+ .ai-message strong {
636
+ color: var(--primary-color);
637
+ font-weight: 600;
638
+ }
639
+
640
+ .ai-message em {
641
+ color: var(--text-secondary);
642
+ font-style: italic;
643
+ }
644
+
645
+ .ai-message code {
646
+ background: var(--surface-dark);
647
+ padding: 0.2rem 0.4rem;
648
+ border-radius: 4px;
649
+ font-family: 'Fira Code', monospace;
650
+ font-size: 0.9em;
651
+ }
652
+
653
+ .ai-message pre {
654
+ background: var(--surface-dark);
655
+ padding: 1rem;
656
+ border-radius: 8px;
657
+ overflow-x: auto;
658
+ margin: 1rem 0;
659
+ }
660
+
661
+ .ai-message blockquote {
662
+ border-left: 3px solid var(--primary-color);
663
+ margin: 1rem 0;
664
+ padding-left: 1rem;
665
+ color: var(--text-secondary);
666
+ }
667
+
668
+ /* Chat Input Area */
669
+ .chat-input-area {
670
+ padding: 1.5rem;
671
+ background: var(--surface-light);
672
+ border-top: 1px solid var(--border-color);
673
+ display: flex;
674
+ gap: 1rem;
675
+ align-items: center;
676
+ }
677
+
678
+ .chat-input {
679
+ flex: 1;
680
+ background: var(--surface-dark);
681
+ border: 1px solid var(--border-color);
682
+ border-radius: 12px;
683
+ padding: 1rem 1.2rem;
684
+ color: var(--text-primary);
685
+ font-size: 0.95rem;
686
+ font-family: 'Space Grotesk', sans-serif;
687
+ transition: all 0.3s ease;
688
+ }
689
+
690
+ .chat-input:focus {
691
+ border-color: var(--primary-color);
692
+ box-shadow: 0 0 0 2px rgba(233, 30, 99, 0.1);
693
+ outline: none;
694
+ }
695
+
696
+ .chat-send-button {
697
+ background: linear-gradient(135deg, var(--primary-color), var(--primary-light));
698
+ color: white;
699
+ border: none;
700
+ padding: 1rem 1.8rem;
701
+ border-radius: 12px;
702
+ font-weight: 500;
703
+ cursor: pointer;
704
+ transition: all 0.3s ease;
705
+ text-transform: uppercase;
706
+ letter-spacing: 1px;
707
+ display: flex;
708
+ align-items: center;
709
+ gap: 0.5rem;
710
+ font-family: 'Space Grotesk', sans-serif;
711
+ box-shadow: 0 4px 15px rgba(233, 30, 99, 0.2);
712
+ }
713
+
714
+ .chat-send-button:hover {
715
+ transform: translateY(-2px);
716
+ box-shadow: 0 6px 20px rgba(233, 30, 99, 0.3);
717
+ }
718
+
719
+ .chat-send-button:active {
720
+ transform: translateY(0);
721
+ }
722
+
723
+ /* AI Thinking Animation */
724
+ .ai-thinking {
725
+ display: flex;
726
+ align-items: center;
727
+ padding: 0.8rem 1.2rem;
728
+ background: var(--surface-dark);
729
+ border-radius: 12px;
730
+ margin-left: 48px;
731
+ }
732
+
733
+ .ai-thinking-dots {
734
+ display: flex;
735
+ gap: 4px;
736
+ }
737
+
738
+ .ai-thinking-dot {
739
+ width: 6px;
740
+ height: 6px;
741
+ background: var(--primary-color);
742
+ border-radius: 50%;
743
+ animation: thinking 1.4s infinite ease-in-out;
744
+ }
745
+
746
+ .ai-thinking-dot:nth-child(1) { animation-delay: 0s; }
747
+ .ai-thinking-dot:nth-child(2) { animation-delay: 0.2s; }
748
+ .ai-thinking-dot:nth-child(3) { animation-delay: 0.4s; }
749
+
750
+ @keyframes thinking {
751
+ 0%, 80%, 100% {
752
+ transform: scale(0.8);
753
+ opacity: 0.3;
754
+ }
755
+ 40% {
756
+ transform: scale(1);
757
+ opacity: 1;
758
+ }
759
+ }
760
+
761
+ /* Scrollbar Styling */
762
+ .chat-messages::-webkit-scrollbar {
763
+ width: 8px;
764
+ }
765
+
766
+ .chat-messages::-webkit-scrollbar-track {
767
+ background: var(--surface-dark);
768
+ }
769
+
770
+ .chat-messages::-webkit-scrollbar-thumb {
771
+ background: var(--surface-light);
772
+ border-radius: 4px;
773
+ }
774
+
775
+ .chat-messages::-webkit-scrollbar-thumb:hover {
776
+ background: rgba(255, 255, 255, 0.2);
777
+ }
778
+
779
+ /* Responsive Design */
780
+ @media (max-width: 768px) {
781
+ .chat-container {
782
+ height: calc(100vh - 4rem);
783
+ margin: 1rem;
784
+ }
785
+
786
+ .quick-questions {
787
+ padding: 1rem;
788
+ }
789
+
790
+ .quick-question-btn {
791
+ padding: 0.6rem 1rem;
792
+ font-size: 0.85rem;
793
+ }
794
+
795
+ .message {
796
+ max-width: 90%;
797
+ padding: 1rem;
798
+ }
799
+
800
+ .chat-input-area {
801
+ padding: 1rem;
802
+ }
803
+
804
+ .chat-send-button {
805
+ padding: 0.8rem 1.2rem;
806
+ }
807
+
808
+ .paper-actions {
809
+ flex-direction: column;
810
+ gap: 8px;
811
+ }
812
+
813
+ .action-button {
814
+ width: 100%;
815
+ }
816
+ }
817
+
818
+ /* Breadcrumb Styles */
819
+ .breadcrumb {
820
+ padding: 1rem 2rem;
821
+ background: var(--background-color);
822
+ display: flex;
823
+ align-items: center;
824
+ gap: 1rem;
825
+ }
826
+
827
+ .back-button {
828
+ background: transparent;
829
+ border: none;
830
+ color: var(--primary-color);
831
+ cursor: pointer;
832
+ display: flex;
833
+ align-items: center;
834
+ gap: 0.5rem;
835
+ padding: 0.5rem 1rem;
836
+ border-radius: 15px;
837
+ transition: all 0.3s ease;
838
+ }
839
+
840
+ .back-button:hover {
841
+ background: rgba(255, 255, 255, 0.05);
842
+ }
843
+
844
+ /* Content Sections */
845
+ .content-section {
846
+ display: none;
847
+ padding: 2rem;
848
+ }
849
+
850
+ .content-section.active {
851
+ display: block;
852
+ }
853
+
854
+ /* History and Saved Papers Grid */
855
+ .history-grid, .saved-grid {
856
+ display: grid;
857
+ grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
858
+ gap: 2rem;
859
+ margin-top: 2rem;
860
+ }
861
+
862
+ /* Tab Styles */
863
+ .tabs-container {
864
+ background: transparent;
865
+ padding: 0;
866
+ margin-top: 0;
867
+ }
868
+
869
+ .tabs {
870
+ display: flex;
871
+ gap: 10px;
872
+ margin-bottom: 20px;
873
+ border-bottom: 1px solid rgba(255, 255, 255, 0.1);
874
+ padding-bottom: 10px;
875
+ }
876
+
877
+ .tab-button {
878
+ background: transparent;
879
+ border: none;
880
+ color: var(--text-primary);
881
+ padding: 10px 20px;
882
+ border-radius: 5px;
883
+ cursor: pointer;
884
+ transition: all 0.3s ease;
885
+ opacity: 0.7;
886
+ }
887
+
888
+ .tab-button:hover {
889
+ background: rgba(255, 255, 255, 0.1);
890
+ opacity: 1;
891
+ }
892
+
893
+ .tab-button.active {
894
+ background: var(--primary-color);
895
+ opacity: 1;
896
+ }
897
+
898
+ .tab-content {
899
+ display: none;
900
+ }
901
+
902
+ .tab-content.active {
903
+ display: block;
904
+ }
905
+
906
+ /* Chat Styles */
907
+ .chat-container {
908
+ height: 500px;
909
+ display: flex;
910
+ flex-direction: column;
911
+ }
912
+
913
+ .chat-messages {
914
+ flex: 1;
915
+ overflow-y: auto;
916
+ padding: 20px;
917
+ display: flex;
918
+ flex-direction: column;
919
+ gap: 15px;
920
+ }
921
+
922
+ .chat-input-area {
923
+ display: flex;
924
+ gap: 10px;
925
+ padding: 20px;
926
+ border-top: 1px solid rgba(255, 255, 255, 0.1);
927
+ }
928
+
929
+ .chat-input-area input {
930
+ flex: 1;
931
+ background: rgba(255, 255, 255, 0.05);
932
+ border: 1px solid rgba(255, 255, 255, 0.1);
933
+ padding: 10px 15px;
934
+ border-radius: 5px;
935
+ color: var(--text-primary);
936
+ }
937
+
938
+ .chat-send-button {
939
+ background: var(--primary-color);
940
+ color: white;
941
+ border: none;
942
+ padding: 10px 20px;
943
+ border-radius: 5px;
944
+ cursor: pointer;
945
+ transition: all 0.3s ease;
946
+ }
947
+
948
+ .chat-send-button:hover {
949
+ transform: translateY(-2px);
950
+ box-shadow: 0 4px 15px rgba(233, 30, 99, 0.4);
951
+ }
952
+
953
+ /* Message Styles */
954
+ .message {
955
+ padding: 10px 15px;
956
+ border-radius: 10px;
957
+ max-width: 80%;
958
+ }
959
+
960
+ .user-message {
961
+ background: var(--primary-color);
962
+ align-self: flex-end;
963
+ }
964
+
965
+ .ai-message {
966
+ background: var(--card-background);
967
+ align-self: flex-start;
968
+ }
969
+
970
+ .loading {
971
+ opacity: 0.7;
972
+ }
973
+
974
+ .error {
975
+ background: var(--error-color);
976
+ }
977
+
978
+ /* Add these new styles for navigation */
979
+ .analysis-navigation {
980
+ display: flex;
981
+ align-items: center;
982
+ gap: 20px;
983
+ margin-bottom: 20px;
984
+ padding: 10px 0;
985
+ border-bottom: 1px solid rgba(255, 255, 255, 0.1);
986
+ }
987
+
988
+ .back-to-results {
989
+ background: transparent;
990
+ border: none;
991
+ color: var(--primary-color);
992
+ padding: 8px 16px;
993
+ border-radius: 20px;
994
+ cursor: pointer;
995
+ display: flex;
996
+ align-items: center;
997
+ gap: 8px;
998
+ transition: all 0.3s ease;
999
+ font-size: 0.9rem;
1000
+ }
1001
+
1002
+ .back-to-results:hover {
1003
+ background: rgba(233, 30, 99, 0.1);
1004
+ transform: translateX(-2px);
1005
+ }
1006
+
1007
+ .current-paper-title {
1008
+ font-size: 1.2rem;
1009
+ color: var(--text-primary);
1010
+ margin: 0;
1011
+ flex: 1;
1012
+ white-space: nowrap;
1013
+ overflow: hidden;
1014
+ text-overflow: ellipsis;
1015
+ }
1016
+
1017
+ /* Responsive adjustments */
1018
+ @media (max-width: 768px) {
1019
+ .analysis-navigation {
1020
+ flex-direction: column;
1021
+ align-items: flex-start;
1022
+ gap: 10px;
1023
+ }
1024
+
1025
+ .current-paper-title {
1026
+ font-size: 1rem;
1027
+ }
1028
+
1029
+ .back-to-results {
1030
+ width: 100%;
1031
+ justify-content: center;
1032
+ }
1033
+ }
1034
+
1035
+ /* Saved Papers Styles */
1036
+ .saved-paper-card {
1037
+ background: var(--card-background);
1038
+ border-radius: 12px;
1039
+ padding: 20px;
1040
+ margin-bottom: 20px;
1041
+ transition: all 0.3s ease;
1042
+ border: 1px solid rgba(255, 255, 255, 0.1);
1043
+ }
1044
+
1045
+ .saved-paper-card:hover {
1046
+ transform: translateY(-2px);
1047
+ box-shadow: 0 8px 24px rgba(0, 0, 0, 0.2);
1048
+ }
1049
+
1050
+ .saved-date {
1051
+ font-size: 0.8rem;
1052
+ color: var(--primary-color);
1053
+ margin-bottom: 10px;
1054
+ opacity: 0.8;
1055
+ }
1056
+
1057
+ .empty-state {
1058
+ text-align: center;
1059
+ padding: 40px;
1060
+ color: var(--text-primary);
1061
+ }
1062
+
1063
+ .empty-icon {
1064
+ font-size: 3rem;
1065
+ margin-bottom: 20px;
1066
+ }
1067
+
1068
+ /* Notification Styles */
1069
+ .notification {
1070
+ position: fixed;
1071
+ bottom: 20px;
1072
+ right: 20px;
1073
+ padding: 12px 24px;
1074
+ border-radius: 8px;
1075
+ background: var(--card-background);
1076
+ color: var(--text-primary);
1077
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
1078
+ transform: translateY(100px);
1079
+ opacity: 0;
1080
+ transition: all 0.3s ease;
1081
+ z-index: 1000;
1082
+ }
1083
+
1084
+ .notification.show {
1085
+ transform: translateY(0);
1086
+ opacity: 1;
1087
+ }
1088
+
1089
+ .notification.success {
1090
+ border-left: 4px solid #00C851;
1091
+ }
1092
+
1093
+ .notification.error {
1094
+ border-left: 4px solid var(--error-color);
1095
+ }
1096
+
1097
+ .notification.info {
1098
+ border-left: 4px solid #33b5e5;
1099
+ }
1100
+
1101
+ /* Action Button Styles */
1102
+ .action-button.save {
1103
+ background: #00C851;
1104
+ color: white;
1105
+ }
1106
+
1107
+ .action-button.remove {
1108
+ background: var(--error-color);
1109
+ color: white;
1110
+ }
1111
+
1112
+ /* Saved Papers Grid */
1113
+ .saved-grid {
1114
+ display: grid;
1115
+ grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
1116
+ gap: 20px;
1117
+ padding: 20px;
1118
+ }
1119
+
1120
+ @media (max-width: 768px) {
1121
+ .saved-grid {
1122
+ grid-template-columns: 1fr;
1123
+ }
1124
+
1125
+ .notification {
1126
+ left: 20px;
1127
+ right: 20px;
1128
+ text-align: center;
1129
+ }
1130
+ }
1131
+
1132
+ /* Updated Saved Papers Styles */
1133
+ .section-title {
1134
+ color: var(--text-primary);
1135
+ font-size: 2rem;
1136
+ margin: 2rem 0;
1137
+ padding: 0 2rem;
1138
+ }
1139
+
1140
+ .results-grid {
1141
+ display: grid;
1142
+ grid-template-columns: repeat(auto-fill, minmax(450px, 1fr));
1143
+ gap: 1.5rem;
1144
+ padding: 2rem;
1145
+ max-width: 1400px;
1146
+ margin: 0 auto;
1147
+ }
1148
+
1149
+ .paper-card {
1150
+ background: var(--card-background);
1151
+ border-radius: 15px;
1152
+ padding: 1.5rem;
1153
+ display: flex;
1154
+ flex-direction: column;
1155
+ gap: 1rem;
1156
+ height: 100%;
1157
+ }
1158
+
1159
+ .paper-date {
1160
+ color: var(--primary-color);
1161
+ font-size: 0.9rem;
1162
+ opacity: 0.9;
1163
+ }
1164
+
1165
+ .paper-title {
1166
+ color: var(--text-primary);
1167
+ font-size: 1.5rem;
1168
+ line-height: 1.3;
1169
+ margin: 0;
1170
+ font-weight: 600;
1171
+ }
1172
+
1173
+ .paper-author {
1174
+ color: var(--text-primary);
1175
+ font-size: 1rem;
1176
+ opacity: 0.9;
1177
+ }
1178
+
1179
+ .paper-abstract {
1180
+ color: var(--text-primary);
1181
+ font-size: 0.95rem;
1182
+ line-height: 1.5;
1183
+ opacity: 0.7;
1184
+ margin: 0;
1185
+ display: -webkit-box;
1186
+ -webkit-line-clamp: 3;
1187
+ -webkit-box-orient: vertical;
1188
+ overflow: hidden;
1189
+ }
1190
+
1191
+ .paper-actions {
1192
+ margin-top: auto;
1193
+ display: flex;
1194
+ flex-direction: row;
1195
+ gap: 12px;
1196
+ justify-content: space-between;
1197
+ align-items: center;
1198
+ }
1199
+
1200
+ .action-row {
1201
+ display: flex;
1202
+ gap: 1rem;
1203
+ align-items: center;
1204
+ justify-content: center;
1205
+ }
1206
+
1207
+ .save-row {
1208
+ width: 100%;
1209
+ }
1210
+
1211
+ .action-button.remove {
1212
+ width: 100%;
1213
+ background: #ff4444;
1214
+ color: white;
1215
+ padding: 1rem;
1216
+ border-radius: 12px;
1217
+ font-weight: 600;
1218
+ text-transform: uppercase;
1219
+ border: none;
1220
+ cursor: pointer;
1221
+ transition: all 0.3s ease;
1222
+ }
1223
+
1224
+ .action-button.remove:hover {
1225
+ background: #ff5555;
1226
+ transform: translateY(-2px);
1227
+ box-shadow: 0 4px 12px rgba(255, 68, 68, 0.2);
1228
+ }
1229
+
1230
+ .empty-state {
1231
+ text-align: center;
1232
+ padding: 4rem 2rem;
1233
+ color: var(--text-primary);
1234
+ }
1235
+
1236
+ .empty-state .empty-icon {
1237
+ font-size: 3rem;
1238
+ margin-bottom: 1rem;
1239
+ }
1240
+
1241
+ .empty-state h3 {
1242
+ font-size: 1.5rem;
1243
+ margin-bottom: 0.5rem;
1244
+ }
1245
+
1246
+ .empty-state p {
1247
+ color: var(--text-primary);
1248
+ opacity: 0.7;
1249
+ }
1250
+
1251
+ /* Responsive adjustments */
1252
+ @media (max-width: 768px) {
1253
+ .results-grid {
1254
+ grid-template-columns: 1fr;
1255
+ padding: 1rem;
1256
+ }
1257
+
1258
+ .section-title {
1259
+ font-size: 1.5rem;
1260
+ margin: 1.5rem 0;
1261
+ padding: 0 1rem;
1262
+ }
1263
+
1264
+ .paper-card {
1265
+ padding: 1rem;
1266
+ }
1267
+
1268
+ .action-row {
1269
+ flex-wrap: wrap;
1270
+ }
1271
+
1272
+ .action-button, .action-link {
1273
+ width: 100%;
1274
+ text-align: center;
1275
+ }
1276
+ }
1277
+
1278
+ /* Add styles for back button and header */
1279
+ .analysis-header {
1280
+ margin: 2rem 0;
1281
+ padding: 2rem;
1282
+ background: rgba(30, 27, 46, 0.98);
1283
+ border-radius: 15px;
1284
+ border: 1px solid rgba(255, 255, 255, 0.08);
1285
+ }
1286
+
1287
+ .back-button {
1288
+ display: flex;
1289
+ align-items: center;
1290
+ gap: 0.5rem;
1291
+ background: none;
1292
+ border: none;
1293
+ color: #E91E63;
1294
+ font-size: 1rem;
1295
+ font-weight: 500;
1296
+ cursor: pointer;
1297
+ padding: 0.5rem 1rem;
1298
+ border-radius: 6px;
1299
+ transition: all 0.3s ease;
1300
+ }
1301
+
1302
+ .back-button:hover {
1303
+ background: rgba(233, 30, 99, 0.1);
1304
+ transform: translateX(-2px);
1305
+ }
1306
+
1307
+ .current-paper-title {
1308
+ color: var(--text-primary);
1309
+ font-size: 1.25rem;
1310
+ margin: 0;
1311
+ font-weight: 500;
1312
+ flex: 1;
1313
+ white-space: nowrap;
1314
+ overflow: hidden;
1315
+ text-overflow: ellipsis;
1316
+ }
1317
+
1318
+ /* Update analysis container styles */
1319
+ .analysis-container {
1320
+ padding: 2rem;
1321
+ max-width: 1200px;
1322
+ margin: 0 auto;
1323
+ }
1324
+
1325
+ /* Responsive styles */
1326
+ @media (max-width: 768px) {
1327
+ .analysis-header {
1328
+ flex-direction: column;
1329
+ align-items: flex-start;
1330
+ gap: 1rem;
1331
+ }
1332
+
1333
+ .back-button {
1334
+ width: 100%;
1335
+ justify-content: center;
1336
+ }
1337
+
1338
+ .current-paper-title {
1339
+ font-size: 1.1rem;
1340
+ text-align: center;
1341
+ width: 100%;
1342
+ }
1343
+ }
1344
+
1345
+ /* Updated button styles */
1346
+ .action-link {
1347
+ background: none;
1348
+ border: none;
1349
+ color: #4A7DFF;
1350
+ font-size: 0.95rem;
1351
+ font-weight: 600;
1352
+ padding: 12px 0;
1353
+ cursor: pointer;
1354
+ transition: all 0.2s ease;
1355
+ text-transform: uppercase;
1356
+ letter-spacing: 0.5px;
1357
+ }
1358
+
1359
+ .action-link:hover {
1360
+ opacity: 0.8;
1361
+ }
1362
+
1363
+ .action-button {
1364
+ border: none;
1365
+ padding: 12px 24px;
1366
+ border-radius: 12px;
1367
+ font-size: 0.95rem;
1368
+ font-weight: 600;
1369
+ cursor: pointer;
1370
+ transition: all 0.2s ease;
1371
+ text-transform: uppercase;
1372
+ letter-spacing: 0.5px;
1373
+ width: auto;
1374
+ text-align: center;
1375
+ }
1376
+
1377
+ .action-button.analyze {
1378
+ background: #E91E63;
1379
+ color: white;
1380
+ min-width: 120px;
1381
+ }
1382
+
1383
+ .action-button.save {
1384
+ background: #00C851;
1385
+ color: white;
1386
+ width: 100%;
1387
+ padding: 16px;
1388
+ font-size: 1rem;
1389
+ border-radius: 14px;
1390
+ margin-top: 8px;
1391
+ }
1392
+
1393
+ .paper-actions {
1394
+ display: flex;
1395
+ flex-direction: row;
1396
+ margin-top: auto;
1397
+ }
1398
+
1399
+ .action-row {
1400
+ display: flex;
1401
+ align-items: center;
1402
+ gap: 24px;
1403
+ }
1404
+
1405
+ .save-row {
1406
+ display: flex;
1407
+ width: 100%;
1408
+ margin-top: 4px;
1409
+ }
1410
+
1411
+ /* Hover effects */
1412
+ .action-button:hover {
1413
+ transform: translateY(-1px);
1414
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
1415
+ }
1416
+
1417
+ .action-button.analyze:hover {
1418
+ background: #F0196B;
1419
+ }
1420
+
1421
+ .action-button.save:hover {
1422
+ background: #00D455;
1423
+ }
1424
+
1425
+ /* Paper card adjustments */
1426
+ .paper-card {
1427
+ background: var(--card-background);
1428
+ border-radius: 16px;
1429
+ padding: 24px;
1430
+ display: flex;
1431
+ flex-direction: column;
1432
+ gap: 16px;
1433
+ min-height: 320px;
1434
+ }
1435
+
1436
+ /* Responsive adjustments */
1437
+ @media (max-width: 768px) {
1438
+ .action-row {
1439
+ flex-wrap: wrap;
1440
+ gap: 16px;
1441
+ }
1442
+
1443
+ .action-button {
1444
+ width: 100%;
1445
+ }
1446
+
1447
+ .action-link {
1448
+ text-align: center;
1449
+ padding: 12px;
1450
+ flex: 1;
1451
+ }
1452
+ }
1453
+
1454
+ /* Add these new styles */
1455
+ .action-button.remove {
1456
+ background: #ff4444;
1457
+ color: white;
1458
+ width: 100%;
1459
+ padding: 16px;
1460
+ font-size: 1rem;
1461
+ border-radius: 14px;
1462
+ margin-top: 8px;
1463
+ }
1464
+
1465
+ .action-button.remove:hover {
1466
+ background: #ff5555;
1467
+ }
1468
+
1469
+ .action-button.saved {
1470
+ background: #666;
1471
+ cursor: default;
1472
+ }
1473
+
1474
+ .action-button.saved:hover {
1475
+ transform: none;
1476
+ box-shadow: none;
1477
+ }
1478
+
1479
+ .empty-state {
1480
+ text-align: center;
1481
+ padding: 60px 20px;
1482
+ color: var(--text-primary);
1483
+ }
1484
+
1485
+ .empty-state .empty-icon {
1486
+ font-size: 48px;
1487
+ margin-bottom: 20px;
1488
+ }
1489
+
1490
+ .empty-state h3 {
1491
+ font-size: 24px;
1492
+ margin-bottom: 10px;
1493
+ }
1494
+
1495
+ .empty-state p {
1496
+ color: var(--text-primary);
1497
+ opacity: 0.7;
1498
+ }
1499
+
1500
+ /* Notification styles */
1501
+ .notification {
1502
+ position: fixed;
1503
+ bottom: 20px;
1504
+ right: 20px;
1505
+ padding: 12px 24px;
1506
+ border-radius: 8px;
1507
+ background: var(--card-background);
1508
+ color: var(--text-primary);
1509
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
1510
+ transform: translateY(100px);
1511
+ opacity: 0;
1512
+ transition: all 0.3s ease;
1513
+ z-index: 1000;
1514
+ }
1515
+
1516
+ .notification.show {
1517
+ transform: translateY(0);
1518
+ opacity: 1;
1519
+ }
1520
+
1521
+ .notification.success {
1522
+ border-left: 4px solid #00C851;
1523
+ }
1524
+
1525
+ .notification.error {
1526
+ border-left: 4px solid #ff4444;
1527
+ }
1528
+
1529
+ .notification.info {
1530
+ border-left: 4px solid #33b5e5;
1531
+ }
1532
+
1533
+ /* Updated saved papers grid styles */
1534
+ .saved-papers-grid {
1535
+ display: grid;
1536
+ grid-template-columns: repeat(2, 1fr);
1537
+ gap: 24px;
1538
+ padding: 24px;
1539
+ max-width: 1400px;
1540
+ margin: 0 auto;
1541
+ }
1542
+
1543
+ .saved-paper-card {
1544
+ background: var(--card-background);
1545
+ border-radius: 16px;
1546
+ padding: 24px;
1547
+ display: flex;
1548
+ flex-direction: column;
1549
+ gap: 16px;
1550
+ }
1551
+
1552
+ .saved-date {
1553
+ color: #E91E63;
1554
+ font-size: 14px;
1555
+ font-weight: 500;
1556
+ }
1557
+
1558
+ .paper-title {
1559
+ color: var(--text-primary);
1560
+ font-size: 24px;
1561
+ line-height: 1.3;
1562
+ margin: 0;
1563
+ font-weight: 600;
1564
+ }
1565
+
1566
+ .paper-author {
1567
+ color: var(--text-primary);
1568
+ font-size: 16px;
1569
+ opacity: 0.9;
1570
+ }
1571
+
1572
+ .paper-abstract {
1573
+ color: var(--text-primary);
1574
+ font-size: 15px;
1575
+ line-height: 1.5;
1576
+ opacity: 0.7;
1577
+ margin: 0;
1578
+ display: -webkit-box;
1579
+ -webkit-line-clamp: 3;
1580
+ -webkit-box-orient: vertical;
1581
+ overflow: hidden;
1582
+ }
1583
+
1584
+ .paper-actions {
1585
+ margin-top: auto;
1586
+ display: flex;
1587
+ flex-direction: row;
1588
+ gap: 16px;
1589
+ align-items: center;
1590
+ }
1591
+
1592
+ .action-row {
1593
+ display: flex;
1594
+ gap: 16px;
1595
+ align-items: center;
1596
+ }
1597
+
1598
+ .action-link {
1599
+ background: none;
1600
+ border: none;
1601
+ color: #4A7DFF;
1602
+ font-size: 14px;
1603
+ font-weight: 600;
1604
+ padding: 0;
1605
+ cursor: pointer;
1606
+ transition: all 0.2s ease;
1607
+ }
1608
+
1609
+ .action-button {
1610
+ border: none;
1611
+ padding: 12px 24px;
1612
+ border-radius: 12px;
1613
+ font-size: 14px;
1614
+ font-weight: 600;
1615
+ cursor: pointer;
1616
+ transition: all 0.2s ease;
1617
+ }
1618
+
1619
+ .action-button.analyze {
1620
+ background: #E91E63;
1621
+ color: white;
1622
+ }
1623
+
1624
+ .action-button.remove {
1625
+ background: #ff4444;
1626
+ color: white;
1627
+ width: 100%;
1628
+ padding: 16px;
1629
+ }
1630
+
1631
+ .save-row {
1632
+ width: 100%;
1633
+ }
1634
+
1635
+ /* Responsive styles */
1636
+ @media (max-width: 1024px) {
1637
+ .saved-papers-grid {
1638
+ grid-template-columns: 1fr;
1639
+ padding: 16px;
1640
+ }
1641
+
1642
+ .saved-paper-card {
1643
+ padding: 20px;
1644
+ }
1645
+
1646
+ .action-row {
1647
+ flex-wrap: wrap;
1648
+ gap: 12px;
1649
+ }
1650
+
1651
+ .action-button,
1652
+ .action-link {
1653
+ width: 100%;
1654
+ text-align: center;
1655
+ padding: 12px;
1656
+ }
1657
+ }
1658
+
1659
+ /* Search History Styles */
1660
+ .history-title {
1661
+ color: var(--text-primary);
1662
+ font-size: 32px;
1663
+ font-weight: 600;
1664
+ margin: 32px 24px 24px;
1665
+ }
1666
+
1667
+ .search-history-grid {
1668
+ display: grid;
1669
+ grid-template-columns: repeat(3, 1fr);
1670
+ gap: 24px;
1671
+ padding: 0 24px 24px;
1672
+ max-width: 1400px;
1673
+ margin: 0 auto;
1674
+ }
1675
+
1676
+ .history-item {
1677
+ background: var(--card-background);
1678
+ border-radius: 16px;
1679
+ padding: 24px;
1680
+ display: flex;
1681
+ flex-direction: column;
1682
+ gap: 12px;
1683
+ }
1684
+
1685
+ .search-term {
1686
+ color: var(--text-primary);
1687
+ font-size: 24px;
1688
+ font-weight: 600;
1689
+ margin: 0;
1690
+ text-transform: uppercase;
1691
+ }
1692
+
1693
+ .search-date {
1694
+ color: var(--text-primary);
1695
+ font-size: 16px;
1696
+ opacity: 0.7;
1697
+ }
1698
+
1699
+ .search-again-btn {
1700
+ background: var(--background-color);
1701
+ color: var(--text-primary);
1702
+ border: none;
1703
+ border-radius: 100px;
1704
+ padding: 16px 24px;
1705
+ font-size: 14px;
1706
+ font-weight: 600;
1707
+ cursor: pointer;
1708
+ transition: all 0.2s ease;
1709
+ text-transform: uppercase;
1710
+ margin-top: auto;
1711
+ }
1712
+
1713
+ .search-again-btn:hover {
1714
+ background: rgba(255, 255, 255, 0.1);
1715
+ transform: translateY(-2px);
1716
+ }
1717
+
1718
+ /* Empty state */
1719
+ .empty-history {
1720
+ text-align: center;
1721
+ padding: 48px 24px;
1722
+ color: var(--text-primary);
1723
+ }
1724
+
1725
+ .empty-history h3 {
1726
+ font-size: 24px;
1727
+ margin-bottom: 12px;
1728
+ }
1729
+
1730
+ .empty-history p {
1731
+ opacity: 0.7;
1732
+ }
1733
+
1734
+ /* Responsive styles */
1735
+ @media (max-width: 1200px) {
1736
+ .search-history-grid {
1737
+ grid-template-columns: repeat(2, 1fr);
1738
+ }
1739
+ }
1740
+
1741
+ @media (max-width: 768px) {
1742
+ .search-history-grid {
1743
+ grid-template-columns: 1fr;
1744
+ padding: 0 16px 16px;
1745
+ }
1746
+
1747
+ .history-title {
1748
+ font-size: 24px;
1749
+ margin: 24px 16px 16px;
1750
+ }
1751
+
1752
+ .history-item {
1753
+ padding: 20px;
1754
+ }
1755
+
1756
+ .search-term {
1757
+ font-size: 20px;
1758
+ }
1759
+ }
1760
+
1761
+ /* User Navigation Styles */
1762
+ .user-nav {
1763
+ display: flex;
1764
+ align-items: center;
1765
+ gap: 16px;
1766
+ padding: 16px 24px;
1767
+ position: absolute;
1768
+ top: 0;
1769
+ right: 0;
1770
+ }
1771
+
1772
+ .username {
1773
+ color: var(--text-primary);
1774
+ font-size: 16px;
1775
+ font-weight: 400;
1776
+ opacity: 0.9;
1777
+ }
1778
+
1779
+ .logout-button {
1780
+ background: rgba(233, 30, 99, 0.1);
1781
+ color: #E91E63;
1782
+ text-decoration: none;
1783
+ padding: 6px 16px;
1784
+ border-radius: 8px;
1785
+ font-size: 14px;
1786
+ font-weight: 500;
1787
+ transition: all 0.3s ease;
1788
+ border: 1px solid rgba(233, 30, 99, 0.2);
1789
+ }
1790
+
1791
+ .logout-button:hover {
1792
+ background: rgba(233, 30, 99, 0.2);
1793
+ transform: translateY(-1px);
1794
+ }
1795
+
1796
+ /* Responsive adjustments */
1797
+ @media (max-width: 768px) {
1798
+ .user-nav {
1799
+ padding: 12px 16px;
1800
+ }
1801
+
1802
+ .username {
1803
+ font-size: 14px;
1804
+ }
1805
+
1806
+ .logout-button {
1807
+ padding: 4px 12px;
1808
+ font-size: 13px;
1809
+ }
1810
+ }
1811
+
1812
+ /* Features Section Styles */
1813
+ .features-section {
1814
+ display: flex;
1815
+ flex-direction: column;
1816
+ gap: 24px;
1817
+ padding: 32px;
1818
+ max-width: 800px;
1819
+ margin: 0 auto;
1820
+ }
1821
+
1822
+ .feature-card {
1823
+ background: rgba(20, 22, 36, 0.7);
1824
+ border-radius: 24px;
1825
+ padding: 32px;
1826
+ }
1827
+
1828
+ .feature-icon {
1829
+ font-size: 32px;
1830
+ margin-bottom: 24px;
1831
+ width: 48px;
1832
+ height: 48px;
1833
+ background: rgba(30, 33, 43, 0.8);
1834
+ border-radius: 12px;
1835
+ display: flex;
1836
+ align-items: center;
1837
+ justify-content: center;
1838
+ }
1839
+
1840
+ .feature-title {
1841
+ color: #FFFFFF;
1842
+ font-size: 32px;
1843
+ font-weight: 600;
1844
+ margin: 0 0 16px 0;
1845
+ line-height: 1.2;
1846
+ }
1847
+
1848
+ .feature-description {
1849
+ color: rgba(255, 255, 255, 0.6);
1850
+ font-size: 20px;
1851
+ line-height: 1.5;
1852
+ margin: 0;
1853
+ }
1854
+
1855
+ /* Card-specific backgrounds */
1856
+ .feature-card:nth-child(1) {
1857
+ background: linear-gradient(145deg, rgba(20, 22, 36, 0.7), rgba(74, 125, 255, 0.1));
1858
+ }
1859
+
1860
+ .feature-card:nth-child(2) {
1861
+ background: linear-gradient(145deg, rgba(20, 22, 36, 0.7), rgba(233, 30, 99, 0.1));
1862
+ }
1863
+
1864
+ .feature-card:nth-child(3) {
1865
+ background: linear-gradient(145deg, rgba(20, 22, 36, 0.7), rgba(0, 200, 81, 0.1));
1866
+ }
1867
+
1868
+ /* Responsive styles */
1869
+ @media (max-width: 768px) {
1870
+ .features-section {
1871
+ padding: 16px;
1872
+ }
1873
+
1874
+ .feature-card {
1875
+ padding: 24px;
1876
+ }
1877
+
1878
+ .feature-title {
1879
+ font-size: 28px;
1880
+ }
1881
+
1882
+ .feature-description {
1883
+ font-size: 18px;
1884
+ }
1885
+
1886
+ .feature-icon {
1887
+ font-size: 28px;
1888
+ width: 40px;
1889
+ height: 40px;
1890
+ }
1891
+ }
1892
+
1893
+ /* Main Navigation Styles */
1894
+ .main-nav {
1895
+ background: rgba(30, 33, 43, 0.7);
1896
+ backdrop-filter: blur(10px);
1897
+ padding: 16px 24px;
1898
+ position: sticky;
1899
+ top: 0;
1900
+ z-index: 100;
1901
+ border-bottom: 1px solid rgba(255, 255, 255, 0.1);
1902
+ }
1903
+
1904
+ .nav-links {
1905
+ display: flex;
1906
+ gap: 32px;
1907
+ max-width: 1200px;
1908
+ margin: 0 auto;
1909
+ }
1910
+
1911
+ .nav-link {
1912
+ color: rgba(255, 255, 255, 0.7);
1913
+ text-decoration: none;
1914
+ font-size: 16px;
1915
+ font-weight: 500;
1916
+ padding: 8px 16px;
1917
+ border-radius: 8px;
1918
+ transition: all 0.3s ease;
1919
+ }
1920
+
1921
+ .nav-link:hover {
1922
+ color: #FFFFFF;
1923
+ background: rgba(255, 255, 255, 0.1);
1924
+ }
1925
+
1926
+ .nav-link.active {
1927
+ color: #FFFFFF;
1928
+ background: rgba(233, 30, 99, 0.1);
1929
+ position: relative;
1930
+ }
1931
+
1932
+ .nav-link.active::after {
1933
+ content: '';
1934
+ position: absolute;
1935
+ bottom: -2px;
1936
+ left: 50%;
1937
+ transform: translateX(-50%);
1938
+ width: 24px;
1939
+ height: 2px;
1940
+ background: #E91E63;
1941
+ border-radius: 2px;
1942
+ }
1943
+
1944
+ /* Responsive styles */
1945
+ @media (max-width: 768px) {
1946
+ .main-nav {
1947
+ padding: 12px 16px;
1948
+ }
1949
+
1950
+ .nav-links {
1951
+ gap: 16px;
1952
+ justify-content: center;
1953
+ }
1954
+
1955
+ .nav-link {
1956
+ font-size: 14px;
1957
+ padding: 6px 12px;
1958
+ }
1959
+ }
1960
+
1961
+ /* Make sure these styles work with your existing layout */
1962
+ .container {
1963
+ padding-top: 24px;
1964
+ }
1965
+
1966
+ /* Update feature cards to work with new navigation */
1967
+ .features-section {
1968
+ margin-top: 32px;
1969
+ }
1970
+
1971
+ /* Update other sections to maintain spacing */
1972
+ .search-history-grid,
1973
+ .saved-papers-grid {
1974
+ margin-top: 32px;
1975
+ }
1976
+
1977
+ /* Back to Search Link */
1978
+ .back-to-search {
1979
+ display: inline-flex;
1980
+ align-items: center;
1981
+ gap: 0.5rem;
1982
+ color: var(--primary-color);
1983
+ text-decoration: none;
1984
+ padding: 0.5rem 1rem;
1985
+ border-radius: 8px;
1986
+ transition: all 0.3s ease;
1987
+ margin-bottom: 1.5rem;
1988
+ font-size: 0.95rem;
1989
+ }
1990
+
1991
+ .back-to-search:hover {
1992
+ background: rgba(233, 30, 99, 0.1);
1993
+ transform: translateX(-2px);
1994
+ }
1995
+
1996
+ /* Paper Title */
1997
+ .paper-title {
1998
+ font-size: 1.8rem;
1999
+ font-weight: 600;
2000
+ color: var(--text-primary);
2001
+ margin: 1rem 0 2rem;
2002
+ line-height: 1.4;
2003
+ }
2004
+
2005
+ /* Analysis and Chat Container */
2006
+ .analysis-chat-container {
2007
+ background: linear-gradient(145deg, rgba(30, 27, 46, 0.95), rgba(19, 17, 28, 0.95));
2008
+ border-radius: 24px;
2009
+ border: 1px solid rgba(255, 255, 255, 0.08);
2010
+ box-shadow: 0 8px 32px rgba(0, 0, 0, 0.2);
2011
+ margin: 2rem auto;
2012
+ max-width: 1200px;
2013
+ overflow: hidden;
2014
+ }
2015
+
2016
+ /* Enhanced Tab Navigation */
2017
+ .analysis-tabs {
2018
+ display: flex;
2019
+ padding: 1.5rem 1.5rem 0;
2020
+ gap: 1rem;
2021
+ background: transparent;
2022
+ }
2023
+
2024
+ .tab-button {
2025
+ background: rgba(19, 17, 28, 0.6);
2026
+ border: 1px solid rgba(255, 255, 255, 0.08);
2027
+ color: rgba(255, 255, 255, 0.7);
2028
+ padding: 1rem 2rem;
2029
+ font-size: 0.95rem;
2030
+ font-weight: 500;
2031
+ cursor: pointer;
2032
+ transition: all 0.3s ease;
2033
+ border-radius: 12px 12px 0 0;
2034
+ min-width: 160px;
2035
+ }
2036
+
2037
+ .tab-button:hover {
2038
+ background: rgba(30, 27, 46, 0.8);
2039
+ color: rgba(255, 255, 255, 0.9);
2040
+ }
2041
+
2042
+ .tab-button.active {
2043
+ background: linear-gradient(135deg, var(--primary-color), #f06292);
2044
+ color: white;
2045
+ border: none;
2046
+ box-shadow: 0 4px 15px rgba(233, 30, 99, 0.2);
2047
+ }
2048
+
2049
+ /* Tab Content */
2050
+ .tab-content {
2051
+ padding: 2rem;
2052
+ display: none;
2053
+ opacity: 0;
2054
+ transform: translateY(10px);
2055
+ transition: all 0.3s ease;
2056
+ background: rgba(19, 17, 28, 0.6);
2057
+ margin: 0 1.5rem 1.5rem;
2058
+ border-radius: 0 12px 12px 12px;
2059
+ border: 1px solid rgba(255, 255, 255, 0.08);
2060
+ }
2061
+
2062
+ .tab-content.active {
2063
+ display: block;
2064
+ opacity: 1;
2065
+ transform: translateY(0);
2066
+ animation: fadeIn 0.4s ease;
2067
+ }
2068
+
2069
+ /* Analysis Section Styles */
2070
+ .analysis-section {
2071
+ margin-bottom: 2rem;
2072
+ background: rgba(30, 27, 46, 0.4);
2073
+ border-radius: 16px;
2074
+ padding: 1.8rem;
2075
+ border: 1px solid rgba(255, 255, 255, 0.05);
2076
+ transition: all 0.3s ease;
2077
+ }
2078
+
2079
+ .analysis-section:hover {
2080
+ background: rgba(30, 27, 46, 0.6);
2081
+ transform: translateY(-2px);
2082
+ box-shadow: 0 4px 20px rgba(0, 0, 0, 0.2);
2083
+ }
2084
+
2085
+ .analysis-section:last-child {
2086
+ margin-bottom: 0;
2087
+ }
2088
+
2089
+ .analysis-section h3 {
2090
+ color: var(--primary-color);
2091
+ font-size: 1.3rem;
2092
+ margin-bottom: 1.2rem;
2093
+ display: flex;
2094
+ align-items: center;
2095
+ gap: 0.8rem;
2096
+ letter-spacing: 0.5px;
2097
+ }
2098
+
2099
+ .analysis-section p {
2100
+ color: rgba(255, 255, 255, 0.9);
2101
+ line-height: 1.8;
2102
+ font-size: 1rem;
2103
+ margin: 0;
2104
+ letter-spacing: 0.2px;
2105
+ }
2106
+
2107
+ /* Enhanced Chat Interface */
2108
+ .chat-container {
2109
+ height: 600px;
2110
+ display: flex;
2111
+ flex-direction: column;
2112
+ background: transparent;
2113
+ }
2114
+
2115
+ .chat-messages {
2116
+ flex: 1;
2117
+ overflow-y: auto;
2118
+ padding: 1.5rem;
2119
+ display: flex;
2120
+ flex-direction: column;
2121
+ gap: 1.2rem;
2122
+ }
2123
+
2124
+ .message {
2125
+ max-width: 85%;
2126
+ padding: 1.2rem 1.5rem;
2127
+ border-radius: 16px;
2128
+ line-height: 1.6;
2129
+ font-size: 0.95rem;
2130
+ letter-spacing: 0.2px;
2131
+ }
2132
+
2133
+ .user-message {
2134
+ background: linear-gradient(135deg, var(--primary-color), #f06292);
2135
+ color: white;
2136
+ align-self: flex-end;
2137
+ border-bottom-right-radius: 4px;
2138
+ box-shadow: 0 4px 15px rgba(233, 30, 99, 0.2);
2139
+ }
2140
+
2141
+ .ai-message {
2142
+ background: rgba(30, 27, 46, 0.6);
2143
+ color: rgba(255, 255, 255, 0.95);
2144
+ align-self: flex-start;
2145
+ border: 1px solid rgba(255, 255, 255, 0.08);
2146
+ border-bottom-left-radius: 4px;
2147
+ }
2148
+
2149
+ .chat-input-area {
2150
+ padding: 1.5rem;
2151
+ background: rgba(19, 17, 28, 0.4);
2152
+ border-top: 1px solid rgba(255, 255, 255, 0.08);
2153
+ display: flex;
2154
+ gap: 1rem;
2155
+ align-items: center;
2156
+ }
2157
+
2158
+ .chat-input {
2159
+ flex: 1;
2160
+ background: rgba(30, 27, 46, 0.95);
2161
+ border: 1px solid rgba(255, 255, 255, 0.08);
2162
+ border-radius: 12px;
2163
+ padding: 1rem 1.2rem;
2164
+ color: white;
2165
+ font-size: 0.95rem;
2166
+ transition: all 0.3s ease;
2167
+ }
2168
+
2169
+ .chat-input:focus {
2170
+ border-color: var(--primary-color);
2171
+ box-shadow: 0 0 0 2px rgba(233, 30, 99, 0.1);
2172
+ background: rgba(30, 27, 46, 0.8);
2173
+ outline: none;
2174
+ }
2175
+
2176
+ .chat-send-button {
2177
+ background: linear-gradient(135deg, #E91E63, #f06292);
2178
+ color: white;
2179
+ border: none;
2180
+ padding: 1rem 1.8rem;
2181
+ border-radius: 12px;
2182
+ font-weight: 500;
2183
+ cursor: pointer;
2184
+ transition: all 0.3s ease;
2185
+ text-transform: uppercase;
2186
+ letter-spacing: 1px;
2187
+ display: flex;
2188
+ align-items: center;
2189
+ gap: 0.5rem;
2190
+ font-family: 'Space Grotesk', sans-serif;
2191
+ box-shadow: 0 4px 15px rgba(233, 30, 99, 0.2);
2192
+ }
2193
+
2194
+ .chat-send-button:hover {
2195
+ transform: translateY(-2px);
2196
+ box-shadow: 0 6px 20px rgba(233, 30, 99, 0.3);
2197
+ }
2198
+
2199
+ .chat-send-button:active {
2200
+ transform: translateY(0);
2201
+ }
2202
+
2203
+ /* Quick Questions Section */
2204
+ .quick-questions {
2205
+ padding: 1rem 1.5rem;
2206
+ border-bottom: 1px solid rgba(255, 255, 255, 0.08);
2207
+ display: flex;
2208
+ gap: 0.8rem;
2209
+ flex-wrap: wrap;
2210
+ }
2211
+
2212
+ .quick-question-btn {
2213
+ background: rgba(30, 27, 46, 0.6);
2214
+ border: 1px solid rgba(255, 255, 255, 0.08);
2215
+ color: rgba(255, 255, 255, 0.9);
2216
+ padding: 0.8rem 1.2rem;
2217
+ border-radius: 20px;
2218
+ font-size: 0.9rem;
2219
+ cursor: pointer;
2220
+ transition: all 0.3s ease;
2221
+ white-space: nowrap;
2222
+ }
2223
+
2224
+ .quick-question-btn:hover {
2225
+ background: rgba(233, 30, 99, 0.1);
2226
+ border-color: rgba(233, 30, 99, 0.3);
2227
+ color: #E91E63;
2228
+ transform: translateY(-1px);
2229
+ }
2230
+
2231
+ .quick-question-btn:active {
2232
+ transform: translateY(0);
2233
+ }
2234
+
2235
+ /* AI Thinking Indicator */
2236
+ .ai-thinking {
2237
+ display: flex;
2238
+ align-items: center;
2239
+ padding: 0.8rem 1.2rem;
2240
+ background: rgba(30, 27, 46, 0.4);
2241
+ border-radius: 12px;
2242
+ margin-left: 48px; /* To align with other AI messages */
2243
+ }
2244
+
2245
+ .ai-thinking-dots {
2246
+ display: flex;
2247
+ gap: 4px;
2248
+ }
2249
+
2250
+ .ai-thinking-dot {
2251
+ width: 6px;
2252
+ height: 6px;
2253
+ background: var(--primary-color);
2254
+ border-radius: 50%;
2255
+ animation: thinking 1.4s infinite ease-in-out;
2256
+ }
2257
+
2258
+ .ai-thinking-dot:nth-child(1) { animation-delay: 0s; }
2259
+ .ai-thinking-dot:nth-child(2) { animation-delay: 0.2s; }
2260
+ .ai-thinking-dot:nth-child(3) { animation-delay: 0.4s; }
2261
+
2262
+ @keyframes thinking {
2263
+ 0%, 80%, 100% {
2264
+ transform: scale(0.8);
2265
+ opacity: 0.3;
2266
+ }
2267
+ 40% {
2268
+ transform: scale(1);
2269
+ opacity: 1;
2270
+ }
2271
+ }
templates/base.html ADDED
@@ -0,0 +1,28 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <meta name="csrf-token" content="{{ csrf_token() }}">
7
+ <title>{% block title %}ResearchRadar.AI{% endblock %}</title>
8
+ <link href="https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@300;400;500;600;700&display=swap" rel="stylesheet">
9
+ <link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='styles.css') }}">
10
+ <link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='auth.css') }}">
11
+ <link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='analysis.css') }}">
12
+ </head>
13
+ <body>
14
+ <div class="user-nav">
15
+ <span class="username">{{ session.get('username', '') }}</span>
16
+ <a href="{{ url_for('logout') }}" class="logout-button">Logout</a>
17
+ </div>
18
+ {% block content %}{% endblock %}
19
+ <div class="features-section" style="display: none;">
20
+ </div>
21
+
22
+ <style>
23
+ .features-section, .feature-card, .feature-icon-wrapper, .feature-title, .feature-description {
24
+ display: none;
25
+ }
26
+ </style>
27
+ </body>
28
+ </html>
templates/index.html ADDED
@@ -0,0 +1,108 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <meta name="csrf-token" content="{{ csrf_token() }}">
7
+ <title>ResearchRadar.AI</title>
8
+ <link rel="stylesheet" href="{{ url_for('static', filename='styles.css') }}">
9
+ </head>
10
+ <body>
11
+ <div class="app-container">
12
+ <!-- Navigation Bar -->
13
+ <nav class="top-nav">
14
+ <div class="nav-left">
15
+ <h1 class="logo">ResearchRadar.AI</h1>
16
+ </div>
17
+ <div class="nav-right">
18
+ <span class="user-info">{{ current_user.username }}</span>
19
+ <a href="{{ url_for('logout') }}" class="logout-button">Logout</a>
20
+ </div>
21
+ </nav>
22
+
23
+ <!-- Breadcrumb Navigation -->
24
+ <div class="breadcrumb">
25
+ <span id="currentSection">Home</span>
26
+ <button id="backButton" class="back-button" onclick="goBack()" style="display: none;">
27
+ ← Back
28
+ </button>
29
+ </div>
30
+
31
+ <!-- Main Content -->
32
+ <main class="main-content">
33
+ <!-- Home Section -->
34
+ <div id="homeSection" class="content-section active">
35
+ <div class="search-section">
36
+ <div class="search-container">
37
+ <input type="text" id="searchInput" class="search-input" placeholder="Enter research paper title">
38
+ <div class="filter-container">
39
+ <select id="sortBy" class="filter-select">
40
+ <option value="relevance">Sort by Relevance</option>
41
+ <option value="lastUpdated">Sort by Last Updated</option>
42
+ <option value="submitted">Sort by Submission Date</option>
43
+ </select>
44
+
45
+ <select id="maxResults" class="filter-select">
46
+ <option value="10">10 results</option>
47
+ <option value="25">25 results</option>
48
+ <option value="50">50 results</option>
49
+ </select>
50
+ </div>
51
+ <button id="searchButton" class="search-button">SCAN</button>
52
+ </div>
53
+ </div>
54
+
55
+ <div class="features-section">
56
+ <div class="feature-card">
57
+ <div class="feature-icon">🔍</div>
58
+ <div class="feature-content">
59
+ <h2 class="feature-title">Smart Search</h2>
60
+ <p class="feature-description">Find relevant papers instantly with our intelligent search system</p>
61
+ </div>
62
+ </div>
63
+
64
+ <div class="feature-card">
65
+ <div class="feature-icon">🤖</div>
66
+ <div class="feature-content">
67
+ <h2 class="feature-title">AI Analysis</h2>
68
+ <p class="feature-description">Get deep insights with our advanced AI-powered analysis</p>
69
+ </div>
70
+ </div>
71
+
72
+ <div class="feature-card">
73
+ <div class="feature-icon">💬</div>
74
+ <div class="feature-content">
75
+ <h2 class="feature-title">Interactive Chat</h2>
76
+ <p class="feature-description">Engage in meaningful conversations about research papers</p>
77
+ </div>
78
+ </div>
79
+ </div>
80
+ </div>
81
+
82
+ <!-- History Section -->
83
+ <div id="historySection" class="content-section">
84
+ <h2>Recent Searches</h2>
85
+ <div id="searchHistory" class="history-grid"></div>
86
+ </div>
87
+
88
+ <!-- Saved Papers Section -->
89
+ <div id="savedSection" class="content-section">
90
+ <!-- <h2>Saved Papers</h2> -->
91
+ <div id="savedPapers" class="saved-grid"></div>
92
+ </div>
93
+
94
+ <!-- Results Section -->
95
+ <div id="resultsSection" class="content-section"></div>
96
+ </main>
97
+ </div>
98
+
99
+ <!-- Loading Overlay -->
100
+ <div id="loadingOverlay" class="loading-overlay hidden">
101
+ <div class="loading-spinner"></div>
102
+ <p>Processing...</p>
103
+ </div>
104
+
105
+ <script src="{{ url_for('static', filename='search.js') }}"></script>
106
+ <script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
107
+ </body>
108
+ </html>
templates/login.html ADDED
@@ -0,0 +1,92 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Login - ResearchRadar.AI</title>
7
+ <link rel="stylesheet" href="{{ url_for('static', filename='auth.css') }}">
8
+ <!-- Firebase SDK -->
9
+ <script src="https://www.gstatic.com/firebasejs/10.8.0/firebase-app-compat.js"></script>
10
+ <script src="https://www.gstatic.com/firebasejs/10.8.0/firebase-auth-compat.js"></script>
11
+ <script src="{{ url_for('static', filename='firebase-config.js') }}"></script>
12
+ </head>
13
+ <body>
14
+ <div class="auth-container">
15
+ <div class="auth-card">
16
+ <h1 class="auth-title">ResearchRadar.AI</h1>
17
+ <p class="auth-subtitle">Login to your account</p>
18
+
19
+ <form id="loginForm" class="auth-form">
20
+ <div class="form-group">
21
+ <div class="input-icon">📧</div>
22
+ <input type="email" id="email" class="auth-input" placeholder="Email" required>
23
+ </div>
24
+
25
+ <div class="form-group">
26
+ <div class="input-icon">🔒</div>
27
+ <input type="password" id="password" class="auth-input" placeholder="Password" required>
28
+ </div>
29
+
30
+ <div id="error-message" class="error-messages" style="display: none;">
31
+ <p class="error-message"></p>
32
+ </div>
33
+
34
+ <button type="submit" class="auth-button">Login</button>
35
+ </form>
36
+
37
+ <p class="auth-footer">
38
+ Don't have an account?
39
+ <a href="{{ url_for('register') }}" class="auth-link">Register</a>
40
+ </p>
41
+ </div>
42
+ </div>
43
+
44
+ <script>
45
+ document.getElementById('loginForm').addEventListener('submit', async (e) => {
46
+ e.preventDefault();
47
+ const email = document.getElementById('email').value;
48
+ const password = document.getElementById('password').value;
49
+ const errorDiv = document.getElementById('error-message');
50
+ const errorText = errorDiv.querySelector('p');
51
+
52
+ try {
53
+ // Sign in with Firebase
54
+ const userCredential = await firebase.auth().signInWithEmailAndPassword(email, password);
55
+ const user = userCredential.user;
56
+
57
+ // Get ID token
58
+ const idToken = await user.getIdToken();
59
+
60
+ // Send token to backend
61
+ const response = await fetch('/verify-token', {
62
+ method: 'POST',
63
+ headers: {
64
+ 'Content-Type': 'application/json',
65
+ 'X-CSRFToken': '{{ csrf_token() }}'
66
+ },
67
+ body: JSON.stringify({
68
+ idToken: idToken,
69
+ uid: user.uid,
70
+ email: user.email
71
+ })
72
+ });
73
+
74
+ const data = await response.json();
75
+ if (response.ok) {
76
+ if (data.redirect) {
77
+ window.location.href = data.redirect;
78
+ } else {
79
+ window.location.href = '/';
80
+ }
81
+ } else {
82
+ throw new Error(data.error || 'Failed to verify user');
83
+ }
84
+ } catch (error) {
85
+ console.error('Login error:', error);
86
+ errorDiv.style.display = 'block';
87
+ errorText.textContent = error.message;
88
+ }
89
+ });
90
+ </script>
91
+ </body>
92
+ </html>
templates/register.html ADDED
@@ -0,0 +1,105 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Register - ResearchRadar.AI</title>
7
+ <link rel="stylesheet" href="{{ url_for('static', filename='auth.css') }}">
8
+ <!-- Firebase SDK -->
9
+ <script src="https://www.gstatic.com/firebasejs/10.8.0/firebase-app-compat.js"></script>
10
+ <script src="https://www.gstatic.com/firebasejs/10.8.0/firebase-auth-compat.js"></script>
11
+ <script src="{{ url_for('static', filename='firebase-config.js') }}"></script>
12
+ </head>
13
+ <body>
14
+ <div class="auth-container">
15
+ <div class="auth-card">
16
+ <h1 class="auth-title">ResearchRadar.AI</h1>
17
+ <p class="auth-subtitle">Create your account</p>
18
+
19
+ <form id="registerForm" class="auth-form">
20
+ <div class="form-group">
21
+ <div class="input-icon">📧</div>
22
+ <input type="email" id="email" class="auth-input" placeholder="Email" required>
23
+ </div>
24
+
25
+ <div class="form-group">
26
+ <div class="input-icon">🔒</div>
27
+ <input type="password" id="password" class="auth-input" placeholder="Password" required>
28
+ </div>
29
+
30
+ <div class="form-group">
31
+ <div class="input-icon">🔒</div>
32
+ <input type="password" id="confirmPassword" class="auth-input" placeholder="Confirm Password" required>
33
+ </div>
34
+
35
+ <div id="error-message" class="error-messages" style="display: none;">
36
+ <p class="error-message"></p>
37
+ </div>
38
+
39
+ <button type="submit" class="auth-button">Register</button>
40
+ </form>
41
+
42
+ <p class="auth-footer">
43
+ Already have an account?
44
+ <a href="{{ url_for('login') }}" class="auth-link">Login</a>
45
+ </p>
46
+ </div>
47
+ </div>
48
+
49
+ <script>
50
+ document.getElementById('registerForm').addEventListener('submit', async (e) => {
51
+ e.preventDefault();
52
+ const email = document.getElementById('email').value;
53
+ const password = document.getElementById('password').value;
54
+ const confirmPassword = document.getElementById('confirmPassword').value;
55
+ const errorDiv = document.getElementById('error-message');
56
+ const errorText = errorDiv.querySelector('p');
57
+
58
+ if (password !== confirmPassword) {
59
+ errorDiv.style.display = 'block';
60
+ errorText.textContent = 'Passwords do not match';
61
+ return;
62
+ }
63
+
64
+ try {
65
+ // Create user with Firebase Auth
66
+ const userCredential = await firebase.auth().createUserWithEmailAndPassword(email, password);
67
+ const user = userCredential.user;
68
+
69
+ // Get ID token
70
+ const idToken = await user.getIdToken();
71
+
72
+ // Send token to backend
73
+ const response = await fetch('/verify-token', {
74
+ method: 'POST',
75
+ headers: {
76
+ 'Content-Type': 'application/json',
77
+ 'X-CSRFToken': '{{ csrf_token() }}'
78
+ },
79
+ body: JSON.stringify({
80
+ idToken: idToken,
81
+ uid: user.uid,
82
+ email: user.email
83
+ })
84
+ });
85
+
86
+ const data = await response.json();
87
+
88
+ if (response.ok) {
89
+ if (data.redirect) {
90
+ window.location.href = data.redirect;
91
+ } else {
92
+ window.location.href = '/';
93
+ }
94
+ } else {
95
+ throw new Error(data.error || 'Failed to verify user');
96
+ }
97
+ } catch (error) {
98
+ console.error('Registration error:', error);
99
+ errorDiv.style.display = 'block';
100
+ errorText.textContent = error.message;
101
+ }
102
+ });
103
+ </script>
104
+ </body>
105
+ </html>
text.txt ADDED
@@ -0,0 +1,404 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from flask import Flask, render_template, request, jsonify, redirect, url_for, flash, session
2
+ from flask_login import LoginManager, UserMixin, login_user, login_required, logout_user, current_user
3
+ from flask_wtf.csrf import CSRFProtect
4
+ from flask_wtf import FlaskForm
5
+ from wtforms import StringField, PasswordField, SubmitField
6
+ from wtforms.validators import DataRequired
7
+ from werkzeug.security import generate_password_hash, check_password_hash
8
+ import arxiv
9
+ import requests
10
+ import PyPDF2
11
+ from io import BytesIO
12
+ from langchain.text_splitter import RecursiveCharacterTextSplitter
13
+ from langchain_groq import ChatGroq
14
+ from langchain.memory import ConversationBufferMemory
15
+ from langchain_community.embeddings import HuggingFaceEmbeddings
16
+ import numpy as np
17
+ from concurrent.futures import ThreadPoolExecutor, TimeoutError
18
+ from functools import lru_cache
19
+ import time
20
+ import os
21
+ from dotenv import load_dotenv
22
+ import json
23
+ from datetime import datetime
24
+ from flask_sqlalchemy import SQLAlchemy
25
+ from config import Config
26
+
27
+ # Load environment variables
28
+ load_dotenv()
29
+
30
+ # Initialize Flask extensions
31
+ db = SQLAlchemy()
32
+ login_manager = LoginManager()
33
+
34
+ def create_app():
35
+ app = Flask(__name__)
36
+ app.config.from_object(Config)
37
+
38
+ # Initialize extensions
39
+ db.init_app(app)
40
+ login_manager.init_app(app)
41
+ login_manager.login_view = 'login'
42
+
43
+ with app.app_context():
44
+ # Import routes after db initialization
45
+ from routes import init_routes
46
+ init_routes(app)
47
+
48
+ # Create database tables
49
+ db.create_all()
50
+
51
+ # Test database connection
52
+ try:
53
+ version = db.session.execute('SELECT VERSION()').scalar()
54
+ print(f"Connected to PostgreSQL: {version}")
55
+ except Exception as e:
56
+ print(f"Database connection error: {str(e)}")
57
+ raise e
58
+
59
+ return app
60
+
61
+ # Initialize CSRF protection
62
+ csrf = CSRFProtect()
63
+ csrf.init_app(app)
64
+
65
+ # Initialize Groq
66
+ groq_api_key = os.getenv('GROQ_API_KEY')
67
+ llm = ChatGroq(
68
+ temperature=0.1,
69
+ groq_api_key=groq_api_key,
70
+ model_name="mixtral-8x7b-32768"
71
+ )
72
+
73
+ # Initialize embeddings
74
+ embeddings_model = HuggingFaceEmbeddings(
75
+ model_name="sentence-transformers/all-MiniLM-L6-v2"
76
+ )
77
+
78
+ # Constants
79
+ MAX_CHUNKS = 50
80
+ MAX_RESPONSE_LENGTH = 4000
81
+ CACHE_DURATION = 3600 # 1 hour in seconds
82
+
83
+ # Form Classes
84
+ class LoginForm(FlaskForm):
85
+ username = StringField('Username', validators=[DataRequired()])
86
+ password = PasswordField('Password', validators=[DataRequired()])
87
+ submit = SubmitField('Login')
88
+
89
+ class RegisterForm(FlaskForm):
90
+ username = StringField('Username', validators=[DataRequired()])
91
+ password = PasswordField('Password', validators=[DataRequired()])
92
+ submit = SubmitField('Register')
93
+
94
+ # User class
95
+ class User(UserMixin):
96
+ def __init__(self, user_id, username):
97
+ self.id = user_id
98
+ self.username = username
99
+
100
+ @staticmethod
101
+ def get(user_id):
102
+ users = load_users()
103
+ user_data = users.get(str(user_id))
104
+ if user_data:
105
+ return User(user_id=user_data['id'], username=user_data['username'])
106
+ return None
107
+
108
+ # User management functions
109
+ def load_users():
110
+ try:
111
+ with open('users.json', 'r') as f:
112
+ return json.load(f)
113
+ except FileNotFoundError:
114
+ return {}
115
+
116
+ def save_users(users):
117
+ with open('users.json', 'w') as f:
118
+ json.dump(users, f)
119
+
120
+ @login_manager.user_loader
121
+ def load_user(user_id):
122
+ return User.get(user_id)
123
+
124
+ # PDF Processing and Analysis
125
+ def process_pdf(pdf_url):
126
+ try:
127
+ print(f"Starting PDF processing for: {pdf_url}")
128
+
129
+ response = requests.get(pdf_url, timeout=30)
130
+ response.raise_for_status()
131
+ pdf_file = BytesIO(response.content)
132
+
133
+ pdf_reader = PyPDF2.PdfReader(pdf_file)
134
+ # Clean and normalize the text
135
+ text = " ".join(
136
+ page.extract_text().encode('ascii', 'ignore').decode('ascii')
137
+ for page in pdf_reader.pages
138
+ )
139
+
140
+ if not text.strip():
141
+ return {'error': 'No text could be extracted from the PDF'}
142
+
143
+ text_splitter = RecursiveCharacterTextSplitter(
144
+ chunk_size=1000,
145
+ chunk_overlap=200,
146
+ length_function=len,
147
+ separators=["\n\n", "\n", " ", ""]
148
+ )
149
+
150
+ chunks = text_splitter.split_text(text)[:MAX_CHUNKS]
151
+
152
+ analysis = generate_analysis(chunks)
153
+ return {
154
+ 'success': True,
155
+ 'analysis': analysis
156
+ }
157
+
158
+ except Exception as e:
159
+ return {'error': f"PDF processing failed: {str(e)}"}
160
+
161
+ def generate_analysis(chunks):
162
+ analysis_prompts = {
163
+ 'executive_summary': "Provide a concise executive summary of this research paper.",
164
+ 'problem_analysis': "What is the main research problem and objectives?",
165
+ 'methodology': "Describe the key methodology and approach.",
166
+ 'findings': "What are the main findings and conclusions?",
167
+ 'contributions': "What are the key contributions of this work?"
168
+ }
169
+
170
+ analysis_results = {}
171
+
172
+ for aspect, prompt in analysis_prompts.items():
173
+ try:
174
+ # Clean and join the chunks
175
+ context = "\n\n".join(
176
+ chunk.encode('ascii', 'ignore').decode('ascii')
177
+ for chunk in chunks[:3]
178
+ )
179
+ response = llm.invoke(
180
+ f"""Based on the following context from a research paper, {prompt}
181
+
182
+ Context:
183
+ {context}
184
+
185
+ Please provide a clear and specific response."""
186
+ )
187
+ analysis_results[aspect] = response.content[:MAX_RESPONSE_LENGTH]
188
+ except Exception as e:
189
+ analysis_results[aspect] = f"Analysis failed: {str(e)}"
190
+
191
+ return analysis_results
192
+
193
+ # Routes
194
+ @app.route('/')
195
+ @login_required
196
+ def index():
197
+ return render_template('index.html')
198
+
199
+ @app.route('/login', methods=['GET', 'POST'])
200
+ def login():
201
+ if current_user.is_authenticated:
202
+ return redirect(url_for('index'))
203
+
204
+ form = LoginForm()
205
+ if form.validate_on_submit():
206
+ username = form.username.data
207
+ password = form.password.data
208
+
209
+ users = load_users()
210
+ user_found = None
211
+
212
+ for user_id, user_data in users.items():
213
+ if user_data['username'] == username:
214
+ user_found = user_data
215
+ break
216
+
217
+ if user_found and check_password_hash(user_found['password_hash'], password):
218
+ user = User(user_id=user_found['id'], username=username)
219
+ login_user(user, remember=True)
220
+ return redirect(url_for('index'))
221
+
222
+ flash('Invalid username or password')
223
+
224
+ return render_template('login.html', form=form)
225
+
226
+ @app.route('/register', methods=['GET', 'POST'])
227
+ def register():
228
+ if current_user.is_authenticated:
229
+ return redirect(url_for('index'))
230
+
231
+ form = RegisterForm()
232
+ if form.validate_on_submit():
233
+ username = form.username.data
234
+ password = form.password.data
235
+
236
+ users = load_users()
237
+
238
+ if any(user['username'] == username for user in users.values()):
239
+ flash('Username already exists')
240
+ return render_template('register.html', form=form)
241
+
242
+ user_id = str(len(users) + 1)
243
+ users[user_id] = {
244
+ 'id': user_id,
245
+ 'username': username,
246
+ 'password_hash': generate_password_hash(password)
247
+ }
248
+
249
+ save_users(users)
250
+
251
+ user = User(user_id=user_id, username=username)
252
+ login_user(user)
253
+
254
+ return redirect(url_for('index'))
255
+
256
+ return render_template('register.html', form=form)
257
+
258
+ @app.route('/logout')
259
+ @login_required
260
+ def logout():
261
+ logout_user()
262
+ return redirect(url_for('login'))
263
+
264
+ @app.route('/search', methods=['POST'])
265
+ @login_required
266
+ def search():
267
+ try:
268
+ data = request.get_json()
269
+ paper_name = data.get('paper_name')
270
+ sort_by = data.get('sort_by', 'relevance')
271
+ max_results = data.get('max_results', 10)
272
+
273
+ if not paper_name:
274
+ return jsonify({'error': 'No search query provided'}), 400
275
+
276
+ # Map sort_by to arxiv.SortCriterion
277
+ sort_mapping = {
278
+ 'relevance': arxiv.SortCriterion.Relevance,
279
+ 'lastUpdated': arxiv.SortCriterion.LastUpdatedDate,
280
+ 'submitted': arxiv.SortCriterion.SubmittedDate
281
+ }
282
+ sort_criterion = sort_mapping.get(sort_by, arxiv.SortCriterion.Relevance)
283
+
284
+ # Perform the search
285
+ search = arxiv.Search(
286
+ query=paper_name,
287
+ max_results=max_results,
288
+ sort_by=sort_criterion
289
+ )
290
+
291
+ results = []
292
+ for paper in search.results():
293
+ results.append({
294
+ 'title': paper.title,
295
+ 'authors': ', '.join(author.name for author in paper.authors),
296
+ 'abstract': paper.summary,
297
+ 'pdf_link': paper.pdf_url,
298
+ 'arxiv_link': paper.entry_id,
299
+ 'published': paper.published.strftime('%Y-%m-%d'),
300
+ 'category': paper.primary_category,
301
+ 'comment': paper.comment if hasattr(paper, 'comment') else None,
302
+ 'doi': paper.doi if hasattr(paper, 'doi') else None
303
+ })
304
+
305
+ return jsonify(results)
306
+
307
+ except Exception as e:
308
+ print(f"Search error: {str(e)}")
309
+ return jsonify({'error': f'Failed to search papers: {str(e)}'}), 500
310
+
311
+ @app.route('/perform-rag', methods=['POST'])
312
+ @login_required
313
+ def perform_rag():
314
+ try:
315
+ pdf_url = request.json.get('pdf_url')
316
+ if not pdf_url:
317
+ return jsonify({'error': 'PDF URL is required'}), 400
318
+
319
+ result = process_pdf(pdf_url)
320
+
321
+ if 'error' in result:
322
+ return jsonify({'error': result['error']}), 500
323
+
324
+ return jsonify(result)
325
+
326
+ except Exception as e:
327
+ return jsonify({'error': str(e)}), 500
328
+
329
+ @app.route('/chat-with-paper', methods=['POST'])
330
+ @login_required
331
+ def chat_with_paper():
332
+ try:
333
+ pdf_url = request.json.get('pdf_url')
334
+ question = request.json.get('question')
335
+
336
+ if not pdf_url or not question:
337
+ return jsonify({'error': 'PDF URL and question are required'}), 400
338
+
339
+ # Get PDF text and create chunks
340
+ response = requests.get(pdf_url, timeout=30)
341
+ response.raise_for_status()
342
+ pdf_file = BytesIO(response.content)
343
+
344
+ pdf_reader = PyPDF2.PdfReader(pdf_file)
345
+ text = " ".join(page.extract_text() for page in pdf_reader.pages)
346
+
347
+ if not text.strip():
348
+ return jsonify({'error': 'No text could be extracted from the PDF'})
349
+
350
+ # Create text chunks
351
+ text_splitter = RecursiveCharacterTextSplitter(
352
+ chunk_size=1000,
353
+ chunk_overlap=200,
354
+ length_function=len
355
+ )
356
+ chunks = text_splitter.split_text(text)[:MAX_CHUNKS]
357
+
358
+ # Generate embeddings for chunks
359
+ chunk_embeddings = embeddings_model.embed_documents(chunks)
360
+
361
+ # Generate embedding for the question
362
+ question_embedding = embeddings_model.embed_query(question)
363
+
364
+ # Find most relevant chunks using cosine similarity
365
+ similarities = []
366
+ for chunk_embedding in chunk_embeddings:
367
+ similarity = np.dot(question_embedding, chunk_embedding) / (
368
+ np.linalg.norm(question_embedding) * np.linalg.norm(chunk_embedding)
369
+ )
370
+ similarities.append(similarity)
371
+
372
+ # Get top 3 most relevant chunks
373
+ top_chunk_indices = np.argsort(similarities)[-3:][::-1]
374
+ relevant_chunks = [chunks[i] for i in top_chunk_indices]
375
+
376
+ # Construct prompt with relevant context
377
+ context = "\n\n".join(relevant_chunks)
378
+ prompt = f"""Based on the following relevant excerpts from the research paper, please answer this question: {question}
379
+
380
+ Context from paper:
381
+ {context}
382
+
383
+ Please provide a clear, specific, and accurate response based solely on the information provided in these excerpts. If the answer cannot be fully determined from the given context, please indicate this in your response."""
384
+
385
+ # Generate response using Groq
386
+ response = llm.invoke(prompt)
387
+
388
+ # Format and return response
389
+ formatted_response = response.content.strip()
390
+
391
+ # Add source citations
392
+ source_info = "\n\nThis response is based on specific sections from the paper."
393
+
394
+ return jsonify({
395
+ 'response': formatted_response + source_info,
396
+ 'relevance_scores': [float(similarities[i]) for i in top_chunk_indices]
397
+ })
398
+
399
+ except Exception as e:
400
+ print(f"Chat error: {str(e)}")
401
+ return jsonify({'error': f'Failed to process request: {str(e)}'}), 500
402
+
403
+ if __name__ == '__main__':
404
+ app.run(debug=True)
users.json ADDED
@@ -0,0 +1 @@
 
 
1
+ {"1": {"id": "1", "username": "vinit", "password_hash": "scrypt:32768:8:1$eMBXSaH1nm6rZx3c$31f72052845c5c5c67006aa4cf8b29231560bbc67acd56ec4261f655a04b91719579b808d9ed31d73995e13e1f9cf535a04ed528fb1af67b58df8aaf86ab415d"}, "2": {"id": "2", "username": "tavde", "password_hash": "scrypt:32768:8:1$RgIBXfJjYPp20gLj$c62da6c0d894fa04998d666b7944f94c27125d5983a71cf4b57624b8a99e4ada4bad85fcc56ff4e9d08be218af842971d93c07d1c8e89fad60b57aea56dbc587"}, "3": {"id": "3", "username": "divax.shah", "password_hash": "scrypt:32768:8:1$TqaCWPWa9h5yvQzv$0ea4dd33e2dc026f471e24d1f94ed3ed6381400c5d1dfcf099ea66cf32d7c81a4bef0b0e143d1b797e655e9e6cec460a9b2277ec5157efbfc95f0714e4d2377e"}, "4": {"id": "4", "username": "vidhi", "password_hash": "scrypt:32768:8:1$bUMFGywKUGvXpwW4$f3dada6114fa257d4ff7f08906b08d55f2a656d61f4bc6399504c05b03a353d6a2856fae2bc598b848c2b6a41cf82ab64679429b7a8b35680072ad29e3e528b0"}}