Spaces:
Running
Running
add huggingface space
Browse files- .gitignore +31 -0
- Dockerfile +12 -0
- app.py +421 -0
- auth_utils.py +56 -0
- db_utils.py +36 -0
- firebase_config.py +14 -0
- firestore.rules +15 -0
- init_db.py +1 -0
- requirements.txt +16 -0
- researchradarai-firebase-adminsdk-fbsvc-281fee7dee.json +13 -0
- static/analysis.css +246 -0
- static/auth.css +151 -0
- static/firebase-config.js +15 -0
- static/script.js +80 -0
- static/search.js +780 -0
- static/styles.css +2271 -0
- templates/base.html +28 -0
- templates/index.html +108 -0
- templates/login.html +92 -0
- templates/register.html +105 -0
- text.txt +404 -0
- users.json +1 -0
.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, "&")
|
130 |
+
.replace(/</g, "<")
|
131 |
+
.replace(/>/g, ">")
|
132 |
+
.replace(/"/g, """)
|
133 |
+
.replace(/'/g, "'");
|
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"}}
|