CosmickVisions commited on
Commit
d5b9a02
·
verified ·
1 Parent(s): 7c5a246

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +118 -1717
app.py CHANGED
@@ -1,45 +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
- import torch
21
-
22
- # LangChain imports
23
- from langchain_community.embeddings import HuggingFaceInstructEmbeddings, HuggingFaceEmbeddings
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="all-MiniLM-L6-v2", # Using simpler model as default
35
- model_kwargs={"device": "cuda" if torch.cuda.is_available() else "cpu"}
36
- )
37
- except Exception as e:
38
- print(f"Warning: Using basic embeddings due to: {e}")
39
- embeddings = HuggingFaceEmbeddings()
40
-
41
- SERPER_API_KEY = os.getenv("SERPER_API_KEY")
42
- BRAVE_API_KEY = os.getenv("BRAVE_API_KEY")
43
 
44
  # Directory to store FAISS indexes
45
  FAISS_INDEX_DIR = "faiss_indexes_finance"
@@ -49,672 +30,37 @@ if not os.path.exists(FAISS_INDEX_DIR):
49
  # Dictionary to store user-specific vectorstores
50
  user_vectorstores = {}
51
 
52
- # Dictionary to store chart data
53
- chart_data_store = {}
54
-
55
- # Custom CSS for Finance theme with modern UI enhancements
56
  custom_css = """
57
  :root {
58
- --main-bg: #ffffff;
59
- --main-text: #000000;
60
- --accent-primary: #10b981; // Finance green
61
- --accent-secondary: #2563eb; // Blue for references
62
- --accent-tertiary: #fb923c; // Orange for alerts
63
- --border-color: #e5e7eb;
64
- --radius-md: 8px;
65
- --spacing-sm: 8px;
66
- --spacing-md: 16px;
67
- --spacing-lg: 24px;
68
- --shadow-sm: 0 1px 3px rgba(0,0,0,0.12);
69
- --shadow-md: 0 4px 6px -1px rgba(0,0,0,0.1);
70
- --shadow-lg: 0 10px 15px -3px rgba(0,0,0,0.1);
71
- }
72
-
73
- .dark-mode {
74
- --main-bg: #121212;
75
- --main-text: #f3f4f6;
76
- --border-color: #374151;
77
- }
78
-
79
- /* Chat Interface Styling */
80
- .chat-container {
81
- background: var(--main-bg);
82
- border-radius: var(--radius-md);
83
- border: 1px solid var(--border-color);
84
- padding: var(--spacing-md);
85
- margin-bottom: var(--spacing-md);
86
- min-height: 600px;
87
- }
88
-
89
- .message {
90
- padding: 12px 16px;
91
- border-radius: 8px;
92
- margin: 8px 0;
93
- max-width: 80%;
94
- }
95
-
96
- .user-message {
97
- background: #f0f4ff;
98
- margin-left: auto;
99
- border: 1px solid #d6e0ff;
100
- }
101
-
102
- .bot-message {
103
- background: #fff;
104
- margin-right: auto;
105
- border: 1px solid #e0e0e0;
106
- }
107
-
108
- /* Finance-specific styles */
109
- .finance-card {
110
- background: #f8fafc;
111
- border: 1px solid var(--border-color);
112
- border-radius: var(--radius-md);
113
- padding: var(--spacing-lg);
114
- margin: var(--spacing-md) 0;
115
- box-shadow: var(--shadow-md);
116
- transition: transform 0.3s, box-shadow 0.3s;
117
- }
118
-
119
- .header {
120
- text-align: center;
121
- padding: var(--spacing-lg) 0;
122
- border-bottom: 1px solid var(--border-color);
123
- margin-bottom: var(--spacing-lg);
124
- }
125
-
126
- .header-title {
127
- font-size: 1.5rem;
128
- font-weight: 600;
129
- color: var(--main-text);
130
- display: flex;
131
- align-items: center;
132
- justify-content: center;
133
- gap: 8px;
134
- }
135
-
136
- .badge {
137
- font-size: 0.7em;
138
- padding: 2px 8px;
139
- border-radius: 12px;
140
- background: var(--accent-primary);
141
- color: white;
142
- }
143
-
144
- .finance-card:hover {
145
- transform: translateY(-5px);
146
- box-shadow: var(--shadow-lg);
147
- border-color: var(--accent-primary);
148
- }
149
-
150
- .chart-container {
151
- background: var(--main-bg);
152
- border-radius: var(--radius-md);
153
- padding: var(--spacing-md);
154
- margin: var(--spacing-md) 0;
155
- box-shadow: var(--shadow-sm);
156
- }
157
-
158
- .news-feed {
159
- max-height: 400px;
160
- overflow-y: auto;
161
- padding: var(--spacing-md);
162
- border: 1px solid var(--border-color);
163
- border-radius: var(--radius-md);
164
- background: var(--main-bg);
165
- }
166
-
167
- .news-item {
168
- margin-bottom: var(--spacing-md);
169
- padding-bottom: var(--spacing-md);
170
- border-bottom: 1px solid var(--border-color);
171
- }
172
-
173
- .news-title {
174
- font-size: 1.1rem;
175
- font-weight: 600;
176
- color: var(--main-text);
177
- margin-bottom: var(--spacing-sm);
178
- }
179
-
180
- .news-summary {
181
- color: var(--main-text-secondary);
182
- line-height: 1.6;
183
- }
184
-
185
- .sentiment-gauge {
186
- width: 100%;
187
- height: 200px;
188
- margin: var(--spacing-md) 0;
189
- }
190
-
191
- /* Modern Financial Dashboard Styles */
192
- .dashboard-grid {
193
- display: grid;
194
- gap: 1.5rem;
195
- grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
196
- }
197
-
198
- .ticker-tape {
199
- background: linear-gradient(90deg, var(--accent-primary), var(--accent-secondary));
200
- color: white;
201
- padding: 1rem;
202
- border-radius: var(--radius-md);
203
- overflow: hidden;
204
- position: relative;
205
- }
206
-
207
- .stock-card {
208
- background: white;
209
- border-radius: var(--radius-md);
210
- padding: 1.5rem;
211
- transition: transform 0.3s ease;
212
- cursor: pointer;
213
- position: relative;
214
- overflow: hidden;
215
- }
216
-
217
- .stock-card::before {
218
- content: '';
219
- position: absolute;
220
- top: -50%;
221
- left: -50%;
222
- width: 200%;
223
- height: 200%;
224
- background: linear-gradient(45deg, transparent, rgba(255,255,255,0.1));
225
- transform: rotate(45deg);
226
- transition: all 0.5s ease;
227
- }
228
-
229
- .stock-card:hover {
230
- transform: translateY(-5px);
231
- box-shadow: 0 10px 20px rgba(0,0,0,0.1);
232
- }
233
-
234
- .stock-card:hover::before {
235
- transform: rotate(45deg) translateX(50%);
236
- }
237
-
238
- .realtime-chart {
239
- border: 1px solid var(--border-color);
240
- border-radius: var(--radius-md);
241
- padding: 1rem;
242
- background: white;
243
- position: relative;
244
- }
245
-
246
- .chart-tooltip {
247
- position: absolute;
248
- background: var(--main-text);
249
- color: var(--main-bg);
250
- padding: 0.5rem 1rem;
251
- border-radius: var(--radius-sm);
252
- pointer-events: none;
253
- opacity: 0;
254
- transition: opacity 0.2s ease;
255
- }
256
-
257
- .dark-mode .ticker-tape {
258
- background: linear-gradient(90deg, #1e3a8a, #991b1b) !important;
259
- }
260
-
261
- .dark-mode .stock-card {
262
- background: #2d2d2d !important;
263
- }
264
-
265
- /* Modern Chat Interface */
266
- .persistent-chat-container {
267
- position: fixed;
268
- bottom: 0;
269
- left: 0;
270
- right: 0;
271
- height: 400px;
272
- background: var(--main-bg);
273
- border-top: 1px solid var(--border-color);
274
- z-index: 1000;
275
- display: flex;
276
- flex-direction: column;
277
- box-shadow: 0 -2px 10px rgba(0,0,0,0.05);
278
- }
279
-
280
- .dark-mode .persistent-chat-container {
281
- background: #121212;
282
- border-top: 1px solid #374151;
283
- }
284
-
285
- .chat-messages {
286
- flex: 1;
287
- overflow-y: auto;
288
- padding: 1rem;
289
- }
290
-
291
- .chat-input-area {
292
- padding: 1rem;
293
- border-top: 1px solid var(--border-color);
294
- display: flex;
295
- gap: 0.5rem;
296
- }
297
-
298
- .dark-mode .chat-input-area {
299
- border-top: 1px solid #374151;
300
- }
301
-
302
- .chat-input {
303
- flex: 1;
304
- padding: 0.75rem;
305
- border: 1px solid var(--border-color);
306
- border-radius: 8px;
307
- resize: none;
308
- }
309
-
310
- .dark-mode .chat-input {
311
- background: #1e1e1e;
312
- color: white;
313
- border-color: #4b5563;
314
- }
315
-
316
- .send-button {
317
- background: var(--accent-primary);
318
- color: white;
319
- border: none;
320
- border-radius: 8px;
321
- padding: 0 1.5rem;
322
- cursor: pointer;
323
- font-weight: 600;
324
- }
325
-
326
- .content-with-chat {
327
- padding-bottom: 400px;
328
- }
329
-
330
- .tab-content {
331
- padding-bottom: 400px;
332
- }
333
-
334
- @media (max-width: 768px) {
335
- .persistent-chat-container {
336
- height: 300px;
337
- }
338
-
339
- .content-with-chat {
340
- padding-bottom: 300px;
341
- }
342
- }
343
-
344
- .stock-badge {
345
- background: var(--accent-primary);
346
- color: white;
347
- font-size: 0.8rem;
348
- padding: 0.3rem 0.6rem;
349
- border-radius: 1rem;
350
- display: inline-block;
351
- margin-left: 0.5rem;
352
- }
353
-
354
- .stock-up {
355
- color: #10b981;
356
- }
357
-
358
- .stock-down {
359
- color: #ef4444;
360
- }
361
-
362
- .dark-mode .stock-up {
363
- color: #34d399;
364
- }
365
-
366
- .dark-mode .stock-down {
367
- color: #f87171;
368
- }
369
-
370
- .chart-container {
371
- border: 1px solid var(--border-color);
372
- border-radius: var(--radius-md);
373
- padding: var(--spacing-md);
374
- }
375
-
376
- .dark-mode .chart-container {
377
- border-color: #374151;
378
- }
379
-
380
- /* Add sidebar and layout CSS */
381
- custom_css += """
382
- /* Sidebar Navigation */
383
- .sidebar {
384
- position: fixed;
385
- left: 0;
386
- top: 0;
387
- bottom: 400px; /* Leave space for chat */
388
- width: 250px;
389
- background: var(--main-bg);
390
- border-right: 1px solid var(--border-color);
391
- padding: 1rem;
392
- overflow-y: auto;
393
- z-index: 100;
394
- transition: all 0.3s ease;
395
- }
396
-
397
- .dark-mode .sidebar {
398
- background: #1a1a1a;
399
- border-right: 1px solid #333;
400
- }
401
-
402
- .sidebar-header {
403
- padding-bottom: 1rem;
404
- margin-bottom: 1rem;
405
- border-bottom: 1px solid var(--border-color);
406
- }
407
-
408
- .sidebar-nav {
409
- display: flex;
410
- flex-direction: column;
411
- gap: 0.5rem;
412
- }
413
-
414
- .nav-item {
415
- padding: 0.75rem 1rem;
416
- border-radius: 8px;
417
- cursor: pointer;
418
- display: flex;
419
- align-items: center;
420
- gap: 0.75rem;
421
- transition: all 0.2s ease;
422
- }
423
-
424
- .nav-item:hover {
425
- background: rgba(0,0,0,0.05);
426
- }
427
-
428
- .dark-mode .nav-item:hover {
429
- background: rgba(255,255,255,0.05);
430
- }
431
-
432
- .nav-item.active {
433
- background: var(--accent-primary);
434
- color: white;
435
- }
436
-
437
- .nav-icon {
438
- font-size: 1.25rem;
439
- width: 1.5rem;
440
- text-align: center;
441
- }
442
-
443
- /* Main Content Area */
444
- .main-content {
445
- margin-left: 250px;
446
- padding: 1rem;
447
- padding-bottom: 400px; /* Space for chat */
448
- min-height: calc(100vh - 400px);
449
- }
450
-
451
- .collapsed-sidebar .sidebar {
452
- width: 60px;
453
- }
454
-
455
- .collapsed-sidebar .main-content {
456
- margin-left: 60px;
457
- }
458
-
459
- .collapsed-sidebar .nav-text {
460
- display: none;
461
- }
462
-
463
- .collapsed-sidebar .sidebar-header {
464
- text-align: center;
465
- }
466
-
467
- /* Split Screen Mode */
468
- .split-screen .main-content {
469
- height: calc(50vh - 200px);
470
- overflow-y: auto;
471
- }
472
-
473
- .split-screen .persistent-chat-container {
474
- height: 50vh;
475
- }
476
-
477
- .split-screen .content-with-chat {
478
- padding-bottom: 50vh;
479
- }
480
-
481
- /* Toggle Buttons */
482
- .toggle-button {
483
- position: fixed;
484
- z-index: 2000;
485
- background: var(--accent-primary);
486
- color: white;
487
- border: none;
488
- border-radius: 50%;
489
- width: 40px;
490
- height: 40px;
491
- display: flex;
492
- align-items: center;
493
- justify-content: center;
494
- font-size: 1.25rem;
495
- cursor: pointer;
496
- box-shadow: 0 2px 5px rgba(0,0,0,0.2);
497
- }
498
-
499
- .sidebar-toggle {
500
- left: 260px;
501
- top: 20px;
502
- }
503
-
504
- .split-toggle {
505
- right: 80px;
506
- bottom: 410px;
507
- }
508
-
509
- .dark-mode .toggle-button {
510
- background: #333;
511
- }
512
-
513
- /* Improve responsive design */
514
- @media (max-width: 768px) {
515
- .sidebar {
516
- width: 60px;
517
- }
518
-
519
- .main-content {
520
- margin-left: 60px;
521
- }
522
-
523
- .nav-text {
524
- display: none;
525
- }
526
-
527
- .sidebar-header {
528
- text-align: center;
529
- }
530
-
531
- .sidebar-toggle {
532
- left: 70px;
533
- }
534
- }
535
-
536
- /* Add code viewer and PDF viewer styles */
537
- custom_css += """
538
- /* Tool panels */
539
- .tool-panel {
540
- background: var(--main-bg);
541
- border-radius: var(--radius-md);
542
- border: 1px solid var(--border-color);
543
- padding: 1rem;
544
- margin-bottom: 1rem;
545
- }
546
-
547
- .dark-mode .tool-panel {
548
- background: #1a1a1a;
549
- border: 1px solid #333;
550
- }
551
-
552
- /* Code Editor Styles */
553
- .code-editor-container {
554
- display: flex;
555
- flex-direction: column;
556
- height: 100%;
557
- }
558
-
559
- .code-editor-header {
560
- display: flex;
561
- justify-content: space-between;
562
- align-items: center;
563
- padding-bottom: 0.5rem;
564
- margin-bottom: 1rem;
565
- border-bottom: 1px solid var(--border-color);
566
- }
567
-
568
- .code-editor-filename {
569
- font-weight: 600;
570
- color: var(--main-text);
571
- }
572
-
573
- .code-editor-controls {
574
- display: flex;
575
- gap: 0.5rem;
576
- }
577
-
578
- .code-editor-area {
579
- font-family: monospace;
580
- line-height: 1.5;
581
- height: 100%;
582
- border: 1px solid var(--border-color);
583
- border-radius: var(--radius-md);
584
- padding: 1rem;
585
- background: #f8f9fa;
586
- overflow: auto;
587
- }
588
-
589
- .dark-mode .code-editor-area {
590
- background: #1e1e1e;
591
- color: #f0f0f0;
592
- border-color: #333;
593
- }
594
-
595
- .code-editor-area.editable {
596
- border-color: var(--accent-primary);
597
- background: #f0f7f4;
598
- }
599
-
600
- .dark-mode .code-editor-area.editable {
601
- background: #162922;
602
- }
603
-
604
- /* Enhanced PDF Viewer */
605
- .pdf-toolbar {
606
- display: flex;
607
- gap: 0.5rem;
608
- margin-bottom: 1rem;
609
- padding-bottom: 0.5rem;
610
- border-bottom: 1px solid var(--border-color);
611
- }
612
-
613
- .pdf-container {
614
- display: flex;
615
- flex-direction: column;
616
- height: 100%;
617
- }
618
-
619
- .pdf-sidebar {
620
- width: 25%;
621
- border-right: 1px solid var(--border-color);
622
- overflow-y: auto;
623
- padding-right: 0.5rem;
624
- }
625
-
626
- .pdf-content {
627
- flex: 1;
628
- overflow: auto;
629
- padding: 1rem;
630
- background: #f8f9fa;
631
- border-radius: var(--radius-md);
632
- }
633
-
634
- .dark-mode .pdf-content {
635
- background: #1e1e1e;
636
- }
637
-
638
- .pdf-thumbnail {
639
- cursor: pointer;
640
- margin-bottom: 0.5rem;
641
- border: 2px solid transparent;
642
- border-radius: var(--radius-sm);
643
- transition: all 0.2s ease;
644
- }
645
-
646
- .pdf-thumbnail:hover {
647
- transform: translateY(-2px);
648
- }
649
-
650
- .pdf-thumbnail.active {
651
- border-color: var(--accent-primary);
652
- }
653
-
654
- .pdf-search {
655
- margin-bottom: 1rem;
656
- }
657
-
658
- .pdf-search-results {
659
- margin-top: 0.5rem;
660
- font-size: 0.9rem;
661
- }
662
-
663
- .pdf-search-highlight {
664
- background: rgba(255, 213, 0, 0.3);
665
- border-radius: 2px;
666
- }
667
-
668
- /* Initial state with just chatbot */
669
- .tools-hidden .sidebar {
670
- display: block;
671
- }
672
-
673
- .tools-hidden .main-content {
674
- display: none;
675
- }
676
-
677
- .tools-visible .main-content {
678
- display: block;
679
- }
680
  """
