Abid Ali Awan commited on
Commit
0112c49
Β·
1 Parent(s): 42236b2

Refactor app.py: Improve code readability and structure by consolidating conditional statements, enhancing string formatting, and ensuring consistent spacing throughout the analysis functions for budget, portfolio, and stock data.

Browse files
Files changed (1) hide show
  1. app.py +276 -154
app.py CHANGED
@@ -37,33 +37,37 @@ def analyze_data_with_repl(data_type, data):
37
  if categories and values:
38
  total_expenses = sum(values)
39
  analysis_text = "πŸ’° **Comprehensive Budget Analysis**\n\n"
40
-
41
  # Income vs Expenses Overview
42
  analysis_text += "## πŸ“ˆ **Income vs Expenses Overview**\n"
43
  analysis_text += f"- **Monthly Income**: ${income:,.0f}\n"
44
  analysis_text += f"- **Total Expenses**: ${total_expenses:,.0f}\n"
45
-
46
  if income > 0:
47
  remaining = income - total_expenses
48
  savings_rate = (remaining / income * 100) if income > 0 else 0
49
-
50
  if remaining > 0:
51
  analysis_text += f"- **πŸ’š Surplus**: ${remaining:,.0f}\n"
52
  analysis_text += f"- **πŸ’Ž Savings Rate**: {savings_rate:.1f}%\n"
53
  else:
54
  analysis_text += f"- **πŸ”΄ Deficit**: ${abs(remaining):,.0f}\n"
55
- analysis_text += f"- **⚠️ Overspending**: {abs(savings_rate):.1f}%\n"
56
-
 
 
57
  # Expense Breakdown with Progress Bars
58
  analysis_text += "\n## πŸ’³ **Expense Breakdown**\n"
59
  for i, (category, amount) in enumerate(zip(categories, values)):
60
- percentage = (amount / total_expenses * 100) if total_expenses > 0 else 0
 
 
61
  income_percentage = (amount / income * 100) if income > 0 else 0
62
  bar = "β–ˆ" * min(int(percentage / 3), 30) # Max 30 chars
63
-
64
  analysis_text += f"**{category.title()}**: ${amount:,.0f}\n"
65
  analysis_text += f" └─ {percentage:.1f}% of expenses | {income_percentage:.1f}% of income {bar}\n\n"
66
-
67
  # Financial Health Metrics
68
  analysis_text += "## πŸ“Š **Financial Health Metrics**\n"
69
  avg_expense = total_expenses / len(values)
@@ -71,42 +75,54 @@ def analyze_data_with_repl(data_type, data):
71
  smallest_expense = min(values)
72
  largest_category = categories[values.index(largest_expense)]
73
  smallest_category = categories[values.index(smallest_expense)]
74
-
75
- analysis_text += f"- **Average Category Expense**: ${avg_expense:,.0f}\n"
 
 
76
  analysis_text += f"- **Highest Expense**: {largest_category} (${largest_expense:,.0f})\n"
77
  analysis_text += f"- **Lowest Expense**: {smallest_category} (${smallest_expense:,.0f})\n"
78
- analysis_text += f"- **Expense Range**: ${largest_expense - smallest_expense:,.0f}\n"
79
-
 
 
80
  # Budget Recommendations
81
  analysis_text += "\n## πŸ’‘ **Smart Budget Insights**\n"
82
-
83
  # 50/30/20 Rule Analysis
84
  if income > 0:
85
  needs_target = income * 0.50
86
  wants_target = income * 0.30
87
  savings_target = income * 0.20
88
-
89
- analysis_text += f"**50/30/20 Rule Comparison:**\n"
90
  analysis_text += f"- Needs Target (50%): ${needs_target:,.0f}\n"
91
  analysis_text += f"- Wants Target (30%): ${wants_target:,.0f}\n"
92
  analysis_text += f"- Savings Target (20%): ${savings_target:,.0f}\n"
93
-
94
  if savings_rate >= 20:
95
  analysis_text += "βœ… **Excellent savings rate!**\n"
96
  elif savings_rate >= 10:
97
  analysis_text += "⚠️ **Good savings, aim for 20%**\n"
98
  else:
99
- analysis_text += "πŸ”΄ **Consider reducing expenses to save more**\n"
100
-
 
 
101
  # Category Warnings
102
  for category, amount in zip(categories, values):
103
  if income > 0:
104
- cat_percentage = (amount / income * 100)
105
- if category.lower() in ['rent', 'housing'] and cat_percentage > 30:
 
 
 
106
  analysis_text += f"⚠️ **Housing costs high**: {cat_percentage:.1f}% (recommend <30%)\n"
107
- elif category.lower() in ['food', 'dining'] and cat_percentage > 15:
 
 
 
108
  analysis_text += f"⚠️ **Food costs high**: {cat_percentage:.1f}% (recommend <15%)\n"
109
-
110
  return analysis_text
111
  except Exception as e:
112
  return f"Error analyzing budget data: {str(e)}"
@@ -116,42 +132,52 @@ def analyze_data_with_repl(data_type, data):
116
  portfolio_data = json.loads(data)
117
  holdings = portfolio_data.get("holdings", [])
118
  total_value = sum(holding.get("value", 0) for holding in holdings)
119
-
120
  analysis_text = "πŸ“Š **Advanced Portfolio Analysis**\n\n"
121
-
122
  # Portfolio Overview
123
  analysis_text += "## πŸ’Ό **Portfolio Overview**\n"
124
  analysis_text += f"- **Total Portfolio Value**: ${total_value:,.2f}\n"
125
  analysis_text += f"- **Number of Holdings**: {len(holdings)}\n"
126
-
127
  if holdings:
128
  values = [holding.get("value", 0) for holding in holdings]
129
  avg_holding = sum(values) / len(values)
130
  max_holding = max(values)
131
  min_holding = min(values)
132
-
133
  analysis_text += f"- **Average Holding Size**: ${avg_holding:,.2f}\n"
134
  analysis_text += f"- **Largest Position**: ${max_holding:,.2f}\n"
135
  analysis_text += f"- **Smallest Position**: ${min_holding:,.2f}\n"
136
-
137
  # Detailed Holdings breakdown
138
  analysis_text += "\n## πŸ“ˆ **Holdings Breakdown**\n"
139
- sorted_holdings = sorted(holdings, key=lambda x: x.get("value", 0), reverse=True)
140
-
 
 
141
  for i, holding in enumerate(sorted_holdings, 1):
142
  symbol = holding.get("symbol", "Unknown")
143
  value = holding.get("value", 0)
144
  shares = holding.get("shares", 0)
145
- allocation = holding.get("allocation", (value/total_value*100) if total_value > 0 else 0)
 
 
146
  sector = holding.get("sector", "Unknown")
