CosmickVisions commited on
Commit
975cff5
·
verified ·
1 Parent(s): 6b29aac

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +535 -189
app.py CHANGED
@@ -1,21 +1,29 @@
1
- import gradio as gr
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 = "faiss_indexes_finance"
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
- # Custom CSS for Finance theme
 
 
 
 
 
 
 
 
 
 
 
 
 
34
  custom_css = """
35
  :root {
36
- --primary-color: #FFD700; /* Gold */
37
- --secondary-color: #008000; /* Dark Green */
38
- --light-background: #F0FFF0; /* Honeydew */
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(--dark-text) !important; border-radius: 18px 18px 4px 18px !important; padding: 12px 16px !important; margin-left: auto !important; max-width: 80% !important; }
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: #E6F2E6; padding: 10px; border-radius: 8px; margin-top: 10px; }
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
- .tool-title { color: var(--secondary-color); font-size: 1.2rem; font-weight: 600; margin-bottom: 10px; }
60
- .chart-container { height: 400px; width: 100%; border-radius: 8px; overflow: hidden; }
 
 
 
 
 
 
 
 
 
 
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 generate chatbot responses with Finance theme
101
- def generate_response(message, session_id, model_name, history):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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 stock ticker query
113
- if message.startswith("$") and len(message) > 1 and len(message) <= 6:
114
- ticker = message[1:].upper()
115
- try:
116
- stock_data = get_stock_data(ticker)
117
- response = f"**Stock Information for {ticker}**\n\n"
118
- response += f"Current Price: ${stock_data['current_price']}\n"
119
- response += f"52-Week High: ${stock_data['52wk_high']}\n"
120
- response += f"Market Cap: ${stock_data['market_cap']:,}\n"
121
- response += f"P/E Ratio: {stock_data['pe_ratio']}\n"
122
- response += f"More data available in the Stock Analysis tab."
 
123
  history.append((message, response))
124
  return history
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
125
  except Exception as e:
126
- history.append((message, f"Error retrieving stock data for {ticker}: {str(e)}"))
127
  return history
128
 
129
- system_prompt = "You are a financial assistant specializing in analyzing financial reports, statements, and market trends."
130
- system_prompt += " You can help with stock market information, financial terminology, ratio analysis, and investment concepts."
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
- # New Finance-specific tools
174
- def get_stock_data(ticker):
175
- """Tool to fetch latest stock data for a given ticker"""
176
  try:
177
- stock = yf.Ticker(ticker)
178
- info = stock.info
179
- return {
180
- "current_price": info.get("currentPrice", info.get("regularMarketPrice", "N/A")),
181
- "52wk_high": info.get("fiftyTwoWeekHigh", "N/A"),
182
- "market_cap": info.get("marketCap", "N/A"),
183
- "pe_ratio": info.get("trailingPE", "N/A"),
184
- "dividend_yield": info.get("dividendYield", "N/A"),
185
- "beta": info.get("beta", "N/A"),
186
- "average_volume": info.get("averageVolume", "N/A")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
187
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
188
  except Exception as e:
189
- print(f"Error fetching stock data: {e}")
190
- raise e
191
 
192
- def get_stock_history(ticker, period="1y"):
193
- """Get historical data for charting"""
194
  try:
195
- stock = yf.Ticker(ticker)
196
- hist = stock.history(period=period)
197
- return hist
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
198
  except Exception as e:
199
- print(f"Error fetching stock history: {e}")
200
- return pd.DataFrame()
 
 
 
 
201
 
202
- def get_fred_data(indicator):
203
- """Get economic data from FRED API"""
204
- api_key = os.getenv("FRED_API_KEY", "")
205
- if not api_key:
206
- return "FRED API key not configured"
 
207
 
208
- base_url = "https://api.stlouisfed.org/fred/series/observations"
209
- params = {
210
- "series_id": indicator,
211
- "api_key": api_key,
212
- "file_type": "json",
213
- "sort_order": "desc",
214
- "limit": 100
215
- }
216
 
 
217
  try:
218
- response = requests.get(base_url, params=params)
219
- data = response.json()
220
- return data.get("observations", [])
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
221
  except Exception as e:
222
- print(f"Error fetching FRED data: {e}")
223
- return []
224
 
225
- def create_stock_chart(ticker, period="1y"):
226
- """Create an interactive stock chart using Plotly"""
227
  try:
228
- df = get_stock_history(ticker, period)
229
- if df.empty:
230
- return None
231
 
232
- fig = go.Figure()
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
- # Add volume as bar chart on secondary y-axis
247
- fig.add_trace(
248
- go.Bar(
249
- x=df.index,
250
- y=df['Volume'],
251
- name='Volume',
252
- marker_color='rgba(0, 128, 0, 0.3)',
253
- yaxis='y2'
254
- )
255
- )
256
 
257
- # Update layout for dual y-axis
258
- fig.update_layout(
259
- title=f'{ticker} Stock Price',
260
- yaxis_title='Price (USD)',
261
- xaxis_title='Date',
262
- template='plotly_white',
263
- yaxis=dict(
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
- return fig
 
 
 
281
  except Exception as e:
282
- print(f"Error creating stock chart: {e}")
283
- return None
284
 
285
- def analyze_ticker(ticker_input, period):
286
- """Process the ticker input and return analysis"""
287
- if not ticker_input:
288
- return None, "Please enter a valid ticker symbol", None
 
 
 
289
 
290
- ticker = ticker_input.strip().upper()
291
- if ticker.startswith("$"):
292
- ticker = ticker[1:]
293
 
294
  try:
295
- stock_data = get_stock_data(ticker)
296
- chart = create_stock_chart(ticker, period)
297
-
298
- # Create a formatted summary
299
- summary = f"""
300
- ### {ticker} Analysis
301
- **Current Price:** ${stock_data['current_price']}
302
- **52-Week High:** ${stock_data['52wk_high']}
303
- **Market Cap:** ${stock_data['market_cap']:,}
304
- **P/E Ratio:** {stock_data['pe_ratio']}
305
- **Dividend Yield:** {stock_data['dividend_yield'] * 100 if stock_data['dividend_yield'] != 'N/A' else 'N/A'}%
306
- **Beta:** {stock_data['beta']}
307
- **Avg Volume:** {stock_data['average_volume']:,}
308
- """
309
-
310
- return chart, summary, ticker
 
 
311
  except Exception as e:
312
- return None, f"Error analyzing ticker {ticker}: {str(e)}", None
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
- current_ticker = gr.State(None)
319
 
320
  gr.HTML("""