681
 
682
- # Add JavaScript interactions
683
- financial_js = """
684
- <script>
685
- document.addEventListener('DOMContentLoaded', () => {
686
- // Real-time chart interactions
687
- const charts = document.querySelectorAll('.realtime-chart');
688
- charts.forEach(chart => {
689
- chart.addEventListener('mousemove', (e) => {
690
- const tooltip = chart.querySelector('.chart-tooltip');
691
- if(tooltip) {
692
- tooltip.style.left = `${e.offsetX + 10}px`;
693
- tooltip.style.top = `${e.offsetY + 10}px`;
694
- tooltip.style.opacity = '1';
695
- }
696
- });
697
-
698
- chart.addEventListener('mouseleave', () => {
699
- const tooltip = chart.querySelector('.chart-tooltip');
700
- if(tooltip) tooltip.style.opacity = '0';
701
- });
702
- });
703
-
704
- // Animated ticker tape
705
- const ticker = document.querySelector('.ticker-tape');
706
- if(ticker) {
707
- let position = 0;
708
- setInterval(() => {
709
- position -= 1;
710
- ticker.style.backgroundPosition = `${position}px 0`;
711
- }, 50);
712
- }
713
- });
714
- </script>
715
- """
716
-
717
- # Function to process PDF files
718
  def process_pdf(pdf_file):
