CosmickVisions commited on
Commit
2d7ebae
Β·
verified Β·
1 Parent(s): 12c2281

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +566 -119
app.py CHANGED
@@ -1,26 +1,52 @@
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()
22
  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"
@@ -30,37 +56,44 @@ if not os.path.exists(FAISS_INDEX_DIR):
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;
42
  }
43
- body { background-color: var(--light-background); font-family: 'Inter', sans-serif; }
44
  .container { max-width: 1200px !important; margin: 0 auto !important; padding: 10px; }
45
- .header { background-color: var(--white); border-bottom: 2px solid var(--border-color); padding: 15px 0; margin-bottom: 20px; border-radius: 12px 12px 0 0; box-shadow: 0 2px 4px rgba(0,0,0,0.05); }
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; }
53
- .send-btn { background-color: var(--secondary-color) !important; border-radius: 24px !important; color: var(--white) !important; padding: 10px 20px !important; font-weight: 500 !important; }
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)
64
  def process_pdf(pdf_file):
65
  if pdf_file is None:
66
  return None, "No file uploaded", {"page_images": [], "total_pages": 0, "total_words": 0}
@@ -97,8 +130,196 @@ 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:
@@ -114,20 +335,98 @@ def generate_response(message, session_id, model_name, history):
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
 
@@ -147,6 +446,77 @@ def generate_response(message, session_id, model_name, history):
147
  history.append((message, f"Error generating response: {str(e)}"))
148
  return history
149
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
150
  # Functions to update PDF viewer (unchanged)
151
  def update_pdf_viewer(pdf_state):
152
  if not pdf_state["total_pages"]:
@@ -282,7 +652,7 @@ def create_stock_chart(ticker, period="1y"):
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
@@ -293,11 +663,29 @@ def analyze_ticker(ticker_input, period):
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']:,}
@@ -305,13 +693,54 @@ def analyze_ticker(ticker_input, period):
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})
@@ -319,97 +748,114 @@ with gr.Blocks(css=custom_css, theme=gr.themes.Soft()) as demo:
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
 