321
  <div class="header">
322
- <div class="header-title">Fin-Vision</div>
323
- <div class="header-subtitle">Analyze financial documents with Groq's LLM API.</div>
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
- # Finance Tools Section
339
- gr.Markdown("### Financial Tools", elem_classes="tool-title")
340
  with gr.Group(elem_classes="tool-container"):
341
  with gr.Tabs():
342
- with gr.TabItem("Stock Analysis"):
343
- ticker_input = gr.Textbox(label="Enter Ticker Symbol (e.g., AAPL)", placeholder="AAPL")
344
- period_dropdown = gr.Dropdown(
345
- choices=["1mo", "3mo", "6mo", "1y", "2y", "5y", "max"],
346
- value="1y",
347
- label="Time Period"
 
 
 
 
 
 
 
 
 
 
 
348
  )
349
- analyze_button = gr.Button("Analyze Stock")
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("Stock Analysis"):
360
- with gr.Column(elem_classes="pdf-viewer-container"):
361
- stock_chart = gr.Plot(label="Stock Price Chart", elem_classes="chart-container")
362
- stock_summary = gr.Markdown("Enter a ticker symbol to see analysis")
 
 
 
 
 
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(show_label=False, placeholder="Ask about your financial document or type $TICKER for stock info...", scale=5)
 
 
 
 
 
369
  send_btn = gr.Button("Send", scale=1)
370
- clear_btn = gr.Button("Clear Conversation")
 
 
 
 
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, current_ticker]
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
- # Stock analysis handler
408
- analyze_button.click(
409
- analyze_ticker,
410
- inputs=[ticker_input, period_dropdown],
411
- outputs=[stock_chart, stock_summary, current_ticker]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
- Created by Calvin Allen Crawford
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__":