147
-
148
  # Calculate position concentration risk
149
- risk_level = "🟒 Low" if allocation < 10 else "🟑 Medium" if allocation < 25 else "πŸ”΄ High"
150
-
 
 
 
 
 
 
151
  analysis_text += f"**#{i} {symbol}** - {sector}\n"
152
  analysis_text += f" └─ Value: ${value:,.2f} | Shares: {shares:,.0f} | Weight: {allocation:.1f}%\n"
153
  analysis_text += f" └─ Concentration Risk: {risk_level}\n\n"
154
-
155
  # Sector analysis with advanced metrics
156
  sectors = {}
157
  sector_values = {}
@@ -159,18 +185,20 @@ def analyze_data_with_repl(data_type, data):
159
  sector = holding.get("sector", "Unknown")
160
  allocation = holding.get("allocation", 0)
161
  value = holding.get("value", 0)
162
-
163
  sectors[sector] = sectors.get(sector, 0) + allocation
164
  sector_values[sector] = sector_values.get(sector, 0) + value
165
-
166
  if sectors:
167
  analysis_text += "## 🏭 **Sector Diversification Analysis**\n"
168
- sorted_sectors = sorted(sectors.items(), key=lambda x: x[1], reverse=True)
169
-
 
 
170
  for sector, allocation in sorted_sectors:
171
  bar = "β–ˆ" * min(int(allocation / 2), 30)
172
  value = sector_values.get(sector, 0)
173
-
174
  # Sector concentration assessment
175
  if allocation > 40:
176
  risk_emoji = "πŸ”΄"
@@ -181,13 +209,13 @@ def analyze_data_with_repl(data_type, data):
181
  else:
182
  risk_emoji = "🟒"
183
  risk_text = "Well diversified"
184
-
185
  analysis_text += f"**{sector}**: {allocation:.1f}% (${value:,.2f}) {risk_emoji}\n"
186
  analysis_text += f" └─ {bar} {risk_text}\n\n"
187
-
188
  # Portfolio Health Metrics
189
  analysis_text += "## 🎯 **Portfolio Health Assessment**\n"
190
-
191
  # Diversification Score
192
  num_sectors = len(sectors)
193
  if num_sectors >= 8:
@@ -196,42 +224,46 @@ def analyze_data_with_repl(data_type, data):
196
  diversification = "🟑 Good"
197
  else:
198
  diversification = "πŸ”΄ Poor"
199
-
200
  analysis_text += f"- **Sector Diversification**: {diversification} ({num_sectors} sectors)\n"
201
-
202
  # Concentration Risk
203
  if holdings:
204
- top_3_allocation = sum(sorted([h.get("allocation", 0) for h in holdings], reverse=True)[:3])
 
 
205
  if top_3_allocation > 60:
206
  concentration_risk = "πŸ”΄ High"
207
  elif top_3_allocation > 40:
208
  concentration_risk = "🟑 Medium"
209
  else:
210
  concentration_risk = "🟒 Low"
211
-
212
  analysis_text += f"- **Concentration Risk**: {concentration_risk} (Top 3: {top_3_allocation:.1f}%)\n"
213
-
214
  # Portfolio Recommendations
215
  analysis_text += "\n## πŸ’‘ **Portfolio Optimization Recommendations**\n"
216
-
217
  # Check for over-concentration
218
  for holding in holdings:
219
  allocation = holding.get("allocation", 0)
220
  if allocation > 25:
221
  analysis_text += f"⚠️ **{holding.get('symbol', 'Unknown')}** is over-weighted at {allocation:.1f}% (consider rebalancing)\n"
222
-
223
  # Sector recommendations
224
  for sector, allocation in sectors.items():
225
  if allocation > 40:
226
  analysis_text += f"⚠️ **{sector}** sector over-weighted at {allocation:.1f}% (consider diversification)\n"
227
-
228
  # Diversification suggestions
229
  if num_sectors < 5:
230
  analysis_text += "πŸ’‘ **Consider adding exposure to more sectors for better diversification**\n"
231
-
232
  if len(holdings) < 10:
233
- analysis_text += "πŸ’‘ **Consider adding more holdings to reduce single-stock risk**\n"
234
-
 
 
235
  return analysis_text
236
  except Exception as e:
237
  return f"Error analyzing portfolio data: {str(e)}"
@@ -241,9 +273,9 @@ def analyze_data_with_repl(data_type, data):
241
  stock_data = json.loads(data)
242
  symbol = stock_data.get("symbol", "Unknown")
243
  price_str = stock_data.get("current_price", "0")
244
-
245
  analysis_text = f"πŸ“ˆ **Comprehensive Stock Analysis: {symbol}**\n\n"
246
-
247
  # Company Overview
248
  analysis_text += "## 🏒 **Company Overview**\n"
249
  analysis_text += f"- **Symbol**: {symbol}\n"
@@ -251,18 +283,20 @@ def analyze_data_with_repl(data_type, data):
251
  analysis_text += f"- **Company**: {stock_data.get('company_name', 'N/A')}\n"
252
  analysis_text += f"- **Sector**: {stock_data.get('sector', 'N/A')}\n"
253
  analysis_text += f"- **Industry**: {stock_data.get('industry', 'N/A')}\n"
254
- analysis_text += f"- **Market Cap**: {stock_data.get('market_cap', 'N/A')}\n\n"
255
-
 
 
256
  # Financial Metrics
257
  financials = stock_data.get("financials", {})
258
  if financials:
259
  analysis_text += "## πŸ’Ή **Key Financial Metrics**\n"
260
-
261
  # Valuation metrics
262
  pe_ratio = financials.get("pe_ratio", "N/A")
263
  pb_ratio = financials.get("pb_ratio", "N/A")
264
  ps_ratio = financials.get("ps_ratio", "N/A")
265
-
266
  analysis_text += f"- **P/E Ratio**: {pe_ratio}"
267
  if pe_ratio != "N/A" and isinstance(pe_ratio, (int, float)):
268
  if pe_ratio < 15:
@@ -272,26 +306,28 @@ def analyze_data_with_repl(data_type, data):
272
  else:
273
  analysis_text += " 🟑 (Fairly Valued)"
274
  analysis_text += "\n"
275
-
276
  analysis_text += f"- **P/B Ratio**: {pb_ratio}\n"
277
  analysis_text += f"- **P/S Ratio**: {ps_ratio}\n"
278
-
279
  # Profitability metrics
280
  analysis_text += f"- **ROE**: {financials.get('roe', 'N/A')}\n"
281
  analysis_text += f"- **ROA**: {financials.get('roa', 'N/A')}\n"