719
  if pdf_file is None:
720
  return None, "No file uploaded", {"page_images": [], "total_pages": 0, "total_words": 0}
@@ -751,196 +97,8 @@ def process_pdf(pdf_file):
751
  os.unlink(pdf_path)
752
  return None, f"Error processing PDF: {str(e)}", {"page_images": [], "total_pages": 0, "total_words": 0}
753
 
754
- # Serper API functions for enhanced financial data
755
- def serper_search(query, search_type="search"):
756
- """
757
- Perform a search using Serper.dev API to get financial information
758
- """
759
- if not SERPER_API_KEY:
760
- return {"error": "Serper API key not configured. Set SERPER_API_KEY in environment variables."}
761
-
762
- url = "https://google.serper.dev/search"
763
- payload = json.dumps({
764
- "q": query,
765
- "gl": "us",
766
- "hl": "en",
767
- "autocorrect": True
768
- })
769
- headers = {
770
- 'X-API-KEY': SERPER_API_KEY,
771
- 'Content-Type': 'application/json'
772
- }
773
-
774
- try:
775
- response = requests.request("POST", url, headers=headers, data=payload)
776
- return response.json()
777
- except Exception as e:
778
- print(f"Error in Serper search: {e}")
779
- return {"error": str(e)}
780
-
781
- # Brave Search API functions
782
- def brave_search(query, search_type="search"):
783
- """
784
- Perform a search using Brave Search API to get financial information
785
- """
786
- if not BRAVE_API_KEY:
787
- return {"error": "Brave Search API key not configured. Set BRAVE_API_KEY in environment variables."}
788
-
789
- url = "https://api.search.brave.com/res/v1/web/search"
790
- params = {
791
- "q": query,
792
- "count": 10,
793
- "search_lang": "en",
794
- "country": "us"
795
- }
796
- headers = {
797
- 'Accept': 'application/json',
798
- 'Accept-Encoding': 'gzip',
799
- 'X-Subscription-Token': BRAVE_API_KEY
800
- }
801
-
802
- try:
803
- response = requests.get(url, params=params, headers=headers)
804
- return response.json()
805
- except Exception as e:
806
- print(f"Error in Brave search: {e}")
807
- return {"error": str(e)}
808
-
809
- # Add this new function for LLM-based search
810
- def llm_search(query, model_name="llama3-8b-8192"):
811
- """
812
- Fallback search using LLM when no search APIs are configured
813
- """
814
- try:
815
- system_prompt = """You are a financial research assistant. Based on your knowledge,
816
- provide relevant information about the query. Format your response as a list of 3-5
817
- relevant pieces of information, each with a title and brief description."""
818
-
819
- completion = client.chat.completions.create(
820
- model=model_name,
821
- messages=[
822
- {"role": "system", "content": system_prompt},
823
- {"role": "user", "content": query}
824
- ],
825
- temperature=0.3,
826
- max_tokens=500
827
- )
828
-
829
- # Format response as search results
830
- return [{
831
- "title": "LLM-Generated Results",
832
- "link": "",
833
- "snippet": completion.choices[0].message.content,
834
- "source": "AI Knowledge Base"
835
- }]
836
- except Exception as e:
837
- print(f"Error in LLM search: {e}")
838
- return []
839
-
840
- # Update the get_financial_news function
841
- def get_financial_news(ticker, use_brave_search=False, model_name="llama3-8b-8192"):
842
- """
843
- Get latest financial news about a stock using selected search API or LLM fallback
844
- """
845
- query = f"{ticker} stock news financial analysis latest"
846
- news_items = []
847
-
848
- # Try Brave Search first if selected
849
- if use_brave_search and BRAVE_API_KEY:
850
- results = brave_search(query)
851
- if "web" in results and "results" in results["web"]:
852
- for item in results["web"]["results"][:5]:
853
- news_items.append({
854
- "title": item.get("title", ""),
855
- "link": item.get("url", ""),
856
- "snippet": item.get("description", ""),
857
- "source": item.get("source", "")
858
- })
859
- return news_items
860
-
861
- # Try Serper API if Brave Search is not used or failed
862
- if not news_items and SERPER_API_KEY:
863
- results = serper_search(query)
864
- if "organic" in results:
865
- for item in results["organic"][:5]:
866
- news_items.append({
867
- "title": item.get("title", ""),
868
- "link": item.get("link", ""),
869
- "snippet": item.get("snippet", ""),
870
- "source": item.get("source", "")
871
- })
872
- return news_items
873
-
874
- # Fallback to LLM if no API results
875
- if not news_items:
876
- return llm_search(f"Provide recent financial news and analysis about {ticker} stock", model_name)
877
-
878
- # Update the get_market_sentiment function
879
- def get_market_sentiment(ticker, use_brave_search=False, model_name="llama3-8b-8192"):
880
- """
881
- Get market sentiment for a stock using selected search API or LLM fallback
882
- """
883
- query = f"{ticker} stock market sentiment analysis"
884
- snippets = []
885
-
886
- # Try Brave Search first if selected
887
- if use_brave_search and BRAVE_API_KEY:
888
- results = brave_search(query)
889
- if "web" in results and "results" in results["web"]:
890
- for item in results["web"]["results"][:3]:
891
- if "description" in item:
892
- snippets.append(item["description"])
893
-
894
- # Try Serper API if Brave Search is not used or failed
895
- if not snippets and SERPER_API_KEY:
896
- results = serper_search(query)
897
- if "organic" in results:
898
- for item in results["organic"][:3]:
899
- if "snippet" in item:
900
- snippets.append(item["snippet"])
901
-
902
- # Generate sentiment analysis
903
- if snippets:
904
- combined_snippets = "\n".join(snippets)
905
- else:
906
- # If no API results, use LLM to generate market sentiment directly
907
- system_prompt = f"""You are a financial analyst. Based on your knowledge,
908
- provide a brief market sentiment analysis for {ticker} stock. Consider recent
909
- trends, company performance, and market conditions."""
910
-
911
- try:
912
- completion = client.chat.completions.create(
913
- model=model_name,
914
- messages=[
915
- {"role": "system", "content": system_prompt},
916
- {"role": "user", "content": f"What is the current market sentiment for {ticker} stock?"}
917
- ],
918
- temperature=0.2,
919
- max_tokens=150
920
- )
921
- return completion.choices[0].message.content
922
- except Exception as e:
923
- print(f"Error in LLM sentiment analysis: {e}")
924
- return "Unable to determine sentiment"
925
-
926
- # If we have API snippets, analyze them
927
- try:
928
- completion = client.chat.completions.create(
929
- model=model_name,
930
- messages=[
931
- {"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."},
932
- {"role": "user", "content": combined_snippets}
933
- ],
934
- temperature=0.2,
935
- max_tokens=150
936
- )
937
- return completion.choices[0].message.content
938
- except Exception as e:
939
- print(f"Error analyzing sentiment: {e}")
940
- return "Unable to determine sentiment"
941
-
942
  # Function to generate chatbot responses with Finance theme
