"""
components.py - A-GRADE VERSION
COMPLETE AND COHERENT with analytics.py, app.py, llm_agent_A_GRADE.py
NEW FEATURES:
1. Proactive AI insights display (auto-generated at top)
2. Scenario comparison mode (side-by-side current vs scenario)
3. What-if mode (interactive sliders for testing)
4. Query detection for chart updates
5. All existing features preserved
PRESERVED FEATURES:
- Dynamic suggestions (never recycle)
- Topic tracking across conversation
- Category-specific visuals
- Profile editing integration
- All existing chart types
"""
from styles import CHART_PALETTES
import streamlit as st
import plotly.graph_objects as go
from analytics import (
analyze_exam_performance,
get_category_insights,
calculate_budget_metrics,
parse_budget_query,
apply_scenario_changes,
compare_scenarios,
generate_proactive_insights
)
from styles import get_custom_css, get_metric_card_html, get_insight_html
from profile_editor import render_profile_editor
def set_seed_chat(prompt_text):
st.session_state.seed_chat = prompt_text
def create_animated_donut_chart(values, labels, colors, title=""):
fig = go.Figure(data=[go.Pie(
labels=labels, values=values, hole=0.6,
marker=dict(colors=colors, line=dict(color='white', width=2)),
textinfo='label+percent', textposition='outside'
)])
fig.update_layout(
title=dict(text=title, font=dict(size=16, color='#1a1a1a', family='Inter')),
showlegend=False, height=300,
margin=dict(t=40, b=20, l=20, r=20),
paper_bgcolor='rgba(0,0,0,0)', plot_bgcolor='rgba(0,0,0,0)'
)
return fig
def create_gauge_chart(value, max_value, title="Progress"):
fig = go.Figure(go.Indicator(
mode="gauge+number", value=value,
title={'text': title, 'font': {'size': 14}},
number={'font': {'size': 24}},
gauge={
'axis': {'range': [None, max_value]},
'bar': {'color': "#bf6dbf"},
'steps': [
{'range': [0, max_value * 0.5], 'color': '#fee2e2'},
{'range': [max_value * 0.5, max_value * 0.75], 'color': '#fef3c7'},
{'range': [max_value * 0.75, max_value], 'color': '#d1fae5'}
]
}
))
fig.update_layout(
height=250,
margin=dict(t=60, b=20, l=20, r=20),
paper_bgcolor='rgba(0,0,0,0)'
)
return fig
# ===== NEW: QUERY DETECTION FOR SCENARIOS =====
def check_query_for_scenario(query, profile):
"""
Check if user query is asking a what-if question.
Returns scenario_profile if detected, None otherwise.
"""
changes = parse_budget_query(query)
if changes:
scenario_profile = apply_scenario_changes(profile, changes)
return scenario_profile
return None
# ===== NEW: PROACTIVE INSIGHTS RENDERING =====
def render_proactive_insights(profile, category):
"""
Render auto-generated proactive insights at top of dashboard.
These appear automatically based on user's data.
"""
insights = generate_proactive_insights(profile, category)
if not insights:
return
st.markdown("### 🎯 AI Insights for You")
# Show top 2 most relevant insights
for insight in insights[:2]:
insight_type = insight.get("type", "insight")
icon = insight.get("icon", "💡")
text = insight.get("text", "")
action = insight.get("action", "")
# Color coding based on insight type
if insight_type == "critical":
bg_color = "#fee2e2"
border_color = "#ef4444"
elif insight_type == "warning":
bg_color = "#fef3c7"
border_color = "#f59e0b"
elif insight_type == "success":
bg_color = "#d1fae5"
border_color = "#10b981"
else: # opportunity
bg_color = "#e0e7ff"
border_color = "#6366f1"
st.markdown(f"""
""", unsafe_allow_html=True)
if action:
if st.button(f"💬 {action}", key=f"insight_action_{hash(action)}", use_container_width=True):
set_seed_chat(action)
st.markdown("---")
# ===== NEW: SCENARIO COMPARISON RENDERING =====
def render_scenario_comparison(current_profile, scenario_profile, category):
"""
Render side-by-side comparison of current vs scenario.
Shows visual diff in charts.
"""
st.markdown("### 📊 Scenario Comparison")
if category == "Finance":
current_data = calculate_budget_metrics(current_profile)
scenario_data = calculate_budget_metrics(scenario_profile)
comparison = compare_scenarios(current_profile, scenario_profile)
col1, col2 = st.columns(2)
with col1:
st.markdown("#### Current Budget")
st.metric("Spent", f"${current_profile['spend']:,}")
st.metric("Remaining", f"${current_data['remaining']:,}")
# Current chart
fig_current = create_animated_donut_chart(
list(current_data["categories"].values()),
list(current_data["categories"].keys()),
['#10b981', '#f59e0b', '#ef4444']
)
st.plotly_chart(fig_current, use_container_width=True, key="scenario_current_chart")
with col2:
st.markdown("#### Scenario Budget")
st.metric("Spent", f"${scenario_profile['spend']:,}",
delta=f"{comparison['differences']['spend']:+,.0f}")
st.metric("Remaining", f"${scenario_data['remaining']:,}",
delta=f"{comparison['differences']['remaining']:+,.0f}")
# Scenario chart
fig_scenario = create_animated_donut_chart(
list(scenario_data["categories"].values()),
list(scenario_data["categories"].keys()),
['#10b981', '#f59e0b', '#ef4444']
)
st.plotly_chart(fig_scenario, use_container_width=True, key="scenario_new_chart")
# Show insights
st.markdown("#### 💡 What This Means")
for insight in comparison['insights']:
st.info(insight)
elif category == "Education":
current_exam = analyze_exam_performance(current_profile)
scenario_exam = analyze_exam_performance(scenario_profile)
col1, col2 = st.columns(2)
with col1:
st.markdown("#### Current Performance")
st.metric("Score", current_profile['recent_exam'])
st.metric("To Goal", f"{current_exam['points_needed']} pts")
with col2:
st.markdown("#### Scenario Performance")
st.metric("Score", scenario_profile['recent_exam'],
delta=f"{scenario_profile['recent_exam'] - current_profile['recent_exam']:+}")
st.metric("To Goal", f"{scenario_exam['points_needed']} pts",
delta=f"{scenario_exam['points_needed'] - current_exam['points_needed']:+}")
# ===== NEW: WHAT-IF MODE =====
def render_whatif_mode(profile, category):
"""
Interactive what-if mode with sliders.
User can adjust values and see instant impact.
"""
st.markdown("### 🔮 What-If Mode")
st.markdown("*Adjust values to see instant impact*")
if category == "Finance":
col1, col2 = st.columns(2)
with col1:
new_spend = st.slider(
"Monthly Spending",
0, 5000,
profile['spend'],
50,
key="whatif_spend"
)
with col2:
new_budget = st.slider(
"Monthly Budget",
1000, 10000,
profile['budget'],
100,
key="whatif_budget"
)
# Create scenario
scenario = profile.copy()
scenario['spend'] = new_spend
scenario['budget'] = new_budget
# Show impact
st.markdown("#### Impact Analysis")
col1, col2, col3 = st.columns(3)
current_remaining = profile['budget'] - profile['spend']
scenario_remaining = new_budget - new_spend
with col1:
st.metric("Current Remaining", f"${current_remaining:,}")
with col2:
st.metric("Scenario Remaining", f"${scenario_remaining:,}",
delta=f"{scenario_remaining - current_remaining:+,}")
with col3:
spend_pct = round((new_spend / new_budget) * 100, 1)
status = "✅ On Track" if spend_pct <= 75 else "⚠️ Over Target"
st.metric("Status", status)
# Visual comparison
render_scenario_comparison(profile, scenario, category)
elif category == "Education":
col1, col2 = st.columns(2)
with col1:
new_score = st.slider(
"New Exam Score",
400, 1600,
profile['recent_exam'],
10,
key="whatif_score"
)
with col2:
study_hours = st.slider(
"Study Hours/Week",
0, 40,
10,
1,
key="whatif_study"
)
# Create scenario
scenario = profile.copy()
scenario['recent_exam'] = new_score
# Show impact
st.markdown("#### Impact Analysis")
col1, col2, col3 = st.columns(3)
current_to_goal = profile['goal'] - profile['recent_exam']
scenario_to_goal = profile['goal'] - new_score
with col1:
st.metric("Current Score", profile['recent_exam'])
with col2:
st.metric("Scenario Score", new_score,
delta=f"{new_score - profile['recent_exam']:+}")
with col3:
st.metric("Points to Goal", scenario_to_goal,
delta=f"{scenario_to_goal - current_to_goal:+}")
# ===== EXISTING: DASHBOARD SUMMARY =====
def render_dashboard_summary(profile, category):
st.markdown("### 📊 Your Dashboard")
render_profile_editor(profile, category)
col1, col2, col3 = st.columns(3)
if category == "Finance":
with col1:
spend_pct = round((profile['spend'] / profile['budget']) * 100, 1)
delta = f"🔴 {spend_pct}% of ${profile['budget']:,}" if spend_pct > 75 else f"🟢 {spend_pct}% of ${profile['budget']:,}"
st.markdown(get_metric_card_html("This Month's Budget Used", f"${profile['spend']:,}", delta, "💰"), unsafe_allow_html=True)
with col2:
remaining = profile['budget'] - profile['spend']
st.markdown(get_metric_card_html("Remaining Budget", f"${remaining:,}", f"💡 {round((remaining/profile['budget'])*100, 1)}% left", "💳"), unsafe_allow_html=True)
with col3:
st.markdown(get_metric_card_html("Savings Goal", "1,200 dollars", "✨ +300 dollars from last month", "🎯"), unsafe_allow_html=True)
elif category == "Education":
with col1:
exam_data = analyze_exam_performance(profile)
st.markdown(get_metric_card_html("Recent Exam Score", f"{profile['recent_exam']}", f"🎯 Goal: {profile['goal']} ({exam_data['points_needed']} needed)", "📚"), unsafe_allow_html=True)
with col2:
progress_pct = round((profile['recent_exam'] / profile['goal']) * 100, 1)
st.markdown(get_metric_card_html("Goal Progress", f"{progress_pct}%", f"📈 {profile['recent_exam']}/{profile['goal']}", "🎯"), unsafe_allow_html=True)
with col3:
st.markdown(get_metric_card_html("Study Time Today", "-12%", "⚠️ Below average", "⏰"), unsafe_allow_html=True)
# ===== ENHANCED: LEFT PANEL WITH PROACTIVE INSIGHTS =====
def render_left_panel(state, category):
profile = state.profile
# NEW: Show proactive AI insights at top
render_proactive_insights(profile, category)
render_dashboard_summary(profile, category)
st.markdown("---")
# Existing visuals
if category == "Education":
render_education_visuals(profile)
elif category == "Finance":
render_finance_visuals(profile)
st.markdown("---")
# NEW: What-If Mode button
if category in ["Finance", "Education"]:
with st.expander("🔮 Try What-If Mode", expanded=False):
render_whatif_mode(profile, category)
st.markdown("---")
# Existing smart insights (kept for compatibility)
st.markdown("### 💡 Smart Insights")
insights = get_category_insights(category, profile)
for i, insight in enumerate(insights[:3]):
card_type = "warning" if "⚠️" in insight else "insight"
st.markdown(get_insight_html(insight, card_type), unsafe_allow_html=True)
if st.button(f"Ask about this", key=f"insight_{category}_{i}", use_container_width=True):
set_seed_chat(f"Tell me more about: {insight}")
# ===== EXISTING: EDUCATION VISUALS =====
def render_education_visuals(profile):
exam_data = analyze_exam_performance(profile)
col1, col2 = st.columns(2)
with col1:
st.markdown("#### 📈 Score Breakdown")
fig = create_animated_donut_chart(
list(exam_data["score_breakdown"].values()),
list(exam_data["score_breakdown"].keys()),
CHART_PALETTES['education']
)
st.plotly_chart(fig, use_container_width=True, key="main_score_chart")
with col2:
st.markdown("#### 🎯 Goal Progress")
fig = create_gauge_chart(profile['recent_exam'], profile['goal'],
f"{profile['recent_exam']}/{profile['goal']}")
st.plotly_chart(fig, use_container_width=True, key="main_goal_gauge")
# ===== EXISTING: FINANCE VISUALS =====
def render_finance_visuals(profile):
budget_data = calculate_budget_metrics(profile)
col1, col2 = st.columns(2)
with col1:
st.markdown("#### 💸 Spending Breakdown")
fig = create_animated_donut_chart(
list(budget_data["categories"].values()),
list(budget_data["categories"].keys()),
CHART_PALETTES['finance']
)
st.plotly_chart(fig, use_container_width=True, key="main_spending_chart")
with col2:
st.markdown("#### 🎯 Budget Status")
fig = create_gauge_chart(profile['spend'], profile['budget'],
f"${profile['spend']}/${profile['budget']}")
st.plotly_chart(fig, use_container_width=True, key="main_budget_gauge")
# ===== EXISTING: TOPIC EXTRACTION (unchanged) =====
def extract_topics_from_history(chat_history):
"""Extract ALL topics AND subtopics ever discussed"""
all_user_text = " ".join([m['content'].lower() for m in chat_history if m['role'] == 'user'])
topics_discussed = set()
# Finance topics
if any(w in all_user_text for w in ['save', 'savings', 'emergency', 'automate']):
topics_discussed.add('savings')
if any(w in all_user_text for w in ['invest', 'etf', 'stock', 'crypto', 'bitcoin', 'trading', 'options']):
topics_discussed.add('investing')
if any(w in all_user_text for w in ['budget', 'spend', 'expense', 'cost', 'cut', '50/30/20']):
topics_discussed.add('budgeting')
if any(w in all_user_text for w in ['debt', 'loan', 'credit', 'repay']):
topics_discussed.add('debt')
# Finance subtopics (deeper tracking)
if any(w in all_user_text for w in ['etf', 'exchange traded']):
topics_discussed.add('etf_basics')
if any(w in all_user_text for w in ['index fund', 'index', 'mutual fund']):
topics_discussed.add('index_funds')
if any(w in all_user_text for w in ['bitcoin', 'crypto', 'cryptocurrency']):
topics_discussed.add('crypto')
if any(w in all_user_text for w in ['best etf', 'which etf', 'etf for']):
topics_discussed.add('etf_selection')
if any(w in all_user_text for w in ['long-term', 'long term', 'strategy']):
topics_discussed.add('long_term')
if any(w in all_user_text for w in ['dividend', 'yield', 'income']):
topics_discussed.add('dividends')
if any(w in all_user_text for w in ['rebalance', 'allocation', 'portfolio']):
topics_discussed.add('portfolio')
# Savings subtopics
if any(w in all_user_text for w in ['high-yield', 'high yield', 'best savings']):
topics_discussed.add('high_yield')
if any(w in all_user_text for w in ['automate', 'automatic', 'auto-save']):
topics_discussed.add('automate_savings')
if any(w in all_user_text for w in ['emergency fund', 'emergency']):
topics_discussed.add('emergency_fund')
# Budgeting subtopics
if any(w in all_user_text for w in ['50/30/20', 'budget rule']):
topics_discussed.add('budget_rules')
if any(w in all_user_text for w in ['track spending', 'budget app']):
topics_discussed.add('tracking_tools')
if any(w in all_user_text for w in ['cut expenses', 'reduce spending']):
topics_discussed.add('cutting_expenses')
# Education topics
if any(w in all_user_text for w in ['study', 'exam', 'test', 'score', 'improve', 'focus']):
topics_discussed.add('studying')
if any(w in all_user_text for w in ['practice', 'question', 'weak', 'subject']):
topics_discussed.add('practice')
return topics_discussed
def detect_current_topic(user_message_text):
"""Detect which topic the user is asking about RIGHT NOW"""
text = user_message_text.lower()
# Finance
if any(w in text for w in ['save', 'savings', 'automate', 'emergency fund']):
return 'savings'
if any(w in text for w in ['invest', 'etf', 'stock', 'bitcoin', 'crypto', 'options', 'trading']):
return 'investing'
if any(w in text for w in ['budget', 'spend', 'expense', '50/30/20', 'cut', 'stick', 'track']):
return 'budgeting'
if any(w in text for w in ['debt', 'loan', 'credit', 'repay']):
return 'debt'
# Education
if any(w in text for w in ['study', 'focus', 'rush', 'manage time']):
return 'studying'
if any(w in text for w in ['exam', 'test', 'score', 'weak', 'improve']):
return 'exams'
if any(w in text for w in ['practice', 'question', 'quiz']):
return 'practice'
return None
# ===== EXISTING: FINANCE SUGGESTIONS (unchanged) =====
def get_suggestions_for_finance(chat_history, current_topic, topics_discussed):
"""FINANCE SUGGESTIONS - Progressive deepening"""
if current_topic == 'savings':
if 'savings' not in topics_discussed:
return [
("📈 High-yield accounts?", "What are the best high-yield savings accounts for Gen Z?"),
("🎯 Automate savings", "How can I automate my savings?"),
("💡 Cut biggest expenses", "What are my biggest expenses to cut?")
]
else:
return [
("💳 Which account best for me?", "Which high-yield savings account would be best for my situation?"),
("🔄 Emergency fund sizing", "How much should I keep in an emergency fund?"),
("📊 Track savings progress", "How do I track and measure my automated savings?")
]
elif current_topic == 'investing':
# Level 1: Complete beginner
if 'investing' not in topics_discussed:
return [
("📊 Start investing small", "How do I start investing with little money?"),
("🔍 ETFs vs stocks", "Should I invest in ETFs or individual stocks?"),
("📈 Best beginner ETFs", "What are beginner-friendly investment tips for Gen Z?")
]
# Level 2: Asked about ETFs/basics, now go deeper
elif 'etf_selection' not in topics_discussed and 'index_funds' not in topics_discussed:
return [
("💎 Best ETFs for my age", "What are the best ETFs for my age and budget?"),
("📈 Index funds?", "What's the difference between ETFs and index funds?"),
("🔐 Safe investments", "What are the safest investment options for beginners?")
]
# Level 3: Asked about specific ETFs/index funds, now strategy
elif 'long_term' not in topics_discussed and 'portfolio' not in topics_discussed:
return [
("⏰ Long-term strategy", "What's a good long-term investment strategy?"),
("📊 Portfolio allocation", "How should I allocate my investment portfolio?"),
("💰 Dividend investing", "Should I focus on dividend-paying investments?")
]
# Level 4: Advanced investor questions
else:
return [
("🔄 Rebalancing", "When and how should I rebalance my portfolio?"),
("🌍 International ETFs", "Should I invest in international ETFs?"),
("📈 Growth vs value", "What's the difference between growth and value investing?")
]
elif current_topic == 'budgeting':
if 'budgeting' not in topics_discussed:
return [
("✂️ Cut expenses smartly", "Where can I cut expenses without sacrificing quality?"),
("📊 Track spending", "Best way to track my spending automatically?"),
("🎯 Create monthly budget", "Help me create a realistic monthly budget")
]
else:
return [
("50/30/20 rule?", "Should I use the 50/30/20 budgeting rule?"),
("💳 Best budget app", "What budget tracking app should I use?"),
("🎯 Stick to budget", "How do I actually stick to my budget?")
]
elif current_topic == 'debt':
if 'debt' not in topics_discussed:
return [
("💳 Pay off faster", "Best strategy to pay off debt faster?"),
("🔍 Debt vs savings", "Should I focus on debt or savings first?"),
("📉 Lower interest", "How to reduce credit card interest rates?")
]
else:
return [
("📊 Snowball vs avalanche", "Is snowball or avalanche better for me?"),
("⏰ Payoff timeline", "How long would it take to pay off my debt?"),
("🆘 Negotiate rates", "Can I negotiate lower interest rates?")
]
else:
return [
("💰 Save more?", "How can I save more money this month?"),
("📊 Break down spending", "Break down my spending and show where to cut costs"),
("📈 Investment tips", "What are beginner-friendly investment tips for Gen Z?")
]
# ===== EXISTING: EDUCATION SUGGESTIONS (unchanged) =====
def get_suggestions_for_education(chat_history, current_topic, topics_discussed):
"""EDUCATION SUGGESTIONS - Progressive deepening"""
if current_topic == 'studying':
if 'studying' not in topics_discussed:
return [
("⏰ Study schedule", "Help me create an effective study schedule"),
("🎯 Stay focused", "Best techniques to stay focused while studying?"),
("📝 Take notes better", "What's the most effective way to take notes?")
]
else:
return [
("🚀 Pomodoro technique", "How does the Pomodoro technique work for studying?"),
("📍 Study environment", "How do I create a good study environment?"),
("⚡ Study groups", "How can I make study groups effective?")
]
elif current_topic == 'exams':
if 'exams' not in topics_discussed:
return [
("📊 Boost scores fast", "What's the fastest way to improve exam scores?"),
("✏️ Test strategies", "Give me strategies for better test performance"),
("🎯 Fix weak subjects", "How do I tackle my weakest subjects?")
]
else:
return [
("🧠 Active recall", "How does active recall help with exam prep?"),
("📅 Study calendar", "How should I plan my study calendar for exams?"),
("😰 Test anxiety", "How do I handle test anxiety?")
]
elif current_topic == 'practice':
if 'practice' not in topics_discussed:
return [
("✏️ Practice questions", "Give me practice questions for my weakest subject"),
("🎯 Target weak areas", "How do I focus practice on weak areas?"),
("📊 Quiz myself", "What's the best way to quiz myself?")
]
else:
return [
("🔄 Spaced repetition", "How does spaced repetition work?"),
("📈 Track progress", "How do I measure improvement from practice?"),
("💡 Learn from mistakes", "How should I analyze my practice mistakes?")
]
else:
return [
("📚 Analyze exam", "Analyze my last exam and tell me where to improve"),
("✏️ Practice questions", "Give me practice questions for my weakest subject"),
("🎯 Improve scores", "What's the fastest way to improve my exam scores?")
]
# ===== EXISTING: DYNAMIC SUGGESTIONS (unchanged) =====
def get_dynamic_suggestions_v4(chat_history, category):
"""PRODUCTION VERSION - Never recycles suggestions"""
if len(chat_history) <= 1:
if category == "Finance":
return [
("💰 Save more?", "How can I save more money this month?"),
("📊 Break down spending", "Break down my spending and show where to cut costs"),
("📈 Investment tips", "What are beginner-friendly investment tips for Gen Z?")
]
elif category == "Education":
return [
("📚 Analyze exam", "Analyze my last exam and tell me where to improve"),
("✏️ Practice questions", "Give me practice questions for my weakest subject"),
("🎯 Improve scores", "What's the fastest way to improve my exam scores?")
]
topics_discussed = extract_topics_from_history(chat_history)
latest_user_msg = ""
for msg in reversed(chat_history):
if msg['role'] == 'user':
latest_user_msg = msg['content']
break
current_topic = detect_current_topic(latest_user_msg)
if category == "Finance":
return get_suggestions_for_finance(chat_history, current_topic, topics_discussed)
elif category == "Education":
return get_suggestions_for_education(chat_history, current_topic, topics_discussed)
return [("💭 Ask something", "What's on your mind?")]
# ===== EXISTING: CHAT PANEL (unchanged) =====
def render_chat_panel(chat_history_key, category):
"""Chat panel with PRODUCTION-READY suggestion engine"""
icon_map = {"Finance": "💰", "Education": "📚", "Family": "👨👩👧", "Friends": "👥", "Weekend/Vacation": "🏖"}
category_icon = icon_map.get(category, "🎯")
col1, col2 = st.columns([4, 1])
with col1:
st.markdown(f"### 💬 Chat with Aqua about {category_icon} {category}")
with col2:
if st.button("🔄 Reset", key=f"reset_chat_{category}", use_container_width=True, type="secondary"):
st.session_state[chat_history_key] = []
st.rerun()
if not st.session_state[chat_history_key]:
welcome_msg = f"Hey! 👋 I'm Aqua, your {category.lower()} mentor. What's on your mind?"
st.session_state[chat_history_key].append({"role": "assistant", "content": welcome_msg})
# Display all messages
for msg in st.session_state[chat_history_key]:
with st.chat_message(msg["role"], avatar="🌊" if msg["role"] == "assistant" else "👤"):
st.markdown(msg["content"])
st.markdown("---")
st.markdown("### ⚡ Quick Actions")
suggestions = get_dynamic_suggestions_v4(st.session_state[chat_history_key], category)
if suggestions:
cols = st.columns(min(len(suggestions), 3))
for i, (display_text, full_prompt) in enumerate(suggestions):
col_idx = i % len(cols)
button_key = f"quick_{category}_{i}_{len(st.session_state[chat_history_key])}_prod"
if cols[col_idx].button(display_text, key=button_key, use_container_width=True):
set_seed_chat(full_prompt)