282
- analysis_text += f"- **Profit Margin**: {financials.get('profit_margin', 'N/A')}\n"
 
 
283
  analysis_text += f"- **Revenue Growth**: {financials.get('revenue_growth', 'N/A')}\n\n"
284
-
285
  # Performance analysis with trend indicators
286
  performance = stock_data.get("performance", {})
287
  if performance:
288
  analysis_text += "## πŸ“Š **Performance Analysis**\n"
289
-
290
  periods = ["1d", "1w", "1m", "3m", "6m", "1y", "ytd"]
291
  for period in periods:
292
  if period in performance:
293
  return_pct = performance[period]
294
-
295
  # Add trend indicators
296
  if isinstance(return_pct, str) and "%" in return_pct:
297
  try:
@@ -306,19 +342,21 @@ def analyze_data_with_repl(data_type, data):
306
  trend = ""
307
  else:
308
  trend = ""
309
-
310
- analysis_text += f"- **{period.upper()}**: {return_pct} {trend}\n"
 
 
311
  analysis_text += "\n"
312
-
313
  # Advanced Risk Assessment
314
  risk_data = stock_data.get("risk_assessment", {})
315
  if risk_data:
316
  analysis_text += "## ⚠️ **Risk Assessment**\n"
317
-
318
- risk_level = risk_data.get('risk_level', 'N/A')
319
- volatility = risk_data.get('volatility_30d', 'N/A')
320
- beta = risk_data.get('beta', 'N/A')
321
-
322
  # Risk level with emoji indicators
323
  if risk_level.lower() == "low":
324
  risk_emoji = "🟒"
@@ -328,11 +366,11 @@ def analyze_data_with_repl(data_type, data):
328
  risk_emoji = "πŸ”΄"
329
  else:
330
  risk_emoji = ""
331
-
332
  analysis_text += f"- **Risk Level**: {risk_level} {risk_emoji}\n"
333
  analysis_text += f"- **30-Day Volatility**: {volatility}\n"
334
  analysis_text += f"- **Beta**: {beta}"
335
-
336
  if beta != "N/A" and isinstance(beta, (int, float)):
337
  if beta > 1.2:
338
  analysis_text += " (High volatility vs market)"
@@ -341,7 +379,7 @@ def analyze_data_with_repl(data_type, data):
341
  else:
342
  analysis_text += " (Similar to market)"
343
  analysis_text += "\n\n"
344
-
345
  # Technical Analysis
346
  technical = stock_data.get("technical_analysis", {})
347
  if technical:
@@ -349,18 +387,22 @@ def analyze_data_with_repl(data_type, data):
349
  analysis_text += f"- **50-Day MA**: {technical.get('ma_50', 'N/A')}\n"
350
  analysis_text += f"- **200-Day MA**: {technical.get('ma_200', 'N/A')}\n"
351
  analysis_text += f"- **RSI**: {technical.get('rsi', 'N/A')}\n"
352
- analysis_text += f"- **Support Level**: {technical.get('support', 'N/A')}\n"
353
- analysis_text += f"- **Resistance Level**: {technical.get('resistance', 'N/A')}\n\n"
354
-
 
 
 
 
355
  # Investment Recommendation with detailed reasoning
356
  recommendation = stock_data.get("recommendation", {})
357
  if recommendation:
358
- action = recommendation.get('action', 'N/A')
359
- confidence = recommendation.get('confidence', 'N/A')
360
- reasoning = recommendation.get('reasoning', '')
361
-
362
  analysis_text += "## πŸ’‘ **Investment Recommendation**\n"
363
-
364
  # Action with emoji
365
  if action.lower() == "buy":
366
  action_emoji = "🟒"
@@ -370,38 +412,38 @@ def analyze_data_with_repl(data_type, data):
370
  action_emoji = "🟑"
371
  else:
372
  action_emoji = ""
373
-
374
  analysis_text += f"- **Action**: {action} {action_emoji}\n"
375
  analysis_text += f"- **Confidence**: {confidence}\n"
376
-
377
  if reasoning:
378
  analysis_text += f"- **Reasoning**: {reasoning}\n"
379
-
380
  analysis_text += "\n"
381
-
382
  # Additional Investment Considerations
383
  analysis_text += "## 🎯 **Investment Considerations**\n"
384
-
385
  # Dividend info
386
  dividend_yield = stock_data.get("dividend_yield", "N/A")
387
  if dividend_yield != "N/A":
388
  analysis_text += f"- **Dividend Yield**: {dividend_yield}\n"
389
-
390
  # Analyst ratings
391
  analyst_rating = stock_data.get("analyst_rating", "N/A")
392
  if analyst_rating != "N/A":
393
  analysis_text += f"- **Analyst Rating**: {analyst_rating}\n"
394
-
395
  # Price targets
396
  price_target = stock_data.get("price_target", "N/A")
397
  if price_target != "N/A":
398
  analysis_text += f"- **Price Target**: {price_target}\n"
399
-
400
  # ESG score
401
  esg_score = stock_data.get("esg_score", "N/A")
402
  if esg_score != "N/A":
403
  analysis_text += f"- **ESG Score**: {esg_score}\n"
404
-
405
  return analysis_text
406
  except Exception as e:
407
  return f"Error analyzing stock data: {str(e)}"
@@ -412,53 +454,115 @@ def analyze_data_with_repl(data_type, data):
412
  def determine_intended_tool(message):
413
  """Determine which tool the AI intends to use based on the message"""
414
  message_lower = message.lower()
415
-
416
  tool_detection_map = {
417
- "budget_planner": ["budget", "income", "expense", "spending", "allocat", "monthly", "plan", "financial plan", "money", "track", "categoriz", "cost"],
418
- "investment_analyzer": ["stock", "invest", "buy", "sell", "analyze", "AAPL", "GOOGL", "TSLA", "share", "equity"],
419
- "portfolio_analyzer": ["portfolio", "holdings", "allocation", "diversif", "asset", "position"],
420
- "market_trends": ["market", "trend", "news", "sector", "economic", "latest", "current"]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
421
  }
422
-
423
  tool_names = {
424
  "budget_planner": "Budget Planner",
425
  "investment_analyzer": "Investment Analyzer",
426
  "market_trends": "Market Trends Analyzer",
427
  "portfolio_analyzer": "Portfolio Analyzer",
428
  }
429
-
430
  for tool_key, keywords in tool_detection_map.items():
431
  if any(keyword in message_lower for keyword in keywords):
432
  return tool_key, tool_names.get(tool_key, tool_key)
433
-
434
  return None, None
435
 
436
 
437
  def determine_response_type(message):
438
  """Determine if user wants detailed report or short response"""
439
  message_lower = message.lower()
440
-
441
  # Keywords indicating detailed response preference
