Spaces:
Build error
Build error
Update app.py
Browse files
app.py
CHANGED
|
@@ -1,21 +1,29 @@
|
|
| 1 |
-
|
| 2 |
-
import groq
|
| 3 |
import os
|
| 4 |
import tempfile
|
| 5 |
import uuid
|
| 6 |
-
import yfinance as yf
|
| 7 |
-
import pandas as pd
|
| 8 |
-
import plotly.graph_objects as go
|
| 9 |
-
from dotenv import load_dotenv
|
| 10 |
-
from langchain.text_splitter import RecursiveCharacterTextSplitter
|
| 11 |
-
from langchain.vectorstores import FAISS
|
| 12 |
-
from langchain.embeddings import HuggingFaceEmbeddings
|
| 13 |
-
import fitz # PyMuPDF
|
| 14 |
import base64
|
| 15 |
-
from PIL import Image
|
| 16 |
import io
|
| 17 |
-
import requests
|
| 18 |
import json
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 19 |
|
| 20 |
# Load environment variables
|
| 21 |
load_dotenv()
|
|
@@ -23,19 +31,32 @@ client = groq.Client(api_key=os.getenv("GROQ_LEGAL_API_KEY"))
|
|
| 23 |
embeddings = HuggingFaceEmbeddings(model_name="sentence-transformers/all-MiniLM-L6-v2")
|
| 24 |
|
| 25 |
# Directory to store FAISS indexes
|
| 26 |
-
FAISS_INDEX_DIR = "
|
| 27 |
if not os.path.exists(FAISS_INDEX_DIR):
|
| 28 |
os.makedirs(FAISS_INDEX_DIR)
|
| 29 |
|
| 30 |
# Dictionary to store user-specific vectorstores
|
| 31 |
user_vectorstores = {}
|
| 32 |
|
| 33 |
-
#
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 34 |
custom_css = """
|
| 35 |
:root {
|
| 36 |
-
--primary-color: #
|
| 37 |
-
--secondary-color: #
|
| 38 |
-
--light-background: #
|
| 39 |
--dark-text: #333333;
|
| 40 |
--white: #FFFFFF;
|
| 41 |
--border-color: #E5E7EB;
|
|
@@ -46,7 +67,7 @@ body { background-color: var(--light-background); font-family: 'Inter', sans-ser
|
|
| 46 |
.header-title { color: var(--secondary-color); font-size: 1.8rem; font-weight: 700; text-align: center; }
|
| 47 |
.header-subtitle { color: var(--dark-text); font-size: 1rem; text-align: center; margin-top: 5px; }
|
| 48 |
.chat-container { border-radius: 12px !important; box-shadow: 0 4px 6px rgba(0,0,0,0.1) !important; background-color: var(--white) !important; border: 1px solid var(--border-color) !important; min-height: 500px; }
|
| 49 |
-
.message-user { background-color: var(--primary-color) !important; color: var(--
|
| 50 |
.message-bot { background-color: #F0F0F0 !important; color: var(--dark-text) !important; border-radius: 18px 18px 18px 4px !important; padding: 12px 16px !important; margin-right: auto !important; max-width: 80% !important; }
|
| 51 |
.input-area { background-color: var(--white) !important; border-top: 1px solid var(--border-color) !important; padding: 12px !important; border-radius: 0 0 12px 12px !important; }
|
| 52 |
.input-box { border: 1px solid var(--border-color) !important; border-radius: 24px !important; padding: 12px 16px !important; box-shadow: 0 2px 4px rgba(0,0,0,0.05) !important; }
|
|
@@ -54,10 +75,20 @@ body { background-color: var(--light-background); font-family: 'Inter', sans-ser
|
|
| 54 |
.clear-btn { background-color: #F0F0F0 !important; border: 1px solid var(--border-color) !important; border-radius: 24px !important; color: var(--dark-text) !important; padding: 8px 16px !important; font-weight: 500 !important; }
|
| 55 |
.pdf-viewer-container { border-radius: 12px !important; box-shadow: 0 4px 6px rgba(0,0,0,0.1) !important; background-color: var(--white) !important; border: 1px solid var(--border-color) !important; padding: 20px; }
|
| 56 |
.pdf-viewer-image { max-width: 100%; height: auto; border: 1px solid var(--border-color); border-radius: 12px; box-shadow: 0 2px 4px rgba(0,0,0,0.05); }
|
| 57 |
-
.stats-box { background-color: #
|
| 58 |
.tool-container { background-color: var(--white); border-radius: 12px; box-shadow: 0 4px 6px rgba(0,0,0,0.1); padding: 15px; margin-bottom: 20px; }
|
| 59 |
-
.
|
| 60 |
-
.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 61 |
"""
|
| 62 |
|
| 63 |
# Function to process PDF files (unchanged)
|
|
@@ -97,8 +128,80 @@ def process_pdf(pdf_file):
|
|
| 97 |
os.unlink(pdf_path)
|
| 98 |
return None, f"Error processing PDF: {str(e)}", {"page_images": [], "total_pages": 0, "total_words": 0}
|
| 99 |
|
| 100 |
-
# Function to
|
| 101 |
-
def
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 102 |
if not message:
|
| 103 |
return history
|
| 104 |
try:
|
|
@@ -109,28 +212,49 @@ def generate_response(message, session_id, model_name, history):
|
|
| 109 |
if docs:
|
| 110 |
context = "\n\nRelevant information from uploaded PDF:\n" + "\n".join(f"- {doc.page_content}" for doc in docs)
|
| 111 |
|
| 112 |
-
# Check if it's a
|
| 113 |
-
if message.startswith("
|
| 114 |
-
|
| 115 |
-
|
| 116 |
-
|
| 117 |
-
response =
|
| 118 |
-
|
| 119 |
-
|
| 120 |
-
|
| 121 |
-
|
| 122 |
-
|
|
|
|
| 123 |
history.append((message, response))
|
| 124 |
return history
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 125 |
except Exception as e:
|
| 126 |
-
history.append((message, f"Error
|
| 127 |
return history
|
| 128 |
|
| 129 |
-
system_prompt = "You are
|
| 130 |
-
system_prompt += " You can help with
|
| 131 |
if context:
|
| 132 |
system_prompt += " Use the following context to answer the question if relevant: " + context
|
| 133 |
-
|
| 134 |
completion = client.chat.completions.create(
|
| 135 |
model=model_name,
|
| 136 |
messages=[
|
|
@@ -170,157 +294,293 @@ def update_image(page_num, pdf_state):
|
|
| 170 |
print(f"Error decoding image: {e}")
|
| 171 |
return None
|
| 172 |
|
| 173 |
-
#
|
| 174 |
-
def
|
| 175 |
-
"""
|
| 176 |
try:
|
| 177 |
-
|
| 178 |
-
|
| 179 |
-
|
| 180 |
-
|
| 181 |
-
|
| 182 |
-
|
| 183 |
-
|
| 184 |
-
|
| 185 |
-
|
| 186 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 187 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 188 |
except Exception as e:
|
| 189 |
-
print(f"Error
|
| 190 |
-
|
| 191 |
|
| 192 |
-
def
|
| 193 |
-
"""
|
| 194 |
try:
|
| 195 |
-
|
| 196 |
-
|
| 197 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 198 |
except Exception as e:
|
| 199 |
-
print(f"Error
|
| 200 |
-
return
|
|
|
|
|
|
|
|
|
|
|
|
|
| 201 |
|
| 202 |
-
def
|
| 203 |
-
"""
|
| 204 |
-
|
| 205 |
-
|
| 206 |
-
|
|
|
|
| 207 |
|
| 208 |
-
|
| 209 |
-
|
| 210 |
-
|
| 211 |
-
"api_key": api_key,
|
| 212 |
-
"file_type": "json",
|
| 213 |
-
"sort_order": "desc",
|
| 214 |
-
"limit": 100
|
| 215 |
-
}
|
| 216 |
|
|
|
|
| 217 |
try:
|
| 218 |
-
|
| 219 |
-
|
| 220 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 221 |
except Exception as e:
|
| 222 |
-
|
| 223 |
-
return []
|
| 224 |
|
| 225 |
-
def
|
| 226 |
-
"""
|
| 227 |
try:
|
| 228 |
-
|
| 229 |
-
|
| 230 |
-
return None
|
| 231 |
|
| 232 |
-
|
| 233 |
-
|
| 234 |
-
# Add candlestick chart
|
| 235 |
-
fig.add_trace(
|
| 236 |
-
go.Candlestick(
|
| 237 |
-
x=df.index,
|
| 238 |
-
open=df['Open'],
|
| 239 |
-
high=df['High'],
|
| 240 |
-
low=df['Low'],
|
| 241 |
-
close=df['Close'],
|
| 242 |
-
name=ticker
|
| 243 |
-
)
|
| 244 |
-
)
|
| 245 |
|
| 246 |
-
|
| 247 |
-
|
| 248 |
-
|
| 249 |
-
|
| 250 |
-
y=df['Volume'],
|
| 251 |
-
name='Volume',
|
| 252 |
-
marker_color='rgba(0, 128, 0, 0.3)',
|
| 253 |
-
yaxis='y2'
|
| 254 |
-
)
|
| 255 |
-
)
|
| 256 |
|
| 257 |
-
|
| 258 |
-
|
| 259 |
-
|
| 260 |
-
|
| 261 |
-
|
| 262 |
-
|
| 263 |
-
|
| 264 |
-
domain=[0.3, 1.0]
|
| 265 |
-
),
|
| 266 |
-
yaxis2=dict(
|
| 267 |
-
domain=[0, 0.2],
|
| 268 |
-
title='Volume'
|
| 269 |
-
),
|
| 270 |
-
legend=dict(
|
| 271 |
-
orientation="h",
|
| 272 |
-
yanchor="bottom",
|
| 273 |
-
y=1.02,
|
| 274 |
-
xanchor="right",
|
| 275 |
-
x=1
|
| 276 |
-
),
|
| 277 |
-
height=500
|
| 278 |
-
)
|
| 279 |
|
| 280 |
-
|
|
|
|
|
|
|
|
|
|
| 281 |
except Exception as e:
|
| 282 |
-
|
| 283 |
-
return None
|
| 284 |
|
| 285 |
-
|
| 286 |
-
|
| 287 |
-
|
| 288 |
-
|
|
|
|
|
|
|
|
|
|
| 289 |
|
| 290 |
-
|
| 291 |
-
|
| 292 |
-
ticker = ticker[1:]
|
| 293 |
|
| 294 |
try:
|
| 295 |
-
|
| 296 |
-
|
| 297 |
-
|
| 298 |
-
|
| 299 |
-
|
| 300 |
-
|
| 301 |
-
|
| 302 |
-
|
| 303 |
-
|
| 304 |
-
|
| 305 |
-
|
| 306 |
-
|
| 307 |
-
|
| 308 |
-
""
|
| 309 |
-
|
| 310 |
-
|
|
|
|
|
|
|
| 311 |
except Exception as e:
|
| 312 |
-
return
|
| 313 |
|
| 314 |
# Gradio interface
|
| 315 |
with gr.Blocks(css=custom_css, theme=gr.themes.Soft()) as demo:
|
| 316 |
current_session_id = gr.State(None)
|
| 317 |
pdf_state = gr.State({"page_images": [], "total_pages": 0, "total_words": 0})
|
| 318 |
-
|
| 319 |
|
| 320 |
gr.HTML("""
|
| 321 |
<div class="header">
|
| 322 |
-
<div class="header-title">
|
| 323 |
-
<div class="header-subtitle">Analyze
|
| 324 |
</div>
|
| 325 |
""")
|
| 326 |
|
|
@@ -329,25 +589,55 @@ with gr.Blocks(css=custom_css, theme=gr.themes.Soft()) as demo:
|
|
| 329 |
pdf_file = gr.File(label="Upload PDF Document", file_types=[".pdf"], type="binary")
|
| 330 |
upload_button = gr.Button("Process PDF", variant="primary")
|
| 331 |
pdf_status = gr.Markdown("No PDF uploaded yet")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 332 |
model_dropdown = gr.Dropdown(
|
| 333 |
choices=["llama3-70b-8192", "llama3-8b-8192", "mixtral-8x7b-32768", "gemma-7b-it"],
|
| 334 |
value="llama3-70b-8192",
|
| 335 |
label="Select Groq Model"
|
| 336 |
)
|
| 337 |
|
| 338 |
-
#
|
| 339 |
-
gr.Markdown("###
|
| 340 |
with gr.Group(elem_classes="tool-container"):
|
| 341 |
with gr.Tabs():
|
| 342 |
-
with gr.TabItem("
|
| 343 |
-
|
| 344 |
-
|
| 345 |
-
|
| 346 |
-
|
| 347 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 348 |
)
|
| 349 |
-
|
| 350 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 351 |
with gr.Column(scale=2, min_width=600):
|
| 352 |
with gr.Tabs():
|
| 353 |
with gr.TabItem("PDF Viewer"):
|
|
@@ -356,18 +646,32 @@ with gr.Blocks(css=custom_css, theme=gr.themes.Soft()) as demo:
|
|
| 356 |
pdf_image = gr.Image(label="PDF Page", type="pil", elem_classes="pdf-viewer-image")
|
| 357 |
stats_display = gr.Markdown("No PDF uploaded yet", elem_classes="stats-box")
|
| 358 |
|
| 359 |
-
with gr.TabItem("
|
| 360 |
-
|
| 361 |
-
|
| 362 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 363 |
|
| 364 |
with gr.Row(elem_classes="container"):
|
| 365 |
with gr.Column(scale=2, min_width=600):
|
| 366 |
chatbot = gr.Chatbot(height=500, bubble_full_width=False, show_copy_button=True, elem_classes="chat-container")
|
| 367 |
with gr.Row():
|
| 368 |
-
msg = gr.Textbox(
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 369 |
send_btn = gr.Button("Send", scale=1)
|
| 370 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 371 |
|
| 372 |
# Event Handlers
|
| 373 |
upload_button.click(
|
|
@@ -382,20 +686,45 @@ with gr.Blocks(css=custom_css, theme=gr.themes.Soft()) as demo:
|
|
| 382 |
|
| 383 |
msg.submit(
|
| 384 |
generate_response,
|
| 385 |
-
inputs=[msg, current_session_id, model_dropdown, chatbot],
|
| 386 |
outputs=[chatbot]
|
| 387 |
).then(lambda: "", None, [msg])
|
| 388 |
|
| 389 |
send_btn.click(
|
| 390 |
generate_response,
|
| 391 |
-
inputs=[msg, current_session_id, model_dropdown, chatbot],
|
| 392 |
outputs=[chatbot]
|
| 393 |
).then(lambda: "", None, [msg])
|
| 394 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 395 |
clear_btn.click(
|
| 396 |
-
lambda: ([], None, "No PDF uploaded yet", {"page_images": [], "total_pages": 0, "total_words": 0}, 0, None, "No PDF uploaded yet", None),
|
| 397 |
None,
|
| 398 |
-
[chatbot, current_session_id, pdf_status, pdf_state, page_slider, pdf_image, stats_display,
|
| 399 |
)
|
| 400 |
|
| 401 |
page_slider.change(
|
|
@@ -404,19 +733,36 @@ with gr.Blocks(css=custom_css, theme=gr.themes.Soft()) as demo:
|
|
| 404 |
outputs=[pdf_image]
|
| 405 |
)
|
| 406 |
|
| 407 |
-
#
|
| 408 |
-
|
| 409 |
-
|
| 410 |
-
inputs=[
|
| 411 |
-
outputs=[
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 412 |
)
|
| 413 |
|
| 414 |
-
# Add footer with attribution
|
| 415 |
-
gr.HTML("""
|
| 416 |
-
<div style="text-align: center; margin-top: 20px; padding: 10px; color: #666; font-size: 0.8rem; border-top: 1px solid #eee;">
|
| 417 |
-
|
| 418 |
-
</div>
|
| 419 |
-
""")
|
| 420 |
|
| 421 |
# Launch the app
|
| 422 |
if __name__ == "__main__":
|
|
|
|
| 1 |
+
# Standard library imports
|
|
|
|
| 2 |
import os
|
| 3 |
import tempfile
|
| 4 |
import uuid
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 5 |
import base64
|
|
|
|
| 6 |
import io
|
|
|
|
| 7 |
import json
|
| 8 |
+
import re
|
| 9 |
+
from datetime import datetime, timedelta
|
| 10 |
+
|
| 11 |
+
# Third-party imports
|
| 12 |
+
import gradio as gr
|
| 13 |
+
import groq
|
| 14 |
+
import numpy as np
|
| 15 |
+
import requests
|
| 16 |
+
import fitz # PyMuPDF
|
| 17 |
+
from PIL import Image
|
| 18 |
+
from dotenv import load_dotenv
|
| 19 |
+
from transformers import AutoProcessor, AutoModelForVision2Seq
|
| 20 |
+
import torch
|
| 21 |
+
import arxiv
|
| 22 |
+
|
| 23 |
+
# LangChain imports
|
| 24 |
+
from langchain_community.embeddings import HuggingFaceEmbeddings
|
| 25 |
+
from langchain_community.vectorstores import FAISS
|
| 26 |
+
from langchain.text_splitter import RecursiveCharacterTextSplitter
|
| 27 |
|
| 28 |
# Load environment variables
|
| 29 |
load_dotenv()
|
|
|
|
| 31 |
embeddings = HuggingFaceEmbeddings(model_name="sentence-transformers/all-MiniLM-L6-v2")
|
| 32 |
|
| 33 |
# Directory to store FAISS indexes
|
| 34 |
+
FAISS_INDEX_DIR = "faiss_indexes_academic"
|
| 35 |
if not os.path.exists(FAISS_INDEX_DIR):
|
| 36 |
os.makedirs(FAISS_INDEX_DIR)
|
| 37 |
|
| 38 |
# Dictionary to store user-specific vectorstores
|
| 39 |
user_vectorstores = {}
|
| 40 |
|
| 41 |
+
# Load SmolDocling model for image analysis
|
| 42 |
+
def load_docling_model():
|
| 43 |
+
try:
|
| 44 |
+
processor = AutoProcessor.from_pretrained("ds4sd/SmolDocling-256M-preview")
|
| 45 |
+
model = AutoModelForVision2Seq.from_pretrained("ds4sd/SmolDocling-256M-preview")
|
| 46 |
+
return processor, model
|
| 47 |
+
except Exception as e:
|
| 48 |
+
print(f"Error loading SmolDocling model: {e}")
|
| 49 |
+
return None, None
|
| 50 |
+
|
| 51 |
+
# Initialize SmolDocling model
|
| 52 |
+
docling_processor, docling_model = load_docling_model()
|
| 53 |
+
|
| 54 |
+
# Custom CSS for Academic theme
|
| 55 |
custom_css = """
|
| 56 |
:root {
|
| 57 |
+
--primary-color: #003366; /* Deep Blue */
|
| 58 |
+
--secondary-color: #000080; /* Navy */
|
| 59 |
+
--light-background: #F5F5F5; /* Light Gray */
|
| 60 |
--dark-text: #333333;
|
| 61 |
--white: #FFFFFF;
|
| 62 |
--border-color: #E5E7EB;
|
|
|
|
| 67 |
.header-title { color: var(--secondary-color); font-size: 1.8rem; font-weight: 700; text-align: center; }
|
| 68 |
.header-subtitle { color: var(--dark-text); font-size: 1rem; text-align: center; margin-top: 5px; }
|
| 69 |
.chat-container { border-radius: 12px !important; box-shadow: 0 4px 6px rgba(0,0,0,0.1) !important; background-color: var(--white) !important; border: 1px solid var(--border-color) !important; min-height: 500px; }
|
| 70 |
+
.message-user { background-color: var(--primary-color) !important; color: var(--white) !important; border-radius: 18px 18px 4px 18px !important; padding: 12px 16px !important; margin-left: auto !important; max-width: 80% !important; }
|
| 71 |
.message-bot { background-color: #F0F0F0 !important; color: var(--dark-text) !important; border-radius: 18px 18px 18px 4px !important; padding: 12px 16px !important; margin-right: auto !important; max-width: 80% !important; }
|
| 72 |
.input-area { background-color: var(--white) !important; border-top: 1px solid var(--border-color) !important; padding: 12px !important; border-radius: 0 0 12px 12px !important; }
|
| 73 |
.input-box { border: 1px solid var(--border-color) !important; border-radius: 24px !important; padding: 12px 16px !important; box-shadow: 0 2px 4px rgba(0,0,0,0.05) !important; }
|
|
|
|
| 75 |
.clear-btn { background-color: #F0F0F0 !important; border: 1px solid var(--border-color) !important; border-radius: 24px !important; color: var(--dark-text) !important; padding: 8px 16px !important; font-weight: 500 !important; }
|
| 76 |
.pdf-viewer-container { border-radius: 12px !important; box-shadow: 0 4px 6px rgba(0,0,0,0.1) !important; background-color: var(--white) !important; border: 1px solid var(--border-color) !important; padding: 20px; }
|
| 77 |
.pdf-viewer-image { max-width: 100%; height: auto; border: 1px solid var(--border-color); border-radius: 12px; box-shadow: 0 2px 4px rgba(0,0,0,0.05); }
|
| 78 |
+
.stats-box { background-color: #E6E6FA; padding: 10px; border-radius: 8px; margin-top: 10px; }
|
| 79 |
.tool-container { background-color: var(--white); border-radius: 12px; box-shadow: 0 4px 6px rgba(0,0,0,0.1); padding: 15px; margin-bottom: 20px; }
|
| 80 |
+
.paper-card { border-left: 3px solid var(--primary-color); padding: 10px; margin: 15px 0; background-color: #F8F9FA; border-radius: 8px; }
|
| 81 |
+
.paper-title { font-weight: bold; color: var(--primary-color); font-size: 1.1rem; margin-bottom: 5px; }
|
| 82 |
+
.paper-authors { color: var(--dark-text); font-size: 0.9rem; margin-bottom: 5px; }
|
| 83 |
+
.paper-abstract { font-size: 0.95rem; margin: 10px 0; }
|
| 84 |
+
.paper-meta { color: #666; font-size: 0.85rem; display: flex; justify-content: space-between; }
|
| 85 |
+
.citation-box { background-color: #F0F0F8; border: 1px solid #D1D5DB; border-radius: 8px; padding: 15px; margin: 10px 0; font-family: monospace; white-space: pre-wrap; }
|
| 86 |
+
.toggle-container { display: flex; align-items: center; margin-bottom: 15px; }
|
| 87 |
+
.toggle-label { margin-right: 10px; font-weight: 500; }
|
| 88 |
+
.search-toggle { margin-left: 5px; }
|
| 89 |
+
.voice-btn { background-color: var(--primary-color) !important; border-radius: 50% !important; width: 44px !important; height: 44px !important; display: flex !important; align-items: center !important; justify-content: center !important; color: var(--white) !important; box-shadow: 0 2px 5px rgba(0,0,0,0.2) !important; }
|
| 90 |
+
.speak-btn { background-color: var(--secondary-color) !important; border-radius: 24px !important; color: var(--white) !important; padding: 8px 16px !important; font-weight: 500 !important; margin-left: 10px !important; }
|
| 91 |
+
.audio-controls { display: flex; align-items: center; margin-top: 10px; }
|
| 92 |
"""
|
| 93 |
|
| 94 |
# Function to process PDF files (unchanged)
|
|
|
|
| 128 |
os.unlink(pdf_path)
|
| 129 |
return None, f"Error processing PDF: {str(e)}", {"page_images": [], "total_pages": 0, "total_words": 0}
|
| 130 |
|
| 131 |
+
# Function to analyze image using SmolDocling
|
| 132 |
+
def analyze_image(image_file):
|
| 133 |
+
if image_file is None:
|
| 134 |
+
return "No image uploaded. Please upload an image to analyze."
|
| 135 |
+
|
| 136 |
+
if docling_processor is None or docling_model is None:
|
| 137 |
+
return "SmolDocling model not loaded. Please check your installation."
|
| 138 |
+
|
| 139 |
+
try:
|
| 140 |
+
# Process the image - image_file is a filepath string from Gradio
|
| 141 |
+
image = Image.open(image_file)
|
| 142 |
+
|
| 143 |
+
# Use the SmolDocling model
|
| 144 |
+
inputs = docling_processor(images=image, return_tensors="pt")
|
| 145 |
+
with torch.no_grad():
|
| 146 |
+
outputs = docling_model.generate(
|
| 147 |
+
**inputs,
|
| 148 |
+
max_new_tokens=512,
|
| 149 |
+
temperature=0.1,
|
| 150 |
+
do_sample=False
|
| 151 |
+
)
|
| 152 |
+
|
| 153 |
+
# Decode the output
|
| 154 |
+
result = docling_processor.batch_decode(outputs, skip_special_tokens=True)[0]
|
| 155 |
+
|
| 156 |
+
# Format the result for display with academic emphasis
|
| 157 |
+
analysis = f"## Academic Document Analysis Results\n\n{result}\n\n"
|
| 158 |
+
analysis += "### Research Applications\n\n"
|
| 159 |
+
analysis += "* This analysis can support academic research by identifying key information in document images.\n"
|
| 160 |
+
analysis += "* Consider using these results as preliminary observations that may require further scholarly verification.\n"
|
| 161 |
+
|
| 162 |
+
return analysis
|
| 163 |
+
except Exception as e:
|
| 164 |
+
return f"Error analyzing image: {str(e)}"
|
| 165 |
+
|
| 166 |
+
# Function for speech-to-text conversion
|
| 167 |
+
def speech_to_text():
|
| 168 |
+
try:
|
| 169 |
+
r = sr.Recognizer()
|
| 170 |
+
with sr.Microphone() as source:
|
| 171 |
+
r.adjust_for_ambient_noise(source)
|
| 172 |
+
audio = r.listen(source)
|
| 173 |
+
text = r.recognize_google(audio)
|
| 174 |
+
return text
|
| 175 |
+
except sr.UnknownValueError:
|
| 176 |
+
return "Could not understand audio. Please try again."
|
| 177 |
+
except sr.RequestError as e:
|
| 178 |
+
return f"Error with speech recognition service: {e}"
|
| 179 |
+
except Exception as e:
|
| 180 |
+
return f"Error converting speech to text: {str(e)}"
|
| 181 |
+
|
| 182 |
+
# Function for text-to-speech conversion
|
| 183 |
+
def text_to_speech(text, history):
|
| 184 |
+
if not text or not history:
|
| 185 |
+
return None
|
| 186 |
+
|
| 187 |
+
try:
|
| 188 |
+
# Get the last bot response
|
| 189 |
+
last_response = history[-1][1]
|
| 190 |
+
|
| 191 |
+
# Convert text to speech
|
| 192 |
+
tts = gTTS(text=last_response, lang='en', slow=False)
|
| 193 |
+
|
| 194 |
+
# Save to a temporary file
|
| 195 |
+
temp_file = tempfile.NamedTemporaryFile(delete=False, suffix=".mp3")
|
| 196 |
+
tts.save(temp_file.name)
|
| 197 |
+
|
| 198 |
+
return temp_file.name
|
| 199 |
+
except Exception as e:
|
| 200 |
+
print(f"Error in text-to-speech: {e}")
|
| 201 |
+
return None
|
| 202 |
+
|
| 203 |
+
# Function to generate chatbot responses with Academic theme
|
| 204 |
+
def generate_response(message, session_id, model_name, history, web_search_enabled=True):
|
| 205 |
if not message:
|
| 206 |
return history
|
| 207 |
try:
|
|
|
|
| 212 |
if docs:
|
| 213 |
context = "\n\nRelevant information from uploaded PDF:\n" + "\n".join(f"- {doc.page_content}" for doc in docs)
|
| 214 |
|
| 215 |
+
# Check if it's a special command for paper search and web search is enabled
|
| 216 |
+
if web_search_enabled and (message.lower().startswith("/paper ") or message.lower().startswith("/arxiv ")):
|
| 217 |
+
query = message.split(" ", 1)[1]
|
| 218 |
+
paper_results = search_arxiv(query)
|
| 219 |
+
if paper_results:
|
| 220 |
+
response = "**Academic Paper Search Results:**\n\n"
|
| 221 |
+
for paper in paper_results[:3]: # Limit to top 3 results
|
| 222 |
+
response += f"**{paper['title']}**\n"
|
| 223 |
+
response += f"Authors: {paper['authors']}\n"
|
| 224 |
+
response += f"Published: {paper['published']}\n"
|
| 225 |
+
response += f"Summary: {paper['summary'][:250]}...\n"
|
| 226 |
+
response += f"Link: {paper['url']}\n\n"
|
| 227 |
history.append((message, response))
|
| 228 |
return history
|
| 229 |
+
else:
|
| 230 |
+
history.append((message, "No paper results found for your query."))
|
| 231 |
+
return history
|
| 232 |
+
|
| 233 |
+
# Check if it's a citation request and web search is enabled
|
| 234 |
+
citation_match = re.search(r'/cite\s+(.+)', message, re.IGNORECASE)
|
| 235 |
+
if web_search_enabled and citation_match:
|
| 236 |
+
search_term = citation_match.group(1).strip()
|
| 237 |
+
try:
|
| 238 |
+
paper = search_paper_by_title(search_term)
|
| 239 |
+
if paper:
|
| 240 |
+
citations = generate_citations(paper)
|
| 241 |
+
response = f"**Citation for '{paper['title']}':**\n\n"
|
| 242 |
+
response += f"APA: {citations['apa']}\n\n"
|
| 243 |
+
response += f"MLA: {citations['mla']}\n\n"
|
| 244 |
+
response += f"Chicago: {citations['chicago']}\n\n"
|
| 245 |
+
history.append((message, response))
|
| 246 |
+
return history
|
| 247 |
+
else:
|
| 248 |
+
history.append((message, f"Sorry, I couldn't find a paper matching '{search_term}'. Please try a more specific title."))
|
| 249 |
+
return history
|
| 250 |
except Exception as e:
|
| 251 |
+
history.append((message, f"Error generating citation: {str(e)}"))
|
| 252 |
return history
|
| 253 |
|
| 254 |
+
system_prompt = "You are an academic assistant specializing in analyzing research papers, theses, and scholarly articles."
|
| 255 |
+
system_prompt += " You can help with understanding academic content, summarizing research findings, and explaining scholarly concepts."
|
| 256 |
if context:
|
| 257 |
system_prompt += " Use the following context to answer the question if relevant: " + context
|
|
|
|
| 258 |
completion = client.chat.completions.create(
|
| 259 |
model=model_name,
|
| 260 |
messages=[
|
|
|
|
| 294 |
print(f"Error decoding image: {e}")
|
| 295 |
return None
|
| 296 |
|
| 297 |
+
# Academic-specific tools
|
| 298 |
+
def search_arxiv(query, max_results=10, sort_by=arxiv.SortCriterion.Relevance):
|
| 299 |
+
"""Search for papers on arXiv"""
|
| 300 |
try:
|
| 301 |
+
search = arxiv.Search(
|
| 302 |
+
query=query,
|
| 303 |
+
max_results=max_results,
|
| 304 |
+
sort_by=sort_by
|
| 305 |
+
)
|
| 306 |
+
|
| 307 |
+
results = []
|
| 308 |
+
for paper in search.results():
|
| 309 |
+
results.append({
|
| 310 |
+
"title": paper.title,
|
| 311 |
+
"authors": ", ".join(author.name for author in paper.authors),
|
| 312 |
+
"summary": paper.summary,
|
| 313 |
+
"published": paper.published.strftime("%Y-%m-%d"),
|
| 314 |
+
"url": paper.pdf_url,
|
| 315 |
+
"arxiv_id": paper.entry_id.split("/")[-1],
|
| 316 |
+
"categories": ", ".join(paper.categories)
|
| 317 |
+
})
|
| 318 |
+
|
| 319 |
+
return results
|
| 320 |
+
except Exception as e:
|
| 321 |
+
print(f"Error searching arXiv: {e}")
|
| 322 |
+
return []
|
| 323 |
+
|
| 324 |
+
def search_semantic_scholar(query, fields="title,authors,abstract,year,venue,externalIds"):
|
| 325 |
+
"""Search for papers using Semantic Scholar API"""
|
| 326 |
+
api_key = os.getenv("SEMANTIC_SCHOLAR_API_KEY", "")
|
| 327 |
+
|
| 328 |
+
try:
|
| 329 |
+
headers = {}
|
| 330 |
+
if api_key:
|
| 331 |
+
headers["x-api-key"] = api_key
|
| 332 |
+
|
| 333 |
+
params = {
|
| 334 |
+
"query": query,
|
| 335 |
+
"fields": fields,
|
| 336 |
+
"limit": 10
|
| 337 |
}
|
| 338 |
+
|
| 339 |
+
response = requests.get(
|
| 340 |
+
"https://api.semanticscholar.org/graph/v1/paper/search",
|
| 341 |
+
headers=headers,
|
| 342 |
+
params=params
|
| 343 |
+
)
|
| 344 |
+
|
| 345 |
+
if response.status_code != 200:
|
| 346 |
+
print(f"API Error: {response.status_code} - {response.text}")
|
| 347 |
+
return []
|
| 348 |
+
|
| 349 |
+
data = response.json()
|
| 350 |
+
results = []
|
| 351 |
+
|
| 352 |
+
for paper in data.get("data", []):
|
| 353 |
+
authors = ", ".join([author.get("name", "") for author in paper.get("authors", [])])
|
| 354 |
+
paper_data = {
|
| 355 |
+
"title": paper.get("title", "Unknown Title"),
|
| 356 |
+
"authors": authors,
|
| 357 |
+
"abstract": paper.get("abstract", "No abstract available"),
|
| 358 |
+
"year": paper.get("year", "Unknown Year"),
|
| 359 |
+
"venue": paper.get("venue", "Unknown Venue"),
|
| 360 |
+
"s2_id": paper.get("paperId", ""),
|
| 361 |
+
"url": f"https://www.semanticscholar.org/paper/{paper.get('paperId', '')}"
|
| 362 |
+
}
|
| 363 |
+
|
| 364 |
+
# Extract external IDs if available
|
| 365 |
+
external_ids = paper.get("externalIds", {})
|
| 366 |
+
if external_ids:
|
| 367 |
+
if "DOI" in external_ids:
|
| 368 |
+
paper_data["doi"] = external_ids["DOI"]
|
| 369 |
+
if "ArXiv" in external_ids:
|
| 370 |
+
paper_data["arxiv_id"] = external_ids["ArXiv"]
|
| 371 |
+
|
| 372 |
+
results.append(paper_data)
|
| 373 |
+
|
| 374 |
+
return results
|
| 375 |
+
except Exception as e:
|
| 376 |
+
print(f"Error in Semantic Scholar search: {e}")
|
| 377 |
+
return []
|
| 378 |
+
|
| 379 |
+
def search_paper_by_title(title):
|
| 380 |
+
"""Search for a specific paper by title to generate citations"""
|
| 381 |
+
try:
|
| 382 |
+
# Try Semantic Scholar first
|
| 383 |
+
results = search_semantic_scholar(title)
|
| 384 |
+
if results:
|
| 385 |
+
return results[0] # Return the top match
|
| 386 |
+
|
| 387 |
+
# Fallback to arXiv
|
| 388 |
+
results = search_arxiv(title, max_results=1)
|
| 389 |
+
if results:
|
| 390 |
+
return results[0]
|
| 391 |
+
|
| 392 |
+
return None
|
| 393 |
except Exception as e:
|
| 394 |
+
print(f"Error searching for paper: {e}")
|
| 395 |
+
return None
|
| 396 |
|
| 397 |
+
def generate_citations(paper):
|
| 398 |
+
"""Generate citations in various formats"""
|
| 399 |
try:
|
| 400 |
+
# Get current year for citations
|
| 401 |
+
current_year = datetime.now().year
|
| 402 |
+
|
| 403 |
+
# Extract author surnames for citations
|
| 404 |
+
author_list = paper.get("authors", "").split(", ")
|
| 405 |
+
first_author_surname = author_list[0].split()[-1] if author_list else "Unknown"
|
| 406 |
+
|
| 407 |
+
# Publication year
|
| 408 |
+
year = paper.get("year", current_year)
|
| 409 |
+
|
| 410 |
+
# Title
|
| 411 |
+
title = paper.get("title", "Unknown Title")
|
| 412 |
+
|
| 413 |
+
# Publication venue
|
| 414 |
+
venue = paper.get("venue", "")
|
| 415 |
+
|
| 416 |
+
# URLs
|
| 417 |
+
url = paper.get("url", "")
|
| 418 |
+
doi = paper.get("doi", "")
|
| 419 |
+
doi_url = f"https://doi.org/{doi}" if doi else ""
|
| 420 |
+
|
| 421 |
+
# Create citations
|
| 422 |
+
apa = f"{first_author_surname}"
|
| 423 |
+
if len(author_list) > 1:
|
| 424 |
+
apa += " et al."
|
| 425 |
+
apa += f" ({year}). {title}. "
|
| 426 |
+
if venue:
|
| 427 |
+
apa += f"{venue}. "
|
| 428 |
+
if doi:
|
| 429 |
+
apa += f"https://doi.org/{doi}"
|
| 430 |
+
elif url:
|
| 431 |
+
apa += url
|
| 432 |
+
|
| 433 |
+
mla = f"{first_author_surname}"
|
| 434 |
+
if len(author_list) > 1:
|
| 435 |
+
mla += " et al."
|
| 436 |
+
mla += f". \"{title}.\" "
|
| 437 |
+
if venue:
|
| 438 |
+
mla += f"{venue}, "
|
| 439 |
+
mla += f"{year}. "
|
| 440 |
+
if doi:
|
| 441 |
+
mla += f"DOI: {doi}."
|
| 442 |
+
elif url:
|
| 443 |
+
mla += f"Web: {url}."
|
| 444 |
+
|
| 445 |
+
chicago = f"{first_author_surname}"
|
| 446 |
+
if len(author_list) > 1:
|
| 447 |
+
chicago += " et al."
|
| 448 |
+
chicago += f". \"{title}.\" "
|
| 449 |
+
if venue:
|
| 450 |
+
chicago += f"{venue} "
|
| 451 |
+
chicago += f"({year})"
|
| 452 |
+
if doi or url:
|
| 453 |
+
chicago += f". Accessed {datetime.now().strftime('%B %d, %Y')}"
|
| 454 |
+
|
| 455 |
+
return {
|
| 456 |
+
"apa": apa,
|
| 457 |
+
"mla": mla,
|
| 458 |
+
"chicago": chicago
|
| 459 |
+
}
|
| 460 |
except Exception as e:
|
| 461 |
+
print(f"Error generating citations: {e}")
|
| 462 |
+
return {
|
| 463 |
+
"apa": "Error generating APA citation",
|
| 464 |
+
"mla": "Error generating MLA citation",
|
| 465 |
+
"chicago": "Error generating Chicago citation"
|
| 466 |
+
}
|
| 467 |
|
| 468 |
+
def perform_paper_search(query, source, category, sort_by, max_results, web_search_enabled=True):
|
| 469 |
+
"""
|
| 470 |
+
Search for academic papers based on the query and parameters.
|
| 471 |
+
"""
|
| 472 |
+
if not web_search_enabled:
|
| 473 |
+
return "Web search is currently disabled. Please enable web search to use this feature."
|
| 474 |
|
| 475 |
+
# Ensure max_results is an integer
|
| 476 |
+
if isinstance(max_results, str):
|
| 477 |
+
max_results = int(max_results)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 478 |
|
| 479 |
+
# Rest of the function remains the same
|
| 480 |
try:
|
| 481 |
+
results_markdown = ""
|
| 482 |
+
|
| 483 |
+
if source == "arxiv" or source == "both":
|
| 484 |
+
arxiv_results = search_arxiv(query, max_results)
|
| 485 |
+
if arxiv_results:
|
| 486 |
+
results_markdown += "## arXiv Results\n\n"
|
| 487 |
+
for paper in arxiv_results:
|
| 488 |
+
results_markdown += f"### [{paper['title']}]({paper['url']})\n"
|
| 489 |
+
results_markdown += f"**Authors:** {paper['authors']}\n\n"
|
| 490 |
+
results_markdown += f"**Published:** {paper['published']}\n\n"
|
| 491 |
+
results_markdown += f"**Summary:** {paper['summary'][:500]}...\n\n"
|
| 492 |
+
results_markdown += "---\n\n"
|
| 493 |
+
|
| 494 |
+
if source == "semantic_scholar" or source == "both":
|
| 495 |
+
ss_results = search_semantic_scholar(query)
|
| 496 |
+
if ss_results:
|
| 497 |
+
results_markdown += "## Semantic Scholar Results\n\n"
|
| 498 |
+
for paper in ss_results:
|
| 499 |
+
results_markdown += f"### [{paper['title']}]({paper['url']})\n"
|
| 500 |
+
results_markdown += f"**Authors:** {paper['authors']}\n\n"
|
| 501 |
+
if paper.get('abstract'):
|
| 502 |
+
results_markdown += f"**Abstract:** {paper['abstract'][:500]}...\n\n"
|
| 503 |
+
results_markdown += f"**Year:** {paper.get('year', 'N/A')}\n\n"
|
| 504 |
+
results_markdown += "---\n\n"
|
| 505 |
+
|
| 506 |
+
if not results_markdown:
|
| 507 |
+
return "No papers found for this query. Try adjusting your search terms."
|
| 508 |
+
|
| 509 |
+
return results_markdown
|
| 510 |
except Exception as e:
|
| 511 |
+
return f"Error searching for papers: {str(e)}"
|
|
|
|
| 512 |
|
| 513 |
+
def generate_citation_from_search(query):
|
| 514 |
+
"""Search for a paper and generate citations"""
|
| 515 |
try:
|
| 516 |
+
if not query:
|
| 517 |
+
return "Please enter a paper title to cite"
|
|
|
|
| 518 |
|
| 519 |
+
paper = search_paper_by_title(query)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 520 |
|
| 521 |
+
if not paper:
|
| 522 |
+
return "No matching papers found. Try a more specific title."
|
| 523 |
+
|
| 524 |
+
citations = generate_citations(paper)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 525 |
|
| 526 |
+
markdown = f"## Citation for: {paper['title']}\n\n"
|
| 527 |
+
markdown += "### APA Format\n"
|
| 528 |
+
markdown += f"```\n{citations['apa']}\n```\n\n"
|
| 529 |
+
markdown += "### MLA Format\n"
|
| 530 |
+
markdown += f"```\n{citations['mla']}\n```\n\n"
|
| 531 |
+
markdown += "### Chicago Format\n"
|
| 532 |
+
markdown += f"```\n{citations['chicago']}\n```\n\n"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 533 |
|
| 534 |
+
if paper.get('url'):
|
| 535 |
+
markdown += f"[View Original Paper]({paper['url']})\n"
|
| 536 |
+
|
| 537 |
+
return markdown
|
| 538 |
except Exception as e:
|
| 539 |
+
return f"Error generating citation: {str(e)}"
|
|
|
|
| 540 |
|
| 541 |
+
# Update the citation generation function to check for web search toggle
|
| 542 |
+
def generate_citation(query, web_search_enabled=True):
|
| 543 |
+
"""
|
| 544 |
+
Generate citations for a paper based on its title.
|
| 545 |
+
"""
|
| 546 |
+
if not web_search_enabled:
|
| 547 |
+
return "Web search is currently disabled. Please enable web search to use this feature."
|
| 548 |
|
| 549 |
+
if not query:
|
| 550 |
+
return "Please enter a paper title to generate citations."
|
|
|
|
| 551 |
|
| 552 |
try:
|
| 553 |
+
# Search for the paper first
|
| 554 |
+
papers = search_semantic_scholar(query)
|
| 555 |
+
|
| 556 |
+
if not papers:
|
| 557 |
+
return "Could not find the paper. Please check the title and try again."
|
| 558 |
+
|
| 559 |
+
paper = papers[0]
|
| 560 |
+
|
| 561 |
+
# Generate citations using the generate_citations function
|
| 562 |
+
citations = generate_citations(paper)
|
| 563 |
+
|
| 564 |
+
# Format the result as markdown
|
| 565 |
+
citation_md = "## Citation Formats\n\n"
|
| 566 |
+
citation_md += f"### APA Style\n```\n{citations['apa']}\n```\n\n"
|
| 567 |
+
citation_md += f"### MLA Style\n```\n{citations['mla']}\n```\n\n"
|
| 568 |
+
citation_md += f"### Chicago Style\n```\n{citations['chicago']}\n```\n\n"
|
| 569 |
+
|
| 570 |
+
return citation_md
|
| 571 |
except Exception as e:
|
| 572 |
+
return f"Error generating citations: {str(e)}"
|
| 573 |
|
| 574 |
# Gradio interface
|
| 575 |
with gr.Blocks(css=custom_css, theme=gr.themes.Soft()) as demo:
|
| 576 |
current_session_id = gr.State(None)
|
| 577 |
pdf_state = gr.State({"page_images": [], "total_pages": 0, "total_words": 0})
|
| 578 |
+
audio_output = gr.State(None)
|
| 579 |
|
| 580 |
gr.HTML("""
|
| 581 |
<div class="header">
|
| 582 |
+
<div class="header-title">Scholar-Vision</div>
|
| 583 |
+
<div class="header-subtitle">Analyze academic papers and research with Groq's LLM API.</div>
|
| 584 |
</div>
|
| 585 |
""")
|
| 586 |
|
|
|
|
| 589 |
pdf_file = gr.File(label="Upload PDF Document", file_types=[".pdf"], type="binary")
|
| 590 |
upload_button = gr.Button("Process PDF", variant="primary")
|
| 591 |
pdf_status = gr.Markdown("No PDF uploaded yet")
|
| 592 |
+
|
| 593 |
+
# Web Search Toggle
|
| 594 |
+
with gr.Row(elem_classes="toggle-container"):
|
| 595 |
+
gr.Markdown("Academic Search:", elem_classes="toggle-label")
|
| 596 |
+
web_search_toggle = gr.Checkbox(label="Enable Web Search", value=True, elem_classes="search-toggle")
|
| 597 |
+
|
| 598 |
model_dropdown = gr.Dropdown(
|
| 599 |
choices=["llama3-70b-8192", "llama3-8b-8192", "mixtral-8x7b-32768", "gemma-7b-it"],
|
| 600 |
value="llama3-70b-8192",
|
| 601 |
label="Select Groq Model"
|
| 602 |
)
|
| 603 |
|
| 604 |
+
# Academic Tools Section
|
| 605 |
+
gr.Markdown("### Academic Tools", elem_classes="tool-title")
|
| 606 |
with gr.Group(elem_classes="tool-container"):
|
| 607 |
with gr.Tabs():
|
| 608 |
+
with gr.TabItem("Paper Search"):
|
| 609 |
+
paper_query = gr.Textbox(label="Search Query", placeholder="Enter keywords to search for papers")
|
| 610 |
+
with gr.Row():
|
| 611 |
+
paper_source = gr.Dropdown(
|
| 612 |
+
choices=["arxiv", "semantic_scholar", "both"],
|
| 613 |
+
value="arxiv",
|
| 614 |
+
label="Source"
|
| 615 |
+
)
|
| 616 |
+
paper_category = gr.Dropdown(
|
| 617 |
+
choices=["any", "cs", "physics", "math", "q-bio", "econ", "eess", "stat"],
|
| 618 |
+
value="any",
|
| 619 |
+
label="Category"
|
| 620 |
+
)
|
| 621 |
+
paper_sort = gr.Dropdown(
|
| 622 |
+
choices=["relevance", "date", "citations"],
|
| 623 |
+
value="relevance",
|
| 624 |
+
label="Sort By"
|
| 625 |
)
|
| 626 |
+
max_results = gr.Slider(minimum=1, maximum=20, value=5, step=1, label="Max Results")
|
| 627 |
+
paper_search_btn = gr.Button("Search Papers")
|
| 628 |
+
|
| 629 |
+
with gr.TabItem("Citation Generator"):
|
| 630 |
+
citation_query = gr.Textbox(label="Paper Title", placeholder="Enter the paper title for citation")
|
| 631 |
+
citation_btn = gr.Button("Generate Citations")
|
| 632 |
+
|
| 633 |
+
with gr.TabItem("Image Analysis"):
|
| 634 |
+
image_input = gr.File(
|
| 635 |
+
label="Upload Image",
|
| 636 |
+
file_types=["image"],
|
| 637 |
+
type="filepath"
|
| 638 |
+
)
|
| 639 |
+
analyze_btn = gr.Button("Analyze Image")
|
| 640 |
+
|
| 641 |
with gr.Column(scale=2, min_width=600):
|
| 642 |
with gr.Tabs():
|
| 643 |
with gr.TabItem("PDF Viewer"):
|
|
|
|
| 646 |
pdf_image = gr.Image(label="PDF Page", type="pil", elem_classes="pdf-viewer-image")
|
| 647 |
stats_display = gr.Markdown("No PDF uploaded yet", elem_classes="stats-box")
|
| 648 |
|
| 649 |
+
with gr.TabItem("Paper Results"):
|
| 650 |
+
paper_results = gr.Markdown("Search for papers to see results here")
|
| 651 |
+
|
| 652 |
+
with gr.TabItem("Citation Results"):
|
| 653 |
+
citation_results = gr.Markdown("Generate citations to see results here")
|
| 654 |
+
|
| 655 |
+
with gr.TabItem("Image Analysis Results"):
|
| 656 |
+
image_analysis_results = gr.Markdown("Upload an image and click 'Analyze Image' to see analysis results")
|
| 657 |
+
image_preview = gr.Image(label="Image Preview", type="pil")
|
| 658 |
|
| 659 |
with gr.Row(elem_classes="container"):
|
| 660 |
with gr.Column(scale=2, min_width=600):
|
| 661 |
chatbot = gr.Chatbot(height=500, bubble_full_width=False, show_copy_button=True, elem_classes="chat-container")
|
| 662 |
with gr.Row():
|
| 663 |
+
msg = gr.Textbox(
|
| 664 |
+
show_label=False,
|
| 665 |
+
placeholder="Ask about your paper or click the microphone icon to speak...",
|
| 666 |
+
scale=5
|
| 667 |
+
)
|
| 668 |
+
voice_btn = gr.Button("🎤", elem_classes="voice-btn")
|
| 669 |
send_btn = gr.Button("Send", scale=1)
|
| 670 |
+
|
| 671 |
+
with gr.Row(elem_classes="audio-controls"):
|
| 672 |
+
clear_btn = gr.Button("Clear Conversation")
|
| 673 |
+
speak_btn = gr.Button("🔊 Speak Response", elem_classes="speak-btn")
|
| 674 |
+
audio_player = gr.Audio(label="Response Audio", type="filepath", visible=False)
|
| 675 |
|
| 676 |
# Event Handlers
|
| 677 |
upload_button.click(
|
|
|
|
| 686 |
|
| 687 |
msg.submit(
|
| 688 |
generate_response,
|
| 689 |
+
inputs=[msg, current_session_id, model_dropdown, chatbot, web_search_toggle],
|
| 690 |
outputs=[chatbot]
|
| 691 |
).then(lambda: "", None, [msg])
|
| 692 |
|
| 693 |
send_btn.click(
|
| 694 |
generate_response,
|
| 695 |
+
inputs=[msg, current_session_id, model_dropdown, chatbot, web_search_toggle],
|
| 696 |
outputs=[chatbot]
|
| 697 |
).then(lambda: "", None, [msg])
|
| 698 |
|
| 699 |
+
# Speech-to-text button handler
|
| 700 |
+
voice_btn.click(
|
| 701 |
+
speech_to_text,
|
| 702 |
+
inputs=[],
|
| 703 |
+
outputs=[msg]
|
| 704 |
+
)
|
| 705 |
+
|
| 706 |
+
# Text-to-speech button handler
|
| 707 |
+
speak_btn.click(
|
| 708 |
+
text_to_speech,
|
| 709 |
+
inputs=["", chatbot], # Empty string for the text parameter, we'll use the last message from history
|
| 710 |
+
outputs=[audio_player]
|
| 711 |
+
).then(
|
| 712 |
+
lambda x: gr.update(visible=True) if x else gr.update(visible=False),
|
| 713 |
+
inputs=[audio_player],
|
| 714 |
+
outputs=[audio_player]
|
| 715 |
+
)
|
| 716 |
+
|
| 717 |
+
# Update display when web search toggle changes
|
| 718 |
+
web_search_toggle.change(
|
| 719 |
+
lambda x: f"Academic Search {'Enabled' if x else 'Disabled'}",
|
| 720 |
+
inputs=[web_search_toggle],
|
| 721 |
+
outputs=[pdf_status]
|
| 722 |
+
)
|
| 723 |
+
|
| 724 |
clear_btn.click(
|
| 725 |
+
lambda: ([], None, "No PDF uploaded yet", {"page_images": [], "total_pages": 0, "total_words": 0}, 0, None, "No PDF uploaded yet", None, gr.update(visible=False)),
|
| 726 |
None,
|
| 727 |
+
[chatbot, current_session_id, pdf_status, pdf_state, page_slider, pdf_image, stats_display, audio_player, audio_player]
|
| 728 |
)
|
| 729 |
|
| 730 |
page_slider.change(
|
|
|
|
| 733 |
outputs=[pdf_image]
|
| 734 |
)
|
| 735 |
|
| 736 |
+
# Image analysis button handler
|
| 737 |
+
analyze_btn.click(
|
| 738 |
+
analyze_image,
|
| 739 |
+
inputs=[image_input],
|
| 740 |
+
outputs=[image_analysis_results]
|
| 741 |
+
).then(
|
| 742 |
+
lambda x: Image.open(x) if x else None,
|
| 743 |
+
inputs=[image_input],
|
| 744 |
+
outputs=[image_preview]
|
| 745 |
+
)
|
| 746 |
+
|
| 747 |
+
# Academic tool handlers
|
| 748 |
+
paper_search_btn.click(
|
| 749 |
+
perform_paper_search,
|
| 750 |
+
inputs=[paper_query, paper_source, paper_category, paper_sort, max_results, web_search_toggle],
|
| 751 |
+
outputs=[paper_results]
|
| 752 |
+
)
|
| 753 |
+
|
| 754 |
+
citation_btn.click(
|
| 755 |
+
generate_citation,
|
| 756 |
+
inputs=[citation_query, web_search_toggle],
|
| 757 |
+
outputs=[citation_results]
|
| 758 |
)
|
| 759 |
|
| 760 |
+
# Add footer with creator attribution
|
| 761 |
+
gr.HTML("""
|
| 762 |
+
<div style="text-align: center; margin-top: 20px; padding: 10px; color: #666; font-size: 0.8rem; border-top: 1px solid #eee;">
|
| 763 |
+
Created by Calvin Allen Crawford
|
| 764 |
+
</div>
|
| 765 |
+
""")
|
| 766 |
|
| 767 |
# Launch the app
|
| 768 |
if __name__ == "__main__":
|