943
- def generate_response(message, session_id, model_name, history, current_ticker=None, use_brave_search=False):
944
  if not message:
945
  return history
946
  try:
@@ -956,98 +114,20 @@ def generate_response(message, session_id, model_name, history, current_ticker=N
956
  ticker = message[1:].upper()
957
  try:
958
  stock_data = get_stock_data(ticker)
959
- news = get_financial_news(ticker, use_brave_search)
960
- sentiment = get_market_sentiment(ticker, use_brave_search)
961
-
962
  response = f"**Stock Information for {ticker}**\n\n"
963
  response += f"Current Price: ${stock_data['current_price']}\n"
964
  response += f"52-Week High: ${stock_data['52wk_high']}\n"
965
  response += f"Market Cap: ${stock_data['market_cap']:,}\n"
966
- response += f"P/E Ratio: {stock_data['pe_ratio']}\n\n"
967
- response += f"**Market Sentiment:**\n{sentiment}\n\n"
968
- response += "**Recent News:**\n"
969
-
970
- for i, news_item in enumerate(news[:3]):
971
- response += f"{i+1}. [{news_item['title']}]({news_item['link']})\n"
972
- response += f" {news_item['snippet'][:100]}...\n\n"
973
-
974
  response += f"More data available in the Stock Analysis tab."
975
  history.append((message, response))
976
  return history
977
  except Exception as e:
978
  history.append((message, f"Error retrieving stock data for {ticker}: {str(e)}"))
979
  return history
980
-
981
- # Check if it's a news search request
982
- if message.lower().startswith("/news "):
983
- topic = message[6:].strip()
984
- news = get_financial_news(topic, use_brave_search)
985
-
986
- if news:
987
- search_provider = "Brave Search" if use_brave_search else "Serper"
988
- response = f"**Latest Financial News on {topic} (via {search_provider}):**\n\n"
989
- for i, news_item in enumerate(news[:5]):
990
- response += f"{i+1}. **{news_item['title']}**\n"
991
- response += f" Source: {news_item['source']}\n"
992
- response += f" {news_item['snippet']}\n"
993
- response += f" [Read more]({news_item['link']})\n\n"
994
- else:
995
- response = f"No recent news found for {topic}."
996
-
997
- history.append((message, response))
998
- return history
999
-
1000
- # Check if it's a chart analysis request
1001
- if message.lower() == "/chart" or message.lower().startswith("/analyze chart"):
1002
- if current_ticker and current_ticker in chart_data_store:
1003
- chart_context = generate_chart_context(current_ticker)
1004
-
1005
- # Get additional market analysis using selected search API
1006
- market_context = ""
1007
- try:
1008
- news = get_financial_news(current_ticker, use_brave_search)
1009
- sentiment = get_market_sentiment(current_ticker, use_brave_search)
1010
- market_context = f"\n\nMarket Sentiment: {sentiment}\n\nRecent News Context:"
1011
- for item in news[:2]:
1012
- market_context += f"\n- {item['title']}: {item['snippet'][:150]}..."
1013
- except Exception as e:
1014
- print(f"Error getting additional market context: {e}")
1015
-
1016
- 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."
1017
- completion = client.chat.completions.create(
1018
- model=model_name,
1019
- messages=[
1020
- {"role": "system", "content": system_prompt},
1021
- {"role": "user", "content": f"Analyze this stock data and chart information:\n\n{chart_context}{market_context}"}
1022
- ],
1023
- temperature=0.7,
1024
- max_tokens=1024
1025
- )
1026
- response = completion.choices[0].message.content
1027
- history.append((message, response))
1028
- return history
1029
- else:
1030
- history.append((message, "Please analyze a stock first using the Stock Analysis tab before requesting chart analysis."))
1031
- return history
1032
 
1033
  system_prompt = "You are a financial assistant specializing in analyzing financial reports, statements, and market trends."
1034
  system_prompt += " You can help with stock market information, financial terminology, ratio analysis, and investment concepts."
1035
-
1036
- # Add chart context if available
1037
- 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()):
1038
- chart_context = generate_chart_context(current_ticker)
1039
- context += f"\n\nRecent stock data for {current_ticker}:\n{chart_context}"
1040
-
1041
- # Add news and sentiment if it's a stock-related query
1042
- try:
1043
- news = get_financial_news(current_ticker, use_brave_search)
1044
- sentiment = get_market_sentiment(current_ticker, use_brave_search)
1045
- context += f"\n\nMarket Sentiment: {sentiment}\n\nRecent News Headlines:"
1046
- for item in news[:2]:
1047
- context += f"\n- {item['title']}"
1048
- except Exception as e:
1049
- print(f"Error adding news context: {e}")
1050
-
1051
  if context:
1052
  system_prompt += " Use the following context to answer the question if relevant: " + context
1053
 
@@ -1061,86 +141,12 @@ def generate_response(message, session_id, model_name, history, current_ticker=N
1061
  max_tokens=1024
1062
  )
1063
  response = completion.choices[0].message.content
1064
- disclaimer = "\n\n*Note: This is informational only and not financial advice. Consult a professional advisor for investment decisions.*"
1065
- response += disclaimer
1066
-
1067
  history.append((message, response))
1068
  return history
1069
  except Exception as e:
1070
  history.append((message, f"Error generating response: {str(e)}"))
1071
  return history
1072
 
1073
- # Helper function to generate chart context for LLM
1074
- def generate_chart_context(ticker):
1075
- data = chart_data_store[ticker]
1076
- df = data["history"]
1077
- stats = data["stats"]
1078
-
1079
- # Calculate key metrics from the chart data
1080
- start_price = df["Close"].iloc[0]
1081
- end_price = df["Close"].iloc[-1]
1082
- percent_change = ((end_price - start_price) / start_price) * 100
1083
- highest = df["High"].max()
1084
- lowest = df["Low"].min()
1085
-
1086
- # Calculate average volume
1087
- avg_volume = df["Volume"].mean()
1088
-
1089
- # Calculate simple moving averages
1090
- if len(df) > 50:
1091
- sma_50 = df["Close"].rolling(window=50).mean().iloc[-1]
1092
- else:
1093
- sma_50 = "Not enough data"
1094
-
1095
- if len(df) > 200:
1096
- sma_200 = df["Close"].rolling(window=200).mean().iloc[-1]
1097
- else:
1098
- sma_200 = "Not enough data"
1099
-
1100
- # Calculate RSI (Relative Strength Index)
1101
- delta = df['Close'].diff()
1102
- gain = delta.where(delta > 0, 0).rolling(window=14).mean()
1103
- loss = -delta.where(delta < 0, 0).rolling(window=14).mean()
1104
- rs = gain / loss
1105
- rsi = 100 - (100 / (1 + rs.iloc[-1])) if not pd.isna(rs.iloc[-1]) and loss.iloc[-1] != 0 else 50
1106
-
1107
- # Calculate volatility (standard deviation of returns)
1108
- returns = df['Close'].pct_change()
1109
- volatility = returns.std() * 100 # Annualize by multiplying by sqrt(252)
1110
-
1111
- # Get recent price movement (last 5 days)
1112
- recent_prices = []
1113
- if len(df) >= 5:
1114
- for i in range(1, 6):
1115
- if i <= len(df):
1116
- recent_prices.append(df["Close"].iloc[-i])
1117
-
1118
- # Format the context for the LLM
1119
- context = f"""
1120
- Ticker: {ticker}
1121
- Period: {data["period"]}
1122
- Current Price: ${end_price:.2f}
1123
- Price Change: {percent_change:.2f}%
1124
- 52-Week High: ${stats['52wk_high']}
1125
- 52-Week Low: ${lowest:.2f}
1126
- Market Cap: ${stats['market_cap']:,}
1127
- P/E Ratio: {stats['pe_ratio']}
1128
- Average Volume: {avg_volume:.0f}
1129
- Volatility: {volatility:.2f}%
1130
- RSI (14-day): {rsi:.2f}
1131
- """
1132
-
1133
- if isinstance(sma_50, float):
1134
- context += f"50-day Moving Average: ${sma_50:.2f}\n"
1135
- if isinstance(sma_200, float):
1136
- context += f"200-day Moving Average: ${sma_200:.2f}\n"
1137
-
1138
- context += "\nRecent Price Movement (last 5 days, most recent first):\n"
1139
- for i, price in enumerate(recent_prices):
1140
- context += f"Day {i+1}: ${price:.2f}\n"
1141
-
1142
- return context
1143
-
1144
  # Functions to update PDF viewer (unchanged)
