""" 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"""
{icon}

{text}

""", 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)