327
- with gr.Row(elem_classes="container"):
328
- with gr.Column(scale=1, min_width=300):
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"):
354
- with gr.Column(elem_classes="pdf-viewer-container"):
355
- page_slider = gr.Slider(minimum=1, maximum=1, step=1, label="Page Number", value=1)
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(
374
- process_pdf,
375
- inputs=[pdf_file],
376
- outputs=[current_session_id, pdf_status, pdf_state]
377
- ).then(
378
- update_pdf_viewer,
379
- inputs=[pdf_state],
380
- outputs=[page_slider, pdf_image, stats_display]
381
- )
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(
402
- update_image,
403
- inputs=[page_slider, pdf_state],
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("""
@@ -420,4 +866,5 @@ gr.HTML("""
420
 
421
  # Launch the app
422
  if __name__ == "__main__":
 
423
  demo.launch()
 
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 pandas as pd
16
+ import requests
17
+ import fitz # PyMuPDF
18
+ from PIL import Image
19
+ from dotenv import load_dotenv
20
+ import torch
21
+
22
+ # LangChain imports
23
+ from langchain_community.embeddings import HuggingFaceInstructEmbeddings
24
+ from langchain_community.vectorstores import FAISS
25
+ from langchain.text_splitter import RecursiveCharacterTextSplitter
26
 
27
  # Load environment variables
28
  load_dotenv()
29
  client = groq.Client(api_key=os.getenv("GROQ_LEGAL_API_KEY"))
30
+
31
+ # Embeddings initialization with fallback
32
+ try:
33
+ embeddings = HuggingFaceInstructEmbeddings(
34
+ model_name="hkunlp/instructor-base",
35
+ model_kwargs={"device": "cuda" if torch.cuda.is_available() else "cpu"}
36
+ )
37
+ except Exception as e:
38
+ print(f"Warning: Failed to load primary embeddings model: {e}")
39
+ try:
40
+ embeddings = HuggingFaceInstructEmbeddings(
41
+ model_name="all-MiniLM-L6-v2",
42
+ model_kwargs={"device": "cuda" if torch.cuda.is_available() else "cpu"}
43
+ )
44
+ except Exception as e:
45
+ print(f"Warning: Failed to load fallback embeddings model: {e}")
46
+ embeddings = None
47
+
48
+ SERPER_API_KEY = os.getenv("SERPER_API_KEY")
49
+ BRAVE_API_KEY = os.getenv("BRAVE_API_KEY")
50
 
51
  # Directory to store FAISS indexes
52
  FAISS_INDEX_DIR = "faiss_indexes_finance"
 
56
  # Dictionary to store user-specific vectorstores
57
  user_vectorstores = {}
58
 
59
+ # Dictionary to store chart data
60
+ chart_data_store = {}
61
+
62
+ # Custom CSS for Finance theme with new voice and speech buttons
63
  custom_css = """
64
  :root {
65
+ --primary-color: #0C4160;
66
+ --secondary-color: #0D6980;
67
+ --accent-color: #16A6DB;
68
+ --light-color: #EBF5FA;
69
  --dark-text: #333333;
70
+ --light-text: #F5F5F5;
71
  --border-color: #E5E7EB;
72
  }
73
+ body { background-color: var(--light-color); font-family: 'IBM Plex Sans', sans-serif; }
74
  .container { max-width: 1200px !important; margin: 0 auto !important; padding: 10px; }
75
+ .header { background-color: var(--primary-color); padding: 20px 0; margin-bottom: 20px; border-radius: 12px 12px 0 0; box-shadow: 0 2px 4px rgba(0,0,0,0.1); }
76
+ .header-title { color: var(--light-text); font-size: 1.8rem; font-weight: 700; text-align: center; }
77
+ .header-subtitle { color: var(--light-text); opacity: 0.8; font-size: 1rem; text-align: center; margin-top: 5px; }
78
+ .chat-container { border-radius: 12px !important; box-shadow: 0 4px 6px rgba(0,0,0,0.1) !important; background-color: #FFFFFF !important; border: 1px solid var(--border-color) !important; min-height: 500px; }
79
+ .message-user { background-color: var(--accent-color) !important; color: var(--light-text) !important; border-radius: 18px 18px 4px 18px !important; padding: 12px 16px !important; margin-left: auto !important; max-width: 80% !important; }
80
+ .message-bot { background-color: #F5F7FA !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; }
81
+ .input-area { background-color: #FFFFFF !important; border-top: 1px solid var(--border-color) !important; padding: 12px !important; border-radius: 0 0 12px 12px !important; }
82
  .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; }
83
+ .send-btn { background-color: var(--accent-color) !important; border-radius: 24px !important; color: var(--light-text) !important; padding: 10px 20px !important; font-weight: 500 !important; }
84
  .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; }
85
+ .pdf-viewer-container { border-radius: 12px !important; box-shadow: 0 4px 6px rgba(0,0,0,0.1) !important; background-color: #FFFFFF !important; border: 1px solid var(--border-color) !important; padding: 20px; }
86
  .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); }
87
+ .stats-box { background-color: var(--light-color); padding: 10px; border-radius: 8px; margin-top: 10px; }
88
+ .tool-container { background-color: white; border-radius: 12px; box-shadow: 0 2px 4px rgba(0,0,0,0.05); padding: 15px; margin-bottom: 20px; }
89
+ .tool-title { font-weight: bold; color: var(--primary-color); margin-bottom: 10px; font-size: 1.1rem; }
90
  .chart-container { height: 400px; width: 100%; border-radius: 8px; overflow: hidden; }
91
+ .toggle-container { display: flex; align-items: center; margin-bottom: 15px; }
92
+ .toggle-label { margin-right: 10px; font-weight: 500; }
93
+ .search-toggle { margin-left: 5px; }
94
  """
95
 
96
+ # Function to process PDF files
97
  def process_pdf(pdf_file):
98
  if pdf_file is None:
99
  return None, "No file uploaded", {"page_images": [], "total_pages": 0, "total_words": 0}
 
130
  os.unlink(pdf_path)
131
  return None, f"Error processing PDF: {str(e)}", {"page_images": [], "total_pages": 0, "total_words": 0}
132
 
133
+ # Serper API functions for enhanced financial data
134
+ def serper_search(query, search_type="search"):
135
+ """
136
+ Perform a search using Serper.dev API to get financial information
137
+ """
138
+ if not SERPER_API_KEY:
139
+ return {"error": "Serper API key not configured. Set SERPER_API_KEY in environment variables."}
140
+
141
+ url = "https://google.serper.dev/search"
142
+ payload = json.dumps({
143
+ "q": query,
144
+ "gl": "us",
145
+ "hl": "en",
146
+ "autocorrect": True
147
+ })
148
+ headers = {
149
+ 'X-API-KEY': SERPER_API_KEY,
150
+ 'Content-Type': 'application/json'
151
+ }
152
+
153
+ try:
154
+ response = requests.request("POST", url, headers=headers, data=payload)
155
+ return response.json()
156
+ except Exception as e:
157
+ print(f"Error in Serper search: {e}")
158
+ return {"error": str(e)}
159
+
160
+ # Brave Search API functions
161
+ def brave_search(query, search_type="search"):
162
+ """
163
+ Perform a search using Brave Search API to get financial information
164
+ """
165
+ if not BRAVE_API_KEY:
166
+ return {"error": "Brave Search API key not configured. Set BRAVE_API_KEY in environment variables."}
167
+
168
+ url = "https://api.search.brave.com/res/v1/web/search"
169
+ params = {
170
+ "q": query,
171
+ "count": 10,
172
+ "search_lang": "en",
173
+ "country": "us"
174
+ }
175
+ headers = {
176
+ 'Accept': 'application/json',
177
+ 'Accept-Encoding': 'gzip',
178
+ 'X-Subscription-Token': BRAVE_API_KEY
179
+ }
180
+
181
+ try:
182
+ response = requests.get(url, params=params, headers=headers)
183
+ return response.json()
184
+ except Exception as e:
185
+ print(f"Error in Brave search: {e}")
186
+ return {"error": str(e)}
187
+
188
+ # Add this new function for LLM-based search
189
+ def llm_search(query, model_name="llama3-8b-8192"):
190
+ """
191
+ Fallback search using LLM when no search APIs are configured
192
+ """
193
+ try:
194
+ system_prompt = """You are a financial research assistant. Based on your knowledge,
195
+ provide relevant information about the query. Format your response as a list of 3-5
196
+ relevant pieces of information, each with a title and brief description."""
197
+
198
+ completion = client.chat.completions.create(
199
+ model=model_name,
200
+ messages=[
201
+ {"role": "system", "content": system_prompt},
202
+ {"role": "user", "content": query}
203
+ ],
204
+ temperature=0.3,
205
+ max_tokens=500
206
+ )
207
+
208
+ # Format response as search results
209
+ return [{
210
+ "title": "LLM-Generated Results",
211
+ "link": "",
212
+ "snippet": completion.choices[0].message.content,
213
+ "source": "AI Knowledge Base"
214
+ }]
215
+ except Exception as e:
216
+ print(f"Error in LLM search: {e}")
217
+ return []
218
+
219
+ # Update the get_financial_news function
220
+ def get_financial_news(ticker, use_brave_search=False, model_name="llama3-8b-8192"):
221
+ """
222
+ Get latest financial news about a stock using selected search API or LLM fallback
223
+ """
224
+ query = f"{ticker} stock news financial analysis latest"
225
+ news_items = []
226
+
227
+ # Try Brave Search first if selected
228
+ if use_brave_search and BRAVE_API_KEY:
229
+ results = brave_search(query)
230
+ if "web" in results and "results" in results["web"]:
231
+ for item in results["web"]["results"][:5]:
232
+ news_items.append({
233
+ "title": item.get("title", ""),
234
+ "link": item.get("url", ""),
235
+ "snippet": item.get("description", ""),
236
+ "source": item.get("source", "")
237
+ })
238
+ return news_items
239
+
240
+ # Try Serper API if Brave Search is not used or failed
241
+ if not news_items and SERPER_API_KEY:
242
+ results = serper_search(query)
243
+ if "organic" in results:
244
+ for item in results["organic"][:5]:
245
+ news_items.append({
246
+ "title": item.get("title", ""),
247
+ "link": item.get("link", ""),
248
+ "snippet": item.get("snippet", ""),
249
+ "source": item.get("source", "")
250
+ })
251
+ return news_items
252
+
253
+ # Fallback to LLM if no API results
254
+ if not news_items:
255
+ return llm_search(f"Provide recent financial news and analysis about {ticker} stock", model_name)
256
+
257
+ # Update the get_market_sentiment function
258
+ def get_market_sentiment(ticker, use_brave_search=False, model_name="llama3-8b-8192"):
259
+ """
260
+ Get market sentiment for a stock using selected search API or LLM fallback
261
+ """
262
+ query = f"{ticker} stock market sentiment analysis"
263
+ snippets = []
264
+
265
+ # Try Brave Search first if selected
266
+ if use_brave_search and BRAVE_API_KEY:
267
+ results = brave_search(query)
268
+ if "web" in results and "results" in results["web"]:
269
+ for item in results["web"]["results"][:3]:
270
+ if "description" in item:
271
+ snippets.append(item["description"])
272
+
273
+ # Try Serper API if Brave Search is not used or failed
274
+ if not snippets and SERPER_API_KEY:
275
+ results = serper_search(query)
276
+ if "organic" in results:
277
+ for item in results["organic"][:3]:
278
+ if "snippet" in item:
279
+ snippets.append(item["snippet"])
280
+
281
+ # Generate sentiment analysis
282
+ if snippets:
283
+ combined_snippets = "\n".join(snippets)
284
+ else:
285
+ # If no API results, use LLM to generate market sentiment directly
286
+ system_prompt = f"""You are a financial analyst. Based on your knowledge,
287
+ provide a brief market sentiment analysis for {ticker} stock. Consider recent
288
+ trends, company performance, and market conditions."""
289
+
290
+ try:
291
+ completion = client.chat.completions.create(
292
+ model=model_name,
293
+ messages=[
294
+ {"role": "system", "content": system_prompt},
295
+ {"role": "user", "content": f"What is the current market sentiment for {ticker} stock?"}
296
+ ],
297
+ temperature=0.2,
298
+ max_tokens=150
299
+ )
300
+ return completion.choices[0].message.content
301
+ except Exception as e:
302
+ print(f"Error in LLM sentiment analysis: {e}")
303
+ return "Unable to determine sentiment"
304
+
305
+ # If we have API snippets, analyze them
306
+ try:
307
+ completion = client.chat.completions.create(
308
+ model=model_name,
309
+ messages=[
310
+ {"role": "system", "content": "You are a financial sentiment analyzer. Based on the text provided, determine if the market sentiment for the stock is positive, negative, or neutral. Provide a brief explanation."},
311
+ {"role": "user", "content": combined_snippets}
312
+ ],
313
+ temperature=0.2,
314
+ max_tokens=150
315
+ )
316
+ return completion.choices[0].message.content
317
+ except Exception as e:
318
+ print(f"Error analyzing sentiment: {e}")
319
+ return "Unable to determine sentiment"
320
+
321
  # Function to generate chatbot responses with Finance theme
322
+ def generate_response(message, session_id, model_name, history, current_ticker=None, use_brave_search=False):
323
  if not message:
324
  return history
325
  try:
 
335
  ticker = message[1:].upper()
336
  try:
337
  stock_data = get_stock_data(ticker)
338
+ news = get_financial_news(ticker, use_brave_search)
339
+ sentiment = get_market_sentiment(ticker, use_brave_search)
340
+
341
  response = f"**Stock Information for {ticker}**\n\n"
342
  response += f"Current Price: ${stock_data['current_price']}\n"
343
  response += f"52-Week High: ${stock_data['52wk_high']}\n"
344
  response += f"Market Cap: ${stock_data['market_cap']:,}\n"
345
+ response += f"P/E Ratio: {stock_data['pe_ratio']}\n\n"
346
+ response += f"**Market Sentiment:**\n{sentiment}\n\n"
347
+ response += "**Recent News:**\n"
348
+
349
+ for i, news_item in enumerate(news[:3]):
350
+ response += f"{i+1}. [{news_item['title']}]({news_item['link']})\n"
351
+ response += f" {news_item['snippet'][:100]}...\n\n"
352
+
353
  response += f"More data available in the Stock Analysis tab."
354
  history.append((message, response))
355
  return history
356
  except Exception as e:
357
  history.append((message, f"Error retrieving stock data for {ticker}: {str(e)}"))
358
  return history
359
+
360
+ # Check if it's a news search request
361
+ if message.lower().startswith("/news "):
362
+ topic = message[6:].strip()
363
+ news = get_financial_news(topic, use_brave_search)
364
+
365
+ if news:
366
+ search_provider = "Brave Search" if use_brave_search else "Serper"
367
+ response = f"**Latest Financial News on {topic} (via {search_provider}):**\n\n"
368
+ for i, news_item in enumerate(news[:5]):
369
+ response += f"{i+1}. **{news_item['title']}**\n"
370
+ response += f" Source: {news_item['source']}\n"
371
+ response += f" {news_item['snippet']}\n"
372
+ response += f" [Read more]({news_item['link']})\n\n"
373
+ else:
374
+ response = f"No recent news found for {topic}."
375
+
376
+ history.append((message, response))
377
+ return history
378
+
379
+ # Check if it's a chart analysis request
380
+ if message.lower() == "/chart" or message.lower().startswith("/analyze chart"):
381
+ if current_ticker and current_ticker in chart_data_store:
382
+ chart_context = generate_chart_context(current_ticker)
383
+
384
+ # Get additional market analysis using selected search API
385
+ market_context = ""
386
+ try:
387
+ news = get_financial_news(current_ticker, use_brave_search)
388
+ sentiment = get_market_sentiment(current_ticker, use_brave_search)
389
+ market_context = f"\n\nMarket Sentiment: {sentiment}\n\nRecent News Context:"
390
+ for item in news[:2]:
391
+ market_context += f"\n- {item['title']}: {item['snippet'][:150]}..."
392
+ except Exception as e:
393
+ print(f"Error getting additional market context: {e}")
394
+
395
+ system_prompt = "You are a financial analyst specializing in stock market analysis. You have been provided with chart and financial data for a stock, along with recent market sentiment and news. Analyze this data and provide insights about the stock's performance trends, potential support/resistance levels, and overall pattern."
396
+ completion = client.chat.completions.create(
397
+ model=model_name,
398
+ messages=[
399
+ {"role": "system", "content": system_prompt},
400
+ {"role": "user", "content": f"Analyze this stock data and chart information:\n\n{chart_context}{market_context}"}
401
+ ],
402
+ temperature=0.7,
403
+ max_tokens=1024
404
+ )
405
+ response = completion.choices[0].message.content
406
+ history.append((message, response))
407
+ return history
408
+ else:
409
+ history.append((message, "Please analyze a stock first using the Stock Analysis tab before requesting chart analysis."))
410
+ return history
411
 
412
  system_prompt = "You are a financial assistant specializing in analyzing financial reports, statements, and market trends."
413
  system_prompt += " You can help with stock market information, financial terminology, ratio analysis, and investment concepts."
414
+
415
+ # Add chart context if available
416
+ if current_ticker and current_ticker in chart_data_store and ("chart" in message.lower() or "stock" in message.lower() or current_ticker.lower() in message.lower()):
417
+ chart_context = generate_chart_context(current_ticker)
418
+ context += f"\n\nRecent stock data for {current_ticker}:\n{chart_context}"
419
+
420
+ # Add news and sentiment if it's a stock-related query
421
+ try:
422
+ news = get_financial_news(current_ticker, use_brave_search)
423
+ sentiment = get_market_sentiment(current_ticker, use_brave_search)
424
+ context += f"\n\nMarket Sentiment: {sentiment}\n\nRecent News Headlines:"
425
+ for item in news[:2]:
426
+ context += f"\n- {item['title']}"
427
+ except Exception as e:
428
+ print(f"Error adding news context: {e}")
429
+
430
  if context:
431
  system_prompt += " Use the following context to answer the question if relevant: " + context
432
 
 
446
  history.append((message, f"Error generating response: {str(e)}"))
447
  return history
448
 
449
+ # Helper function to generate chart context for LLM
450
+ def generate_chart_context(ticker):
451
+ data = chart_data_store[ticker]
452
+ df = data["history"]
453
+ stats = data["stats"]
454
+
455
+ # Calculate key metrics from the chart data
456
+ start_price = df["Close"].iloc[0]
457
+ end_price = df["Close"].iloc[-1]
458
+ percent_change = ((end_price - start_price) / start_price) * 100
459
+ highest = df["High"].max()
460
+ lowest = df["Low"].min()
461
+
462
+ # Calculate average volume
463
+ avg_volume = df["Volume"].mean()
464
+
465
+ # Calculate simple moving averages
466
+ if len(df) > 50:
467
+ sma_50 = df["Close"].rolling(window=50).mean().iloc[-1]
468
+ else:
469
+ sma_50 = "Not enough data"
470
+
471
+ if len(df) > 200:
472
+ sma_200 = df["Close"].rolling(window=200).mean().iloc[-1]
473
+ else:
474
+ sma_200 = "Not enough data"
475
+
476
+ # Calculate RSI (Relative Strength Index)
477
+ delta = df['Close'].diff()
478
+ gain = delta.where(delta > 0, 0).rolling(window=14).mean()
479
+ loss = -delta.where(delta < 0, 0).rolling(window=14).mean()
480
+ rs = gain / loss
481
+ rsi = 100 - (100 / (1 + rs.iloc[-1])) if not pd.isna(rs.iloc[-1]) and loss.iloc[-1] != 0 else 50
482
+
483
+ # Calculate volatility (standard deviation of returns)
484
+ returns = df['Close'].pct_change()
485
+ volatility = returns.std() * 100 # Annualize by multiplying by sqrt(252)
486
+
487
+ # Get recent price movement (last 5 days)
488
+ recent_prices = []
489
+ if len(df) >= 5:
490
+ for i in range(1, 6):
491
+ if i <= len(df):
492
+ recent_prices.append(df["Close"].iloc[-i])
493
+
494
+ # Format the context for the LLM
495
+ context = f"""
496
+ Ticker: {ticker}
497
+ Period: {data["period"]}
498
+ Current Price: ${end_price:.2f}
499
+ Price Change: {percent_change:.2f}%
500
+ 52-Week High: ${stats['52wk_high']}
501
+ 52-Week Low: ${lowest:.2f}
502
+ Market Cap: ${stats['market_cap']:,}
503
+ P/E Ratio: {stats['pe_ratio']}
504
+ Average Volume: {avg_volume:.0f}
505
+ Volatility: {volatility:.2f}%
506
+ RSI (14-day): {rsi:.2f}
507
+ """
508
+
509
+ if isinstance(sma_50, float):
510
+ context += f"50-day Moving Average: ${sma_50:.2f}\n"
511
+ if isinstance(sma_200, float):
512
+ context += f"200-day Moving Average: ${sma_200:.2f}\n"
513
+
514
+ context += "\nRecent Price Movement (last 5 days, most recent first):\n"
515
+ for i, price in enumerate(recent_prices):
516
+ context += f"Day {i+1}: ${price:.2f}\n"
517
+
518
+ return context
519
+
520
  # Functions to update PDF viewer (unchanged)
521
  def update_pdf_viewer(pdf_state):
522
  if not pdf_state["total_pages"]:
 
652
  print(f"Error creating stock chart: {e}")
653
  return None
654
 
655
+ def analyze_ticker(ticker_input, period, use_brave_search=False):
656
  """Process the ticker input and return analysis"""
657
  if not ticker_input:
658
  return None, "Please enter a valid ticker symbol", None
 
663
 
664
  try:
665
  stock_data = get_stock_data(ticker)
666
+ stock_history = get_stock_history(ticker, period)
667
  chart = create_stock_chart(ticker, period)
668
 
669
+ # Store chart data for LLM analysis
670
+ chart_data_store[ticker] = {
671
+ "history": stock_history,
672
+ "stats": stock_data,
673
+ "period": period
674
+ }
675
+
676
+ # Get market sentiment using selected search API or LLM fallback
677
+ try:
678
+ sentiment = get_market_sentiment(ticker, use_brave_search)
679
+ sentiment_summary = f"\n\n**Market Sentiment:**\n{sentiment}"
680
+ except Exception as e:
681
+ print(f"Error getting sentiment: {e}")
682
+ sentiment_summary = ""
683
+
684
  # Create a formatted summary
685
+ search_provider = "Brave Search" if (use_brave_search and BRAVE_API_KEY) else "Serper" if SERPER_API_KEY else "AI Knowledge Base"
686
  summary = f"""
687
+ ### {ticker} Analysis (Using {search_provider})
688
+
689
  **Current Price:** ${stock_data['current_price']}
690
  **52-Week High:** ${stock_data['52wk_high']}
691
  **Market Cap:** ${stock_data['market_cap']:,}
 
693
  **Dividend Yield:** {stock_data['dividend_yield'] * 100 if stock_data['dividend_yield'] != 'N/A' else 'N/A'}%
694
  **Beta:** {stock_data['beta']}
695
  **Avg Volume:** {stock_data['average_volume']:,}
696
+ {sentiment_summary}
697
+
698
+ For in-depth analysis of this chart, ask the chatbot by typing "/chart" or "/analyze chart".
699
+ For latest news, type "/news {ticker}".
700
  """
701
 
702
  return chart, summary, ticker
703
  except Exception as e:
704
  return None, f"Error analyzing ticker {ticker}: {str(e)}", None
705
 
706
+ # Replace the load_docling_model function with a simpler image analysis function
707
+ def analyze_image(image_file):
708
+ """
709
+ Basic image analysis function that doesn't rely on external models
710
+ """
711
+ if image_file is None:
712
+ return "No image uploaded. Please upload an image to analyze."
713
+
714
+ try:
715
+ image = Image.open(image_file)
716
+ width, height = image.size
717
+ format = image.format
718
+ mode = image.mode
719
+
720
+ analysis = f"""## Technical Document Analysis
721
+
722
+ **Image Properties:**
723
+ - Dimensions: {width}x{height} pixels
724
+ - Format: {format}
725
+ - Color Mode: {mode}
726
+
727
+ **Technical Analysis:**
728
+ 1. Document Quality:
729
+ - Resolution: {'High' if width > 2000 or height > 2000 else 'Medium' if width > 1000 or height > 1000 else 'Low'}
730
+ - Color Depth: {mode}
731
+
732
+ 2. Recommendations:
733
+ - For text extraction, consider using PDF format
734
+ - For technical diagrams, ensure high resolution
735
+ - Consider OCR for text content
736
+
737
+ **Note:** For detailed technical analysis, please convert to PDF format
738
+ """
739
+ return analysis
740
+ except Exception as e:
741
+ return f"Error analyzing image: {str(e)}\n\nPlease try using PDF format instead."
742
+
743
+ # Update the Gradio interface
744
  with gr.Blocks(css=custom_css, theme=gr.themes.Soft()) as demo:
745
  current_session_id = gr.State(None)
746
  pdf_state = gr.State({"page_images": [], "total_pages": 0, "total_words": 0})
 
748
 
749
  gr.HTML("""
750
  <div class="header">
751
+ <div class="header-title">Fin-Vision AI</div>
752
+ <div class="header-subtitle">Advanced Financial Analysis Assistant</div>
753
  </div>
754
  """)
755
 
756
+ # Main container with all functionality in tabs
757
+ with gr.Tabs() as main_tabs:
758
+ # Chat Assistant Tab
759
+ with gr.TabItem("πŸ’¬ Chat Assistant", id=0):
760
+ with gr.Row():
761
+ with gr.Column(scale=1):
762
+ model_dropdown = gr.Dropdown(
763
+ choices=["llama3-70b-8192", "llama3-8b-8192", "mixtral-8x7b-32768", "gemma-7b-it"],
764
+ value="llama3-70b-8192",
765
+ label="Model Selection"
766
+ )
767
+ web_search_toggle = gr.Checkbox(
768
+ label="Enable Financial Search",
769
+ value=True,
770
+ info="Toggle web search functionality"
771
+ )
772
+
773
+ chatbot = gr.Chatbot(
774
+ height=600,
775
+ show_copy_button=True,
776
+ elem_classes="chat-container",
777
+ container=True
778
  )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
779
  with gr.Row():
780
+ msg = gr.Textbox(
781
+ show_label=False,
782
+ placeholder="Ask about financial markets, analyze stocks, or discuss financial documents...",
783
+ scale=8
784
+ )
785
  send_btn = gr.Button("Send", scale=1)
786
  clear_btn = gr.Button("Clear Conversation")
787
+
788
+ # Document Analysis Tab
789
+ with gr.TabItem("πŸ“„ Document Analysis", id=1):
790
+ with gr.Row():
791
+ with gr.Column(scale=1):
792
+ pdf_file = gr.File(
793
+ label="Upload Financial Document",
794
+ file_types=[".pdf"],
795
+ type="binary"
796
+ )
797
+ upload_button = gr.Button("Process Document", variant="primary")
798
+ pdf_status = gr.Markdown("Upload a document to begin analysis")
799
+
800
+ with gr.Column(scale=2):
801
+ with gr.Tabs():
802
+ with gr.TabItem("Document Viewer"):
803
+ page_slider = gr.Slider(
804
+ minimum=1,
805
+ maximum=1,
806
+ step=1,
807
+ label="Page Navigation",
808
+ value=1
809
+ )
810
+ pdf_image = gr.Image(label="Document Page", type="pil")
811
+ stats_display = gr.Markdown(elem_classes="stats-box")
812
+
813
+ # Financial Tools Tab
814
+ with gr.TabItem("πŸ” Financial Tools", id=2):
815
+ with gr.Tabs() as financial_tabs:
816
+ # Stock Analysis
817
+ with gr.TabItem("Stock Analysis"):
818
+ with gr.Row():
819
+ with gr.Column():
820
+ ticker_input = gr.Textbox(
821
+ label="Ticker Symbol",
822
+ placeholder="e.g., AAPL, MSFT, GOOGL"
823
+ )
824
+ period_dropdown = gr.Dropdown(
825
+ choices=["1mo", "3mo", "6mo", "1y", "2y", "5y", "max"],
826
+ value="1y",
827
+ label="Time Period"
828
+ )
829
+ analyze_stock_btn = gr.Button("Analyze Stock")
830
+ with gr.Row():
831
+ stock_chart = gr.Plot(label="Stock Price Chart")
832
+ stock_analysis = gr.Markdown()
833
+
834
+ # Market News
835
+ with gr.TabItem("Market News"):
836
+ news_ticker = gr.Textbox(
837
+ label="Company/Ticker",
838
+ placeholder="Enter company name or ticker symbol"
839
+ )
840
+ news_btn = gr.Button("Fetch News")
841
+ news_results = gr.Markdown()
842
+
843
+ # Financial Report Analysis
844
+ with gr.TabItem("Report Analysis"):
845
+ with gr.Row():
846
+ with gr.Column():
847
+ report_image = gr.File(
848
+ label="Upload Financial Chart/Image",
849
+ file_types=["image"],
850
+ type="filepath"
851
+ )
852
+ analyze_report_btn = gr.Button("Analyze Image")
853
+ with gr.Column():
854
+ report_preview = gr.Image(label="Preview", type="pil")
855
+ report_analysis = gr.Markdown()
856
 
857
  # Event Handlers
858
+ # [Add appropriate event handlers based on fin-vision functions]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
859
 
860
  # Add footer with attribution
861
  gr.HTML("""
 
866
 
867
  # Launch the app
868
  if __name__ == "__main__":
869
+ demo = create_interface()
870
  demo.launch()