1145
  def update_pdf_viewer(pdf_state):
1146
  if not pdf_state["total_pages"]:
@@ -1276,7 +282,7 @@ def create_stock_chart(ticker, period="1y"):
1276
  print(f"Error creating stock chart: {e}")
1277
  return None
1278
 
1279
- def analyze_ticker(ticker_input, period, use_brave_search=False):
1280
  """Process the ticker input and return analysis"""
1281
  if not ticker_input:
1282
  return None, "Please enter a valid ticker symbol", None
@@ -1287,29 +293,11 @@ def analyze_ticker(ticker_input, period, use_brave_search=False):
1287
 
1288
  try:
1289
  stock_data = get_stock_data(ticker)
1290
- stock_history = get_stock_history(ticker, period)
1291
  chart = create_stock_chart(ticker, period)
1292
 
1293
- # Store chart data for LLM analysis
1294
- chart_data_store[ticker] = {
1295
- "history": stock_history,
1296
- "stats": stock_data,
1297
- "period": period
1298
- }
1299
-
1300
- # Get market sentiment using selected search API or LLM fallback
1301
- try:
1302
- sentiment = get_market_sentiment(ticker, use_brave_search)
1303
- sentiment_summary = f"\n\n**Market Sentiment:**\n{sentiment}"
1304
- except Exception as e:
1305
- print(f"Error getting sentiment: {e}")
1306
- sentiment_summary = ""
1307
-
1308
  # Create a formatted summary
1309
- search_provider = "Brave Search" if (use_brave_search and BRAVE_API_KEY) else "Serper" if SERPER_API_KEY else "AI Knowledge Base"
1310
  summary = f"""
1311
- ### {ticker} Analysis (Using {search_provider})
1312
-
1313
  **Current Price:** ${stock_data['current_price']}
1314
  **52-Week High:** ${stock_data['52wk_high']}
1315
  **Market Cap:** ${stock_data['market_cap']:,}
@@ -1317,576 +305,71 @@ def analyze_ticker(ticker_input, period, use_brave_search=False):
1317
  **Dividend Yield:** {stock_data['dividend_yield'] * 100 if stock_data['dividend_yield'] != 'N/A' else 'N/A'}%
1318
  **Beta:** {stock_data['beta']}
1319
  **Avg Volume:** {stock_data['average_volume']:,}
1320
- {sentiment_summary}
1321
-
1322
- For in-depth analysis of this chart, ask the chatbot by typing "/chart" or "/analyze chart".
1323
- For latest news, type "/news {ticker}".
1324
  """
1325
 
1326
  return chart, summary, ticker
1327
  except Exception as e:
1328
  return None, f"Error analyzing ticker {ticker}: {str(e)}", None
1329
 
1330
- # Replace the load_docling_model function with a simpler image analysis function
1331
- def analyze_image(image_file):
1332
- """
1333
- Basic image analysis function that doesn't rely on external models
1334
- """
1335
- if image_file is None:
1336
- return "No image uploaded. Please upload an image to analyze."
1337
-
1338
- try:
1339
- image = Image.open(image_file)
1340
- width, height = image.size
1341
- format = image.format
1342
- mode = image.mode
1343
-
1344
- analysis = f"""## Technical Document Analysis
1345
-
1346
- **Image Properties:**
1347
- - Dimensions: {width}x{height} pixels
1348
- - Format: {format}
1349
- - Color Mode: {mode}
1350
-
1351
- **Technical Analysis:**
1352
- 1. Document Quality:
1353
- - Resolution: {'High' if width > 2000 or height > 2000 else 'Medium' if width > 1000 or height > 1000 else 'Low'}
1354
- - Color Depth: {mode}
1355
-
1356
- 2. Recommendations:
1357
- - For text extraction, consider using PDF format
1358
- - For technical diagrams, ensure high resolution
1359
- - Consider OCR for text content
1360
-
1361
- **Note:** For detailed technical analysis, please convert to PDF format
1362
- """
1363
- return analysis
1364
- except Exception as e:
1365
- return f"Error analyzing image: {str(e)}\n\nPlease try using PDF format instead."
1366
-
1367
- # Update the main interface layout with sidebar, content area, and persistent chat
1368
  with gr.Blocks(css=custom_css, theme=gr.themes.Soft()) as demo:
1369
- # Shared state
1370
  current_session_id = gr.State(None)
1371
  pdf_state = gr.State({"page_images": [], "total_pages": 0, "total_words": 0})
1372
- market_data = gr.State({})
1373
- chat_history = gr.State([])
1374
- selected_tool = gr.State("market-analysis") # Default selected tool
1375
- is_split_screen = gr.State(False) # Split screen toggle state
1376
- is_sidebar_collapsed = gr.State(False) # Sidebar collapsed state
1377
 
1378
- # App Header
1379
  gr.HTML("""
1380
  <div class="header">
1381
- <div class="header-title">💹 Fin-Vision AI
1382
- <span class="stock-badge">Market Analysis</span>
1383
- </div>
1384
- <div class="header-subtitle">Financial Document & Market Copilot</div>
1385
  </div>
1386
  """)
1387
 
