CosmickVisions commited on
Commit
6046e11
Β·
verified Β·
1 Parent(s): 99f3982

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +635 -125
app.py CHANGED
@@ -10,6 +10,7 @@ from datetime import datetime, timedelta
10
 
11
  # Third-party imports
12
  import gradio as gr
 
13
  import numpy as np
14
  import pandas as pd
15
  import requests
@@ -17,39 +18,32 @@ import fitz # PyMuPDF
17
  from PIL import Image
18
  from dotenv import load_dotenv
19
  import torch
20
- import yfinance as yf
21
- import plotly.graph_objects as go
22
 
23
- # Assuming groq is a custom module or typo; replace with actual import if needed
24
- from groq import Client as GroqClient # Adjust based on your setup
25
-
26
- # LangChain imports (optional, only if embeddings are available)
27
- try:
28
- from langchain_community.embeddings import HuggingFaceEmbeddings # Changed to basic embeddings
29
- from langchain_community.vectorstores import FAISS
30
- from langchain.text_splitter import RecursiveCharacterTextSplitter
31
- langchain_available = True
32
- except ImportError:
33
- langchain_available = False
34
- print("LangChain dependencies not found. PDF processing will be limited.")
35
 
36
  # Load environment variables
37
  load_dotenv()
38
- client = GroqClient(api_key=os.getenv("GROQ_LEGAL_API_KEY"))
39
 
40
  # Embeddings initialization with fallback
41
- if langchain_available:
 
 
 
 
 
 
42
  try:
43
- embeddings = HuggingFaceEmbeddings(
44
- model_name="sentence-transformers/all-MiniLM-L6-v2",
45
  model_kwargs={"device": "cuda" if torch.cuda.is_available() else "cpu"}
46
  )
47
  except Exception as e:
48
- print(f"Warning: Failed to load embeddings model: {e}")
49
  embeddings = None
50
- else:
51
- embeddings = None
52
- print("Embeddings disabled due to missing LangChain dependencies.")
53
 
54
  SERPER_API_KEY = os.getenv("SERPER_API_KEY")
55
  BRAVE_API_KEY = os.getenv("BRAVE_API_KEY")
@@ -65,7 +59,7 @@ user_vectorstores = {}
65
  # Dictionary to store chart data
66
  chart_data_store = {}
67
 
68
- # Custom CSS (unchanged)
69
  custom_css = """
70
  :root {
71
  --primary-color: #0C4160;
@@ -99,7 +93,7 @@ body { background-color: var(--light-color); font-family: 'IBM Plex Sans', sans-
99
  .search-toggle { margin-left: 5px; }
100
  """
101
 
102
- # Function definitions
103
  def process_pdf(pdf_file):
104
  if pdf_file is None:
105
  return None, "No file uploaded", {"page_images": [], "total_pages": 0, "total_words": 0}
@@ -121,76 +115,211 @@ def process_pdf(pdf_file):
121
  total_words = sum(len(text.split()) for text in texts)
122
  doc.close()
123
 
124
- if langchain_available and embeddings:
125
- text_splitter = RecursiveCharacterTextSplitter(chunk_size=1000, chunk_overlap=200)
126
- chunks = text_splitter.create_documents(texts)
127
- vectorstore = FAISS.from_documents(chunks, embeddings)
128
- index_path = os.path.join(FAISS_INDEX_DIR, session_id)
129
- vectorstore.save_local(index_path)
130
- user_vectorstores[session_id] = vectorstore
131
 
132
  os.unlink(pdf_path)
133
  pdf_state = {"page_images": page_images, "total_pages": total_pages, "total_words": total_words}
134
- return session_id, f"βœ… Successfully processed {len(texts)} pages from your PDF", pdf_state
135
  except Exception as e:
136
  if "pdf_path" in locals() and os.path.exists(pdf_path):
137
  os.unlink(pdf_path)
138
  return None, f"Error processing PDF: {str(e)}", {"page_images": [], "total_pages": 0, "total_words": 0}
139
 
140
- def update_pdf_viewer(pdf_state):
141
- if not pdf_state["total_pages"]:
142
- return 0, None, "No PDF uploaded yet"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
143
  try:
144
- img_data = base64.b64decode(pdf_state["page_images"][0])
145
- img = Image.open(io.BytesIO(img_data))
146
- return pdf_state["total_pages"], img, f"**Total Pages:** {pdf_state['total_pages']}\n**Total Words:** {pdf_state['total_words']}"
147
  except Exception as e:
148
- print(f"Error decoding image: {e}")
149
- return 0, None, "Error displaying PDF"
150
 
151
- def update_image(page_num, pdf_state):
152
- if not pdf_state["total_pages"] or page_num < 1 or page_num > pdf_state["total_pages"]:
153
- return None
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
154
  try:
155
- img_data = base64.b64decode(pdf_state["page_images"][page_num - 1])
156
- img = Image.open(io.BytesIO(img_data))
157
- return img
158
  except Exception as e:
159
- print(f"Error decoding image: {e}")
160
- return None
161
 
162
- def analyze_ticker(ticker, period, web_search_enabled):
 
 
 
 
163
  try:
164
- stock = yf.Ticker(ticker)
165
- hist = stock.history(period=period)
166
- if hist.empty:
167
- return None, f"No data found for {ticker}", ticker
168
 
169
- fig = go.Figure()
170
- fig.add_trace(go.Candlestick(
171
- x=hist.index,
172
- open=hist['Open'],
173
- high=hist['High'],
174
- low=hist['Low'],
175
- close=hist['Close'],
176
- name='OHLC'
177
- ))
178
- fig.update_layout(
179
- title=f'{ticker} Stock Price ({period})',
180
- yaxis_title='Price',
181
- template='plotly_white'
182
  )
183
 
184
- analysis = f"## {ticker} Analysis\n\n"
185
- analysis += f"Latest Close: ${hist['Close'][-1]:.2f}\n"
186
- analysis += f"52 Week High: ${hist['High'].max():.2f}\n"
187
- analysis += f"52 Week Low: ${hist['Low'].min():.2f}\n"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
188
 
189
- return fig, analysis, ticker
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
190
  except Exception as e:
191
- return None, f"Error analyzing ticker: {str(e)}", ticker
 
192
 
193
- def generate_response(message, session_id, model_name, history, ticker, web_search_enabled):
 
194
  if not message:
195
  return history
196
  try:
@@ -199,12 +328,105 @@ def generate_response(message, session_id, model_name, history, ticker, web_sear
199
  vectorstore = user_vectorstores[session_id]
200
  docs = vectorstore.similarity_search(message, k=3)
201
  if docs:
202
- context = "\n\nRelevant information from uploaded document:\n" + "\n".join(f"- {doc.page_content}" for doc in docs)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
203
 
204
- system_prompt = "You are a financial assistant specializing in analyzing markets, stocks, and financial documents."
205
- system_prompt += " You can help with understanding financial data, analyzing stocks, and explaining financial concepts."
206
- if ticker:
207
- system_prompt += f" Current ticker in focus: {ticker}"
208
  if context:
209
  system_prompt += " Use the following context to answer the question if relevant: " + context
210
 
@@ -214,19 +436,311 @@ def generate_response(message, session_id, model_name, history, ticker, web_sear
214
  {"role": "system", "content": system_prompt},
215
  {"role": "user", "content": message}
216
  ],
217
- temperature=0.5,
218
  max_tokens=1024
219
  )
220
  response = completion.choices[0].message.content
221
- history.append({"role": "user", "content": message})
222
- history.append({"role": "assistant", "content": response})
223
  return history
224
  except Exception as e:
225
- history.append({"role": "user", "content": message})
226
- history.append({"role": "assistant", "content": f"Error generating response: {str(e)}"})
227
  return history
228
 
229
- # Gradio interface
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
230
  with gr.Blocks(css=custom_css, theme=gr.themes.Soft()) as demo:
231
  current_session_id = gr.State(None)
232
  pdf_state = gr.State({"page_images": [], "total_pages": 0, "total_words": 0})
@@ -239,7 +753,9 @@ with gr.Blocks(css=custom_css, theme=gr.themes.Soft()) as demo:
239
  </div>
