Spaces:
Sleeping
Sleeping
| import streamlit as st | |
| import requests | |
| import pandas as pd | |
| import plotly.express as px | |
| from datetime import datetime | |
| # ---------- BACKEND & HIDDEN USER ---------- | |
| BACKEND_URL = "http://127.0.0.1:8000" | |
| # backend requires user_id; keep it hidden (not editable in UI) | |
| USER_ID = "user1" | |
| # ---------- PAGE CONFIG ---------- | |
| st.set_page_config(page_title="FinSync AI", layout="wide", initial_sidebar_state="expanded") | |
| # ---------- THEME COLORS ---------- | |
| PRIMARY_COLOR = "#6d28d9" # purple | |
| ACCENT_COLOR = "#10b981" # green | |
| BG_COLOR = "#f5f5f5" # app background | |
| CARD_COLOR = "#ffffff" # cards / sidebar | |
| TEXT_COLOR = "#111827" # main text | |
| # ---------- SIDEBAR (TITLE + NAV) ---------- | |
| st.sidebar.markdown( | |
| f""" | |
| <div style="text-align:center; padding-top:6px; padding-bottom:6px;"> | |
| <h2 style="color:{PRIMARY_COLOR}; margin:0 0 6px 0;">π° FinSync AI</h2> | |
| <div style="color:{TEXT_COLOR}; font-size:13px; margin-bottom:6px;"> | |
| Your AI that tracks, budgets & reminds you automatically. | |
| </div> | |
| </div> | |
| <hr style="border:1px solid {PRIMARY_COLOR}; margin-bottom:8px;"> | |
| """, | |
| unsafe_allow_html=True, | |
| ) | |
| menu = ["Dashboard", "Expenses", "Budget", "Reminders", "AI Chat"] | |
| choice = st.sidebar.radio("Navigation", menu) | |
| st.sidebar.markdown( | |
| f"<hr style='border:1px solid {PRIMARY_COLOR};'>" | |
| f"<p style='color:{TEXT_COLOR}; font-size:12px; text-align:center; margin-top:8px;'>Hackathon Demo | Powered by Groq LLM</p>", | |
| unsafe_allow_html=True, | |
| ) | |
| # ---------- CRITICAL CSS FIXES ---------- | |
| # This CSS uses stable selectors (html, body, data-testid, header, section[data-testid="stSidebar"]) | |
| # to ensure background/text color consistency and avoid the "black top / black sidebar" issues. | |
| st.markdown( | |
| f""" | |
| <style> | |
| /* Root backgrounds (fix black areas and collapsed sidebar top bar) */ | |
| html, body, [data-testid="stAppViewContainer"], .block-container, header {{ | |
| background-color: {BG_COLOR} !important; | |
| color: {TEXT_COLOR} !important; | |
| }} | |
| /* Sidebar box */ | |
| section[data-testid="stSidebar"] {{ | |
| background-color: {CARD_COLOR} !important; | |
| color: {TEXT_COLOR} !important; | |
| padding-top: 12px; | |
| }} | |
| /* Toolbar (top-right icons area) */ | |
| [data-testid="stToolbar"] {{ | |
| background-color: {BG_COLOR} !important; | |
| }} | |
| /* Inputs / textareas / selects - readable */ | |
| input, textarea, select, .stTextInput>div>div>input, .stTextArea>div>div>textarea {{ | |
| background-color: {CARD_COLOR} !important; | |
| color: {TEXT_COLOR} !important; | |
| border: 1px solid #e6e6e6 !important; | |
| border-radius: 6px !important; | |
| padding: 6px !important; | |
| }} | |
| /* Selectbox inner text */ | |
| .stSelectbox>div>div>div>span, .stSelectbox>div>div>div {{ | |
| color: {TEXT_COLOR} !important; | |
| }} | |
| /* Buttons */ | |
| .stButton>button {{ | |
| background-color: {PRIMARY_COLOR} !important; | |
| color: white !important; | |
| border-radius: 8px !important; | |
| padding: 6px 14px !important; | |
| font-weight: 600 !important; | |
| }} | |
| .stButton>button:hover {{ | |
| background-color: #5b21b6 !important; | |
| }} | |
| /* Dataframes and tables */ | |
| .stDataFrame, .stTable, .element-container .stDataFrame {{ | |
| background-color: {CARD_COLOR} !important; | |
| color: {TEXT_COLOR} !important; | |
| border-radius:8px !important; | |
| padding:6px !important; | |
| }} | |
| /* Metrics */ | |
| [data-testid="stMetricValue"], [data-testid="stMetricLabel"] {{ | |
| color: {TEXT_COLOR} !important; | |
| }} | |
| /* Headings / markdown */ | |
| .css-1v0mbdj h1, .css-1v0mbdj h2, .css-1v0mbdj h3, h1, h2, h3 {{ | |
| color: {TEXT_COLOR} !important; | |
| }} | |
| /* Small responsive tweak: ensure block-container uses the app background */ | |
| .block-container {{ | |
| background-color: {BG_COLOR} !important; | |
| }} | |
| </style> | |
| """, | |
| unsafe_allow_html=True, | |
| ) | |
| # ---------- HELPERS: API CALLS (include user_id param) ---------- | |
| def get_expenses(): | |
| try: | |
| r = requests.get(f"{BACKEND_URL}/expenses", params={"user_id": USER_ID}, timeout=5) | |
| if r.ok: | |
| return r.json().get("expenses", []) | |
| except Exception: | |
| pass | |
| return [] | |
| def add_expense(payload): | |
| try: | |
| payload["user_id"] = USER_ID | |
| r = requests.post(f"{BACKEND_URL}/add_expense", json=payload, timeout=5) | |
| return r.ok | |
| except Exception: | |
| return False | |
| def get_budget(): | |
| try: | |
| r = requests.get(f"{BACKEND_URL}/budget", params={"user_id": USER_ID}, timeout=6) | |
| if r.ok: | |
| return r.json().get("budget", {}) | |
| except Exception: | |
| pass | |
| return {} | |
| def get_reminders(): | |
| try: | |
| r = requests.get(f"{BACKEND_URL}/reminders", params={"user_id": USER_ID}, timeout=5) | |
| if r.ok: | |
| return r.json().get("reminders", []) | |
| except Exception: | |
| pass | |
| return [] | |
| def add_reminder(payload): | |
| try: | |
| payload["user_id"] = USER_ID | |
| r = requests.post(f"{BACKEND_URL}/reminder", json=payload, timeout=5) | |
| return r.ok | |
| except Exception: | |
| return False | |
| def analyze_text(payload): | |
| try: | |
| payload["user_id"] = USER_ID | |
| r = requests.post(f"{BACKEND_URL}/analyze", json=payload, timeout=10) | |
| if r.ok: | |
| return r.json().get("result", {}) | |
| except Exception: | |
| pass | |
| return {"intent":"error","message":"AI request failed or backend unreachable."} | |
| # ---------- UI: DASHBOARD ---------- | |
| if choice == "Dashboard": | |
| st.header("π Dashboard Overview") | |
| expenses = get_expenses() | |
| if expenses: | |
| df = pd.DataFrame(expenses) | |
| # normalize types and dates | |
| df["amount"] = pd.to_numeric(df["amount"], errors="coerce").fillna(0.0) | |
| df["date"] = pd.to_datetime(df["date"], errors="coerce") | |
| total = df["amount"].sum() | |
| left, right = st.columns([1, 2], gap="large") | |
| with left: | |
| st.metric("π΅ Total Expenses", f"${total:.2f}") | |
| # show top categories | |
| cat = df.groupby("category")["amount"].sum().sort_values(ascending=False).reset_index() | |
| if not cat.empty: | |
| st.write("Top categories") | |
| st.dataframe(cat.head(5), use_container_width=True) | |
| with right: | |
| fig = px.pie(cat, names="category", values="amount", title="Spending Breakdown", | |
| color_discrete_sequence=px.colors.sequential.Purples) | |
| st.plotly_chart(fig, use_container_width=True) | |
| st.subheader("Recent Transactions") | |
| st.dataframe(df.sort_values("date", ascending=False).head(10), use_container_width=True) | |
| else: | |
| st.info("No expenses recorded yet. Add one from the Expenses menu.") | |
| # ---------- UI: EXPENSES ---------- | |
| elif choice == "Expenses": | |
| st.header("πΈ Add Expense") | |
| with st.form("expense_form", clear_on_submit=True): | |
| name = st.text_input("Name", placeholder="e.g., Rent, Internet, Groceries") | |
| amount = st.number_input("Amount (USD)", min_value=0.0, format="%.2f") | |
| category = st.selectbox("Category", ["Rent", "Food", "Utilities", "Subscription", "Shopping", "Misc"]) | |
| date = st.date_input("Date", value=datetime.today()) | |
| submitted = st.form_submit_button("Add Expense") | |
| if submitted: | |
| ok = add_expense({ | |
| "name": name, | |
| "amount": float(amount), | |
| "category": category, | |
| "date": date.isoformat() | |
| }) | |
| if ok: | |
| st.success("Expense added.") | |
| else: | |
| st.error("Failed to add expense. Check backend.") | |
| st.subheader("All Expenses") | |
| expenses = get_expenses() | |
| if expenses: | |
| df = pd.DataFrame(expenses) | |
| df["date"] = pd.to_datetime(df["date"], errors="coerce") | |
| st.dataframe(df.sort_values("date", ascending=False), use_container_width=True) | |
| else: | |
| st.info("No expenses yet.") | |
| # ---------- UI: BUDGET ---------- | |
| elif choice == "Budget": | |
| st.header("π AI Budget Recommendation") | |
| st.markdown("FinSync AI suggests an allocation based on your expenses.") | |
| budget = get_budget() | |
| if budget: | |
| left, right = st.columns([1, 2], gap="large") | |
| with left: | |
| st.metric("π Spending", f"{budget.get('Spending',0)*100:.0f}%") | |
| st.metric("π° Savings", f"{budget.get('Savings',0)*100:.0f}%") | |
| st.metric("π Investments", f"{budget.get('Investments',0)*100:.0f}%") | |
| with right: | |
| fig = px.pie( | |
| names=["Spending", "Savings", "Investments"], | |
| values=[budget.get("Spending",0), budget.get("Savings",0), budget.get("Investments",0)], | |
| color_discrete_sequence=px.colors.qualitative.Set3 | |
| ) | |
| st.plotly_chart(fig, use_container_width=True) | |
| if budget.get("message"): | |
| st.info(budget.get("message")) | |
| else: | |
| st.info("Budget data not available or backend unreachable.") | |
| # ---------- UI: REMINDERS ---------- | |
| elif choice == "Reminders": | |
| st.header("β° Reminders") | |
| with st.form("reminder_form", clear_on_submit=True): | |
| name = st.text_input("Reminder name", placeholder="e.g., Pay Rent") | |
| date = st.date_input("Due date", value=datetime.today()) | |
| amount = st.number_input("Amount (optional)", min_value=0.0, format="%.2f") | |
| notes = st.text_area("Notes (optional)") | |
| submitted = st.form_submit_button("Add Reminder") | |
| if submitted: | |
| ok = add_reminder({ | |
| "name": name, | |
| "date": date.isoformat(), | |
| "amount": float(amount), | |
| "notes": notes | |
| }) | |
| if ok: | |
| st.success("Reminder added.") | |
| else: | |
| st.error("Failed to add reminder. Check backend.") | |
| st.subheader("Your reminders") | |
| reminders = get_reminders() | |
| if reminders: | |
| df = pd.DataFrame(reminders) | |
| df["date"] = pd.to_datetime(df["date"], errors="coerce") | |
| st.dataframe(df.sort_values("date"), use_container_width=True) | |
| else: | |
| st.info("No reminders found.") | |
| # ---------- UI: AI CHAT ---------- | |
| elif choice == "AI Chat": | |
| st.header("π€ AI Chat") | |
| st.markdown("Ask FinSync AI to add expenses, show budget, or set reminders. Example: \"Add expense Rent 500\"") | |
| prompt = st.text_input("Enter command") | |
| if st.button("Send"): | |
| if not prompt or prompt.strip() == "": | |
| st.warning("Please enter a command.") | |
| else: | |
| res = analyze_text({"text": prompt}) | |
| st.success(res.get("message", "No response")) | |
| # ---------- END ---------- |