1388
- # Sidebar Navigation
1389
- with gr.Column(elem_classes=["sidebar"]):
1390
- gr.HTML("""
1391
- <div class="sidebar-header">
1392
- <div class="sidebar-logo">📊 Fin-AI</div>
1393
- </div>
1394
- """)
1395
-
1396
- with gr.Column(elem_classes=["sidebar-nav"]):
1397
- with gr.Group():
1398
- market_btn = gr.Button("📈 Market Analysis", elem_classes=["nav-item"])
1399
- document_btn = gr.Button("📄 Documents", elem_classes=["nav-item"])
1400
- economic_btn = gr.Button("📊 Economic Data", elem_classes=["nav-item"])
1401
- news_btn = gr.Button("📰 Financial News", elem_classes=["nav-item"])
1402
- portfolio_btn = gr.Button("💼 Portfolio", elem_classes=["nav-item"])
1403
- pdf_viewer_btn = gr.Button("📕 PDF Viewer", elem_classes=["nav-item", "active"])
1404
- code_editor_btn = gr.Button("💻 Code Editor", elem_classes=["nav-item"])
1405
- settings_btn = gr.Button("⚙️ Settings", elem_classes=["nav-item"])
1406
-
1407
- # Main Content Area
1408
- with gr.Column(elem_classes=["main-content"]):
1409
- # PDF Viewer Tool
1410
- with gr.Group(visible=True) as pdf_viewer_tool:
1411
- with gr.Row():
1412
- gr.Markdown("## 📕 PDF Viewer Tool")
1413
-
1414
- with gr.Row():
1415
- with gr.Column(scale=1):
1416
- pdf_file_upload = gr.File(
1417
- label="Upload PDF Document",
1418
- file_types=[".pdf"],
1419
- type="binary"
1420
- )
1421
- with gr.Row():
1422
- pdf_process_btn = gr.Button("Process PDF", variant="primary")
1423
- pdf_clear_btn = gr.Button("Clear", variant="secondary")
1424
-
1425
- # PDF Navigation
1426
- with gr.Box():
1427
- gr.Markdown("### Navigation")
1428
- with gr.Row():
1429
- pdf_prev_btn = gr.Button("◀ Previous")
1430
- pdf_page_slider = gr.Slider(
1431
- minimum=1,
1432
- maximum=1,
1433
- step=1,
1434
- label="Page",
1435
- value=1
1436
- )
1437
- pdf_next_btn = gr.Button("Next ▶")
1438
-
1439
- # Search functionality
1440
- with gr.Row():
1441
- pdf_search_input = gr.Textbox(
1442
- label="Search in PDF",
1443
- placeholder="Enter search term...",
1444
- elem_classes=["pdf-search"]
1445
- )
1446
- pdf_search_btn = gr.Button("Search")
1447
-
1448
- pdf_search_results = gr.Markdown(elem_classes=["pdf-search-results"])
1449
-
1450
- # Statistics and metadata
1451
- pdf_stats = gr.Markdown(elem_classes=["stats-box"])
1452
-
1453
- with gr.Column(scale=2):
1454
- # PDF Display
1455
- with gr.Box(elem_classes=["pdf-container"]):
1456
- with gr.Row():
1457
- pdf_thumbnail_gallery = gr.Gallery(
1458
- label="Thumbnails",
1459
- elem_classes=["pdf-thumbnail-gallery"],
1460
- columns=1,
1461
- rows=5,
1462
- height=500
1463
- )
1464
-
1465
- pdf_display = gr.Image(
1466
- label="Document View",
1467
- type="pil",
1468
- elem_classes=["pdf-display"],
1469
- height=600
1470
- )
1471
-
1472
- # Extracted text
1473
- pdf_text = gr.Textbox(
1474
- label="Extracted Text",
1475
- lines=10,
1476
- max_lines=20,
1477
- interactive=False,
1478
- elem_classes=["pdf-text"]
1479
- )
1480
-
1481
- # Code Editor Tool
1482
- with gr.Group(visible=False) as code_editor_tool:
1483
- with gr.Row():
1484
- gr.Markdown("## 💻 Code Editor")
1485
-
1486
- with gr.Row():
1487
- with gr.Column(scale=1):
1488
- # File selection and management
1489
- code_file_upload = gr.File(
1490
- label="Upload Code File",
1491
- file_types=[".py", ".js", ".html", ".css", ".json", ".txt"],
1492
- type="binary"
1493
- )
1494
-
1495
- with gr.Row():
1496
- code_load_btn = gr.Button("Load File", variant="primary")
1497
- code_clear_btn = gr.Button("Clear", variant="secondary")
1498
-
1499
- # File browser
1500
- with gr.Box():
1501
- gr.Markdown("### File Browser")
1502
- code_file_path = gr.Textbox(
1503
- label="File Path",
1504
- placeholder="Enter file path to open...",
1505
- value=""
1506
- )
1507
- code_file_list = gr.Dropdown(
1508
- label="Recent Files",
1509
- choices=[],
1510
- interactive=True
1511
- )
1512
- with gr.Row():
1513
- code_browse_btn = gr.Button("Browse")
1514
- code_refresh_btn = gr.Button("↻ Refresh")
1515
-
1516
- # Code analysis
1517
- with gr.Box():
1518
- gr.Markdown("### Code Analysis")
1519
- code_analyze_btn = gr.Button("Analyze Code")
1520
- code_analysis_results = gr.Markdown("Click 'Analyze Code' to get analysis")
1521
-
1522
- with gr.Column(scale=2):
1523
- # Code editor header
1524
- with gr.Row(elem_classes=["code-editor-header"]):
1525
- code_filename = gr.Textbox(
1526
- label="Filename",
1527
- value="",
1528
- interactive=False,
1529
- elem_classes=["code-editor-filename"]
1530
- )
1531
- with gr.Row(elem_classes=["code-editor-controls"]):
1532
- code_edit_toggle = gr.Checkbox(
1533
- label="Edit Mode",
1534
- value=False
1535
- )
1536
- code_save_btn = gr.Button("Save", variant="primary")
1537
-
1538
- # Code editor
1539
- code_content = gr.Code(
1540
- label="",
1541
- language="python",
1542
- value="# No file loaded\n\n# Upload or select a file to edit",
1543
- interactive=False,
1544
- elem_classes=["code-editor-area"]
1545
- )
1546
-
1547
- # Execution results
1548
- code_output = gr.Textbox(
1549
- label="Output",
1550
- lines=5,
1551
- interactive=False
1552
- )
1553
-
1554
- # Market Analysis Tool (keeping as reference but initially hidden)
1555
- with gr.Group(visible=False) as market_tool:
1556
- gr.Markdown("## 📈 Market Analysis")
1557
- with gr.Row():
1558
- with gr.Column(scale=1):
1559
- ticker_input = gr.Textbox(
1560
- label="Stock Ticker",
1561
- placeholder="Enter ticker symbol (e.g., AAPL, MSFT)",
1562
- value=""
1563
- )
1564
- period_dropdown = gr.Dropdown(
1565
- choices=["1mo", "3mo", "6mo", "1y", "2y", "5y", "10y", "ytd", "max"],
1566
- value="1y",
1567
- label="Time Period"
1568
- )
1569
- analyze_btn = gr.Button("Analyze Stock", variant="primary")
1570
-
1571
- with gr.Column(scale=2):
1572
- stock_chart = gr.Plot(label="Stock Price Chart")
1573
- stock_info = gr.Markdown(elem_classes="finance-card")
1574
-
1575
- # Document Analysis Tool (keeping as reference but initially hidden)
1576
- with gr.Group(visible=False) as document_tool:
1577
- gr.Markdown("## 📄 Financial Documents")
1578
- with gr.Row():
1579
- with gr.Column(scale=1):
1580
- pdf_file = gr.File(
1581
- label="Upload Financial Document",
1582
- file_types=[".pdf"],
1583
- type="binary"
1584
- )
1585
- upload_button = gr.Button("Process Document", variant="primary")
1586
- pdf_status = gr.Markdown("Upload a document to begin analysis")
1587
 
1588
- with gr.Column(scale=2):
 
 
1589
  with gr.Tabs():
1590
- with gr.TabItem("Document Viewer"):
1591
- page_slider = gr.Slider(
1592
- minimum=1,
1593
- maximum=1,
1594
- step=1,
1595
- label="Page Navigation",
1596
- value=1
1597
  )
1598
- pdf_image = gr.Image(label="Document Page", type="pil")
1599
- stats_display = gr.Markdown(elem_classes="stats-box")
1600
-
1601
- # Economic Data Tool (keeping as reference but initially hidden)
1602
- with gr.Group(visible=False) as economic_tool:
1603
- gr.Markdown("## 📊 Economic Indicators")
1604
- with gr.Row():
1605
- with gr.Column(scale=1):
1606
- indicator_dropdown = gr.Dropdown(
1607
- choices=["GDP", "Unemployment", "Inflation", "Interest Rates", "Consumer Confidence"],
1608
- value="GDP",
1609
- label="Economic Indicator"
1610
- )
1611
- indicator_btn = gr.Button("Fetch Data", variant="primary")
1612
-
1613
- with gr.Column(scale=2):
1614
- indicator_chart = gr.Plot(label="Economic Indicator")
1615
- indicator_info = gr.Markdown(elem_classes="finance-card")
1616
-
1617
- # News Tool (keeping as reference but initially hidden)
1618
- with gr.Group(visible=False) as news_tool:
1619
- gr.Markdown("## 📰 Financial News")
1620
- with gr.Row():
1621
- with gr.Column():
1622
- news_query = gr.Textbox(
1623
- label="Search News",
1624
- placeholder="Enter topic, company, or leave blank for top financial news"
1625
- )
1626
- news_btn = gr.Button("Get News", variant="primary")
1627
- news_results = gr.Markdown("Click 'Get News' to fetch the latest financial headlines")
1628
-
1629
- # Portfolio Tool (keeping as reference but initially hidden)
1630
- with gr.Group(visible=False) as portfolio_tool:
1631
- gr.Markdown("## 💼 Portfolio Tracker")
1632
- with gr.Row():
1633
- gr.Markdown("Portfolio tracking features coming soon!")
1634
-
1635
- # Settings Tool (keeping as reference but initially hidden)
1636
- with gr.Group(visible=False) as settings_tool:
1637
- gr.Markdown("## ⚙️ Settings")
1638
- with gr.Row():
1639
- with gr.Column():
1640
- theme_toggle = gr.Checkbox(label="Dark Mode", value=False)
1641
- api_key_input = gr.Textbox(
1642
- label="API Key (Optional)",
1643
- placeholder="Enter your own API key for extended functionality",
1644
- type="password"
1645
- )
1646
- save_btn = gr.Button("Save Settings", variant="primary")
1647
-
1648
- # Toggle buttons for sidebar collapse and split screen
1649
- gr.HTML("""
1650
- <div class="sidebar-toggle toggle-button">
1651
- <span id="sidebar-toggle-icon">◀</span>
1652
- </div>
1653
- <div class="split-toggle toggle-button">
1654
- <span id="split-toggle-icon">⬍</span>
1655
- </div>
1656
- """)
1657
-
1658
- # Persistent Chat Interface
1659
- with gr.Column(elem_classes=["persistent-chat-container"]):
1660
- with gr.Column(elem_classes=["chat-messages"]) as chat_display:
1661
- chatbot = gr.Chatbot(
1662
- show_copy_button=True,
1663
- avatar_images=(
1664
- "assets/user.png",
1665
- "assets/bot_finance.png"
1666
- )
1667
- )
1668
 