442
  detailed_keywords = [
443
- "detailed", "detail", "comprehensive", "thorough", "in-depth", "full analysis",
444
- "complete", "report", "breakdown", "explain", "elaborate", "deep dive",
445
- "extensive", "detailed analysis", "full report", "comprehensive report"
 
 
 
 
 
 
 
 
 
 
 
 
 
446
  ]
447
-
448
- # Keywords indicating short response preference
449
  short_keywords = [
450
- "quick", "brief", "short", "summary", "concise", "simple", "fast",
451
- "just tell me", "quickly", "in short", "tldr", "bottom line"
 
 
 
 
 
 
 
 
 
 
452
  ]
453
-
454
  # Check for detailed indicators first
455
  if any(keyword in message_lower for keyword in detailed_keywords):
456
  return "detailed"
457
-
458
  # Check for short indicators
459
  if any(keyword in message_lower for keyword in short_keywords):
460
  return "short"
461
-
462
  # Default to short response
463
  return "short"
464
 
@@ -494,37 +598,46 @@ def process_financial_query(message, history):
494
  # Start timer
495
  start_time = time.time()
496
  init_message_start_index = len(history)
497
-
498
  try:
499
  # Show what tool will be used and processing status
500
- intended_tool_key, intended_tool_name = determine_intended_tool(last_user_message)
 
 
501
  response_type = determine_response_type(last_user_message)
502
-
503
  # Always show status for all tools with expected time estimates
504
  if intended_tool_name:
505
  if intended_tool_key == "market_trends":
506
- status_msg = f"πŸ” Fetching market news & analyzing trends (estimated 20-30 seconds)..."
507
  elif intended_tool_key == "investment_analyzer":
508
- status_msg = f"πŸ“ˆ Analyzing stock data & calculating metrics (estimated 10-15 seconds)..."
509
  elif intended_tool_key == "budget_planner":
510
- status_msg = f"πŸ’° Processing budget analysis (estimated 5-10 seconds)..."
511
  elif intended_tool_key == "portfolio_analyzer":
512
- status_msg = f"πŸ“Š Analyzing portfolio data (estimated 8-12 seconds)..."
513
  else:
514
- status_msg = f"πŸ”„ Using {intended_tool_name} (estimated 5-15 seconds)..."
515
-
 
 
516
  history.append(ChatMessage(role="assistant", content=status_msg))
517
  yield history
518
  else:
519
  # If no tool detected, show generic processing message
520
- history.append(ChatMessage(role="assistant", content=f"🧠 Processing your request (estimated 10-15 seconds)..."))
 
 
 
 
 
521
  yield history
522
-
523
  # Process message through agent
524
- response, tool_used, tool_result, all_tools, all_results = agent.process_message_with_details(
525
- last_user_message, agent_history
526
  )
527
-
528
  # Clear the processing message now that tool is complete
529
  if len(history) > init_message_start_index:
530
  history.pop() # Remove the processing message
@@ -532,25 +645,25 @@ def process_financial_query(message, history):
532
  if all_tools and all_results:
533
  # Remove initialization messages but keep all previous conversation and tool info
534
  history = history[:init_message_start_index]
535
-
536
  tool_names = {
537
  "budget_planner": "Budget Planner",
538
  "investment_analyzer": "Investment Analyzer",
539
  "market_trends": "Market Trends Analyzer",
540
  "portfolio_analyzer": "Portfolio Analyzer",
541
  }
542
-
543
  tool_emojis = {
544
  "Budget Planner": "πŸ’°",
545
  "Investment Analyzer": "πŸ“ˆ",
546
  "Market Trends Analyzer": "πŸ“°",
547
  "Portfolio Analyzer": "πŸ“Š",
548
  }
549
-
550
  # Show results for all tools used
551
  for i, (used_tool, result) in enumerate(zip(all_tools, all_results)):
552
  tool_display_name = tool_names.get(used_tool, used_tool)
553
-
554
  if result:
555
  # Format tool result for display
556
  try:
@@ -578,11 +691,9 @@ def process_financial_query(message, history):
578
  else:
579
  # Truncate non-JSON results
580
  display_result = (
581
- result[:1000] + "..."
582
- if len(result) > 1000
583
- else result
584
  )
585
- except Exception as e:
586
  display_result = (
587
  str(result)[:1000] + "..."
588
  if len(str(result)) > 1000
@@ -590,7 +701,7 @@ def process_financial_query(message, history):
590
  )
591
 
592
  tool_emoji = tool_emojis.get(tool_display_name, "πŸ”§")
593
-
594
  collapsible_content = f"""
595
  <details>
596
  <summary><strong>{tool_emoji} {tool_display_name} Results</strong> - Click to expand</summary>
@@ -599,20 +710,26 @@ def process_financial_query(message, history):
599
 
600
  </details>
601
  """
602
-
603
- history.append(ChatMessage(
604
- role="assistant",
605
- content=collapsible_content,
606
- ))
 
 
607
  yield history
608
 
609
  # Add visualization for all applicable tools
610
  if all_tools and all_results:
611
  for used_tool, result in zip(all_tools, all_results):
612
- if result and used_tool in ["budget_planner", "portfolio_analyzer", "investment_analyzer"]:
 
 
 
 
613
  viz_type = {
614
  "budget_planner": "budget",
615
- "portfolio_analyzer": "portfolio",
616
  "investment_analyzer": "stock",
617
  }.get(used_tool)
618
 
@@ -625,7 +742,7 @@ def process_financial_query(message, history):
625
  "portfolio_analyzer": "Portfolio",
626
  "investment_analyzer": "Stock",
627
  }.get(used_tool, "Data")
628
-
629
  # Create collapsible data analysis output
630
  collapsible_analysis = f"""
631
  <details>
@@ -635,14 +752,16 @@ def process_financial_query(message, history):
635
 
636
  </details>
637
  """
638
-
639
- history.append(ChatMessage(
640
- role="assistant",
641
- content=collapsible_analysis,
642
- ))
 
 
643
  yield history
644
 
645
- except Exception as e:
646
  # Silently continue if analysis fails
647
  pass
648
 
@@ -651,8 +770,10 @@ def process_financial_query(message, history):
651
  # Use real LLM streaming with response type
652
  streaming_content = ""
653
  history.append(ChatMessage(role="assistant", content=""))
654
-
655
- for chunk in agent.stream_response(last_user_message, tool_result, tool_used, response_type):
 
 
656
  streaming_content += chunk
657
  history[-1] = ChatMessage(role="assistant", content=streaming_content)
658
  yield history
@@ -675,10 +796,11 @@ def process_financial_query(message, history):
675
 
676
  # Create the Gradio interface
677
  with gr.Blocks(theme=gr.themes.Base(), title="Financial Advisory Agent") as demo:
678
- gr.HTML("""<center><img src="/gradio_api/file=public/images/fin_logo.png" alt="Fin Logo" style="width: 50px; vertical-align: middle;">
 
679
  <h1 style="text-align: center;">AI Financial Advisory Agent</h1>
680
- Your AI-powered financial advisor for budgeting, investments, portfolio analysis, and market trends.
681
- </center>
682
  """)
683
 
684
  chatbot = gr.Chatbot(
 
37
  if categories and values:
38
  total_expenses = sum(values)
39
  analysis_text = "πŸ’° **Comprehensive Budget Analysis**\n\n"
40
+
41
  # Income vs Expenses Overview
42
  analysis_text += "## πŸ“ˆ **Income vs Expenses Overview**\n"
43
  analysis_text += f"- **Monthly Income**: ${income:,.0f}\n"
44
  analysis_text += f"- **Total Expenses**: ${total_expenses:,.0f}\n"
45
+
46
  if income > 0:
47
  remaining = income - total_expenses
48
  savings_rate = (remaining / income * 100) if income > 0 else 0
49
+
50
  if remaining > 0:
51
  analysis_text += f"- **πŸ’š Surplus**: ${remaining:,.0f}\n"
52
  analysis_text += f"- **πŸ’Ž Savings Rate**: {savings_rate:.1f}%\n"
53
  else:
54
  analysis_text += f"- **πŸ”΄ Deficit**: ${abs(remaining):,.0f}\n"
55
+ analysis_text += (
56
+ f"- **⚠️ Overspending**: {abs(savings_rate):.1f}%\n"
57
+ )
58
+
59
  # Expense Breakdown with Progress Bars
60
  analysis_text += "\n## πŸ’³ **Expense Breakdown**\n"
61
  for i, (category, amount) in enumerate(zip(categories, values)):
62
+ percentage = (
63
+ (amount / total_expenses * 100) if total_expenses > 0 else 0
64
+ )
65
  income_percentage = (amount / income * 100) if income > 0 else 0
66
  bar = "β–ˆ" * min(int(percentage / 3), 30) # Max 30 chars
67
+
68
  analysis_text += f"**{category.title()}**: ${amount:,.0f}\n"
69
  analysis_text += f" └─ {percentage:.1f}% of expenses | {income_percentage:.1f}% of income {bar}\n\n"
70
+
71
  # Financial Health Metrics
72
  analysis_text += "## πŸ“Š **Financial Health Metrics**\n"
73
  avg_expense = total_expenses / len(values)
 
75
  smallest_expense = min(values)
76
  largest_category = categories[values.index(largest_expense)]
77
  smallest_category = categories[values.index(smallest_expense)]
78
+
79
+ analysis_text += (
80
+ f"- **Average Category Expense**: ${avg_expense:,.0f}\n"
81
+ )
82
  analysis_text += f"- **Highest Expense**: {largest_category} (${largest_expense:,.0f})\n"
83
  analysis_text += f"- **Lowest Expense**: {smallest_category} (${smallest_expense:,.0f})\n"
84
+ analysis_text += (
85
+ f"- **Expense Range**: ${largest_expense - smallest_expense:,.0f}\n"
86
+ )
87
+
88
  # Budget Recommendations
89
  analysis_text += "\n## πŸ’‘ **Smart Budget Insights**\n"
90
+
91
  # 50/30/20 Rule Analysis
92
  if income > 0:
93
  needs_target = income * 0.50
94
  wants_target = income * 0.30
95
  savings_target = income * 0.20
96
+
97
+ analysis_text += "**50/30/20 Rule Comparison:**\n"
98
  analysis_text += f"- Needs Target (50%): ${needs_target:,.0f}\n"
99
  analysis_text += f"- Wants Target (30%): ${wants_target:,.0f}\n"
100
  analysis_text += f"- Savings Target (20%): ${savings_target:,.0f}\n"
101
+
102
  if savings_rate >= 20:
103
  analysis_text += "βœ… **Excellent savings rate!**\n"
104
  elif savings_rate >= 10:
105
  analysis_text += "⚠️ **Good savings, aim for 20%**\n"
106
  else:
107
+ analysis_text += (
108
+ "πŸ”΄ **Consider reducing expenses to save more**\n"
109
+ )
110
+
111
  # Category Warnings
112
  for category, amount in zip(categories, values):
113
  if income > 0:
114
+ cat_percentage = amount / income * 100
115
+ if (
116
+ category.lower() in ["rent", "housing"]
117
+ and cat_percentage > 30
118
+ ):
119
  analysis_text += f"⚠️ **Housing costs high**: {cat_percentage:.1f}% (recommend <30%)\n"
120
+ elif (
121
+ category.lower() in ["food", "dining"]
122
+ and cat_percentage > 15
123
+ ):
124
  analysis_text += f"⚠️ **Food costs high**: {cat_percentage:.1f}% (recommend <15%)\n"
125
+
126
  return analysis_text
127
  except Exception as e:
128
  return f"Error analyzing budget data: {str(e)}"
 
132
  portfolio_data = json.loads(data)
133
  holdings = portfolio_data.get("holdings", [])
134
  total_value = sum(holding.get("value", 0) for holding in holdings)
135
+
136
  analysis_text = "πŸ“Š **Advanced Portfolio Analysis**\n\n"
137
+
138
  # Portfolio Overview
139
  analysis_text += "## πŸ’Ό **Portfolio Overview**\n"
140
  analysis_text += f"- **Total Portfolio Value**: ${total_value:,.2f}\n"
141
  analysis_text += f"- **Number of Holdings**: {len(holdings)}\n"
142
+
143
  if holdings:
144
  values = [holding.get("value", 0) for holding in holdings]
145
  avg_holding = sum(values) / len(values)
146
  max_holding = max(values)
147
  min_holding = min(values)
148
+
149
  analysis_text += f"- **Average Holding Size**: ${avg_holding:,.2f}\n"
150
  analysis_text += f"- **Largest Position**: ${max_holding:,.2f}\n"
151
  analysis_text += f"- **Smallest Position**: ${min_holding:,.2f}\n"
152
+
153
  # Detailed Holdings breakdown
154
  analysis_text += "\n## πŸ“ˆ **Holdings Breakdown**\n"
155
+ sorted_holdings = sorted(
156
+ holdings, key=lambda x: x.get("value", 0), reverse=True
157
+ )
158
+
159
  for i, holding in enumerate(sorted_holdings, 1):
160
  symbol = holding.get("symbol", "Unknown")
161
  value = holding.get("value", 0)
162
  shares = holding.get("shares", 0)
163
+ allocation = holding.get(
164
+ "allocation", (value / total_value * 100) if total_value > 0 else 0
165
+ )
166
  sector = holding.get("sector", "Unknown")
167
+
168
  # Calculate position concentration risk
169
+ risk_level = (
170
+ "🟒 Low"
171
+ if allocation < 10
172
+ else "🟑 Medium"
173
+ if allocation < 25
174
+ else "πŸ”΄ High"
175
+ )
176
+
177
  analysis_text += f"**#{i} {symbol}** - {sector}\n"
178
  analysis_text += f" └─ Value: ${value:,.2f} | Shares: {shares:,.0f} | Weight: {allocation:.1f}%\n"
179
  analysis_text += f" └─ Concentration Risk: {risk_level}\n\n"
180
+
181
  # Sector analysis with advanced metrics
182
  sectors = {}
183
  sector_values = {}
 
185
  sector = holding.get("sector", "Unknown")
186
  allocation = holding.get("allocation", 0)
187
  value = holding.get("value", 0)
188
+
189
  sectors[sector] = sectors.get(sector, 0) + allocation
190
  sector_values[sector] = sector_values.get(sector, 0) + value
191
+
192
  if sectors:
193
  analysis_text += "## 🏭 **Sector Diversification Analysis**\n"
194
+ sorted_sectors = sorted(
195
+ sectors.items(), key=lambda x: x[1], reverse=True
196
+ )
197
+
198
  for sector, allocation in sorted_sectors:
199
  bar = "β–ˆ" * min(int(allocation / 2), 30)
200
  value = sector_values.get(sector, 0)
201
+
202
  # Sector concentration assessment
203
  if allocation > 40:
204
  risk_emoji = "πŸ”΄"
 
209
  else:
210
  risk_emoji = "🟒"
211
  risk_text = "Well diversified"
212
+
213
  analysis_text += f"**{sector}**: {allocation:.1f}% (${value:,.2f}) {risk_emoji}\n"
214
  analysis_text += f" └─ {bar} {risk_text}\n\n"
215
+
216
  # Portfolio Health Metrics
217
  analysis_text += "## 🎯 **Portfolio Health Assessment**\n"
218
+
219
  # Diversification Score
220
  num_sectors = len(sectors)
221
  if num_sectors >= 8:
 
224
  diversification = "🟑 Good"
225
  else:
226
  diversification = "πŸ”΄ Poor"
227
+
228
  analysis_text += f"- **Sector Diversification**: {diversification} ({num_sectors} sectors)\n"
229
+
230
  # Concentration Risk
231
  if holdings:
232
+ top_3_allocation = sum(
233
+ sorted([h.get("allocation", 0) for h in holdings], reverse=True)[:3]
234
+ )
235
  if top_3_allocation > 60:
236
  concentration_risk = "πŸ”΄ High"
237
  elif top_3_allocation > 40:
238
  concentration_risk = "🟑 Medium"
239
  else:
240
  concentration_risk = "🟒 Low"
241
+
242
  analysis_text += f"- **Concentration Risk**: {concentration_risk} (Top 3: {top_3_allocation:.1f}%)\n"
243
+
244
  # Portfolio Recommendations
245
  analysis_text += "\n## πŸ’‘ **Portfolio Optimization Recommendations**\n"
246
+
247
  # Check for over-concentration
248
  for holding in holdings:
249
  allocation = holding.get("allocation", 0)
250
  if allocation > 25:
251
  analysis_text += f"⚠️ **{holding.get('symbol', 'Unknown')}** is over-weighted at {allocation:.1f}% (consider rebalancing)\n"
252
+
253
  # Sector recommendations
254
  for sector, allocation in sectors.items():
255
  if allocation > 40:
256
  analysis_text += f"⚠️ **{sector}** sector over-weighted at {allocation:.1f}% (consider diversification)\n"
257
+
258
  # Diversification suggestions
259
  if num_sectors < 5:
260
  analysis_text += "πŸ’‘ **Consider adding exposure to more sectors for better diversification**\n"
261
+
262
  if len(holdings) < 10:
263
+ analysis_text += (
264
+ "πŸ’‘ **Consider adding more holdings to reduce single-stock risk**\n"
265
+ )
266
+
267
  return analysis_text
268
  except Exception as e:
269
  return f"Error analyzing portfolio data: {str(e)}"
 
273
  stock_data = json.loads(data)
274
  symbol = stock_data.get("symbol", "Unknown")
275
  price_str = stock_data.get("current_price", "0")
276
+
277
  analysis_text = f"πŸ“ˆ **Comprehensive Stock Analysis: {symbol}**\n\n"
278
+
279
  # Company Overview
280
  analysis_text += "## 🏒 **Company Overview**\n"
281
  analysis_text += f"- **Symbol**: {symbol}\n"
 
283
  analysis_text += f"- **Company**: {stock_data.get('company_name', 'N/A')}\n"
284
  analysis_text += f"- **Sector**: {stock_data.get('sector', 'N/A')}\n"
285
  analysis_text += f"- **Industry**: {stock_data.get('industry', 'N/A')}\n"
286
+ analysis_text += (
287
+ f"- **Market Cap**: {stock_data.get('market_cap', 'N/A')}\n\n"
288
+ )
289
+
290
  # Financial Metrics
291
  financials = stock_data.get("financials", {})
292
  if financials:
293
  analysis_text += "## πŸ’Ή **Key Financial Metrics**\n"
294
+
295
  # Valuation metrics
296
  pe_ratio = financials.get("pe_ratio", "N/A")
297
  pb_ratio = financials.get("pb_ratio", "N/A")
298
  ps_ratio = financials.get("ps_ratio", "N/A")
299
+
300
  analysis_text += f"- **P/E Ratio**: {pe_ratio}"
301
  if pe_ratio != "N/A" and isinstance(pe_ratio, (int, float)):
302
  if pe_ratio < 15:
 
306
  else:
307
  analysis_text += " 🟑 (Fairly Valued)"
308
  analysis_text += "\n"
309
+
310
  analysis_text += f"- **P/B Ratio**: {pb_ratio}\n"
311
  analysis_text += f"- **P/S Ratio**: {ps_ratio}\n"
312
+
313
  # Profitability metrics
314
  analysis_text += f"- **ROE**: {financials.get('roe', 'N/A')}\n"
315
  analysis_text += f"- **ROA**: {financials.get('roa', 'N/A')}\n"
316
+ analysis_text += (
317
+ f"- **Profit Margin**: {financials.get('profit_margin', 'N/A')}\n"
318
+ )
319
  analysis_text += f"- **Revenue Growth**: {financials.get('revenue_growth', 'N/A')}\n\n"
320
+
321
  # Performance analysis with trend indicators
322
  performance = stock_data.get("performance", {})
323
  if performance:
324
  analysis_text += "## πŸ“Š **Performance Analysis**\n"
325
+
326
  periods = ["1d", "1w", "1m", "3m", "6m", "1y", "ytd"]
327
  for period in periods:
328
  if period in performance:
329
  return_pct = performance[period]
330
+
331
  # Add trend indicators
332
  if isinstance(return_pct, str) and "%" in return_pct:
333
  try:
 
342
  trend = ""
343
  else:
344
  trend = ""
345
+
346
+ analysis_text += (
347
+ f"- **{period.upper()}**: {return_pct} {trend}\n"
348
+ )
349
  analysis_text += "\n"
350
+
351
  # Advanced Risk Assessment
352
  risk_data = stock_data.get("risk_assessment", {})
353
  if risk_data:
354
  analysis_text += "## ⚠️ **Risk Assessment**\n"
355
+
356
+ risk_level = risk_data.get("risk_level", "N/A")
357
+ volatility = risk_data.get("volatility_30d", "N/A")
358
+ beta = risk_data.get("beta", "N/A")
359
+
360
  # Risk level with emoji indicators
361
  if risk_level.lower() == "low":
362
  risk_emoji = "🟒"
 
366
  risk_emoji = "πŸ”΄"
367
  else:
368
  risk_emoji = ""
369
+
370
  analysis_text += f"- **Risk Level**: {risk_level} {risk_emoji}\n"
371
  analysis_text += f"- **30-Day Volatility**: {volatility}\n"
372
  analysis_text += f"- **Beta**: {beta}"
373
+
374
  if beta != "N/A" and isinstance(beta, (int, float)):
375
  if beta > 1.2:
376
  analysis_text += " (High volatility vs market)"
 
379
  else:
380
  analysis_text += " (Similar to market)"
381
  analysis_text += "\n\n"
382
+
383
  # Technical Analysis
384
  technical = stock_data.get("technical_analysis", {})
385
  if technical:
 
387
  analysis_text += f"- **50-Day MA**: {technical.get('ma_50', 'N/A')}\n"
388
  analysis_text += f"- **200-Day MA**: {technical.get('ma_200', 'N/A')}\n"
389
  analysis_text += f"- **RSI**: {technical.get('rsi', 'N/A')}\n"
390
+ analysis_text += (
391
+ f"- **Support Level**: {technical.get('support', 'N/A')}\n"
392
+ )
393
+ analysis_text += (
394
+ f"- **Resistance Level**: {technical.get('resistance', 'N/A')}\n\n"
395
+ )
396
+
397
  # Investment Recommendation with detailed reasoning
398
  recommendation = stock_data.get("recommendation", {})
399
  if recommendation:
400
+ action = recommendation.get("action", "N/A")
401
+ confidence = recommendation.get("confidence", "N/A")
402
+ reasoning = recommendation.get("reasoning", "")
403
+
404
  analysis_text += "## πŸ’‘ **Investment Recommendation**\n"
405
+
406
  # Action with emoji
407
  if action.lower() == "buy":
408
  action_emoji = "🟒"
 
412
  action_emoji = "🟑"
413
  else:
414
  action_emoji = ""
415
+
416
  analysis_text += f"- **Action**: {action} {action_emoji}\n"
417
  analysis_text += f"- **Confidence**: {confidence}\n"
418
+
419
  if reasoning:
420
  analysis_text += f"- **Reasoning**: {reasoning}\n"
421
+
422
  analysis_text += "\n"
423
+
424
  # Additional Investment Considerations
425
  analysis_text += "## 🎯 **Investment Considerations**\n"
426
+
427
  # Dividend info
428
  dividend_yield = stock_data.get("dividend_yield", "N/A")
429
  if dividend_yield != "N/A":
430
  analysis_text += f"- **Dividend Yield**: {dividend_yield}\n"
431
+
432
  # Analyst ratings
433
  analyst_rating = stock_data.get("analyst_rating", "N/A")
434
  if analyst_rating != "N/A":
435
  analysis_text += f"- **Analyst Rating**: {analyst_rating}\n"
436
+
437
  # Price targets
438
  price_target = stock_data.get("price_target", "N/A")
439
  if price_target != "N/A":
440
  analysis_text += f"- **Price Target**: {price_target}\n"
441
+
442
  # ESG score
443
  esg_score = stock_data.get("esg_score", "N/A")
444
  if esg_score != "N/A":
445
  analysis_text += f"- **ESG Score**: {esg_score}\n"
446
+
447
  return analysis_text
448
  except Exception as e:
449
  return f"Error analyzing stock data: {str(e)}"
 
454
  def determine_intended_tool(message):
455
  """Determine which tool the AI intends to use based on the message"""
456
  message_lower = message.lower()
457
+
458
  tool_detection_map = {
459
+ "budget_planner": [
460
+ "budget",
461
+ "income",
462
+ "expense",
463
+ "spending",
464
+ "allocat",
465
+ "monthly",
466
+ "plan",
467
+ "financial plan",
468
+ "money",
469
+ "track",
470
+ "categoriz",
471
+ "cost",
472
+ ],
473
+ "investment_analyzer": [
474
+ "stock",
475
+ "invest",
476
+ "buy",
477
+ "sell",
478
+ "analyze",
479
+ "AAPL",
480
+ "GOOGL",
481
+ "TSLA",
482
+ "share",
483
+ "equity",
484
+ ],
485
+ "portfolio_analyzer": [
486
+ "portfolio",
487
+ "holdings",
488
+ "allocation",
489
+ "diversif",
490
+ "asset",
491
+ "position",
492
+ ],
493
+ "market_trends": [
494
+ "market",
495
+ "trend",
496
+ "news",
497
+ "sector",
498
+ "economic",
499
+ "latest",
500
+ "current",
501
+ ],
502
  }
503
+
504
  tool_names = {
505
  "budget_planner": "Budget Planner",
506
  "investment_analyzer": "Investment Analyzer",
507
  "market_trends": "Market Trends Analyzer",
508
  "portfolio_analyzer": "Portfolio Analyzer",
509
  }
510
+
511
  for tool_key, keywords in tool_detection_map.items():
512
  if any(keyword in message_lower for keyword in keywords):
513
  return tool_key, tool_names.get(tool_key, tool_key)
514
+
515
  return None, None
516
 
517
 
518
  def determine_response_type(message):
519
  """Determine if user wants detailed report or short response"""
520
  message_lower = message.lower()
521
+
522
  # Keywords indicating detailed response preference
523
  detailed_keywords = [
524
+ "detailed",
525
+ "detail",
526
+ "comprehensive",
527
+ "thorough",
528
+ "in-depth",
529
+ "full analysis",
530
+ "complete",
531
+ "report",
532
+ "breakdown",
533
+ "explain",
534
+ "elaborate",
535
+ "deep dive",
536
+ "extensive",
537
+ "detailed analysis",
538
+ "full report",
539
+ "comprehensive report",
540
  ]
541
+
542
+ # Keywords indicating short response preference
543
  short_keywords = [
544
+ "quick",
545
+ "brief",
546
+ "short",
547
+ "summary",
548
+ "concise",
549
+ "simple",
550
+ "fast",
551
+ "just tell me",
552
+ "quickly",
553
+ "in short",
554
+ "tldr",
555
+ "bottom line",
556
  ]
557
+
558
  # Check for detailed indicators first
559
  if any(keyword in message_lower for keyword in detailed_keywords):
560
  return "detailed"
561
+
562
  # Check for short indicators
563
  if any(keyword in message_lower for keyword in short_keywords):
564
  return "short"
565
+
566
  # Default to short response
567
  return "short"
568
 
 
598
  # Start timer
599
  start_time = time.time()
600
  init_message_start_index = len(history)
601
+
602
  try:
603
  # Show what tool will be used and processing status
604
+ intended_tool_key, intended_tool_name = determine_intended_tool(
605
+ last_user_message
606
+ )
607
  response_type = determine_response_type(last_user_message)
608
+
609
  # Always show status for all tools with expected time estimates
610
  if intended_tool_name:
611
  if intended_tool_key == "market_trends":
612
+ status_msg = "πŸ” Fetching market news & analyzing trends (estimated 20-30 seconds)..."
613
  elif intended_tool_key == "investment_analyzer":
614
+ status_msg = "πŸ“ˆ Analyzing stock data & calculating metrics (estimated 10-15 seconds)..."
615
  elif intended_tool_key == "budget_planner":
616
+ status_msg = "πŸ’° Processing budget analysis (estimated 5-10 seconds)..."
617
  elif intended_tool_key == "portfolio_analyzer":
618
+ status_msg = "πŸ“Š Analyzing portfolio data (estimated 8-12 seconds)..."
619
  else:
620
+ status_msg = (
621
+ f"πŸ”„ Using {intended_tool_name} (estimated 5-15 seconds)..."
622
+ )
623
+
624
  history.append(ChatMessage(role="assistant", content=status_msg))
625
  yield history
626
  else:
627
  # If no tool detected, show generic processing message
628
+ history.append(
629
+ ChatMessage(
630
+ role="assistant",
631
+ content="🧠 Processing your request (estimated 10-15 seconds)...",
632
+ )
633
+ )
634
  yield history
635
+
636
  # Process message through agent
637
+ response, tool_used, tool_result, all_tools, all_results = (
638
+ agent.process_message_with_details(last_user_message, agent_history)
639
  )
640
+
641
  # Clear the processing message now that tool is complete
642
  if len(history) > init_message_start_index:
643
  history.pop() # Remove the processing message
 
645
  if all_tools and all_results:
646
  # Remove initialization messages but keep all previous conversation and tool info
647
  history = history[:init_message_start_index]
648
+
649
  tool_names = {
650
  "budget_planner": "Budget Planner",
651
  "investment_analyzer": "Investment Analyzer",
652
  "market_trends": "Market Trends Analyzer",
653
  "portfolio_analyzer": "Portfolio Analyzer",
654
  }
655
+
656
  tool_emojis = {
657
  "Budget Planner": "πŸ’°",
658
  "Investment Analyzer": "πŸ“ˆ",
659
  "Market Trends Analyzer": "πŸ“°",
660
  "Portfolio Analyzer": "πŸ“Š",
661
  }
662
+
663
  # Show results for all tools used
664
  for i, (used_tool, result) in enumerate(zip(all_tools, all_results)):
665
  tool_display_name = tool_names.get(used_tool, used_tool)
666
+
667
  if result:
668
  # Format tool result for display
669
  try:
 
691
  else:
692
  # Truncate non-JSON results
693
  display_result = (
694
+ result[:1000] + "..." if len(result) > 1000 else result
 
 
695
  )
696
+ except Exception:
697
  display_result = (
698
  str(result)[:1000] + "..."
699
  if len(str(result)) > 1000
 
701
  )
702
 
703
  tool_emoji = tool_emojis.get(tool_display_name, "πŸ”§")
704
+
705
  collapsible_content = f"""
706
  <details>
707
  <summary><strong>{tool_emoji} {tool_display_name} Results</strong> - Click to expand</summary>
 
710
 
711
  </details>
712
  """
713
+
714
+ history.append(
715
+ ChatMessage(
716
+ role="assistant",
717
+ content=collapsible_content,
718
+ )
719
+ )
720
  yield history
721
 
722
  # Add visualization for all applicable tools
723
  if all_tools and all_results:
724
  for used_tool, result in zip(all_tools, all_results):
725
+ if result and used_tool in [
726
+ "budget_planner",
727
+ "portfolio_analyzer",
728
+ "investment_analyzer",
729
+ ]:
730
  viz_type = {
731
  "budget_planner": "budget",
732
+ "portfolio_analyzer": "portfolio",
733
  "investment_analyzer": "stock",
734
  }.get(used_tool)
735
 
 
742
  "portfolio_analyzer": "Portfolio",
743
  "investment_analyzer": "Stock",
744
  }.get(used_tool, "Data")
