Spaces:
Sleeping
Sleeping
| import streamlit as st | |
| import os, json, datetime, hashlib | |
| from langchain_community.vectorstores import FAISS | |
| from langchain_community.embeddings import HuggingFaceEmbeddings | |
| from langchain.prompts import PromptTemplate | |
| from langchain.chains import LLMChain | |
| from gtts import gTTS, gTTSError | |
| from pathlib import Path | |
| from dotenv import load_dotenv | |
| from sentence_transformers import SentenceTransformer, util | |
| import altair as alt | |
| import speech_recognition as sr | |
| from transformers import pipeline | |
| import torch | |
| import pickle | |
| import re | |
| import matplotlib.pyplot as plt | |
| import numpy as np | |
| import base64 | |
| from io import BytesIO | |
| from reportlab.lib.pagesizes import letter | |
| from reportlab.platypus import SimpleDocTemplate, Paragraph, Spacer, Image, Flowable | |
| from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle | |
| from reportlab.lib.units import inch | |
| from reportlab.lib import colors | |
| import pandas as pd | |
| import requests | |
| import time | |
| # --- Streamlit Page Configuration (MUST be the first Streamlit command) --- | |
| st.set_page_config(page_title="BridgeYield", page_icon="📈", layout="wide") | |
| # --- Initialize session state (VERY FIRST THING AFTER PAGE CONFIG) --- | |
| if "authenticated" not in st.session_state: | |
| st.session_state.authenticated = False | |
| if "username" not in st.session_state: | |
| st.session_state.username = None | |
| if "is_admin" not in st.session_state: | |
| st.session_state.is_admin = False | |
| if "transcribed_text" not in st.session_state: | |
| st.session_state.transcribed_text = "" | |
| if "uploaded_file_text" not in st.session_state: | |
| st.session_state.uploaded_file_text = "" | |
| if "page" not in st.session_state: | |
| st.session_state.page = "auth" | |
| if 'user_interaction_history' not in st.session_state: | |
| st.session_state.user_interaction_history = [] | |
| if 'emotional_levels_for_graph' not in st.session_state: | |
| st.session_state.emotional_levels_for_graph = [] | |
| if 'last_graph_path' in st.session_state: | |
| st.session_state.last_graph_path = None | |
| if 'api_key_status' not in st.session_state: | |
| st.session_state.api_key_status = "unverified" | |
| if 'last_interaction' not in st.session_state: | |
| st.session_state.last_interaction = {} | |
| if 'last_output' not in st.session_state: | |
| st.session_state.last_output = None | |
| if 'voice_recording_active' not in st.session_state: | |
| st.session_state.voice_recording_active = False | |
| if 'financial_data' not in st.session_state: | |
| st.session_state.financial_data = {} | |
| # --- NEW: Session state for UI control --- | |
| if "show_quick_login_input" not in st.session_state: | |
| st.session_state.show_quick_login_input = False | |
| if "auth_view" not in st.session_state: | |
| st.session_state.auth_view = "Login" | |
| if 'signup_success_message' not in st.session_state: | |
| st.session_state.signup_success_message = None | |
| # Load environment variables | |
| load_dotenv() | |
| GEMINI_API_KEY = os.getenv("GEMINI_API_KEY") | |
| CRISIS_KEYWORDS = ["suicide", "kill myself", "end it all", "worthless", "can't go on", "hurt myself", "self harm", "want to disappear", "no reason to live"] | |
| # Admin configuration | |
| ADMIN_USERNAME = os.getenv("ADMIN_USERNAME") | |
| ADMIN_PASSWORD = os.getenv("ADMIN_PASSWORD") | |
| # Custom Flowable for a horizontal rule | |
| class HRFlowable(Flowable): | |
| def __init__(self, width, thickness, lineCap, color, spaceBefore, spaceAfter): | |
| Flowable.__init__(self) | |
| self.width = width | |
| self.thickness = thickness | |
| self.lineCap = lineCap | |
| self.color = color | |
| self.spaceBefore = spaceBefore | |
| self.spaceAfter = spaceAfter | |
| def wrap(self, availWidth, availHeight): | |
| self.width = availWidth | |
| return (availWidth, self.thickness) | |
| def draw(self): | |
| self.canv.setStrokeColor(self.color) | |
| self.canv.setLineWidth(self.thickness) | |
| self.canv.setLineCap(self.lineCap) | |
| self.canv.line(0, 0, self.width, 0) | |
| # User management functions | |
| def hash_password(password): | |
| """Hash password using SHA-256 with salt""" | |
| salt = "wealthtech_secure_salt_2024" | |
| return hashlib.sha256((password + salt).encode()).hexdigest() | |
| def get_secure_users_path(): | |
| """Get path to users file in a hidden directory""" | |
| secure_dir = ".secure_data" | |
| os.makedirs(secure_dir, exist_ok=True) | |
| return os.path.join(secure_dir, "users_encrypted.json") | |
| def load_users(): | |
| """Load users from secure file""" | |
| users_path = get_secure_users_path() | |
| if os.path.exists(users_path): | |
| try: | |
| with open(users_path, "r") as f: | |
| return json.load(f) | |
| except: | |
| return {} | |
| return {} | |
| def save_users(users): | |
| """Save users to secure file""" | |
| users_path = get_secure_users_path() | |
| with open(users_path, "w") as f: | |
| json.dump(users, f, indent=4) | |
| def create_user_directory(username): | |
| """Create user-specific directory structure""" | |
| user_dir = f"users/{username}" | |
| os.makedirs(user_dir, exist_ok=True) | |
| return user_dir | |
| def get_user_file_path(username, filename): | |
| """Get path to user-specific file""" | |
| user_dir = f"users/{username}" | |
| return os.path.join(user_dir, filename) | |
| def signup(username, password, email): | |
| """Register new user""" | |
| users = load_users() | |
| if username in users: | |
| return False, "Username already exists" | |
| email_pattern = r"^[\w\.-]+@[\w\.-]+\.\w+$" | |
| if not re.match(email_pattern, email): | |
| return False, "Invalid email format" | |
| users[username] = { | |
| "password": hash_password(password), | |
| "email": email, | |
| "created_at": str(datetime.datetime.now()) | |
| } | |
| save_users(users) | |
| create_user_directory(username) | |
| return True, "Account created successfully!" | |
| def login(username, password): | |
| """Authenticate user or admin""" | |
| if username == ADMIN_USERNAME and password == ADMIN_PASSWORD: | |
| return True, "Admin login successful!", True | |
| users = load_users() | |
| if username not in users: | |
| return False, "User not found. Please signup.", False | |
| if users[username]["password"] == hash_password(password): | |
| return True, "Login successful!", False | |
| return False, "Incorrect password", False | |
| # --- NEW: Function for quick login without password --- | |
| def login_by_username_only(username): | |
| """Authenticate user by username only for quick login.""" | |
| if username == ADMIN_USERNAME: | |
| return False, "Admin quick login is not supported.", False | |
| users = load_users() | |
| if username in users: | |
| return True, "Login successful!", False | |
| return False, "User not found. Please sign up or check your username.", False | |
| # Emotion detection | |
| def load_emotion_model(): | |
| return pipeline( | |
| "text-classification", | |
| model="j-hartmann/emotion-english-distilroberta-base", | |
| top_k=1, | |
| device=-1 | |
| ) | |
| def detect_emotion(text): | |
| emotion_pipeline = load_emotion_model() | |
| prediction = emotion_pipeline(text)[0][0] | |
| return prediction['label'].lower(), prediction['score'] | |
| def is_crisis(text): | |
| """Check for crisis keywords""" | |
| return any(phrase in text.lower() for phrase in CRISIS_KEYWORDS) | |
| def build_user_vectorstore(username, quotes): | |
| """Build and save user-specific vectorstore""" | |
| embeddings = HuggingFaceEmbeddings(model_name="sentence-transformers/all-MiniLM-L6-v2") | |
| vectorstore = FAISS.from_texts(quotes, embedding=embeddings) | |
| vectorstore_path = get_user_file_path(username, "vectorstore") | |
| vectorstore.save_local(vectorstore_path) | |
| return vectorstore | |
| def load_user_vectorstore(username): | |
| """Load user-specific vectorstore""" | |
| vectorstore_path = get_user_file_path(username, "vectorstore") | |
| if os.path.exists(vectorstore_path): | |
| embeddings = HuggingFaceEmbeddings(model_name="sentence-transformers/all-MiniLM-L6-v2") | |
| return FAISS.load_local(vectorstore_path, embeddings, allow_dangerous_deserialization=True) | |
| return None | |
| def save_user_journal(username, user_input, emotion, score, response): | |
| """Save journal entry for specific user""" | |
| journal_path = get_user_file_path(username, "journal.json") | |
| entry = { | |
| "date": str(datetime.date.today()), | |
| "timestamp": str(datetime.datetime.now()), | |
| "user_input": user_input, | |
| "emotion": emotion, | |
| "confidence": round(score * 100, 2), | |
| "response": response | |
| } | |
| journal = [] | |
| if os.path.exists(journal_path): | |
| with open(journal_path, "r") as f: | |
| journal = json.load(f) | |
| journal.append(entry) | |
| with open(journal_path, "w") as f: | |
| json.dump(journal, f, indent=4) | |
| def load_user_journal(username): | |
| """Load journal for specific user""" | |
| journal_path = get_user_file_path(username, "journal.json") | |
| if os.path.exists(journal_path): | |
| with open(journal_path, "r") as f: | |
| return json.load(f) | |
| return [] | |
| def generate_audio_file(text, username): | |
| """Generate and save audio response, return the path.""" | |
| try: | |
| # --- NEW: Added try...except block to handle gTTS errors gracefully --- | |
| tts = gTTS(text=text, lang='en') | |
| audio_path = get_user_file_path(username, "response.mp3") | |
| tts.save(audio_path) | |
| return audio_path | |
| except gTTSError as e: | |
| st.warning(f"Failed to generate audio due to a temporary service error. Please try again later. Error: {e}") | |
| return None | |
| except Exception as e: | |
| st.error(f"An unexpected error occurred during audio generation: {e}") | |
| return None | |
| def transcribe_audio_file(uploaded_audio): | |
| """Transcribe uploaded audio file""" | |
| recognizer = sr.Recognizer() | |
| try: | |
| with sr.AudioFile(uploaded_audio) as source: | |
| audio_data = recognizer.record(source) | |
| text = recognizer.recognize_google(audio_data) | |
| return text | |
| except Exception as e: | |
| return f"Error: {str(e)}" | |
| def get_gemini_response(prompt, context): | |
| """Get response from Google's Gemini API""" | |
| if not GEMINI_API_KEY: | |
| st.error("Google Gemini API key not found. Please add it to your .env file.") | |
| return None | |
| api_url = f"https://generativelanguage.googleapis.com/v1beta/models/gemini-1.5-flash-latest:generateContent?key={GEMINI_API_KEY}" | |
| full_prompt = f"""You are BridgeYield, an empathetic financial support AI companion. Your goal is to guide the user in their financial journey. Use the following statements and user context to provide a detailed, bulleted response with actionable financial advice. The response should be concise, yet informative, and avoid a conversational tone.\n\nContext statements:\n{context}\n\nUser's message:\n{prompt}\n\nProvide a detailed response in bullet points with actionable steps for the user.""" | |
| headers = {'Content-Type': 'application/json'} | |
| payload = { | |
| "contents": [ | |
| { | |
| "parts": [ | |
| {"text": full_prompt} | |
| ] | |
| } | |
| ] | |
| } | |
| try: | |
| response = requests.post(api_url, headers=headers, data=json.dumps(payload)) | |
| response.raise_for_status() | |
| result = response.json() | |
| if 'candidates' in result and len(result['candidates']) > 0: | |
| return result['candidates'][0]['content']['parts'][0]['text'] | |
| else: | |
| return "Sorry, I couldn't generate a response. Please try again." | |
| except requests.exceptions.RequestException as e: | |
| st.error(f"API request failed: {e}") | |
| return None | |
| def generate_financial_metrics(user_input_length, emotion_score): | |
| np.random.seed(int(user_input_length * 100 + emotion_score * 100)) | |
| base_liquidity = np.random.randint(15000, 35000) | |
| current_liquidity = int(base_liquidity + (emotion_score - 0.5) * 10000) | |
| simulated_liquidity = int(current_liquidity * np.random.uniform(0.1, 0.5)) | |
| liquid_percent = np.random.uniform(0.3, 0.7) | |
| liquid_percent = round(liquid_percent, 2) | |
| illiquid_percent = round(1 - liquid_percent, 2) | |
| ratio = round(liquid_percent / illiquid_percent, 1) | |
| assets = { | |
| "ETU": int(np.random.randint(25000, 40000) * (1 + emotion_score)), | |
| "Angel Capital": int(np.random.randint(40000, 60000) * (1 + emotion_score)), | |
| "LP | Estate": int(np.random.randint(100000, 150000) * (1 + emotion_score)), | |
| "Brokerage": int(np.random.randint(20000, 35000) * (1 + emotion_score)), | |
| } | |
| actions = [] | |
| # Existing actions | |
| if current_liquidity < 20000: actions.append("Increase cash reserves to >$20K. Create a plan to transfer a set amount each week.") | |
| if ratio < 0.5: actions.append("Rebalance portfolio towards more liquid assets. Consider selling a small portion of illiquid assets to free up cash.") | |
| if simulated_liquidity < 5000: actions.append("Review spending habits to reduce expenses. Categorize your expenses for the last 30 days to identify areas for reduction.") | |
| # New, more detailed actions | |
| total_assets_value = sum(assets.values()) | |
| if assets.get("LP | Estate", 0) > 0.5 * total_assets_value: | |
| actions.append("Your illiquid assets are significant. Review your estate plan and beneficiaries to ensure your family's future is secure.") | |
| if emotion_score < 0.3 and current_liquidity > 25000: | |
| actions.append("Your current sentiment is low, which can impact financial decisions. Focus on small, manageable goals like setting up an automatic savings transfer to regain control.") | |
| if assets.get("Brokerage", 0) > 0.3 * total_assets_value: | |
| actions.append("Your brokerage account is a large part of your portfolio. Consider diversifying into other asset classes to reduce risk exposure.") | |
| # Adding more detailed, smartly suggested actions | |
| if total_assets_value > 300000: | |
| actions.append("Consider consulting with a Certified Financial Planner to create a more comprehensive long-term wealth management strategy.") | |
| if "debt" in st.session_state.user_input_text.lower(): | |
| actions.append("Analyze your current debt and interest rates. Create a priority list to tackle high-interest loans first, potentially using the 'avalanche method'.") | |
| if "saving" in st.session_state.user_input_text.lower(): | |
| actions.append("Set up an automatic contribution to a high-yield savings account or a retirement fund to ensure consistent growth without active management.") | |
| if not actions: actions.append("Continue with your current strategy, it's working well! Your financial health seems stable.") | |
| return { | |
| "liquidity_current": current_liquidity, "liquidity_simulated": simulated_liquidity, | |
| "risk_liquid": int(liquid_percent * 100), "risk_illiquid": int(illiquid_percent * 100), | |
| "risk_ratio": ratio, "assets": assets, "suggested_actions": actions | |
| } | |
| def generate_pdf_report(username, history, last_interaction, graph_image): | |
| buffer = BytesIO() | |
| doc = SimpleDocTemplate(buffer, pagesize=letter, rightMargin=inch, leftMargin=inch, topMargin=inch, bottomMargin=inch) | |
| styles = getSampleStyleSheet() | |
| styles.add(ParagraphStyle(name='Justify', alignment=4)) | |
| story = [] | |
| title = f"BridgeYield Report for {username}" | |
| story.append(Paragraph(title, styles['h1'])) | |
| story.append(Spacer(1, 0.2*inch)) | |
| intro_text = f"This report was generated on {datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')}. It summarizes your recent interactions and sentiment trends with BridgeYield." | |
| story.append(Paragraph(intro_text, styles['Normal'])) | |
| story.append(Spacer(1, 0.2*inch)) | |
| hr = HRFlowable(width="100%", thickness=1, lineCap=1, color=colors.grey, spaceBefore=1, spaceAfter=1) | |
| story.append(hr) | |
| story.append(Spacer(1, 0.2*inch)) | |
| if last_interaction: | |
| story.append(Paragraph("Most Recent Interaction", styles['h2'])) | |
| story.append(Spacer(1, 0.1*inch)) | |
| last_prompt = f"<b>Your Prompt:</b> {last_interaction.get('prompt', 'N/A')}" | |
| last_response = f"<b>BridgeYield's Response:</b> {last_interaction.get('response', 'N/A')}" | |
| story.append(Paragraph(last_prompt, styles['Normal'])) | |
| story.append(Spacer(1, 0.1*inch)) | |
| story.append(Paragraph(last_response, styles['Normal'])) | |
| story.append(Spacer(1, 0.2*inch)) | |
| if graph_image: | |
| story.append(Paragraph("Your Sentiment Trend", styles['h2'])) | |
| graph_image.seek(0) | |
| img = Image(graph_image, width=6*inch, height=3*inch) | |
| story.append(img) | |
| story.append(Spacer(1, 0.2*inch)) | |
| story.append(Paragraph("Full Interaction History", styles['h2'])) | |
| for entry in reversed(history): | |
| story.append(hr) | |
| entry_text = f"<b>Timestamp:</b> {entry['timestamp']}<br/><b>Detected Sentiment:</b> {entry['emotion'].capitalize()} ({entry['level']}%)<br/><b>Your Prompt:</b> {entry['prompt']}" | |
| story.append(Paragraph(entry_text, styles['Normal'])) | |
| story.append(Spacer(1, 0.1*inch)) | |
| story.append(hr) | |
| story.append(Spacer(1, 0.3*inch)) | |
| story.append(Paragraph("Path to Improvement", styles['h2'])) | |
| improvement_text = "Financial well-being is a journey of continuous learning and adaptation. Reviewing your sentiment trends can offer insights into your emotional responses to financial topics. Use this awareness to build a more resilient and confident financial strategy. Stay consistent, keep tracking your goals, and remember that every step forward, no matter how small, is progress." | |
| story.append(Paragraph(improvement_text, styles['Justify'])) | |
| doc.build(story) | |
| buffer.seek(0) | |
| return buffer | |
| def set_background_and_styles(): | |
| st.markdown( | |
| """ | |
| <style> | |
| @import url('https://fonts.googleapis.com/css2?family=Montserrat:wght@300;400;600;700&display=swap'); | |
| @import url('https://fonts.googleapis.com/css2?family=Merriweather:wght@300;400;700&display=swap'); | |
| @keyframes float-animation { | |
| 0% { transform: translateY(0); } | |
| 50% { transform: translateY(-10px); } | |
| 100% { transform: translateY(0); } | |
| } | |
| .stApp { | |
| background: #000000; | |
| font-family: 'Montserrat', sans-serif; | |
| color: #FFFFFF; | |
| } | |
| h1, h2, h3, h4, h5, h6, .stMarkdown, label { | |
| font-family: 'Merriweather', serif; | |
| color: #FFFFFF; | |
| } | |
| .stButton>button { | |
| background: #000000; | |
| color: #FFFFFF; | |
| border-radius: 8px; | |
| border: 1px solid #FFFFFF; | |
| padding: 10px 20px; | |
| font-size: 16px; | |
| font-weight: bold; | |
| transition: all 0.2s ease-in-out; | |
| box-shadow: 0 4px 6px rgba(255, 255, 255, 0.1); | |
| } | |
| .stButton>button:hover { | |
| transform: translateY(-2px); | |
| box-shadow: 0 6px 12px rgba(255, 255, 255, 0.2); | |
| background: #FFFFFF; | |
| color: #000000; | |
| } | |
| [data-testid="stDownloadButton"] button { | |
| background: #000000; | |
| color: #FFFFFF; | |
| border-radius: 8px; | |
| border: 1px solid #FFFFFF; | |
| padding: 10px 20px; | |
| font-size: 16px; | |
| font-weight: bold; | |
| transition: all 0.2s ease-in-out; | |
| box-shadow: 0 4px 6px rgba(255, 255, 255, 0.1); | |
| } | |
| [data-testid="stDownloadButton"] button:hover { | |
| transform: translateY(-2px); | |
| box-shadow: 0 6px 12px rgba(255, 255, 255, 0.2); | |
| background: #FFFFFF; | |
| color: #000000; | |
| } | |
| .stTextInput>div>div>input, .stTextArea>div>div>textarea, .stSelectbox>div>div>div { | |
| border-radius: 8px; | |
| border: 1px solid #FFFFFF; | |
| padding: 10px; | |
| background-color: #1E1E1E; | |
| } | |
| .stTextInput>div>div>input::placeholder, .stTextArea>div>div>textarea::placeholder { | |
| color: #FFFFFF; | |
| opacity: 0.7; | |
| } | |
| /* MODIFIED: Placeholder for signup/signin to be white */ | |
| .auth-content-container .stTextInput input::placeholder { | |
| color: white !important; | |
| } | |
| /* MODIFIED: Placeholder for Download PDF to be black */ | |
| .download-button-placeholder .stDownloadButton button::placeholder { | |
| color: black !important; | |
| } | |
| .auth-content-container { | |
| background-color: rgba(25, 25, 25, 0.8); | |
| backdrop-filter: blur(0px); /* MODIFIED: Removed blur */ | |
| border-radius: 15px; | |
| padding: 30px; | |
| margin: 20px auto; | |
| max-width: 450px; | |
| box-shadow: 0 10px 25px rgba(0, 0, 0, 0.1); | |
| text-align: center; | |
| } | |
| .main-app-content-container { | |
| background-color: rgba(25, 25, 25, 0.8); | |
| backdrop-filter: blur(8px); | |
| border-radius: 15px; | |
| padding: 30px; | |
| margin: 20px auto; | |
| max-width: 800px; | |
| box-shadow: 0 10px 25px rgba(0, 0, 0, 0.1); | |
| color: #FFFFFF; | |
| } | |
| .text-output-container { | |
| background-color: #FFFFFF; | |
| padding: 20px; | |
| border-radius: 10px; | |
| box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); | |
| margin-top: 20px; | |
| } | |
| .text-output-container h4, .text-output-container p, .text-output-container li, .text-output-container div { | |
| color: #000000 !important; | |
| } | |
| .feature-item .icon-img { | |
| height: 60px; | |
| margin-bottom: 10px; | |
| display: block; | |
| margin-left: auto; | |
| margin-right: auto; | |
| animation: float-animation 3s ease-in-out infinite; | |
| filter: brightness(0) invert(1); | |
| } | |
| .app-footer { | |
| position: fixed; | |
| left: 0; | |
| bottom: 0; | |
| width: 100%; | |
| background-color: #111111; | |
| color: #FFFFFF; | |
| text-align: center; | |
| padding: 10px; | |
| font-size: 14px; | |
| border-top: 1px solid #333333; | |
| z-index: 1000; | |
| } | |
| .stApp { | |
| padding-bottom: 50px; | |
| } | |
| header.st-emotion-cache-1gh8zsi, div.st-emotion-cache-z5inrg { display: none !important; } | |
| div.st-emotion-cache-fis6y8 { padding-top: 0 !important; } | |
| .st-emotion-cache-fg4lbf { max-width: 1000px !important; padding-left: 0 !important; padding-right: 0 !important; } | |
| .quick-login-container { | |
| display: flex; | |
| flex-direction: column; | |
| gap: 10px; | |
| padding: 20px; | |
| background-color: rgba(25, 25, 25, 0.9); | |
| border-radius: 15px; | |
| box-shadow: 0 10px 30px rgba(0,0,0,0.5); | |
| margin-top: 10px; | |
| } | |
| </style> | |
| """, | |
| unsafe_allow_html=True | |
| ) | |
| pin_point_statements = { | |
| "Investing": "Analyze your risk tolerance before making any investment.", | |
| "Budgeting": "A solid budget is the foundation of all financial planning.", | |
| "Goals": "Focus on the goal, not the target.", | |
| "Retirement Planning": "Start saving for retirement as early as possible to take advantage of compound interest.", | |
| "Debt Management": "Prioritize high-interest debt and create a clear plan to pay it off.", | |
| "Estate Planning": "Secure your family's future with a will and clear estate plan.", | |
| "Tax Strategy": "Understand your tax obligations and explore legal deductions to optimize your finances.", | |
| "Emergency Fund": "Aim to save at least 3-6 months of living expenses in an easily accessible emergency fund." | |
| } | |
| def show_auth_page(): | |
| set_background_and_styles() | |
| # --- MODIFIED: Top Right Login button to show username input directly --- | |
| _, col2 = st.columns([0.8, 0.2]) | |
| with col2: | |
| if st.button("Quick Login", key="top_right_login_btn"): | |
| st.session_state.show_quick_login_input = not st.session_state.show_quick_login_input | |
| st.session_state.auth_view = "Login" | |
| if st.session_state.show_quick_login_input: | |
| st.markdown("<div class='quick-login-container'>", unsafe_allow_html=True) | |
| st.write("Enter your username to log in quickly.") | |
| quick_login_username = st.text_input("Username", key="quick_login_user", placeholder="Enter your username") | |
| if st.button("Quick Login", key="quick_login_btn", use_container_width=True): | |
| if quick_login_username: | |
| success, message, is_admin = login_by_username_only(quick_login_username) | |
| if success: | |
| st.session_state.authenticated = True | |
| st.session_state.username = quick_login_username | |
| st.session_state.is_admin = is_admin | |
| st.session_state.page = "main_app" | |
| st.session_state.show_quick_login_input = False | |
| st.rerun() | |
| else: | |
| st.error(message) | |
| else: | |
| st.warning("Please enter a username.") | |
| st.markdown("</div>", unsafe_allow_html=True) | |
| st.markdown( | |
| """ | |
| <div style="display: flex; flex-direction: column; align-items: center; text-align: center; margin-top: 20px; margin-bottom: 40px;"> | |
| <img src="https://static.vecteezy.com/system/resources/thumbnails/013/760/485/small/abstract-connection-logo-illustration-in-trendy-and-minimal-style-png.png" style="height: 60px; margin-bottom: 10px; filter: brightness(0) invert(1);"> | |
| <h1 style='font-size: 3.5em; color: #FFFFFF;'>BridgeYield</h1> | |
| <p style='font-size: 1.2em; color: #FFFFFF;'>Unlock Your Financial Potential</p> | |
| </div> | |
| """, unsafe_allow_html=True | |
| ) | |
| col1, col2 = st.columns(2) | |
| with col1: | |
| if st.button("Paid Plan", key="paid_plan_btn", use_container_width=True): st.info("COMING SOON") | |
| with col2: | |
| if st.button("Login Below FOR FREE", key="login_free_btn", use_container_width=True): st.info("SCROLL A BIT DOWN BELOW") | |
| st.markdown(""" | |
| <div class='feature-grid' style="display: grid; grid-template-columns: repeat(2, 1fr); gap: 20px; margin: 30px auto; max-width: 900px;"> | |
| <div class="feature-item" style="text-align: center;"><img src="https://png.pngtree.com/png-vector/20220619/ourmid/pngtree-business-acounting-money-mobile-cash-logo-vector-template-png-image_5225351.png" class="icon-img"><h3>Custom Investment Strategies</h3><p>Tailored guidance based on your financial aspirations.</p></div> | |
| <div class="feature-item" style="text-align: center;"><img src="https://static.vecteezy.com/system/resources/thumbnails/035/861/087/small_2x/simple-people-icon-in-black-and-grey-colors-png.png" class="icon-img"><h3>AI-Powered Market Insights</h3><p>Predictive analysis to help you stay ahead of trends.</p></div> | |
| <div class="feature-item" style="text-align: center;"><img src="https://www.freeiconspng.com/thumbs/growth-icon/growth-icon-17.png" class="icon-img"><h3>Budgeting and Savings Tools</h3><p>Manage your expenses with smart, automated tools.</p></div> | |
| <div class="feature-item" style="text-align: center;"><img src="https://static.vecteezy.com/system/resources/thumbnails/036/105/045/small_2x/artificial-intelligence-ai-processor-chip-icon-symbol-for-graphic-design-logo-web-site-social-media-png.png" class="icon-img"><h3>Financial Goal Tracking</h3><p>Visually track your progress toward long-term goals.</p></div> | |
| </div> | |
| """, unsafe_allow_html=True) | |
| st.markdown("<div class='auth-content-container' id='auth-section'>", unsafe_allow_html=True) | |
| # --- MODIFIED: Replaced st.tabs with state-driven buttons for better control --- | |
| auth_view = st.session_state.get('auth_view', 'Login') | |
| cols = st.columns(2) | |
| with cols[0]: | |
| if st.button("Login", use_container_width=True, type="secondary" if auth_view != "Login" else "primary"): | |
| st.session_state.auth_view = "Login" | |
| st.rerun() | |
| with cols[1]: | |
| if st.button("Sign Up", use_container_width=True, type="secondary" if auth_view != "Sign Up" else "primary"): | |
| st.session_state.auth_view = "Sign Up" | |
| st.rerun() | |
| st.markdown("<hr style='opacity:0.1; margin-top:1rem; margin-bottom:1rem;'>", unsafe_allow_html=True) | |
| if st.session_state.signup_success_message: | |
| st.success(st.session_state.signup_success_message) | |
| st.session_state.signup_success_message = None | |
| if auth_view == "Login": | |
| st.subheader("Login to Your Account") | |
| login_username = st.text_input("Username", key="login_user", placeholder="Enter your username") | |
| login_password = st.text_input("Password", type="password", key="login_pass", placeholder="Enter your password") | |
| if st.button("Login", key="login_btn", use_container_width=True): | |
| if login_username and login_password: | |
| success, message, is_admin = login(login_username, login_password) | |
| if success: | |
| st.session_state.authenticated = True | |
| st.session_state.username = login_username | |
| st.session_state.is_admin = is_admin | |
| st.session_state.page = "main_app" if not is_admin else "admin_dashboard" | |
| st.rerun() | |
| else: st.error(message) | |
| else: st.warning("Please fill in all fields") | |
| elif auth_view == "Sign Up": | |
| st.subheader("Create New Account") | |
| signup_username = st.text_input("Choose Username", key="signup_user", placeholder="Create a username") | |
| signup_email = st.text_input("Email Address", key="signup_email", placeholder="Enter your email") | |
| signup_password = st.text_input("Choose Password", type="password", key="signup_pass", placeholder="Create a strong password") | |
| signup_confirm = st.text_input("Confirm Password", type="password", key="signup_confirm", placeholder="Confirm your password") | |
| if st.button("Create Account", key="signup_btn", use_container_width=True): | |
| if all([signup_username, signup_email, signup_password, signup_confirm]): | |
| if signup_password != signup_confirm: st.error("Passwords don't match!") | |
| elif len(signup_password) < 6: st.error("Password must be at least 6 characters long!") | |
| else: | |
| success, message = signup(signup_username, signup_password, signup_email) | |
| if success: | |
| st.session_state.signup_success_message = message | |
| st.session_state.auth_view = "Login" | |
| st.rerun() | |
| else: st.error(message) | |
| else: st.warning("Please fill in all fields") | |
| st.markdown("</div>", unsafe_allow_html=True) | |
| def show_main_app(): | |
| username = st.session_state.username | |
| set_background_and_styles() | |
| header_cols = st.columns([0.85, 0.15]) | |
| with header_cols[0]: | |
| st.markdown( | |
| """ | |
| <div style="display: flex; align-items: center; height: 50px;"> | |
| <img src="https://static.vecteezy.com/system/resources/thumbnails/013/760/485/small/abstract-connection-logo-illustration-in-trendy-and-minimal-style-png.png" style="height: 30px; margin-right: 10px; filter: brightness(0) invert(1);"> | |
| <div style="font-family: 'Merriweather', serif; font-size: 24px; font-weight: bold; color: #FFFFFF;">BridgeYield</div> | |
| </div> | |
| """, unsafe_allow_html=True | |
| ) | |
| with header_cols[1]: | |
| if st.button("Logout", key="logout_btn_top"): | |
| for key in list(st.session_state.keys()): | |
| if key not in ['auth_view']: del st.session_state[key] | |
| st.session_state.page = "auth" | |
| st.rerun() | |
| st.markdown("<div class='main-app-content-container'>", unsafe_allow_html=True) | |
| st.markdown("<br>", unsafe_allow_html=True) | |
| welcome_col, prompt_col = st.columns([1, 2]) | |
| with welcome_col: | |
| st.title(f"Welcome back, {username}!") | |
| st.markdown("Your personal financial AI companion") | |
| st.markdown("---") | |
| # MODIFIED: Text within selectbox is also white | |
| st.markdown( | |
| """ | |
| <style> | |
| .stSelectbox>div>div>div { | |
| color: #FFFFFF !important; | |
| } | |
| </style> | |
| """, unsafe_allow_html=True | |
| ) | |
| selected_category = st.selectbox("Choose a category to focus on:", list(pin_point_statements.keys())) | |
| with prompt_col: | |
| st.subheader("What's on your mind?") | |
| prompt_and_mic_cols = st.columns([0.1, 1]) | |
| with prompt_and_mic_cols[0]: | |
| if st.button("🎤"): | |
| st.session_state.transcribed_text = "" | |
| st.session_state.uploaded_file_text = "" | |
| with prompt_and_mic_cols[1]: | |
| final_input = st.session_state.uploaded_file_text or st.session_state.transcribed_text | |
| user_input = st.text_area("", value=final_input, height=100, placeholder="Share your thoughts, feelings, or experiences about your financial journey...", key="user_input_text") | |
| with st.expander("Upload a File"): | |
| uploaded_audio = st.file_uploader("Upload a voice message (.wav)", type=["wav"]) | |
| uploaded_text_file = st.file_uploader("Upload a text file (.txt)", type=["txt"]) | |
| col_audio, col_text = st.columns(2) | |
| with col_audio: | |
| if uploaded_audio and st.button("Transcribe Voice", use_container_width=True): | |
| with st.spinner("Transcribing your voice..."): | |
| transcribed = transcribe_audio_file(uploaded_audio) | |
| if transcribed.startswith("Error:"): st.error(transcribed) | |
| else: | |
| st.session_state.transcribed_text = transcribed | |
| st.session_state.uploaded_file_text = "" | |
| st.success("Voice transcribed successfully!") | |
| with col_text: | |
| if uploaded_text_file and st.button("Process Text File", use_container_width=True): | |
| with st.spinner("Processing text file..."): | |
| file_content = uploaded_text_file.read().decode("utf-8") | |
| st.session_state.uploaded_file_text = file_content | |
| st.session_state.transcribed_text = "" | |
| st.success("Text file processed successfully!") | |
| if st.button("Talk to BridgeYield", use_container_width=True, key="talk_btn"): | |
| input_to_process = st.session_state.user_input_text.strip() | |
| if not input_to_process: | |
| st.warning("Please enter something to share, upload a voice message, or a text file.") | |
| else: | |
| with st.spinner("BridgeYield is thinking..."): | |
| emotion, score = detect_emotion(input_to_process) | |
| emotional_level = round(score * 100) | |
| selected_statement = pin_point_statements[selected_category] | |
| response = get_gemini_response(input_to_process, selected_statement) | |
| st.session_state.financial_data = generate_financial_metrics(len(input_to_process), score) | |
| if response: | |
| audio_path = generate_audio_file(response, username) | |
| st.session_state.last_output = {"emotion": emotion, "score": score, "is_crisis": is_crisis(input_to_process), "selected_statement": selected_statement, "response": response, "audio_path": audio_path} | |
| st.session_state.last_interaction = {"prompt": input_to_process, "response": response} | |
| st.session_state.user_interaction_history.append({"prompt": input_to_process, "emotion": emotion, "level": emotional_level, "timestamp": datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")}) | |
| st.session_state.emotional_levels_for_graph.append(emotional_level) | |
| save_user_journal(username, input_to_process, emotion, score, response) | |
| st.session_state.transcribed_text = "" | |
| st.session_state.uploaded_file_text = "" | |
| st.rerun() | |
| if st.session_state.last_output: | |
| st.markdown("---") | |
| st.subheader("Your Latest Analysis") | |
| output = st.session_state.last_output | |
| financials = st.session_state.financial_data | |
| # MODIFIED: Adjusted column widths to prevent overlap | |
| col1, col2, col3 = st.columns([1, 1.8, 1.3]) | |
| with col1: | |
| st.markdown(f"**Sentiment Detected:** <span style='font-size:20px; color: #FFFFFF;'>{output['emotion'].capitalize()}</span>", unsafe_allow_html=True) | |
| st.markdown(f"**Confidence Level:** <span style='font-size:20px; color: #FFFFFF;'>{round(output['score']*100)}%</span>", unsafe_allow_html=True) | |
| if output['is_crisis']: st.error("**Crisis Detected!** Please reach out to a mental health professional immediately.") | |
| st.markdown("### **Liquidity**", unsafe_allow_html=True) | |
| st.markdown(f"**Current**: ${financials['liquidity_current']:,}", unsafe_allow_html=True) | |
| st.markdown(f"**Simulated**: ${financials['liquidity_simulated']:,}", unsafe_allow_html=True) | |
| st.markdown("### **Frequency**", unsafe_allow_html=True) | |
| freq_fig, freq_ax = plt.subplots(figsize=(6, 3)) | |
| freq_ax.bar(np.arange(1, 11), np.random.randint(10, 100, size=10), color='#FFFFFF') | |
| freq_ax.set_facecolor('#1E1E1E'); freq_fig.patch.set_facecolor('#1E1E1E') | |
| freq_ax.tick_params(axis='x', colors='white'); freq_ax.tick_params(axis='y', colors='white') | |
| for spine in ['left', 'bottom']: freq_ax.spines[spine].set_color('white') | |
| for spine in ['top', 'right']: freq_ax.spines[spine].set_color('#1E1E1E') | |
| st.pyplot(freq_fig); plt.close(freq_fig) | |
| st.markdown("### **Risk Profile**", unsafe_allow_html=True) | |
| st.markdown(f"**Liquid**: {financials['risk_liquid']}%", unsafe_allow_html=True) | |
| st.markdown(f"**Illiquid**: {financials['risk_illiquid']}%", unsafe_allow_html=True) | |
| st.markdown(f"**Ratio**: {financials['risk_ratio']}", unsafe_allow_html=True) | |
| with col2: | |
| st.markdown("<div class='text-output-container'>", unsafe_allow_html=True) | |
| st.markdown(f"**Insight for you:** _{output['selected_statement']}_") | |
| st.markdown(f"**BridgeYield's Response:** <div>{output['response']}</div>", unsafe_allow_html=True) | |
| st.markdown("</div>", unsafe_allow_html=True) | |
| st.markdown("---") | |
| st.markdown("### **Suggested Actions**", unsafe_allow_html=True) | |
| for action in financials['suggested_actions']: st.markdown(f" - **{action}**", unsafe_allow_html=True) | |
| with col3: | |
| st.markdown(f"**Audio Response:**", unsafe_allow_html=True) | |
| if output['audio_path']: st.audio(output['audio_path']) | |
| else: st.info("Audio generation failed. Displaying text response instead.") | |
| st.markdown("---"); st.subheader("Your Sentiment Trend") | |
| if st.session_state.emotional_levels_for_graph: | |
| fig, ax = plt.subplots(figsize=(8, 4)) | |
| ax.plot(st.session_state.emotional_levels_for_graph, marker='o', linestyle='-', color='#FFFFFF') | |
| ax.set_title("Interaction Sentiment Level Over Time", color='#FFFFFF'); ax.set_xlabel("Interaction Count", color='#FFFFFF'); ax.set_ylabel("Sentiment Level (%)", color='#FFFFFF') | |
| ax.grid(True, linestyle='--', alpha=0.7, color='#555555'); ax.set_facecolor('#1E1E1E'); fig.patch.set_facecolor('#1E1E1E') | |
| ax.tick_params(axis='x', colors='white'); ax.tick_params(axis='y', colors='white') | |
| for spine in ['left', 'bottom']: ax.spines[spine].set_color('white') | |
| for spine in ['top', 'right']: ax.spines[spine].set_color('#1E1E1E') | |
| st.pyplot(fig) | |
| buf = BytesIO(); fig.savefig(buf, format='png', bbox_inches='tight'); st.session_state.last_graph_path = buf; plt.close(fig) | |
| else: st.info("Interact with BridgeYield to see your sentiment trends here!") | |
| st.markdown("---"); st.markdown("### **Assets**", unsafe_allow_html=True) | |
| for asset, value in financials['assets'].items(): st.markdown(f" - **{asset}:** ${value:,}", unsafe_allow_html=True) | |
| st.markdown("---") | |
| st.header("History of User Interactions") | |
| if st.session_state.user_interaction_history: | |
| all_levels = [entry['level'] for entry in st.session_state.user_interaction_history] | |
| overall_avg_level = np.mean(all_levels) if all_levels else 0 | |
| st.markdown(f"**Overall Sentiment Level Achieved Till Today: {overall_avg_level:.2f}%**") | |
| for i, entry in reversed(list(enumerate(st.session_state.user_interaction_history))): | |
| st.markdown(f"<div class='history-entry'><h4>Interaction {len(st.session_state.user_interaction_history) - i} (on {entry['timestamp']})</h4><p><strong>Your Prompt:</strong> {entry['prompt']}</p><p><strong>Detected Sentiment:</strong> {entry['emotion'].capitalize()}</p><p><strong>Sentiment Level:</strong> {entry['level']}%</p></div>", unsafe_allow_html=True) | |
| else: st.info("Your interaction history will appear here after you talk to BridgeYield.") | |
| st.markdown("---") | |
| if st.session_state.user_interaction_history: | |
| pdf_buffer = generate_pdf_report(st.session_state.username, st.session_state.user_interaction_history, st.session_state.get('last_interaction'), st.session_state.get('last_graph_path')) | |
| st.download_button(label="Download Report as PDF", data=pdf_buffer, file_name=f"BridgeYield_Report_{st.session_state.username}_{datetime.date.today()}.pdf", mime="application/pdf", use_container_width=True) | |
| else: st.info("Complete at least one interaction to download your report.") | |
| st.markdown("</div>", unsafe_allow_html=True) | |
| def show_admin_dashboard(): | |
| if not st.session_state.is_admin: | |
| st.error("Access Denied: You must be an administrator to view this page.") | |
| st.session_state.page = "auth"; st.rerun(); return | |
| set_background_and_styles() | |
| col1, col2, col3 = st.columns([1, 4, 1]) | |
| with col2: | |
| st.markdown( | |
| """ | |
| <div style="display: flex; align-items: center; justify-content: center;"> | |
| <img src="https://static.vecteezy.com/system/resources/thumbnails/013/760/485/small/abstract-connection-logo-illustration-in-trendy-and-minimal-style-png.png" style="height: 30px; margin-right: 10px; filter: brightness(0) invert(1);"> | |
| <div style="font-family: 'Merriweather', serif; font-size: 24px; font-weight: bold; color: #FFFFFF;">BridgeYield</div> | |
| </div> | |
| """, unsafe_allow_html=True) | |
| with col3: | |
| if st.button("Logout", key="logout_btn_top"): | |
| st.session_state.authenticated = False; st.session_state.page = "auth"; st.session_state.username = None; st.rerun() | |
| st.markdown("<div class='main-app-content-container'>", unsafe_allow_html=True) | |
| st.title("Admin Dashboard"); st.markdown("---"); st.subheader("User Management") | |
| users = load_users() | |
| user_data_display = [{"Username": u, "Email": d.get("email", "N/A"), "Created At": d.get("created_at", "N/A")} for u, d in users.items()] | |
| st.dataframe(user_data_display, use_container_width=True) | |
| st.subheader("System Statistics"); st.info(f"Total Registered Users: {len(users)}") | |
| st.markdown("</div>", unsafe_allow_html=True) | |
| def main(): | |
| if not st.session_state.authenticated: | |
| show_auth_page() | |
| elif st.session_state.is_admin: | |
| show_admin_dashboard() | |
| else: | |
| show_main_app() | |
| st.markdown("""<div class="app-footer">SmithaPitch~Project #Scale Your Business</div>""", unsafe_allow_html=True) | |
| if __name__ == "__main__": | |
| main() | |