1669
- with gr.Row(elem_classes=["chat-input-area"]):
1670
- msg = gr.Textbox(
1671
- show_label=False,
1672
- placeholder="Ask questions about financial data & documents...",
1673
- elem_classes=["chat-input"]
1674
- )
1675
- send_btn = gr.Button("Send", elem_classes=["send-button"])
1676
-
1677
- # Event Handlers for Navigation
1678
- def switch_to_tool(tool_name):
1679
- """Switch to a specific tool by name"""
1680
- is_market = tool_name == "market"
1681
- is_document = tool_name == "document"
1682
- is_economic = tool_name == "economic"
1683
- is_news = tool_name == "news"
1684
- is_portfolio = tool_name == "portfolio"
1685
- is_pdf_viewer = tool_name == "pdf_viewer"
1686
- is_code_editor = tool_name == "code_editor"
1687
- is_settings = tool_name == "settings"
1688
-
1689
- return (
1690
- is_market,
1691
- is_document,
1692
- is_economic,
1693
- is_news,
1694
- is_portfolio,
1695
- is_pdf_viewer,
1696
- is_code_editor,
1697
- is_settings,
1698
- "active" if is_market else "",
1699
- "active" if is_document else "",
1700
- "active" if is_economic else "",
1701
- "active" if is_news else "",
1702
- "active" if is_portfolio else "",
1703
- "active" if is_pdf_viewer else "",
1704
- "active" if is_code_editor else "",
1705
- "active" if is_settings else ""
1706
- )
1707
-
1708
- def select_market_tool():
1709
- return switch_to_tool("market")
1710
-
1711
- def select_document_tool():
1712
- return switch_to_tool("document")
1713
-
1714
- def select_economic_tool():
1715
- return switch_to_tool("economic")
1716
-
1717
- def select_news_tool():
1718
- return switch_to_tool("news")
1719
-
1720
- def select_portfolio_tool():
1721
- return switch_to_tool("portfolio")
1722
-
1723
- def select_pdf_viewer_tool():
1724
- return switch_to_tool("pdf_viewer")
1725
-
1726
- def select_code_editor_tool():
1727
- return switch_to_tool("code_editor")
1728
-
1729
- def select_settings_tool():
1730
- return switch_to_tool("settings")
1731
-
1732
- market_btn.click(
1733
- fn=select_market_tool,
1734
- inputs=None,
1735
- outputs=[
1736
- market_tool, document_tool, economic_tool, news_tool, portfolio_tool, pdf_viewer_tool, code_editor_tool, settings_tool,
1737
- market_btn, document_btn, economic_btn, news_btn, portfolio_btn, pdf_viewer_btn, code_editor_btn, settings_btn
1738
- ]
1739
- )
1740
-
1741
- document_btn.click(
1742
- fn=select_document_tool,
1743
- inputs=None,
1744
- outputs=[
1745
- market_tool, document_tool, economic_tool, news_tool, portfolio_tool, pdf_viewer_tool, code_editor_tool, settings_tool,
1746
- market_btn, document_btn, economic_btn, news_btn, portfolio_btn, pdf_viewer_btn, code_editor_btn, settings_btn
1747
- ]
1748
- )
1749
-
1750
- economic_btn.click(
1751
- fn=select_economic_tool,
1752
- inputs=None,
1753
- outputs=[
1754
- market_tool, document_tool, economic_tool, news_tool, portfolio_tool, pdf_viewer_tool, code_editor_tool, settings_tool,
1755
- market_btn, document_btn, economic_btn, news_btn, portfolio_btn, pdf_viewer_btn, code_editor_btn, settings_btn
1756
- ]
1757
- )
1758
-
1759
- news_btn.click(
1760
- fn=select_news_tool,
1761
- inputs=None,
1762
- outputs=[
1763
- market_tool, document_tool, economic_tool, news_tool, portfolio_tool, pdf_viewer_tool, code_editor_tool, settings_tool,
1764
- market_btn, document_btn, economic_btn, news_btn, portfolio_btn, pdf_viewer_btn, code_editor_btn, settings_btn
1765
- ]
1766
- )
1767
-
1768
- portfolio_btn.click(
1769
- fn=select_portfolio_tool,
1770
- inputs=None,
1771
- outputs=[
1772
- market_tool, document_tool, economic_tool, news_tool, portfolio_tool, pdf_viewer_tool, code_editor_tool, settings_tool,
1773
- market_btn, document_btn, economic_btn, news_btn, portfolio_btn, pdf_viewer_btn, code_editor_btn, settings_btn
1774
- ]
1775
- )
1776
-
1777
- pdf_viewer_btn.click(
1778
- fn=select_pdf_viewer_tool,
1779
- inputs=None,
1780
- outputs=[
1781
- market_tool, document_tool, economic_tool, news_tool, portfolio_tool, pdf_viewer_tool, code_editor_tool, settings_tool,
1782
- market_btn, document_btn, economic_btn, news_btn, portfolio_btn, pdf_viewer_btn, code_editor_btn, settings_btn
1783
- ]
1784
- )
1785
-
1786
- code_editor_btn.click(
1787
- fn=select_code_editor_tool,
1788
- inputs=None,
1789
- outputs=[
1790
- market_tool, document_tool, economic_tool, news_tool, portfolio_tool, pdf_viewer_tool, code_editor_tool, settings_tool,
1791
- market_btn, document_btn, economic_btn, news_btn, portfolio_btn, pdf_viewer_btn, code_editor_btn, settings_btn
1792
- ]
1793
- )
1794
-
1795
- settings_btn.click(
1796
- fn=select_settings_tool,
1797
- inputs=None,
1798
- outputs=[
1799
- market_tool, document_tool, economic_tool, news_tool, portfolio_tool, pdf_viewer_tool, code_editor_tool, settings_tool,
1800
- market_btn, document_btn, economic_btn, news_btn, portfolio_btn, pdf_viewer_btn, code_editor_btn, settings_btn
1801
- ]
1802
- )
1803
-
1804
- # Existing event handlers
1805
- def process_message(message, history, session_id, pdf_state, market_data):
1806
- if not message:
1807
- return history, ""
1808
-
1809
- # Access context from document analysis if available
1810
- context = ""
1811
- if session_id and session_id in user_vectorstores:
1812
- vectorstore = user_vectorstores[session_id]
1813
- docs = vectorstore.similarity_search(message, k=3)
1814
- if docs:
1815
- context = "\n\nRelevant information from financial document:\n" + "\n".join(f"- {doc.page_content}" for doc in docs)
1816
-
1817
- # Add stock market data if available
1818
- if market_data and 'ticker' in market_data:
1819
- ticker = market_data['ticker']
1820
- context += f"\n\nStock data for {ticker} is available from the market analysis tab."
1821
- if 'price' in market_data:
1822
- context += f"\nCurrent price: ${market_data['price']}"
1823
- if 'change' in market_data:
1824
- context += f"\nChange: {market_data['change']}%"
1825
-
1826
- # Check if it's a special command for stock lookup
1827
- if message.lower().startswith("/stock "):
1828
- ticker = message.split(" ", 1)[1].strip().upper()
1829
- try:
1830
- # Get stock data
1831
- stock_data = get_stock_data(ticker)
1832
- # Format response
1833
- response = f"**Stock Information for {ticker}**\n\n"
1834
- response += f"Current Price: ${stock_data['price']:.2f}\n"
1835
- response += f"Change: {stock_data['change']:.2f}% "
1836
- response += "📈" if stock_data['change'] > 0 else "📉"
1837
- response += f"\nVolume: {stock_data['volume']:,}\n"
1838
- response += f"Market Cap: ${stock_data['market_cap']:,.2f}\n"
1839
 
