“vinit5112” commited on
Commit
5672ed8
·
1 Parent(s): 769a5dd

add all files

Browse files
Files changed (9) hide show
  1. .dockerignore +62 -0
  2. Dockerfile +38 -0
  3. app.py +900 -0
  4. config.py +53 -0
  5. docker-compose.yml +24 -0
  6. requirements.txt +15 -0
  7. static/css/style.css +0 -0
  8. static/js/script.js +0 -0
  9. templates/index.html +1013 -0
.dockerignore ADDED
@@ -0,0 +1,62 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Git
2
+ .git
3
+ .gitignore
4
+
5
+ # Python
6
+ __pycache__
7
+ *.pyc
8
+ *.pyo
9
+ *.pyd
10
+ .Python
11
+ env
12
+ pip-log.txt
13
+ pip-delete-this-directory.txt
14
+ .tox
15
+ .coverage
16
+ .coverage.*
17
+ .cache
18
+ nosetests.xml
19
+ coverage.xml
20
+ *.cover
21
+ *.log
22
+ .git
23
+ .mypy_cache
24
+ .pytest_cache
25
+ .hypothesis
26
+
27
+ # Virtual environments
28
+ venv/
29
+ env/
30
+ ENV/
31
+
32
+ # IDE
33
+ .vscode/
34
+ .idea/
35
+ *.swp
36
+ *.swo
37
+ *~
38
+
39
+ # OS
40
+ .DS_Store
41
+ .DS_Store?
42
+ ._*
43
+ .Spotlight-V100
44
+ .Trashes
45
+ ehthumbs.db
46
+ Thumbs.db
47
+
48
+ # Project specific
49
+ uploads/*
50
+ !uploads/.gitkeep
51
+ chroma_db/
52
+ *.db
53
+ *.sqlite
54
+ *.sqlite3
55
+
56
+ # Logs
57
+ *.log
58
+ logs/
59
+
60
+ # Temporary files
61
+ *.tmp
62
+ *.temp
Dockerfile ADDED
@@ -0,0 +1,38 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Use Python 3.11 slim image
2
+ FROM python:3.11-slim
3
+
4
+ # Set working directory
5
+ WORKDIR /app
6
+
7
+ # Install system dependencies
8
+ RUN apt-get update && apt-get install -y \
9
+ gcc \
10
+ g++ \
11
+ && rm -rf /var/lib/apt/lists/*
12
+
13
+ # Copy requirements first for better caching
14
+ COPY requirements.txt .
15
+
16
+ # Install Python dependencies
17
+ RUN pip install --no-cache-dir -r requirements.txt
18
+
19
+ # Copy application code
20
+ COPY . .
21
+
22
+ # Create uploads directory
23
+ RUN mkdir -p uploads
24
+
25
+ # Expose port
26
+ EXPOSE 7860
27
+
28
+ # Set environment variables
29
+ ENV FLASK_APP=app.py
30
+ ENV FLASK_ENV=production
31
+ ENV PORT=7860
32
+
33
+ # Health check
34
+ HEALTHCHECK --interval=30s --timeout=30s --start-period=5s --retries=3 \
35
+ CMD curl -f http://localhost:7860/health || exit 1
36
+
37
+ # Run the application
38
+ CMD ["python", "app.py"]
app.py ADDED
@@ -0,0 +1,900 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import json
3
+ import tempfile
4
+ from datetime import datetime
5
+ from flask import Flask, render_template, request, jsonify, session, redirect, url_for
6
+ import google.generativeai as genai
7
+ from sentence_transformers import SentenceTransformer
8
+ # Removed ChromaDB and added Qdrant
9
+ from qdrant_client import QdrantClient
10
+ from qdrant_client.models import VectorParams, Distance, Filter, FieldCondition, MatchValue, PointStruct, SearchParams
11
+ # LangChain splitter
12
+ from langchain.text_splitter import RecursiveCharacterTextSplitter
13
+ import arxiv
14
+ import PyPDF2
15
+ from docx import Document
16
+ import requests
17
+ from werkzeug.utils import secure_filename
18
+ from dotenv import load_dotenv
19
+ import uuid
20
+ import re
21
+ from bs4 import BeautifulSoup
22
+ import logging
23
+ import numpy as np
24
+
25
+ # Load environment variables
26
+ load_dotenv()
27
+
28
+ # Set up logging
29
+ logging.basicConfig(level=logging.INFO)
30
+ logger = logging.getLogger(__name__)
31
+
32
+ app = Flask(__name__)
33
+ app.secret_key = os.getenv('SECRET_KEY', 'research-radar-secret-key-2024')
34
+
35
+ # Configuration
36
+ UPLOAD_FOLDER = 'uploads'
37
+ ALLOWED_EXTENSIONS = {'txt', 'pdf', 'docx'}
38
+ MAX_CONTENT_LENGTH = 16 * 1024 * 1024 # 16MB max file size
39
+
40
+ app.config['UPLOAD_FOLDER'] = UPLOAD_FOLDER
41
+ app.config['MAX_CONTENT_LENGTH'] = MAX_CONTENT_LENGTH
42
+
43
+ # Ensure directories exist
44
+ os.makedirs(UPLOAD_FOLDER, exist_ok=True)
45
+
46
+ # Initialize models and services
47
+ try:
48
+ # Configure Gemini API
49
+ gemini_api_key = os.getenv('GEMINI_API_KEY')
50
+ if gemini_api_key:
51
+ genai.configure(api_key=gemini_api_key)
52
+ gemini_model = genai.GenerativeModel('gemini-2.5-flash')
53
+ logger.info("✅ Gemini API initialized successfully")
54
+ else:
55
+ gemini_model = None
56
+ logger.warning("⚠️ Gemini API key not found. AI features will be limited.")
57
+
58
+ # Initialize sentence transformer for embeddings (free)
59
+ embedding_model = SentenceTransformer('all-MiniLM-L6-v2')
60
+ logger.info("✅ Sentence transformer model loaded")
61
+
62
+ # Determine vector size dynamically
63
+ try:
64
+ _probe_vec = embedding_model.encode(["probe text"])
65
+ VECTOR_SIZE = int(_probe_vec.shape[-1]) if hasattr(_probe_vec, 'shape') else len(_probe_vec[0])
66
+ except Exception:
67
+ VECTOR_SIZE = 384 # fallback for all-MiniLM-L6-v2
68
+
69
+ # Initialize Qdrant client
70
+ qdrant_url = os.getenv('QDRANT_URL')
71
+ qdrant_api_key = os.getenv('QDRANT_API_KEY')
72
+ qdrant_client = QdrantClient(url=qdrant_url, api_key=qdrant_api_key, timeout=120)
73
+
74
+ logger.info("✅ Qdrant client initialized")
75
+
76
+ # Ensure default collection exists
77
+ def ensure_qdrant_collection(collection_name: str, vector_size: int) -> None:
78
+ try:
79
+ qdrant_client.get_collection(collection_name)
80
+ except Exception:
81
+ qdrant_client.recreate_collection(
82
+ collection_name=collection_name,
83
+ vectors_config=VectorParams(size=vector_size, distance=Distance.COSINE)
84
+ )
85
+ logger.info(f"✅ Created Qdrant collection: {collection_name}")
86
+ # Ensure payload index for document_id exists
87
+ try:
88
+ qdrant_client.create_payload_index(
89
+ collection_name=collection_name,
90
+ field_name="document_id",
91
+ field_schema="keyword"
92
+ )
93
+ logger.info("✅ Ensured payload index for 'document_id'")
94
+ except Exception:
95
+ # Likely already exists
96
+ pass
97
+
98
+ ensure_qdrant_collection('research_papers', VECTOR_SIZE)
99
+
100
+ except Exception as e:
101
+ logger.error(f"❌ Initialization error: {e}")
102
+ embedding_model = None
103
+ gemini_model = None
104
+ qdrant_client = None
105
+ VECTOR_SIZE = None
106
+
107
+
108
+ def allowed_file(filename):
109
+ """Check if file extension is allowed"""
110
+ return '.' in filename and filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS
111
+
112
+
113
+ def extract_text_from_pdf(file_path):
114
+ """Extract text from PDF file"""
115
+ try:
116
+ with open(file_path, 'rb') as file:
117
+ pdf_reader = PyPDF2.PdfReader(file)
118
+ text = ""
119
+ for page in pdf_reader.pages:
120
+ text += page.extract_text() + "\n"
121
+ return text
122
+ except Exception as e:
123
+ print(f"PDF extraction error: {e}")
124
+ return ""
125
+
126
+
127
+ def extract_text_from_docx(file_path):
128
+ """Extract text from DOCX file"""
129
+ try:
130
+ doc = Document(file_path)
131
+ text = ""
132
+ for paragraph in doc.paragraphs:
133
+ text += paragraph.text + "\n"
134
+ return text
135
+ except Exception as e:
136
+ print(f"DOCX extraction error: {e}")
137
+ return ""
138
+
139
+
140
+ def extract_text_from_txt(file_path):
141
+ """Extract text from TXT file"""
142
+ try:
143
+ with open(file_path, 'r', encoding='utf-8') as file:
144
+ return file.read()
145
+ except Exception as e:
146
+ print(f"TXT extraction error: {e}")
147
+ return ""
148
+
149
+
150
+ def process_document(file_path, filename):
151
+ """Process uploaded document and extract text"""
152
+ file_extension = filename.rsplit('.', 1)[1].lower()
153
+
154
+ if file_extension == 'pdf':
155
+ return extract_text_from_pdf(file_path)
156
+ elif file_extension == 'docx':
157
+ return extract_text_from_docx(file_path)
158
+ elif file_extension == 'txt':
159
+ return extract_text_from_txt(file_path)
160
+ else:
161
+ return ""
162
+
163
+
164
+ def search_arxiv_papers(query, max_results=10):
165
+ """Search arXiv papers"""
166
+ try:
167
+ client = arxiv.Client()
168
+ search = arxiv.Search(
169
+ query=query,
170
+ max_results=max_results,
171
+ sort_by=arxiv.SortCriterion.Relevance
172
+ )
173
+
174
+ papers = []
175
+ for result in client.results(search):
176
+ paper = {
177
+ 'title': result.title,
178
+ 'authors': [author.name for author in result.authors],
179
+ 'summary': result.summary,
180
+ 'url': result.entry_id,
181
+ 'pdf_url': result.pdf_url,
182
+ 'published': result.published.strftime('%Y-%m-%d'),
183
+ 'category': result.primary_category
184
+ }
185
+ papers.append(paper)
186
+
187
+ return papers
188
+ except Exception as e:
189
+ print(f"arXiv search error: {e}")
190
+ return []
191
+
192
+
193
+ def generate_summary(text, max_length=500):
194
+ """Generate summary using Gemini API"""
195
+ try:
196
+ if not gemini_model:
197
+ return "Summary generation unavailable - API not configured"
198
+
199
+ prompt = f"""
200
+ Please provide a comprehensive summary of this research paper/document in approximately {max_length} words.
201
+ Focus on:
202
+ 1. Main research question/objective
203
+ 2. Key methodology
204
+ 3. Important findings
205
+ 4. Conclusions and implications
206
+
207
+ Text to summarize:
208
+ {text[:80000]}
209
+ """
210
+
211
+ response = gemini_model.generate_content(prompt)
212
+ return response.text
213
+ except Exception as e:
214
+ logger.error(f"Summary generation error: {e}")
215
+ return "Error generating summary. Please try again."
216
+
217
+ # Text chunking using LangChain
218
+
219
+ def chunk_text(text: str, chunk_size: int = 1000, chunk_overlap: int = 200):
220
+ splitter = RecursiveCharacterTextSplitter(
221
+ chunk_size=chunk_size,
222
+ chunk_overlap=chunk_overlap,
223
+ separators=["\n\n", "\n", " ", ""]
224
+ )
225
+ return splitter.split_text(text)
226
+
227
+ # Qdrant helpers
228
+
229
+ def ensure_qdrant_collection(collection_name: str, vector_size: int) -> None:
230
+ """Create Qdrant collection if it doesn't exist"""
231
+ if not qdrant_client:
232
+ return
233
+ try:
234
+ qdrant_client.get_collection(collection_name)
235
+ except Exception:
236
+ qdrant_client.recreate_collection(
237
+ collection_name=collection_name,
238
+ vectors_config=VectorParams(size=vector_size, distance=Distance.COSINE)
239
+ )
240
+ # Ensure payload index for document_id exists for efficient filtering/scrolling
241
+ try:
242
+ qdrant_client.create_payload_index(
243
+ collection_name=collection_name,
244
+ field_name="document_id",
245
+ field_schema="keyword"
246
+ )
247
+ except Exception:
248
+ pass
249
+
250
+
251
+ def add_document_to_vector_db(text, metadata, doc_id, collection_name="research_papers"):
252
+ """Add chunked document vectors to Qdrant for chat functionality"""
253
+ try:
254
+ if not embedding_model or not qdrant_client or not VECTOR_SIZE:
255
+ return False
256
+
257
+ ensure_qdrant_collection(collection_name, VECTOR_SIZE)
258
+
259
+ # Split text using recursive text splitter
260
+ chunks = chunk_text(text, chunk_size=1200, chunk_overlap=250)
261
+ if not chunks:
262
+ return False
263
+
264
+ embeddings = embedding_model.encode(chunks)
265
+ vectors = embeddings.tolist() if hasattr(embeddings, 'tolist') else embeddings
266
+
267
+ points = []
268
+ for i, (chunk, vector) in enumerate(zip(chunks, vectors)):
269
+ payload = dict(metadata or {})
270
+ payload.update({
271
+ 'document_id': doc_id,
272
+ 'chunk_index': i,
273
+ 'total_chunks': len(chunks),
274
+ 'content': chunk,
275
+ })
276
+ points.append(
277
+ PointStruct(
278
+ id=str(uuid.uuid4()),
279
+ vector=vector,
280
+ payload=payload
281
+ )
282
+ )
283
+
284
+ qdrant_client.upsert(collection_name=collection_name, points=points, wait=True)
285
+ return True
286
+ except Exception as e:
287
+ print(f"Vector DB error: {e}")
288
+ return False
289
+
290
+
291
+ def query_vector_db(query, doc_id, collection_name="research_papers", n_results=3):
292
+ """Query Qdrant for similar chunks for the given document_id"""
293
+ try:
294
+ if not embedding_model or not qdrant_client or not VECTOR_SIZE:
295
+ return []
296
+
297
+ ensure_qdrant_collection(collection_name, VECTOR_SIZE)
298
+
299
+ query_embedding = embedding_model.encode([query])
300
+ query_vector = query_embedding[0].tolist() if hasattr(query_embedding, 'tolist') else list(query_embedding[0])
301
+
302
+ flt = Filter(must=[FieldCondition(key="document_id", match=MatchValue(value=doc_id))])
303
+ results = qdrant_client.search(
304
+ collection_name=collection_name,
305
+ query_vector=query_vector,
306
+ limit=n_results,
307
+ query_filter=flt,
308
+ with_payload=True,
309
+ with_vectors=False
310
+ )
311
+
312
+ documents = []
313
+ for r in results or []:
314
+ payload = getattr(r, 'payload', None) or {}
315
+ documents.append(payload.get('content', ''))
316
+ return {'documents': [documents]}
317
+ except Exception as e:
318
+ print(f"Vector DB query error: {e}")
319
+ return []
320
+
321
+
322
+ def get_all_chunks_for_document(doc_id: str, collection_name: str = "research_papers"):
323
+ """Retrieve all chunks for a document from Qdrant, ordered by chunk_index"""
324
+ try:
325
+ all_points = []
326
+ next_offset = None
327
+ flt = Filter(must=[FieldCondition(key="document_id", match=MatchValue(value=doc_id))])
328
+ while True:
329
+ points, next_offset = qdrant_client.scroll(
330
+ collection_name=collection_name,
331
+ scroll_filter=flt,
332
+ limit=500,
333
+ offset=next_offset,
334
+ with_payload=True,
335
+ with_vectors=False
336
+ )
337
+ all_points.extend(points)
338
+ if not next_offset:
339
+ break
340
+ # Order by chunk_index
341
+ all_points.sort(key=lambda p: p.payload.get('chunk_index', 0))
342
+ return [p.payload.get('content', '') for p in all_points]
343
+ except Exception as e:
344
+ print(f"Qdrant scroll error: {e}")
345
+ return []
346
+
347
+
348
+ def get_all_documents(collection_name: str = "research_papers"):
349
+ """Get all unique documents from Qdrant with their metadata"""
350
+ try:
351
+ if not qdrant_client:
352
+ return []
353
+
354
+ # Get all points to extract unique documents
355
+ all_points = []
356
+ next_offset = None
357
+ while True:
358
+ points, next_offset = qdrant_client.scroll(
359
+ collection_name=collection_name,
360
+ limit=1000,
361
+ offset=next_offset,
362
+ with_payload=True,
363
+ with_vectors=False
364
+ )
365
+ all_points.extend(points)
366
+ if not next_offset:
367
+ break
368
+
369
+ # Group by document_id and extract metadata
370
+ documents = {}
371
+ for point in all_points:
372
+ payload = point.payload or {}
373
+ doc_id = payload.get('document_id')
374
+ if not doc_id:
375
+ continue
376
+
377
+ if doc_id not in documents:
378
+ # Create document metadata from first chunk
379
+ doc_type = payload.get('type', 'document')
380
+
381
+ # Generate proper title based on type
382
+ title = payload.get('title', 'Untitled Document')
383
+ if doc_type == 'arxiv_paper' and payload.get('pdf_url'):
384
+ # Extract arXiv ID from URL for better title
385
+ pdf_url = payload.get('pdf_url', '')
386
+ if 'arxiv.org/pdf/' in pdf_url:
387
+ arxiv_id = pdf_url.split('/')[-1].replace('.pdf', '')
388
+ title = f"arXiv:{arxiv_id}"
389
+ elif 'arxiv.org/abs/' in pdf_url:
390
+ arxiv_id = pdf_url.split('/')[-1]
391
+ title = f"arXiv:{arxiv_id}"
392
+ elif doc_type == 'uploaded_document' and payload.get('filename'):
393
+ title = payload.get('filename')
394
+
395
+ documents[doc_id] = {
396
+ 'document_id': doc_id,
397
+ 'title': title,
398
+ 'authors': payload.get('authors', ['Unknown']),
399
+ 'published': payload.get('published', 'Unknown Date'),
400
+ 'category': payload.get('category', 'Research'),
401
+ 'filename': payload.get('filename', ''),
402
+ 'pdf_url': payload.get('pdf_url', ''),
403
+ 'type': doc_type,
404
+ 'upload_date': payload.get('upload_date', ''),
405
+ 'total_chunks': payload.get('total_chunks', 0),
406
+ 'word_count': payload.get('word_count', 0)
407
+ }
408
+
409
+ # Convert to list and sort by upload date (newest first)
410
+ doc_list = list(documents.values())
411
+ doc_list.sort(key=lambda x: x.get('upload_date', ''), reverse=True)
412
+
413
+ return doc_list
414
+ except Exception as e:
415
+ print(f"Error getting documents: {e}")
416
+ return []
417
+
418
+
419
+ def get_document_metadata(doc_id: str, collection_name: str = "research_papers"):
420
+ """Get metadata for a specific document"""
421
+ try:
422
+ if not qdrant_client:
423
+ return None
424
+
425
+ # Get first chunk to extract metadata
426
+ flt = Filter(must=[FieldCondition(key="document_id", match=MatchValue(value=doc_id))])
427
+ results = qdrant_client.scroll(
428
+ collection_name=collection_name,
429
+ scroll_filter=flt,
430
+ limit=1,
431
+ with_payload=True,
432
+ with_vectors=False
433
+ )
434
+
435
+ if results and results[0]:
436
+ payload = results[0][0].payload or {}
437
+ return {
438
+ 'document_id': doc_id,
439
+ 'title': payload.get('title', 'Untitled Document'),
440
+ 'authors': payload.get('authors', ['Unknown']),
441
+ 'published': payload.get('published', 'Unknown Date'),
442
+ 'category': payload.get('category', 'Research'),
443
+ 'filename': payload.get('filename', ''),
444
+ 'pdf_url': payload.get('pdf_url', ''),
445
+ 'type': payload.get('type', 'document'),
446
+ 'upload_date': payload.get('upload_date', ''),
447
+ 'total_chunks': payload.get('total_chunks', 0),
448
+ 'word_count': payload.get('word_count', 0)
449
+ }
450
+ return None
451
+ except Exception as e:
452
+ print(f"Error getting document metadata: {e}")
453
+ return None
454
+
455
+ # Paper ingestion helpers
456
+
457
+ def resolve_pdf_url(url_or_pdf: str) -> str:
458
+ if not url_or_pdf:
459
+ return ''
460
+ if 'arxiv.org/pdf/' in url_or_pdf and url_or_pdf.endswith('.pdf'):
461
+ return url_or_pdf
462
+ # convert arXiv abs to pdf
463
+ m = re.search(r"arxiv\.org/(abs|pdf)/([\w\.-]+)", url_or_pdf)
464
+ if m:
465
+ arxiv_id = m.group(2)
466
+ if not arxiv_id.endswith('.pdf'):
467
+ return f"https://arxiv.org/pdf/{arxiv_id}.pdf"
468
+ return f"https://arxiv.org/pdf/{arxiv_id}"
469
+ return url_or_pdf
470
+
471
+
472
+ def download_pdf_to_temp(pdf_url: str) -> str:
473
+ r = requests.get(pdf_url, stream=True, timeout=30)
474
+ r.raise_for_status()
475
+ with tempfile.NamedTemporaryFile(delete=False, suffix='.pdf') as tmp:
476
+ for chunk in r.iter_content(chunk_size=8192):
477
+ if chunk:
478
+ tmp.write(chunk)
479
+ return tmp.name
480
+
481
+
482
+ def ingest_paper(pdf_url: str, paper_meta: dict = None) -> tuple:
483
+ """Download PDF, extract text, chunk, embed and store in Qdrant. Returns (doc_id, word_count)."""
484
+ pdf_url = resolve_pdf_url(pdf_url)
485
+ doc_id = str(uuid.uuid4())
486
+ tmp_path = None
487
+ try:
488
+ tmp_path = download_pdf_to_temp(pdf_url)
489
+ text_content = extract_text_from_pdf(tmp_path)
490
+ if not text_content.strip():
491
+ return None, 0
492
+ metadata = {
493
+ 'source': 'arxiv',
494
+ 'pdf_url': pdf_url,
495
+ 'type': 'arxiv_paper'
496
+ }
497
+ if paper_meta:
498
+ metadata.update(paper_meta)
499
+ ok = add_document_to_vector_db(text_content, metadata, doc_id)
500
+ if not ok:
501
+ return None, 0
502
+ # set active document
503
+ session['active_document_id'] = doc_id
504
+ return doc_id, len(text_content.split())
505
+ finally:
506
+ if tmp_path and os.path.exists(tmp_path):
507
+ try:
508
+ os.remove(tmp_path)
509
+ except Exception:
510
+ pass
511
+
512
+
513
+ def generate_summary_from_qdrant(doc_id: str, max_chars: int = 80000) -> str:
514
+ chunks = get_all_chunks_for_document(doc_id)
515
+ if not chunks:
516
+ return "No content available to summarize."
517
+ # Concatenate up to max_chars
518
+ full_text = ''
519
+ for chunk in chunks:
520
+ if len(full_text) + len(chunk) > max_chars:
521
+ break
522
+ full_text += (chunk + '\n')
523
+ return generate_summary(full_text)
524
+
525
+
526
+ def generate_chat_response(question, context_docs):
527
+ """Generate chat response using Gemini with context"""
528
+ try:
529
+ if not gemini_model:
530
+ return "Chat functionality unavailable - API not configured"
531
+
532
+ context = "\n\n".join(context_docs) if context_docs else ""
533
+
534
+ prompt = f"""
535
+ You are a research assistant helping users understand academic papers.
536
+ Answer the following question based on the provided context from research papers.
537
+ If the context doesn't contain relevant information, say so politely and suggest what information would be needed.
538
+
539
+ Context from research papers:
540
+ {context}
541
+
542
+ Question: {question}
543
+
544
+ Please provide a clear, accurate, and helpful response.
545
+ """
546
+
547
+ response = gemini_model.generate_content(prompt)
548
+ return response.text
549
+ except Exception as e:
550
+ logger.error(f"Chat response error: {e}")
551
+ return "Error generating response. Please try again."
552
+
553
+ # Routes
554
+ @app.route('/')
555
+ def index():
556
+ """Main page"""
557
+ return render_template('index.html')
558
+
559
+ @app.route('/search', methods=['POST'])
560
+ def search_papers():
561
+ """Search arXiv papers"""
562
+ try:
563
+ data = request.get_json()
564
+ query = data.get('query', '').strip()
565
+
566
+ if not query:
567
+ return jsonify({'error': 'Query is required'}), 400
568
+
569
+ papers = search_arxiv_papers(query, max_results=10)
570
+ return jsonify({'papers': papers})
571
+
572
+ except Exception as e:
573
+ return jsonify({'error': f'Search failed: {str(e)}'}), 500
574
+
575
+ @app.route('/ingest-paper', methods=['POST'])
576
+ def ingest_paper_endpoint():
577
+ """Ingest a paper PDF by URL: download, chunk, embed, store in Qdrant."""
578
+ try:
579
+ data = request.get_json()
580
+ pdf_url = data.get('pdf_url') or data.get('url')
581
+ title = data.get('title')
582
+ authors = data.get('authors')
583
+ published = data.get('published')
584
+ if not pdf_url:
585
+ return jsonify({'error': 'pdf_url is required'}), 400
586
+ doc_id, word_count = ingest_paper(pdf_url, paper_meta={'title': title, 'authors': authors, 'published': published})
587
+ if not doc_id:
588
+ return jsonify({'error': 'Failed to ingest paper'}), 500
589
+ return jsonify({'success': True, 'doc_id': doc_id, 'word_count': word_count})
590
+ except Exception as e:
591
+ logger.error(f"Ingestion failed: {e}", exc_info=True)
592
+ return jsonify({'error': f'Ingestion failed: {str(e)}'}), 500
593
+
594
+ @app.route('/upload', methods=['POST'])
595
+ def upload_file():
596
+ """Handle file upload"""
597
+ try:
598
+ if 'file' not in request.files:
599
+ return jsonify({'error': 'No file selected'}), 400
600
+
601
+ file = request.files['file']
602
+ if file.filename == '':
603
+ return jsonify({'error': 'No file selected'}), 400
604
+
605
+ if file and allowed_file(file.filename):
606
+ filename = secure_filename(file.filename)
607
+
608
+ # Generate a unique ID for this document session
609
+ doc_id = str(uuid.uuid4())
610
+
611
+ # Use a temporary file to avoid cluttering the upload folder
612
+ with tempfile.NamedTemporaryFile(delete=False, suffix=f"_{filename}") as tmp_file:
613
+ file.save(tmp_file.name)
614
+ tmp_file_path = tmp_file.name
615
+
616
+ # Extract text from document
617
+ text_content = process_document(tmp_file_path, filename)
618
+
619
+ # Clean up temporary file immediately
620
+ os.remove(tmp_file_path)
621
+
622
+ if not text_content.strip():
623
+ return jsonify({'error': 'Could not extract text from file'}), 400
624
+
625
+ # Generate summary
626
+ summary = generate_summary(text_content)
627
+
628
+ # Add to vector database for chat
629
+ metadata = {
630
+ 'filename': file.filename,
631
+ 'upload_date': datetime.now().isoformat(),
632
+ 'type': 'uploaded_document'
633
+ }
634
+ add_document_to_vector_db(text_content, metadata, doc_id)
635
+
636
+ # Store the active document ID in the session
637
+ session['active_document_id'] = doc_id
638
+
639
+ return jsonify({
640
+ 'success': True,
641
+ 'filename': file.filename,
642
+ 'summary': summary,
643
+ 'word_count': len(text_content.split()),
644
+ 'doc_id': doc_id # Send doc_id to frontend
645
+ })
646
+
647
+ return jsonify({'error': 'Invalid file type'}), 400
648
+
649
+ except Exception as e:
650
+ logger.error(f"Upload failed: {e}", exc_info=True)
651
+ return jsonify({'error': f'Upload failed: {str(e)}'}), 500
652
+
653
+ @app.route('/summarize-paper', methods=['POST'])
654
+ def summarize_paper():
655
+ """Summarize paper: if doc_id provided, summarize from Qdrant; else ingest then summarize."""
656
+ try:
657
+ data = request.get_json()
658
+ doc_id = data.get('doc_id')
659
+ paper_url = data.get('url', '').strip()
660
+ pdf_url = data.get('pdf_url')
661
+
662
+ if not doc_id and not (paper_url or pdf_url):
663
+ return jsonify({'error': 'doc_id or url/pdf_url is required'}), 400
664
+
665
+ # If doc_id not provided, ingest first
666
+ paper_data = None
667
+ if not doc_id:
668
+ # If only abs URL provided, try resolve via arxiv client for metadata
669
+ try:
670
+ # Extract arXiv ID from URL
671
+ arxiv_id = None
672
+ if paper_url:
673
+ arxiv_id = paper_url.split('/')[-1].replace('.pdf', '')
674
+ if arxiv_id:
675
+ client = arxiv.Client()
676
+ search = arxiv.Search(id_list=[arxiv_id])
677
+ for result in client.results(search):
678
+ paper_data = {
679
+ 'title': result.title,
680
+ 'authors': [author.name for author in result.authors],
681
+ 'summary': result.summary,
682
+ 'url': result.entry_id,
683
+ 'pdf_url': result.pdf_url,
684
+ 'published': result.published.strftime('%Y-%m-%d')
685
+ }
686
+ break
687
+ except Exception:
688
+ paper_data = None
689
+ ingest_pdf = pdf_url or (paper_data['pdf_url'] if paper_data and paper_data.get('pdf_url') else resolve_pdf_url(paper_url))
690
+ new_doc_id, _ = ingest_paper(ingest_pdf, paper_meta=paper_data or {})
691
+ if not new_doc_id:
692
+ return jsonify({'error': 'Failed to ingest paper'}), 500
693
+ doc_id = new_doc_id
694
+ session['active_document_id'] = doc_id
695
+
696
+ # Summarize from Qdrant chunks
697
+ summary = generate_summary_from_qdrant(doc_id)
698
+
699
+ return jsonify({
700
+ 'success': True,
701
+ 'summary': summary,
702
+ 'doc_id': doc_id,
703
+ 'paper': paper_data
704
+ })
705
+ except Exception as e:
706
+ return jsonify({'error': f'Request failed: {str(e)}'}), 500
707
+
708
+ @app.route('/chat', methods=['POST'])
709
+ def chat():
710
+ """Handle chat queries for the active document"""
711
+ try:
712
+ data = request.get_json()
713
+ # Accept both 'message' and 'question' for backward compatibility
714
+ question = data.get('message', data.get('question', '')).strip()
715
+ doc_id = session.get('active_document_id')
716
+
717
+ if not question:
718
+ return jsonify({'error': 'Message is required'}), 400
719
+
720
+ # If no active document, provide general help
721
+ if not doc_id:
722
+ if not gemini_model:
723
+ return jsonify({'error': 'AI service is not available. Please check your API configuration.'}), 500
724
+
725
+ # Generate a general response without document context
726
+ try:
727
+ prompt = f"""
728
+ You are a helpful AI research assistant for Research Radar. The user asked: "{question}"
729
+
730
+ Since no document is currently loaded, provide a helpful response about:
731
+ 1. How to use Research Radar (search papers, upload documents, chat features)
732
+ 2. General research guidance if the question is research-related
733
+ 3. Suggest they upload a document or search for papers to get more specific help
734
+
735
+ Keep your response friendly and informative.
736
+ """
737
+
738
+ response = gemini_model.generate_content(prompt)
739
+ return jsonify({
740
+ 'success': True,
741
+ 'response': response.text,
742
+ 'context_found': False,
743
+ 'no_document': True
744
+ })
745
+ except Exception as e:
746
+ return jsonify({
747
+ 'success': True,
748
+ 'response': "Hello! I'm your AI research assistant. To get started, please upload a document or search for papers using the navigation above. Then I can help you analyze content, answer questions, and provide insights about your research materials.",
749
+ 'context_found': False,
750
+ 'no_document': True
751
+ })
752
+
753
+ # Query vector database for relevant context from the active document
754
+ search_results = query_vector_db(question, doc_id)
755
+
756
+ context_docs = []
757
+ if search_results and isinstance(search_results, dict) and 'documents' in search_results:
758
+ context_docs = search_results['documents'][0]
759
+
760
+ # Generate response
761
+ response = generate_chat_response(question, context_docs)
762
+
763
+ return jsonify({
764
+ 'success': True,
765
+ 'response': response,
766
+ 'context_found': len(context_docs) > 0
767
+ })
768
+
769
+ except Exception as e:
770
+ return jsonify({'error': f'Chat failed: {str(e)}'}), 500
771
+
772
+ @app.route('/documents', methods=['GET'])
773
+ def get_documents():
774
+ """Get all documents from the vector database"""
775
+ try:
776
+ documents = get_all_documents()
777
+ return jsonify({'success': True, 'documents': documents})
778
+ except Exception as e:
779
+ return jsonify({'error': f'Failed to get documents: {str(e)}'}), 500
780
+
781
+
782
+ @app.route('/documents/<doc_id>', methods=['GET'])
783
+ def get_document(doc_id):
784
+ """Get a specific document's metadata"""
785
+ try:
786
+ metadata = get_document_metadata(doc_id)
787
+ if not metadata:
788
+ return jsonify({'error': 'Document not found'}), 404
789
+ return jsonify({'success': True, 'document': metadata})
790
+ except Exception as e:
791
+ return jsonify({'error': f'Failed to get document: {str(e)}'}), 500
792
+
793
+
794
+ @app.route('/documents/<doc_id>/summary', methods=['GET'])
795
+ def get_document_summary(doc_id):
796
+ """Get summary for a specific document"""
797
+ try:
798
+ summary = generate_summary_from_qdrant(doc_id)
799
+ metadata = get_document_metadata(doc_id)
800
+ if not metadata:
801
+ return jsonify({'error': 'Document not found'}), 404
802
+ return jsonify({
803
+ 'success': True,
804
+ 'summary': summary,
805
+ 'document': metadata
806
+ })
807
+ except Exception as e:
808
+ return jsonify({'error': f'Failed to get summary: {str(e)}'}), 500
809
+
810
+
811
+ @app.route('/documents/<doc_id>/activate', methods=['POST'])
812
+ def activate_document(doc_id):
813
+ """Set a document as the active document for chat"""
814
+ try:
815
+ metadata = get_document_metadata(doc_id)
816
+ if not metadata:
817
+ return jsonify({'error': 'Document not found'}), 404
818
+
819
+ session['active_document_id'] = doc_id
820
+ return jsonify({
821
+ 'success': True,
822
+ 'message': 'Document activated',
823
+ 'document': metadata
824
+ })
825
+ except Exception as e:
826
+ return jsonify({'error': f'Failed to activate document: {str(e)}'}), 500
827
+
828
+
829
+ @app.route('/documents/<doc_id>', methods=['DELETE'])
830
+ def delete_document(doc_id):
831
+ """Delete a document from Qdrant"""
832
+ try:
833
+ if not qdrant_client:
834
+ return jsonify({'error': 'Vector database not available'}), 500
835
+
836
+ # Delete all points for this document
837
+ flt = Filter(must=[FieldCondition(key="document_id", match=MatchValue(value=doc_id))])
838
+ qdrant_client.delete(
839
+ collection_name="research_papers",
840
+ points_selector=flt
841
+ )
842
+
843
+ return jsonify({
844
+ 'success': True,
845
+ 'message': 'Document deleted successfully'
846
+ })
847
+ except Exception as e:
848
+ return jsonify({'error': f'Failed to delete document: {str(e)}'}), 500
849
+
850
+
851
+ @app.route('/documents', methods=['DELETE'])
852
+ def clear_all_documents():
853
+ """Clear all documents from Qdrant"""
854
+ try:
855
+ if not qdrant_client:
856
+ return jsonify({'error': 'Vector database not available'}), 500
857
+
858
+ # Delete all points
859
+ qdrant_client.delete(
860
+ collection_name="research_papers",
861
+ points_selector=None
862
+ )
863
+
864
+ return jsonify({
865
+ 'success': True,
866
+ 'message': 'All documents cleared successfully'
867
+ })
868
+ except Exception as e:
869
+ return jsonify({'error': f'Failed to clear documents: {str(e)}'}), 500
870
+
871
+
872
+ @app.route('/clear-session', methods=['POST'])
873
+ def clear_session():
874
+ """Clear the active document from the session"""
875
+ session.pop('active_document_id', None)
876
+ return jsonify({'success': True, 'message': 'Session cleared.'})
877
+
878
+ @app.route('/health')
879
+ def health_check():
880
+ """Health check endpoint"""
881
+ return jsonify({
882
+ 'status': 'healthy',
883
+ 'gemini_available': gemini_model is not None,
884
+ 'embeddings_available': embedding_model is not None,
885
+ 'vector_db_available': qdrant_client is not None
886
+ })
887
+
888
+ if __name__ == '__main__':
889
+ print("🚀 Research Radar - Starting Flask Application...")
890
+ print("📚 Features: arXiv search, document upload, AI summaries, chat functionality")
891
+ print("🔑 Make sure to set GEMINI_API_KEY in your .env file")
892
+ print("🗄 Using Qdrant as Vector DB. Ensure Qdrant is reachable via QDRANT_URL")
893
+
894
+ # Get port from environment variable (for Hugging Face Spaces)
895
+ port = int(os.environ.get('PORT', 5000))
896
+ debug = os.environ.get('FLASK_ENV') == 'development'
897
+
898
+ print(f"🌐 Access the app at: http://localhost:{port}")
899
+
900
+ app.run(debug=debug, host='0.0.0.0', port=port)
config.py ADDED
@@ -0,0 +1,53 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ from dotenv import load_dotenv
3
+
4
+ # Load environment variables
5
+ load_dotenv()
6
+
7
+ class Config:
8
+ """Base configuration class"""
9
+ SECRET_KEY = os.getenv('SECRET_KEY', 'research-radar-secret-key-2024')
10
+ GEMINI_API_KEY = os.getenv('GEMINI_API_KEY')
11
+
12
+ # Upload settings
13
+ UPLOAD_FOLDER = 'uploads'
14
+ MAX_CONTENT_LENGTH = 16 * 1024 * 1024 # 16MB
15
+ ALLOWED_EXTENSIONS = {'txt', 'pdf', 'docx'}
16
+
17
+ # ChromaDB settings
18
+ CHROMA_PERSIST_DIRECTORY = 'chroma_db'
19
+
20
+ # Model settings
21
+ EMBEDDING_MODEL = 'all-MiniLM-L6-v2' # Free sentence transformer model
22
+ GEMINI_MODEL = 'gemini-1.5-flash-latest'
23
+
24
+ # ArXiv settings
25
+ ARXIV_MAX_RESULTS = 10
26
+
27
+ @staticmethod
28
+ def init_app(app):
29
+ """Initialize application with config"""
30
+ pass
31
+
32
+ class DevelopmentConfig(Config):
33
+ """Development configuration"""
34
+ DEBUG = True
35
+ FLASK_ENV = 'development'
36
+
37
+ class ProductionConfig(Config):
38
+ """Production configuration"""
39
+ DEBUG = False
40
+ FLASK_ENV = 'production'
41
+
42
+ class TestingConfig(Config):
43
+ """Testing configuration"""
44
+ TESTING = True
45
+ WTF_CSRF_ENABLED = False
46
+
47
+ # Configuration dictionary
48
+ config = {
49
+ 'development': DevelopmentConfig,
50
+ 'production': ProductionConfig,
51
+ 'testing': TestingConfig,
52
+ 'default': DevelopmentConfig
53
+ }
docker-compose.yml ADDED
@@ -0,0 +1,24 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ version: '3.8'
2
+
3
+ services:
4
+ research-radar:
5
+ build: .
6
+ ports:
7
+ - "7860:7860"
8
+ environment:
9
+ - FLASK_APP=app.py
10
+ - FLASK_ENV=development
11
+ - PORT=7860
12
+ - GEMINI_API_KEY=${GEMINI_API_KEY}
13
+ - QDRANT_URL=${QDRANT_URL}
14
+ - QDRANT_API_KEY=${QDRANT_API_KEY}
15
+ - SECRET_KEY=${SECRET_KEY:-research-radar-secret-key-2024}
16
+ volumes:
17
+ - ./uploads:/app/uploads
18
+ restart: unless-stopped
19
+ healthcheck:
20
+ test: ["CMD", "curl", "-f", "http://localhost:7860/health"]
21
+ interval: 30s
22
+ timeout: 10s
23
+ retries: 3
24
+ start_period: 40s
requirements.txt ADDED
@@ -0,0 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ Flask
2
+ google-generativeai
3
+ sentence-transformers
4
+ qdrant-client
5
+ langchain
6
+ arxiv
7
+ PyPDF2
8
+ python-docx
9
+ requests
10
+ python-dotenv
11
+ Werkzeug
12
+ numpy
13
+ pandas
14
+ beautifulsoup4
15
+ lxml
static/css/style.css ADDED
The diff for this file is too large to render. See raw diff
 
static/js/script.js ADDED
The diff for this file is too large to render. See raw diff
 
templates/index.html ADDED
@@ -0,0 +1,1013 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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>Research Radar - AI-Powered Paper Analysis</title>
7
+ <link rel="stylesheet" href="{{ url_for('static', filename='css/style.css') }}">
8
+ <link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css" rel="stylesheet">
9
+ <link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800&display=swap" rel="stylesheet">
10
+ <meta name="description" content="AI-powered research paper analysis tool for searching, uploading, and chatting with academic documents">
11
+ </head>
12
+ <body>
13
+ <!-- Landing Page -->
14
+ <div id="landingPage" class="page active">
15
+ <!-- Navigation for Landing -->
16
+ <nav class="navbar landing-nav">
17
+ <div class="nav-container">
18
+ <div class="nav-brand">
19
+ <div class="brand-icon">
20
+ <i class="fas fa-brain"></i>
21
+ </div>
22
+ <div class="brand-text">
23
+ <span class="brand-name">Research Radar</span>
24
+ <span class="brand-tagline">AI Research Assistant</span>
25
+ </div>
26
+ </div>
27
+ <button class="mobile-nav-toggle" aria-label="Toggle navigation">
28
+ <i class="fas fa-bars"></i>
29
+ </button>
30
+ <div class="landing-nav-links">
31
+ <a href="#features" class="nav-link">
32
+ <i class="fas fa-star"></i>
33
+ <span>Features</span>
34
+ </a>
35
+ <a href="#about" class="nav-link">
36
+ <i class="fas fa-info-circle"></i>
37
+ <span>About</span>
38
+ </a>
39
+ <button class="nav-cta-btn" onclick="navigateToApp()">
40
+ <i class="fas fa-rocket"></i>
41
+ <span>Get Started</span>
42
+ </button>
43
+ </div>
44
+ </div>
45
+ </nav>
46
+
47
+ <!-- Hero Section -->
48
+ <section class="hero">
49
+ <div class="hero-background">
50
+ <div class="floating-shapes">
51
+ <div class="shape shape-1"></div>
52
+ <div class="shape shape-2"></div>
53
+ <div class="shape shape-3"></div>
54
+ </div>
55
+ </div>
56
+ <div class="hero-content">
57
+ <div class="hero-badge">
58
+ <i class="fas fa-sparkles"></i>
59
+ <span>Powered by AI</span>
60
+ </div>
61
+ <h1 class="hero-title">
62
+ <span class="gradient-text">Research Radar</span>
63
+ </h1>
64
+ <p class="hero-subtitle">
65
+ Discover, analyze, and understand research papers with our AI-powered assistant.
66
+ Search arXiv, upload documents, and get intelligent insights instantly.
67
+ </p>
68
+ <div class="hero-cta">
69
+ <button class="cta-button primary" onclick="navigateToApp('search')">
70
+ <i class="fas fa-rocket"></i>
71
+ Start Exploring
72
+ </button>
73
+ <button class="cta-button secondary" onclick="navigateToApp('upload')">
74
+ <i class="fas fa-upload"></i>
75
+ Upload Paper
76
+ </button>
77
+ </div>
78
+ <div class="hero-features">
79
+ <div class="feature-item" data-aos="fade-up" data-aos-delay="100">
80
+ <div class="feature-icon">
81
+ <i class="fas fa-search"></i>
82
+ </div>
83
+ <div class="feature-content">
84
+ <h3>Smart Search</h3>
85
+ <p>Search millions of papers from arXiv</p>
86
+ </div>
87
+ </div>
88
+ <div class="feature-item" data-aos="fade-up" data-aos-delay="200">
89
+ <div class="feature-icon">
90
+ <i class="fas fa-file-upload"></i>
91
+ </div>
92
+ <div class="feature-content">
93
+ <h3>Document Upload</h3>
94
+ <p>Upload PDFs, DOCX, and TXT files</p>
95
+ </div>
96
+ </div>
97
+ <div class="feature-item" data-aos="fade-up" data-aos-delay="300">
98
+ <div class="feature-icon">
99
+ <i class="fas fa-brain"></i>
100
+ </div>
101
+ <div class="feature-content">
102
+ <h3>AI Summaries</h3>
103
+ <p>Get intelligent paper summaries</p>
104
+ </div>
105
+ </div>
106
+ <div class="feature-item" data-aos="fade-up" data-aos-delay="400">
107
+ <div class="feature-icon">
108
+ <i class="fas fa-robot"></i>
109
+ </div>
110
+ <div class="feature-content">
111
+ <h3>Smart Chat</h3>
112
+ <p>Ask questions about your documents</p>
113
+ </div>
114
+ </div>
115
+ </div>
116
+ </div>
117
+ </section>
118
+
119
+ <!-- Features Section -->
120
+ <section id="features" class="features-section">
121
+ <div class="features-container">
122
+ <div class="features-header">
123
+ <h2>Why Choose Research Radar?</h2>
124
+ <p>Powerful AI tools designed for researchers, students, and academics</p>
125
+ </div>
126
+ <div class="features-grid">
127
+ <div class="feature-card">
128
+ <div class="card-icon">
129
+ <i class="fas fa-lightning-bolt"></i>
130
+ </div>
131
+ <h3>Lightning Fast</h3>
132
+ <p>Get instant AI-powered summaries and insights from research papers in seconds</p>
133
+ </div>
134
+ <div class="feature-card">
135
+ <div class="card-icon">
136
+ <i class="fas fa-shield-alt"></i>
137
+ </div>
138
+ <h3>Secure & Private</h3>
139
+ <p>Your documents are processed securely with enterprise-grade privacy protection</p>
140
+ </div>
141
+ <div class="feature-card">
142
+ <div class="card-icon">
143
+ <i class="fas fa-globe"></i>
144
+ </div>
145
+ <h3>Comprehensive</h3>
146
+ <p>Access millions of papers from arXiv and upload your own documents for analysis</p>
147
+ </div>
148
+ <div class="feature-card">
149
+ <div class="card-icon">
150
+ <i class="fas fa-comments"></i>
151
+ </div>
152
+ <h3>Interactive Chat</h3>
153
+ <p>Ask questions and get detailed explanations about any aspect of your papers</p>
154
+ </div>
155
+ </div>
156
+ </div>
157
+ </section>
158
+
159
+ <!-- About Section -->
160
+ <section id="about" class="about-section">
161
+ <div class="about-container">
162
+ <div class="about-content">
163
+ <h2>About Research Radar</h2>
164
+ <p>Research Radar is an AI-powered research assistant that helps you discover, analyze, and understand academic papers more efficiently. Whether you're a student working on assignments, a researcher exploring new topics, or an academic keeping up with the latest developments in your field, Research Radar makes it easy to extract insights from complex research papers.</p>
165
+ <div class="about-stats">
166
+ <div class="stat-item">
167
+ <div class="stat-number">2M+</div>
168
+ <div class="stat-label">Papers Analyzed</div>
169
+ </div>
170
+ <div class="stat-item">
171
+ <div class="stat-number">50K+</div>
172
+ <div class="stat-label">Active Users</div>
173
+ </div>
174
+ <div class="stat-item">
175
+ <div class="stat-number">99.9%</div>
176
+ <div class="stat-label">Uptime</div>
177
+ </div>
178
+ </div>
179
+ </div>
180
+ </div>
181
+ </section>
182
+
183
+ <!-- Footer -->
184
+ <footer class="landing-footer">
185
+ <div class="footer-container">
186
+ <div class="footer-content">
187
+ <div class="footer-brand">
188
+ <div class="brand-icon">
189
+ <i class="fas fa-brain"></i>
190
+ </div>
191
+ <span class="brand-name">Research Radar</span>
192
+ </div>
193
+ <p>&copy; 2024 Research Radar. Empowering research with AI.</p>
194
+ </div>
195
+ </div>
196
+ </footer>
197
+ </div>
198
+
199
+ <!-- Application Page -->
200
+ <div id="appPage" class="page">
201
+ <!-- Navigation for App -->
202
+ <nav class="navbar app-nav">
203
+ <div class="nav-container">
204
+ <div class="nav-brand" onclick="navigateToLanding()">
205
+ <div class="brand-icon">
206
+ <i class="fas fa-brain"></i>
207
+ </div>
208
+ <div class="brand-text">
209
+ <span class="brand-name">Research Radar</span>
210
+ <span class="brand-tagline">AI Research Assistant</span>
211
+ </div>
212
+ </div>
213
+ <button class="mobile-nav-toggle" aria-label="Toggle navigation">
214
+ <i class="fas fa-bars"></i>
215
+ </button>
216
+ <div class="nav-links">
217
+ <a href="#search" class="nav-link active" data-section="search">
218
+ <i class="fas fa-search"></i>
219
+ <span>Search</span>
220
+ </a>
221
+ <a href="#upload" class="nav-link" data-section="upload">
222
+ <i class="fas fa-upload"></i>
223
+ <span>Upload</span>
224
+ </a>
225
+ <a href="#mypapers" class="nav-link" data-section="mypapers">
226
+ <i class="fas fa-folder-open"></i>
227
+ <span>My Papers</span>
228
+ </a>
229
+ </div>
230
+ <!-- Status Indicator -->
231
+ <div class="status-indicator" id="statusIndicator">
232
+ <div class="status-dot"></div>
233
+ <span class="status-text">Ready</span>
234
+ </div>
235
+ <!-- Back to Landing -->
236
+ <button class="back-to-landing" onclick="navigateToLanding()" title="Back to Home">
237
+ <i class="fas fa-home"></i>
238
+ </button>
239
+ </div>
240
+ </nav>
241
+
242
+ <!-- Main Container -->
243
+ <div class="main-container">
244
+ <!-- Search Section -->
245
+ <section id="search" class="section active">
246
+ <div class="search-hero">
247
+ <div class="search-hero-content">
248
+ <div class="search-title-area">
249
+ <div class="search-icon-large">
250
+ <i class="fas fa-search"></i>
251
+ </div>
252
+ <h1>Discover Research Papers</h1>
253
+ <p>Search through millions of academic papers from arXiv and get AI-powered insights</p>
254
+ </div>
255
+
256
+ <!-- Enhanced Search Box -->
257
+ <div class="search-box-enhanced">
258
+ <div class="search-input-container">
259
+ <div class="search-input-wrapper">
260
+ <i class="fas fa-search search-icon"></i>
261
+ <input type="text" id="searchInput" placeholder="What are you researching today?" autocomplete="off">
262
+ <button id="searchBtn" class="search-submit-btn">
263
+ <i class="fas fa-arrow-right"></i>
264
+ <span>Search</span>
265
+ </button>
266
+ </div>
267
+ <div class="search-actions">
268
+ <button class="voice-search-btn" title="Voice Search">
269
+ <i class="fas fa-microphone"></i>
270
+ </button>
271
+ <button class="advanced-search-btn" title="Advanced Search" onclick="toggleAdvancedSearch()">
272
+ <i class="fas fa-sliders-h"></i>
273
+ </button>
274
+ </div>
275
+ </div>
276
+
277
+ <!-- Advanced Filters -->
278
+ <div class="advanced-filters" id="advancedFilters">
279
+ <div class="filters-row">
280
+ <div class="filter-group">
281
+ <label class="filter-label">
282
+ <i class="fas fa-calendar"></i>
283
+ Time Range
284
+ </label>
285
+ <select id="timeFilter" class="filter-select">
286
+ <option value="all">All Time</option>
287
+ <option value="2024">2024</option>
288
+ <option value="2023">2023</option>
289
+ <option value="2022">2022</option>
290
+ <option value="recent">Last 6 months</option>
291
+ <option value="month">Last month</option>
292
+ </select>
293
+ </div>
294
+ <div class="filter-group">
295
+ <label class="filter-label">
296
+ <i class="fas fa-tag"></i>
297
+ Category
298
+ </label>
299
+ <select id="categoryFilter" class="filter-select">
300
+ <option value="all">All Categories</option>
301
+ <option value="cs">Computer Science</option>
302
+ <option value="math">Mathematics</option>
303
+ <option value="physics">Physics</option>
304
+ <option value="stat">Statistics</option>
305
+ <option value="bio">Biology</option>
306
+ <option value="econ">Economics</option>
307
+ </select>
308
+ </div>
309
+ <div class="filter-group">
310
+ <label class="filter-label">
311
+ <i class="fas fa-sort"></i>
312
+ Sort By
313
+ </label>
314
+ <select id="sortFilter" class="filter-select">
315
+ <option value="relevance">Relevance</option>
316
+ <option value="date">Latest First</option>
317
+ <option value="citations">Most Cited</option>
318
+ <option value="title">Title A-Z</option>
319
+ </select>
320
+ </div>
321
+ </div>
322
+ </div>
323
+ </div>
324
+
325
+ <!-- Quick Search Categories -->
326
+ <div class="quick-search-section">
327
+ <h3>Popular Research Areas</h3>
328
+ <div class="quick-search-grid">
329
+ <button class="quick-search-card" data-query="machine learning">
330
+ <div class="card-icon">
331
+ <i class="fas fa-robot"></i>
332
+ </div>
333
+ <div class="card-content">
334
+ <h4>Machine Learning</h4>
335
+ <span>12,847 papers</span>
336
+ </div>
337
+ </button>
338
+ <button class="quick-search-card" data-query="artificial intelligence">
339
+ <div class="card-icon">
340
+ <i class="fas fa-brain"></i>
341
+ </div>
342
+ <div class="card-content">
343
+ <h4>Artificial Intelligence</h4>
344
+ <span>8,932 papers</span>
345
+ </div>
346
+ </button>
347
+ <button class="quick-search-card" data-query="quantum computing">
348
+ <div class="card-icon">
349
+ <i class="fas fa-atom"></i>
350
+ </div>
351
+ <div class="card-content">
352
+ <h4>Quantum Computing</h4>
353
+ <span>3,456 papers</span>
354
+ </div>
355
+ </button>
356
+ <button class="quick-search-card" data-query="deep learning">
357
+ <div class="card-icon">
358
+ <i class="fas fa-network-wired"></i>
359
+ </div>
360
+ <div class="card-content">
361
+ <h4>Deep Learning</h4>
362
+ <span>15,234 papers</span>
363
+ </div>
364
+ </button>
365
+ <button class="quick-search-card" data-query="computer vision">
366
+ <div class="card-icon">
367
+ <i class="fas fa-eye"></i>
368
+ </div>
369
+ <div class="card-content">
370
+ <h4>Computer Vision</h4>
371
+ <span>9,876 papers</span>
372
+ </div>
373
+ </button>
374
+ <button class="quick-search-card" data-query="natural language processing">
375
+ <div class="card-icon">
376
+ <i class="fas fa-comments"></i>
377
+ </div>
378
+ <div class="card-content">
379
+ <h4>NLP</h4>
380
+ <span>7,543 papers</span>
381
+ </div>
382
+ </button>
383
+ </div>
384
+ </div>
385
+ </div>
386
+ </div>
387
+
388
+ <!-- Search Tips -->
389
+ <div class="search-tips" id="searchTips">
390
+ <div class="tips-header">
391
+ <h3><i class="fas fa-lightbulb"></i> Search Tips</h3>
392
+ <button class="tips-toggle" onclick="toggleSearchTips()">
393
+ <i class="fas fa-chevron-down"></i>
394
+ </button>
395
+ </div>
396
+ <div class="tips-content">
397
+ <div class="tips-grid">
398
+ <div class="tip-item">
399
+ <div class="tip-icon">
400
+ <i class="fas fa-quotes-left"></i>
401
+ </div>
402
+ <div class="tip-content">
403
+ <h4>Use Quotes</h4>
404
+ <p>Search for exact phrases like <code>"neural networks"</code></p>
405
+ </div>
406
+ </div>
407
+ <div class="tip-item">
408
+ <div class="tip-icon">
409
+ <i class="fas fa-plus"></i>
410
+ </div>
411
+ <div class="tip-content">
412
+ <h4>Combine Terms</h4>
413
+ <p>Use AND, OR like <code>machine learning AND ethics</code></p>
414
+ </div>
415
+ </div>
416
+ <div class="tip-item">
417
+ <div class="tip-icon">
418
+ <i class="fas fa-user"></i>
419
+ </div>
420
+ <div class="tip-content">
421
+ <h4>Search Authors</h4>
422
+ <p>Find papers by author <code>author:smith</code></p>
423
+ </div>
424
+ </div>
425
+ <div class="tip-item">
426
+ <div class="tip-icon">
427
+ <i class="fas fa-asterisk"></i>
428
+ </div>
429
+ <div class="tip-content">
430
+ <h4>Use Wildcards</h4>
431
+ <p>Use * for variations like <code>neural*</code></p>
432
+ </div>
433
+ </div>
434
+ </div>
435
+ </div>
436
+ </div>
437
+
438
+ <!-- Recent Searches -->
439
+ <div class="recent-searches" id="recentSearches" style="display: none;">
440
+ <div class="recent-header">
441
+ <h3><i class="fas fa-history"></i> Recent Searches</h3>
442
+ <button class="clear-history" onclick="clearSearchHistory()">
443
+ <i class="fas fa-trash"></i> Clear
444
+ </button>
445
+ </div>
446
+ <div class="recent-items" id="recentSearchItems">
447
+ <!-- Dynamic content -->
448
+ </div>
449
+ </div>
450
+
451
+ <!-- Search Results -->
452
+ <div id="searchResults" class="results-container">
453
+ <div class="search-placeholder-enhanced">
454
+ <div class="placeholder-animation">
455
+ <div class="floating-papers">
456
+ <div class="paper-icon">📄</div>
457
+ <div class="paper-icon">📊</div>
458
+ <div class="paper-icon">🔬</div>
459
+ </div>
460
+ </div>
461
+ <h3>Ready to explore the world of research?</h3>
462
+ <p>Start by typing a topic above or click on one of the popular research areas</p>
463
+ <div class="placeholder-stats">
464
+ <div class="stat">
465
+ <strong>2.3M+</strong>
466
+ <span>Papers Available</span>
467
+ </div>
468
+ <div class="stat">
469
+ <strong>50+</strong>
470
+ <span>Research Fields</span>
471
+ </div>
472
+ <div class="stat">
473
+ <strong>24/7</strong>
474
+ <span>Updated</span>
475
+ </div>
476
+ </div>
477
+ </div>
478
+ </div>
479
+
480
+ <!-- Search Suggestions Dropdown -->
481
+ <div class="search-suggestions-dropdown" id="searchSuggestions">
482
+ <div class="suggestions-section">
483
+ <h4>Suggestions</h4>
484
+ <div class="suggestion-item" data-query="machine learning">
485
+ <i class="fas fa-search"></i>
486
+ <span>machine learning</span>
487
+ <small>12.8k papers</small>
488
+ </div>
489
+ <div class="suggestion-item" data-query="neural networks">
490
+ <i class="fas fa-search"></i>
491
+ <span>neural networks</span>
492
+ <small>8.3k papers</small>
493
+ </div>
494
+ <div class="suggestion-item" data-query="quantum computing">
495
+ <i class="fas fa-search"></i>
496
+ <span>quantum computing</span>
497
+ <small>3.4k papers</small>
498
+ </div>
499
+ </div>
500
+ <div class="suggestions-section">
501
+ <h4>Trending Today</h4>
502
+ <div class="suggestion-item trending" data-query="large language models">
503
+ <i class="fas fa-fire"></i>
504
+ <span>large language models</span>
505
+ <small class="trending-badge">Hot</small>
506
+ </div>
507
+ <div class="suggestion-item trending" data-query="transformer architecture">
508
+ <i class="fas fa-fire"></i>
509
+ <span>transformer architecture</span>
510
+ <small class="trending-badge">Hot</small>
511
+ </div>
512
+ </div>
513
+ </div>
514
+ </section>
515
+
516
+ <!-- Upload Section -->
517
+ <section id="upload" class="section">
518
+ <div class="upload-hero">
519
+ <div class="upload-hero-content">
520
+ <div class="upload-title-area">
521
+ <div class="upload-icon-large">
522
+ <i class="fas fa-cloud-upload-alt"></i>
523
+ </div>
524
+ <h1>Upload & Analyze Documents</h1>
525
+ <p>Upload research papers, documents, or analyze from URLs to get AI-powered insights and summaries</p>
526
+ </div>
527
+
528
+ <!-- Enhanced Upload Methods -->
529
+ <div class="upload-methods-enhanced">
530
+ <!-- File Upload Zone -->
531
+ <div class="upload-method-card primary-upload" id="uploadZone">
532
+ <div class="upload-card-header">
533
+ <div class="upload-card-icon">
534
+ <i class="fas fa-file-upload"></i>
535
+ </div>
536
+ <div class="upload-card-title">
537
+ <h3>Upload Document</h3>
538
+ <p>Drag & drop or browse files</p>
539
+ </div>
540
+ </div>
541
+
542
+ <div class="upload-drop-zone">
543
+ <div class="drop-zone-content">
544
+ <div class="upload-animation">
545
+ <div class="upload-cloud">
546
+ <i class="fas fa-cloud-upload-alt"></i>
547
+ </div>
548
+ <div class="upload-arrow">↑</div>
549
+ </div>
550
+ <h4>Drop files here</h4>
551
+ <p>or click to browse</p>
552
+
553
+ <div class="supported-formats">
554
+ <div class="format-badge pdf">
555
+ <i class="fas fa-file-pdf"></i>
556
+ <span>PDF</span>
557
+ </div>
558
+ <div class="format-badge docx">
559
+ <i class="fas fa-file-word"></i>
560
+ <span>DOCX</span>
561
+ </div>
562
+ <div class="format-badge txt">
563
+ <i class="fas fa-file-alt"></i>
564
+ <span>TXT</span>
565
+ </div>
566
+ </div>
567
+
568
+ <div class="upload-specs">
569
+ <span><i class="fas fa-info-circle"></i> Max size: 16MB</span>
570
+ <span><i class="fas fa-shield-alt"></i> Secure upload</span>
571
+ </div>
572
+ </div>
573
+ <input type="file" id="fileInput" accept=".pdf,.txt,.docx" hidden>
574
+ </div>
575
+
576
+ <div class="upload-actions">
577
+ <button class="browse-btn" onclick="document.getElementById('fileInput').click()">
578
+ <i class="fas fa-folder-open"></i>
579
+ <span>Browse Files</span>
580
+ </button>
581
+ </div>
582
+ </div>
583
+
584
+ <!-- URL Analysis -->
585
+ <div class="upload-method-card secondary-upload">
586
+ <div class="upload-card-header">
587
+ <div class="upload-card-icon">
588
+ <i class="fas fa-link"></i>
589
+ </div>
590
+ <div class="upload-card-title">
591
+ <h3>Analyze from URL</h3>
592
+ <p>Paste arXiv or research paper URLs</p>
593
+ </div>
594
+ </div>
595
+
596
+ <div class="url-input-enhanced">
597
+ <div class="url-input-wrapper">
598
+ <i class="fas fa-link url-icon"></i>
599
+ <input type="text" id="paperUrl" placeholder="https://arxiv.org/abs/2301.00001" autocomplete="off">
600
+ <button id="analyzeUrlBtn" class="analyze-btn">
601
+ <i class="fas fa-search"></i>
602
+ <span>Analyze</span>
603
+ </button>
604
+ </div>
605
+ </div>
606
+
607
+ <div class="url-suggestions">
608
+ <h4>Popular Sources</h4>
609
+ <div class="source-buttons">
610
+ <button class="source-btn" data-source="arxiv">
611
+ <i class="fas fa-graduation-cap"></i>
612
+ <span>arXiv</span>
613
+ </button>
614
+ <button class="source-btn" data-source="pubmed">
615
+ <i class="fas fa-microscope"></i>
616
+ <span>PubMed</span>
617
+ </button>
618
+ <button class="source-btn" data-source="ieee">
619
+ <i class="fas fa-microchip"></i>
620
+ <span>IEEE</span>
621
+ </button>
622
+ <button class="source-btn" data-source="acm">
623
+ <i class="fas fa-code"></i>
624
+ <span>ACM</span>
625
+ </button>
626
+ </div>
627
+ </div>
628
+
629
+ <div class="example-urls">
630
+ <details class="url-examples-details">
631
+ <summary>
632
+ <i class="fas fa-lightbulb"></i>
633
+ Example URLs
634
+ </summary>
635
+ <div class="example-list">
636
+ <button class="example-url" data-url="https://arxiv.org/abs/2301.00001">
637
+ <i class="fas fa-external-link-alt"></i>
638
+ <span>https://arxiv.org/abs/2301.00001</span>
639
+ <small>Machine Learning Paper</small>
640
+ </button>
641
+ <button class="example-url" data-url="https://arxiv.org/abs/2205.11487">
642
+ <i class="fas fa-external-link-alt"></i>
643
+ <span>https://arxiv.org/abs/2205.11487</span>
644
+ <small>PaLM Language Model</small>
645
+ </button>
646
+ <button class="example-url" data-url="https://arxiv.org/abs/2203.02155">
647
+ <i class="fas fa-external-link-alt"></i>
648
+ <span>https://arxiv.org/abs/2203.02155</span>
649
+ <small>Training Language Models</small>
650
+ </button>
651
+ </div>
652
+ </details>
653
+ </div>
654
+ </div>
655
+ </div>
656
+
657
+ <!-- Upload Tips -->
658
+ <div class="upload-tips">
659
+ <div class="tips-header">
660
+ <h3><i class="fas fa-lightbulb"></i> Upload Tips</h3>
661
+ <button class="tips-toggle" onclick="toggleUploadTips()">
662
+ <i class="fas fa-chevron-down"></i>
663
+ </button>
664
+ </div>
665
+ <div class="tips-content" id="uploadTipsContent">
666
+ <div class="tips-grid">
667
+ <div class="tip-item">
668
+ <div class="tip-icon">
669
+ <i class="fas fa-file-pdf"></i>
670
+ </div>
671
+ <div class="tip-content">
672
+ <h4>Best Format</h4>
673
+ <p>PDF files work best for accurate text extraction</p>
674
+ </div>
675
+ </div>
676
+ <div class="tip-item">
677
+ <div class="tip-icon">
678
+ <i class="fas fa-compress-alt"></i>
679
+ </div>
680
+ <div class="tip-content">
681
+ <h4>File Size</h4>
682
+ <p>Keep files under 16MB for faster processing</p>
683
+ </div>
684
+ </div>
685
+ <div class="tip-item">
686
+ <div class="tip-icon">
687
+ <i class="fas fa-language"></i>
688
+ </div>
689
+ <div class="tip-content">
690
+ <h4>Language</h4>
691
+ <p>English documents get the best analysis results</p>
692
+ </div>
693
+ </div>
694
+ <div class="tip-item">
695
+ <div class="tip-icon">
696
+ <i class="fas fa-shield-alt"></i>
697
+ </div>
698
+ <div class="tip-content">
699
+ <h4>Privacy</h4>
700
+ <p>Your documents are processed securely and privately</p>
701
+ </div>
702
+ </div>
703
+ </div>
704
+ </div>
705
+ </div>
706
+ </div>
707
+ </div>
708
+
709
+ <!-- Enhanced Upload Progress -->
710
+ <div class="upload-progress-enhanced" id="uploadProgress" style="display: none;">
711
+ <div class="progress-container">
712
+ <div class="progress-header">
713
+ <div class="progress-title">
714
+ <h3>Processing Document</h3>
715
+ <p id="progressSubtitle">Preparing your document for analysis...</p>
716
+ </div>
717
+ <div class="progress-stats">
718
+ <span class="progress-percentage" id="progressPercentage">0%</span>
719
+ <span class="progress-time" id="progressTime">Estimating...</span>
720
+ </div>
721
+ </div>
722
+
723
+ <div class="progress-bar-container">
724
+ <div class="progress-bar">
725
+ <div class="progress-fill" id="progressFill"></div>
726
+ <div class="progress-glow"></div>
727
+ </div>
728
+ </div>
729
+
730
+ <div class="progress-steps-enhanced">
731
+ <div class="progress-step active" data-step="1">
732
+ <div class="step-icon">
733
+ <i class="fas fa-upload"></i>
734
+ </div>
735
+ <div class="step-content">
736
+ <h4>Uploading</h4>
737
+ <p>Transferring your document</p>
738
+ </div>
739
+ </div>
740
+ <div class="progress-step" data-step="2">
741
+ <div class="step-icon">
742
+ <i class="fas fa-file-text"></i>
743
+ </div>
744
+ <div class="step-content">
745
+ <h4>Extracting</h4>
746
+ <p>Reading document content</p>
747
+ </div>
748
+ </div>
749
+ <div class="progress-step" data-step="3">
750
+ <div class="step-icon">
751
+ <i class="fas fa-brain"></i>
752
+ </div>
753
+ <div class="step-content">
754
+ <h4>Analyzing</h4>
755
+ <p>AI processing in progress</p>
756
+ </div>
757
+ </div>
758
+ <div class="progress-step" data-step="4">
759
+ <div class="step-icon">
760
+ <i class="fas fa-check-circle"></i>
761
+ </div>
762
+ <div class="step-content">
763
+ <h4>Complete</h4>
764
+ <p>Ready for questions</p>
765
+ </div>
766
+ </div>
767
+ </div>
768
+ </div>
769
+ </div>
770
+
771
+ <!-- Enhanced Results -->
772
+ <div id="uploadResults" class="results-container">
773
+ <div class="upload-placeholder-enhanced">
774
+ <div class="placeholder-animation">
775
+ <div class="floating-documents">
776
+ <div class="doc-icon">📄</div>
777
+ <div class="doc-icon">📊</div>
778
+ <div class="doc-icon">📋</div>
779
+ </div>
780
+ </div>
781
+ <h3>Ready to analyze your documents?</h3>
782
+ <p>Upload a file or paste a URL to get started with AI-powered analysis</p>
783
+ <div class="placeholder-features">
784
+ <div class="feature">
785
+ <i class="fas fa-magic"></i>
786
+ <span>AI Summaries</span>
787
+ </div>
788
+ <div class="feature">
789
+ <i class="fas fa-question-circle"></i>
790
+ <span>Q&A Chat</span>
791
+ </div>
792
+ <div class="feature">
793
+ <i class="fas fa-key"></i>
794
+ <span>Key Insights</span>
795
+ </div>
796
+ </div>
797
+ </div>
798
+ </div>
799
+ </section>
800
+
801
+ <!-- My Papers Section -->
802
+ <section id="mypapers" class="section">
803
+ <div class="mypapers-hero">
804
+ <div class="mypapers-hero-content">
805
+ <div class="mypapers-title-area">
806
+ <div class="mypapers-icon-large">
807
+ <i class="fas fa-folder-open"></i>
808
+ </div>
809
+ <h1>My Papers</h1>
810
+ <p>Access and manage all your uploaded and analyzed research papers</p>
811
+ </div>
812
+
813
+ <div class="mypapers-actions">
814
+ <button class="refresh-papers-btn" onclick="researchRadar.loadMyPapers()">
815
+ <i class="fas fa-sync-alt"></i>
816
+ <span>Refresh</span>
817
+ </button>
818
+ <button class="clear-all-btn" onclick="researchRadar.clearAllPapers()">
819
+ <i class="fas fa-trash"></i>
820
+ <span>Clear All</span>
821
+ </button>
822
+ </div>
823
+ </div>
824
+ </div>
825
+
826
+ <div class="mypapers-content">
827
+ <div class="mypapers-loading" id="mypapersLoading" style="display: none;">
828
+ <div class="loading-spinner"></div>
829
+ <p>Loading your papers...</p>
830
+ </div>
831
+
832
+ <div class="mypapers-empty" id="mypapersEmpty" style="display: none;">
833
+ <div class="empty-state">
834
+ <div class="empty-icon">
835
+ <i class="fas fa-folder-open"></i>
836
+ </div>
837
+ <h3>No Papers Yet</h3>
838
+ <p>Upload documents or search for papers to get started</p>
839
+ <div class="empty-actions">
840
+ <button class="btn-primary" onclick="researchRadar.switchSection('upload')">
841
+ <i class="fas fa-upload"></i>
842
+ <span>Upload Document</span>
843
+ </button>
844
+ <button class="btn-secondary" onclick="researchRadar.switchSection('search')">
845
+ <i class="fas fa-search"></i>
846
+ <span>Search Papers</span>
847
+ </button>
848
+ </div>
849
+ </div>
850
+ </div>
851
+
852
+ <div class="papers-grid" id="papersGrid">
853
+ <!-- Papers will be loaded here dynamically -->
854
+ </div>
855
+ </div>
856
+ </section>
857
+
858
+ <!-- Summary + Chat Section -->
859
+ <section id="summary-chat" class="section" style="display: none;">
860
+ <div class="summary-chat-header">
861
+ <div class="header-content">
862
+ <button class="back-btn" onclick="goBackToSearch()">
863
+ <i class="fas fa-arrow-left"></i>
864
+ <span>Back to Search</span>
865
+ </button>
866
+ <div class="paper-info" id="paperInfo">
867
+ <h2 id="paperTitle">Paper Title</h2>
868
+ <div class="paper-meta">
869
+ <span class="author" id="paperAuthor">Author Name</span>
870
+ <span class="date" id="paperDate">2024</span>
871
+ <span class="category" id="paperCategory">cs.LG</span>
872
+ </div>
873
+ </div>
874
+ </div>
875
+ </div>
876
+
877
+ <!-- Tab Navigation -->
878
+ <div class="tab-navigation">
879
+ <div class="tab-nav-container">
880
+ <button class="tab-btn active" data-tab="summary" onclick="switchTab('summary')">
881
+ <i class="fas fa-file-text"></i>
882
+ <span>AI Summary</span>
883
+ <div class="tab-indicator"></div>
884
+ </button>
885
+ <button class="tab-btn" data-tab="chat" onclick="switchTab('chat')">
886
+ <i class="fas fa-comments"></i>
887
+ <span>Ask Questions</span>
888
+ <div class="tab-indicator"></div>
889
+ </button>
890
+ </div>
891
+ </div>
892
+
893
+ <!-- Tab Content -->
894
+ <div class="tab-content-container">
895
+ <!-- Summary Tab -->
896
+ <div id="summary-tab" class="tab-content active">
897
+ <div class="summary-container">
898
+ <div class="summary-header">
899
+ <div class="summary-title">
900
+ <div class="summary-icon">
901
+ <i class="fas fa-brain"></i>
902
+ </div>
903
+ <div class="summary-title-text">
904
+ <h3>AI-Generated Summary</h3>
905
+ <p>Comprehensive analysis of the research paper</p>
906
+ </div>
907
+ </div>
908
+ </div>
909
+
910
+ <div class="summary-main-content">
911
+ <div class="summary-content" id="summaryContent">
912
+ <div class="summary-loading" id="summaryLoading">
913
+ <div class="loading-spinner"></div>
914
+ <div class="loading-text">
915
+ <h4>Analyzing paper...</h4>
916
+ <p>Our AI is reading the document to generate a comprehensive summary.</p>
917
+ </div>
918
+ </div>
919
+ <div class="summary-text" id="summaryText" style="display: none;">
920
+ <!-- Summary will be loaded here -->
921
+ </div>
922
+ </div>
923
+ </div>
924
+ </div>
925
+ </div>
926
+
927
+ <!-- Chat Tab -->
928
+ <div id="chat-tab" class="tab-content">
929
+ <div class="chat-container">
930
+ <div class="chat-header">
931
+ <div class="chat-title">
932
+ <div class="chat-icon">
933
+ <i class="fas fa-robot"></i>
934
+ </div>
935
+ <div class="chat-title-text">
936
+ <h3>Interactive Q&A</h3>
937
+ <p>Ask me anything about this paper</p>
938
+ </div>
939
+ </div>
940
+ <div class="chat-status">
941
+ <div class="status-indicator online">
942
+ <div class="status-dot"></div>
943
+ <span>AI Ready</span>
944
+ </div>
945
+ </div>
946
+ </div>
947
+
948
+ <div class="chat-messages-container" id="chatMessagesPanel">
949
+ <div class="chat-welcome">
950
+ <div class="welcome-avatar">
951
+ <div class="avatar-icon">
952
+ <i class="fas fa-robot"></i>
953
+ </div>
954
+ <div class="avatar-status"></div>
955
+ </div>
956
+ <div class="welcome-content">
957
+ <h4>👋 Welcome! I'm ready to help</h4>
958
+ <p>I've thoroughly analyzed this research paper. Ask me about its contributions, methodology, or any other details.</p>
959
+ </div>
960
+ </div>
961
+ <!-- Chat messages will be dynamically added here -->
962
+ </div>
963
+
964
+ <div class="chat-input-area">
965
+ <!-- Quick Questions -->
966
+ <div class="quick-questions-chips" id="quickQuestionsChips">
967
+ <button class="quick-question-chip" onclick="askQuickQuestion('Main contribution?')">Main contribution?</button>
968
+ <button class="quick-question-chip" onclick="askQuickQuestion('Methodology?')">Methodology?</button>
969
+ <button class="quick-question-chip" onclick="askQuickQuestion('Key findings?')">Key findings?</button>
970
+ <button class="quick-question-chip" onclick="askQuickQuestion('Limitations?')">Limitations?</button>
971
+ </div>
972
+
973
+ <!-- Chat Input -->
974
+ <div class="chat-input-container">
975
+ <div class="input-wrapper">
976
+ <textarea id="chatInputPanel" placeholder="Ask a follow-up question..." rows="1"></textarea>
977
+ <button id="chatSendBtnPanel" class="send-btn-panel" title="Send Message">
978
+ <i class="fas fa-paper-plane"></i>
979
+ </button>
980
+ </div>
981
+ </div>
982
+ </div>
983
+ </div>
984
+ </div>
985
+ </div>
986
+ </section>
987
+ </div>
988
+ </div>
989
+
990
+ <!-- Loading Overlay -->
991
+ <div id="loadingOverlay" class="loading-overlay">
992
+ <div class="loading-content">
993
+ <div class="loading-animation">
994
+ <div class="loading-spinner"></div>
995
+ <div class="loading-dots">
996
+ <div class="dot"></div>
997
+ <div class="dot"></div>
998
+ <div class="dot"></div>
999
+ </div>
1000
+ </div>
1001
+ <h3 id="loadingTitle">Processing your request...</h3>
1002
+ <p id="loadingSubtitle">This may take a few moments</p>
1003
+ </div>
1004
+ </div>
1005
+
1006
+ <!-- Toast Notifications -->
1007
+ <div id="toastContainer" class="toast-container"></div>
1008
+
1009
+
1010
+
1011
+ <script src="{{ url_for('static', filename='js/script.js') }}"></script>
1012
+ </body>
1013
+ </html>