240
  """)
241
 
 
242
  with gr.Tabs() as main_tabs:
 
243
  with gr.TabItem("πŸ’¬ Chat Assistant", id=0):
244
  with gr.Row():
245
  with gr.Column(scale=1):
@@ -258,8 +774,7 @@ with gr.Blocks(css=custom_css, theme=gr.themes.Soft()) as demo:
258
  height=600,
259
  show_copy_button=True,
260
  elem_classes="chat-container",
261
- container=True,
262
- type="messages"
263
  )
264
  with gr.Row():
265
  msg = gr.Textbox(
@@ -270,6 +785,7 @@ with gr.Blocks(css=custom_css, theme=gr.themes.Soft()) as demo:
270
  send_btn = gr.Button("Send", scale=1)
271
  clear_btn = gr.Button("Clear Conversation")
272
 
 
273
  with gr.TabItem("πŸ“„ Document Analysis", id=1):
274
  with gr.Row():
275
  with gr.Column(scale=1):
@@ -294,8 +810,10 @@ with gr.Blocks(css=custom_css, theme=gr.themes.Soft()) as demo:
294
  pdf_image = gr.Image(label="Document Page", type="pil")
295
  stats_display = gr.Markdown(elem_classes="stats-box")
296
 
 
297
  with gr.TabItem("πŸ” Financial Tools", id=2):
298
  with gr.Tabs() as financial_tabs:
 
299
  with gr.TabItem("Stock Analysis"):
300
  with gr.Row():
301
  with gr.Column():
@@ -313,48 +831,40 @@ with gr.Blocks(css=custom_css, theme=gr.themes.Soft()) as demo:
313
  stock_chart = gr.Plot(label="Stock Price Chart")
314
  stock_analysis = gr.Markdown()
315
 
316
- # Event Handlers
317
- send_btn.click(
318
- fn=generate_response,
319
- inputs=[msg, current_session_id, model_dropdown, chatbot, current_ticker, web_search_toggle],
320
- outputs=[chatbot]
321
- )
322
-
323
- clear_btn.click(
324
- fn=lambda: [],
325
- inputs=None,
326
- outputs=[chatbot],
327
- queue=False
328
- )
329
-
330
- upload_button.click(
331
- fn=process_pdf,
332
- inputs=[pdf_file],
333
- outputs=[current_session_id, pdf_status, pdf_state]
334
- ).then(
335
- fn=update_pdf_viewer,
336
- inputs=[pdf_state],
337
- outputs=[page_slider, pdf_image, stats_display]
338
- )
339
-
340
- page_slider.change(
341
- fn=update_image,
342
- inputs=[page_slider, pdf_state],
343
- outputs=[pdf_image]
344
- )
345
 
346
- analyze_stock_btn.click(
347
- fn=analyze_ticker,
348
- inputs=[ticker_input, period_dropdown, web_search_toggle],
349
- outputs=[stock_chart, stock_analysis, current_ticker]
350
- )
 
 
 
 
 
 
 
 
 
 
 
351
 
352
- gr.HTML("""
353
- <div style="text-align: center; margin-top: 20px; padding: 10px; color: #666; font-size: 0.8rem; border-top: 1px solid #eee;">
354
- Created by Calvin Allen Crawford
355
- </div>
356
- """)
 
357
 
 
358
  if __name__ == "__main__":
359
  demo = create_interface()
360
  demo.launch()
 
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
 
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")
 
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;
 
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}
 
115
  total_words = sum(len(text.split()) for text in texts)
116
  doc.close()
117
 
118
+ text_splitter = RecursiveCharacterTextSplitter(chunk_size=1000, chunk_overlap=200)
119
+ chunks = text_splitter.create_documents(texts)
120
+ vectorstore = FAISS.from_documents(chunks, embeddings)
121
+ index_path = os.path.join(FAISS_INDEX_DIR, session_id)
122
+ vectorstore.save_local(index_path)
123
+ user_vectorstores[session_id] = vectorstore
 
124
 
125
  os.unlink(pdf_path)
126
  pdf_state = {"page_images": page_images, "total_pages": total_pages, "total_words": total_words}
127
+ return session_id, f"βœ… Successfully processed {len(chunks)} text chunks from your PDF", pdf_state
128
  except Exception as e:
129
  if "pdf_path" in locals() and os.path.exists(pdf_path):
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:
 
328
  vectorstore = user_vectorstores[session_id]
329
  docs = vectorstore.similarity_search(message, k=3)
330
  if docs:
331
+ context = "\n\nRelevant information from uploaded PDF:\n" + "\n".join(f"- {doc.page_content}" for doc in docs)
332
+
333
+ # Check if it's a stock ticker query
334
+ if message.startswith("$") and len(message) > 1 and len(message) <= 6:
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
 
 
436
  {"role": "system", "content": system_prompt},
437
  {"role": "user", "content": message}
438
  ],
439
+ temperature=0.7,
440
  max_tokens=1024
441
  )
442
  response = completion.choices[0].message.content
443
+ history.append((message, response))
 
444
  return history
445
  except Exception as e:
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"]:
523
+ return 0, None, "No PDF uploaded yet"
524
+ try:
525
+ img_data = base64.b64decode(pdf_state["page_images"][0])
526
+ img = Image.open(io.BytesIO(img_data))
527
+ return pdf_state["total_pages"], img, f"**Total Pages:** {pdf_state['total_pages']}\n**Total Words:** {pdf_state['total_words']}"
528
+ except Exception as e:
529
+ print(f"Error decoding image: {e}")
530
+ return 0, None, "Error displaying PDF"
531
+
532
+ def update_image(page_num, pdf_state):
533
+ if not pdf_state["total_pages"] or page_num < 1 or page_num > pdf_state["total_pages"]:
534
+ return None
535
+ try:
536
+ img_data = base64.b64decode(pdf_state["page_images"][page_num - 1])
537
+ img = Image.open(io.BytesIO(img_data))
538
+ return img
539
+ except Exception as e:
540
+ print(f"Error decoding image: {e}")
541
+ return None
542
+
543
+ # New Finance-specific tools
544
+ def get_stock_data(ticker):
545
+ """Tool to fetch latest stock data for a given ticker"""
546
+ try:
547
+ stock = yf.Ticker(ticker)
548
+ info = stock.info
549
+ return {
550
+ "current_price": info.get("currentPrice", info.get("regularMarketPrice", "N/A")),
551
+ "52wk_high": info.get("fiftyTwoWeekHigh", "N/A"),
552
+ "market_cap": info.get("marketCap", "N/A"),
553
+ "pe_ratio": info.get("trailingPE", "N/A"),
554
+ "dividend_yield": info.get("dividendYield", "N/A"),
555
+ "beta": info.get("beta", "N/A"),
556
+ "average_volume": info.get("averageVolume", "N/A")
557
+ }
558
+ except Exception as e:
559
+ print(f"Error fetching stock data: {e}")
560
+ raise e
561
+
562
+ def get_stock_history(ticker, period="1y"):
563
+ """Get historical data for charting"""
564
+ try:
565
+ stock = yf.Ticker(ticker)
566
+ hist = stock.history(period=period)
567
+ return hist
568
+ except Exception as e:
569
+ print(f"Error fetching stock history: {e}")
570
+ return pd.DataFrame()
571
+
572
+ def get_fred_data(indicator):
573
+ """Get economic data from FRED API"""
574
+ api_key = os.getenv("FRED_API_KEY", "")
575
+ if not api_key:
576
+ return "FRED API key not configured"
577
+
578
+ base_url = "https://api.stlouisfed.org/fred/series/observations"
579
+ params = {
580
+ "series_id": indicator,
581
+ "api_key": api_key,
582
+ "file_type": "json",
583
+ "sort_order": "desc",
584
+ "limit": 100
585
+ }
586
+
587
+ try:
588
+ response = requests.get(base_url, params=params)
589
+ data = response.json()
590
+ return data.get("observations", [])
591
+ except Exception as e:
592
+ print(f"Error fetching FRED data: {e}")
593
+ return []
594
+
595
+ def create_stock_chart(ticker, period="1y"):
596
+ """Create an interactive stock chart using Plotly"""
597
+ try:
598
+ df = get_stock_history(ticker, period)
599
+ if df.empty:
600
+ return None
601
+
602
+ fig = go.Figure()
603
+
604
+ # Add candlestick chart
605
+ fig.add_trace(
606
+ go.Candlestick(
607
+ x=df.index,
608
+ open=df['Open'],
609
+ high=df['High'],
610
+ low=df['Low'],
611
+ close=df['Close'],
612
+ name=ticker
613
+ )
614
+ )
615
+
616
+ # Add volume as bar chart on secondary y-axis
617
+ fig.add_trace(
618
+ go.Bar(
619
+ x=df.index,
620
+ y=df['Volume'],
621
+ name='Volume',
622
+ marker_color='rgba(0, 128, 0, 0.3)',
623
+ yaxis='y2'
624
+ )
625
+ )
626
+
627
+ # Update layout for dual y-axis
628
+ fig.update_layout(
629
+ title=f'{ticker} Stock Price',
630
+ yaxis_title='Price (USD)',
631
+ xaxis_title='Date',
632
+ template='plotly_white',
633
+ yaxis=dict(
634
+ domain=[0.3, 1.0]
635
+ ),
636
+ yaxis2=dict(
637
+ domain=[0, 0.2],
638
+ title='Volume'
639
+ ),
640
+ legend=dict(
641
+ orientation="h",
642
+ yanchor="bottom",
643
+ y=1.02,
644
+ xanchor="right",
645
+ x=1
646
+ ),
647
+ height=500
648
+ )
649
+
650
+ return fig
651
+ except Exception as e:
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
659
+
660
+ ticker = ticker_input.strip().upper()
661
+ if ticker.startswith("$"):
662
+ ticker = ticker[1:]
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']:,}
692
+ **P/E Ratio:** {stock_data['pe_ratio']}
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})
 
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):
 
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(
 
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):
 
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():
 
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("""
862
+ <div style="text-align: center; margin-top: 20px; padding: 10px; color: #666; font-size: 0.8rem; border-top: 1px solid #eee;">
863
+ Created by Calvin Allen Crawford
864
+ </div>
865
+ """)
866
 
867
+ # Launch the app
868
  if __name__ == "__main__":
869
  demo = create_interface()
870
  demo.launch()