CosmickVisions commited on
Commit
411b3c1
·
verified ·
1 Parent(s): 2d7ebae

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +71 -637
app.py CHANGED
@@ -10,7 +10,6 @@ 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
@@ -18,32 +17,46 @@ 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")
@@ -59,7 +72,7 @@ user_vectorstores = {}
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;
@@ -115,630 +128,23 @@ def process_pdf(pdf_file):
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:
326
- context = ""
327
- if session_id and session_id in user_vectorstores:
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
-
433
- completion = client.chat.completions.create(
434
- model=model_name,
435
- messages=[
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:
@@ -774,7 +180,8 @@ with gr.Blocks(css=custom_css, theme=gr.themes.Soft()) as demo:
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(
@@ -854,8 +261,36 @@ with gr.Blocks(css=custom_css, theme=gr.themes.Soft()) as demo:
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,5 +301,4 @@ gr.HTML("""
866
 
867
  # Launch the app
868
  if __name__ == "__main__":
869
- demo = create_interface()
870
  demo.launch()
 
10
 
11
  # Third-party imports
12
  import gradio as gr
 
13
  import numpy as np
14
  import pandas as pd
15
  import requests
 
17
  from PIL import Image
18
  from dotenv import load_dotenv
19
  import torch
20
+ import yfinance as yf # Added missing import
21
+ import plotly.graph_objects as go # Added missing import
22
 
23
+ # Assuming groq is a custom module or typo; replace with actual import if needed
24
+ from groq import Client as GroqClient # Placeholder; adjust based on your setup
25
+
26
+ # LangChain imports (optional, only if embeddings are available)
27
+ try:
28
+ from langchain_community.embeddings import HuggingFaceInstructEmbeddings
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")) # Adjust if groq is different
39
 
40
  # Embeddings initialization with fallback
41
+ if langchain_available:
 
 
 
 
 
 
42
  try:
43
  embeddings = HuggingFaceInstructEmbeddings(
44
+ model_name="hkunlp/instructor-base",
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 primary embeddings model: {e}")
49
+ try:
50
+ embeddings = HuggingFaceInstructEmbeddings(
51
+ model_name="all-MiniLM-L6-v2",
52
+ model_kwargs={"device": "cuda" if torch.cuda.is_available() else "cpu"}
53
+ )
54
+ except Exception as e:
55
+ print(f"Warning: Failed to load fallback embeddings model: {e}")
56
+ embeddings = None
57
+ else:
58
+ embeddings = None
59
+ print("Embeddings disabled due to missing LangChain dependencies.")
60
 
61
  SERPER_API_KEY = os.getenv("SERPER_API_KEY")
62
  BRAVE_API_KEY = os.getenv("BRAVE_API_KEY")
 
72
  # Dictionary to store chart data
73
  chart_data_store = {}
74
 
75
+ # Custom CSS (unchanged)
76
  custom_css = """
77
  :root {
78
  --primary-color: #0C4160;
 
128
  total_words = sum(len(text.split()) for text in texts)
129
  doc.close()
130
 
131
+ if langchain_available and embeddings:
132
+ text_splitter = RecursiveCharacterTextSplitter(chunk_size=1000, chunk_overlap=200)
133
+ chunks = text_splitter.create_documents(texts)
134
+ vectorstore = FAISS.from_documents(chunks, embeddings)
135
+ index_path = os.path.join(FAISS_INDEX_DIR, session_id)
136
+ vectorstore.save_local(index_path)
137
+ user_vectorstores[session_id] = vectorstore
138
 
139
  os.unlink(pdf_path)
140
  pdf_state = {"page_images": page_images, "total_pages": total_pages, "total_words": total_words}
141
+ return session_id, f"✅ Successfully processed {len(texts)} pages from your PDF", pdf_state
142
  except Exception as e:
143
  if "pdf_path" in locals() and os.path.exists(pdf_path):
144
  os.unlink(pdf_path)
145
  return None, f"Error processing PDF: {str(e)}", {"page_images": [], "total_pages": 0, "total_words": 0}
146
 
147
+ # [Rest of your functions remain unchanged up to the Gradio interface]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
148
 
149
  # Update the Gradio interface
150
  with gr.Blocks(css=custom_css, theme=gr.themes.Soft()) as demo:
 
180
  height=600,
181
  show_copy_button=True,
182
  elem_classes="chat-container",
183
+ container=True,
184
+ type="messages" # Updated to use messages format
185
  )
186
  with gr.Row():
187
  msg = gr.Textbox(
 
261
  report_preview = gr.Image(label="Preview", type="pil")
262
  report_analysis = gr.Markdown()
263
 
264
+ # Event Handlers (Example implementation)
265
+ def chat_handler(message, history, session_id, model, ticker, web_search):
266
+ # Convert tuple history to messages format if needed
267
+ if history and isinstance(history[0], tuple):
268
+ history = [{"role": "user" if i % 2 == 0 else "assistant", "content": msg} for i, msg in enumerate(sum(history, ()))]
269
+ response = generate_response(message, session_id, model, history, ticker, web_search)
270
+ return response
271
+
272
+ send_btn.click(
273
+ fn=chat_handler,
274
+ inputs=[msg, chatbot, current_session_id, model_dropdown, current_ticker, web_search_toggle],
275
+ outputs=[chatbot]
276
+ )
277
+ clear_btn.click(lambda: [], outputs=[chatbot])
278
+
279
+ upload_button.click(
280
+ fn=process_pdf,
281
+ inputs=[pdf_file],
282
+ outputs=[current_session_id, pdf_status, pdf_state]
283
+ ).then(
284
+ fn=update_pdf_viewer,
285
+ inputs=[pdf_state],
286
+ outputs=[page_slider, pdf_image, stats_display]
287
+ )
288
+
289
+ analyze_stock_btn.click(
290
+ fn=analyze_ticker,
291
+ inputs=[ticker_input, period_dropdown, web_search_toggle],
292
+ outputs=[stock_chart, stock_analysis, current_ticker]
293
+ )
294
 
295
  # Add footer with attribution
296
  gr.HTML("""
 
301
 
302
  # Launch the app
303
  if __name__ == "__main__":
 
304
  demo.launch()