745
+
746
  # Create collapsible data analysis output
747
  collapsible_analysis = f"""
748
  <details>
 
752
 
753
  </details>
754
  """
755
+
756
+ history.append(
757
+ ChatMessage(
758
+ role="assistant",
759
+ content=collapsible_analysis,
760
+ )
761
+ )
762
  yield history
763
 
764
+ except Exception:
765
  # Silently continue if analysis fails
766
  pass
767
 
 
770
  # Use real LLM streaming with response type
771
  streaming_content = ""
772
  history.append(ChatMessage(role="assistant", content=""))
773
+
774
+ for chunk in agent.stream_response(
775
+ last_user_message, tool_result, tool_used, response_type
776
+ ):
777
  streaming_content += chunk
778
  history[-1] = ChatMessage(role="assistant", content=streaming_content)
779
  yield history
 
796
 
797
  # Create the Gradio interface
798
  with gr.Blocks(theme=gr.themes.Base(), title="Financial Advisory Agent") as demo:
799
+ gr.HTML("""<div style="text-align: center;">
800
+ <img src="/gradio_api/file=public/images/fin_logo.png" alt="Fin Logo" style="width: 50px; vertical-align: middle;">
801
  <h1 style="text-align: center;">AI Financial Advisory Agent</h1>
802
+ <p>Your AI-powered financial advisor for budgeting, investments, portfolio analysis, and market trends.</p>
803
+ </div>
804
  """)
805
 
806
  chatbot = gr.Chatbot(