CosmickVisions commited on
Commit
22dcc8b
·
verified ·
1 Parent(s): 49a9bbe

Update app.py

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