1840
- # Add market data for future context
1841
- market_data['ticker'] = ticker
1842
- market_data['price'] = stock_data['price']
1843
- market_data['change'] = stock_data['change']
1844
-
1845
- history.append((message, response))
1846
- return history, ""
1847
- except Exception as e:
1848
- response = f"Error retrieving stock data for {ticker}: {str(e)}"
1849
- history.append((message, response))
1850
- return history, ""
1851
-
1852
- # Generate response
1853
- system_prompt = """You are a financial assistant specializing in analyzing financial data, market trends, and investment documents.
1854
- You provide clear, accurate information about stocks, economic indicators, and financial reports.
1855
- Always note that you're not providing investment advice and users should consult with a qualified financial advisor."""
1856
-
1857
- if context:
1858
- system_prompt += f"\nUse this context to inform your response if relevant: {context}"
1859
-
1860
- completion = client.chat.completions.create(
1861
- model="llama3-70b-8192",
1862
- messages=[
1863
- {"role": "system", "content": system_prompt},
1864
- {"role": "user", "content": message}
1865
- ],
1866
- temperature=0.7,
1867
- max_tokens=1024
1868
- )
1869
-
1870
- response = completion.choices[0].message.content
1871
- disclaimer = "\n\n*Note: This is informational only and not financial advice. Consult a professional advisor for investment decisions.*"
1872
- response += disclaimer
1873
-
1874
- history.append((message, response))
1875
- return history, ""
1876
-
1877
- # Keep other existing event handlers
1878
- send_btn.click(
1879
- process_message,
1880
- inputs=[msg, chatbot, current_session_id, pdf_state, market_data],
1881
- outputs=[chatbot, msg]
1882
- )
1883
-
1884
- msg.submit(
1885
- process_message,
1886
- inputs=[msg, chatbot, current_session_id, pdf_state, market_data],
1887
- outputs=[chatbot, msg]
1888
- )
1889
 
 
1890
  upload_button.click(
1891
  process_pdf,
1892
  inputs=[pdf_file],
@@ -1897,126 +380,44 @@ with gr.Blocks(css=custom_css, theme=gr.themes.Soft()) as demo:
1897
  outputs=[page_slider, pdf_image, stats_display]
1898
  )
1899
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1900
  page_slider.change(
1901
  update_image,
1902
  inputs=[page_slider, pdf_state],
1903
  outputs=[pdf_image]
1904
  )
1905
 
1906
- analyze_btn.click(
1907
- lambda ticker, period: (create_stock_chart(ticker, period), get_stock_analysis(ticker)),
 
1908
  inputs=[ticker_input, period_dropdown],
1909
- outputs=[stock_chart, stock_info]
1910
- ).then(
1911
- lambda ticker: {'ticker': ticker},
1912
- inputs=[ticker_input],
1913
- outputs=[market_data]
1914
- )
1915
-
1916
- indicator_btn.click(
1917
- lambda indicator: (get_economic_indicator_chart(indicator), get_economic_indicator_info(indicator)),
1918
- inputs=[indicator_dropdown],
1919
- outputs=[indicator_chart, indicator_info]
1920
  )
1921
-
1922
- # Add logic for news search
1923
- news_btn.click(
1924
- lambda query: get_financial_news(query) if query else get_financial_news("top financial news"),
1925
- inputs=[news_query],
1926
- outputs=[news_results]
1927
- )
1928
-
1929
- # Add JavaScript for toggle functionality
1930
- gr.HTML("""
1931
- <script>
1932
- document.addEventListener('DOMContentLoaded', () => {
1933
- // Dark mode toggle
1934
- const darkModeToggle = document.querySelector('input[aria-label="Dark Mode"]');
1935
- if (darkModeToggle) {
1936
- darkModeToggle.addEventListener('change', () => {
1937
- document.body.classList.toggle('dark-mode', darkModeToggle.checked);
1938
- localStorage.setItem('dark-mode', darkModeToggle.checked);
1939
- });
1940
-
1941
- // Check for saved preference
1942
- if (localStorage.getItem('dark-mode') === 'true') {
1943
- darkModeToggle.checked = true;
1944
- document.body.classList.add('dark-mode');
1945
- }
1946
- }
1947
-
1948
- // Sidebar toggle
1949
- const sidebarToggle = document.querySelector('.sidebar-toggle');
1950
- const sidebarToggleIcon = document.getElementById('sidebar-toggle-icon');
1951
-
1952
- if (sidebarToggle) {
1953
- sidebarToggle.addEventListener('click', () => {
1954
- document.body.classList.toggle('collapsed-sidebar');
1955
- sidebarToggleIcon.textContent = document.body.classList.contains('collapsed-sidebar') ? '▶' : '◀';
1956
- localStorage.setItem('sidebar-collapsed', document.body.classList.contains('collapsed-sidebar'));
1957
- });
1958
-
1959
- // Check for saved preference
1960
- if (localStorage.getItem('sidebar-collapsed') === 'true') {
1961
- document.body.classList.add('collapsed-sidebar');
1962
- sidebarToggleIcon.textContent = '▶';
1963
- }
1964
- }
1965
-
1966
- // Split screen toggle
1967
- const splitToggle = document.querySelector('.split-toggle');
1968
- const splitToggleIcon = document.getElementById('split-toggle-icon');
1969
-
1970
- if (splitToggle) {
1971
- splitToggle.addEventListener('click', () => {
1972
- document.body.classList.toggle('split-screen');
1973
- document.body.classList.toggle('tools-visible');
1974
- splitToggleIcon.textContent = document.body.classList.contains('split-screen') ? '⬆' : '⬍';
1975
- localStorage.setItem('split-screen', document.body.classList.contains('split-screen'));
1976
- });
1977
-
1978
- // Check for saved preference
1979
- if (localStorage.getItem('split-screen') === 'true') {
1980
- document.body.classList.add('split-screen');
1981
- document.body.classList.add('tools-visible');
1982
- splitToggleIcon.textContent = '⬆';
1983
- } else {
1984
- // Default: show just chat at first
1985
- document.body.classList.add('tools-hidden');
1986
- }
1987
- }
1988
-
1989
- // Code editor syntax highlighting enhancement
1990
- const codeEditor = document.querySelector('.code-editor-area');
1991
- if (codeEditor) {
1992
- // Add line numbers
1993
- const addLineNumbers = () => {
1994
- const content = codeEditor.textContent;
1995
- const lines = content.split('\n');
1996
- const numberedLines = lines.map((line, i) => `${i+1} | ${line}`);
1997
- codeEditor.textContent = numberedLines.join('\n');
1998
- };
1999
-
2000
- // Observe when edit mode is toggled
2001
- const editToggle = document.querySelector('input[aria-label="Edit Mode"]');
2002
- if (editToggle) {
2003
- editToggle.addEventListener('change', () => {
2004
- codeEditor.classList.toggle('editable', editToggle.checked);
2005
- });
2006
- }
2007
- }
2008
- });
2009
- </script>
2010
- """)
2011
-
2012
- # Add footer
2013
- gr.HTML("""
2014
- <div style="text-align: center; margin-top: 20px; padding: 20px; color: #666; font-size: 0.9rem; border-top: 1px solid #eee;">
2015
- Created by Calvin Allen-Crawford<br>
2016
- <span style="font-style: italic; font-size: 0.8rem;">Founder of Cosmick Visions</span>
2017
- </div>
2018
- """)
2019
 
2020
- # Launch the app with modern UI
 
 
 
 
 
 
 
2021
  if __name__ == "__main__":
2022
  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
 
 
141
  max_tokens=1024
142
  )
143
  response = completion.choices[0].message.content
 
 
 
144
  history.append((message, response))
145
  return history
146
  except Exception as e:
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],
 
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("""
416
+ <div style="text-align: center; margin-top: 20px; padding: 10px; color: #666; font-size: 0.8rem; border-top: 1px solid #eee;">
417
+ Created by Calvin Allen Crawford
418
+ </div>
419
+ """)
420
+
421
+ # Launch the app
422
  if __name__ == "__main__":
423
  demo.launch()