expensynth / app.py
Shrijeeth-Suresh
feat: add Makefile and integrate live financial data with auto-refresh charts
01c92a0
import datetime
import gradio as gr
import pandas as pd
import plotly.express as px
import requests
from gradio.themes import Default
def get_financial_summary():
try:
response = requests.get(
f"https://green-smoke-labs-dev--green-smoke-labs-expensynth-api-server.modal.run/api/transactions/monthly-summary/{datetime.datetime.now().year}",
)
response.raise_for_status()
data = response.json()
except Exception:
data = {
"months": [
"Jan",
"Feb",
"Mar",
"Apr",
"May",
"Jun",
"Jul",
"Aug",
"Sep",
"Oct",
"Nov",
"Dec",
],
"credits": [1000, 1200, 900, 1300, 1500, 1700],
"expenses": [400, 600, 500, 700, 800, 900],
}
return data
def get_category_distribution():
try:
response = requests.get(
f"https://green-smoke-labs-dev--green-smoke-labs-expensynth-api-server.modal.run/api/transactions/category-distribution/{datetime.datetime.now().year}",
)
response.raise_for_status()
data = response.json()
except Exception:
data = {
"categories": ["Food", "Retail", "Others"],
"distribution": [40, 30, 30],
}
return data
def display_financial_charts():
data = get_financial_summary()
df = pd.DataFrame(data)
df["savings"] = df["credits"] - df["expenses"]
# fig1 = px.line(df, x="Month", y="Credit", title="Monthly Credit")
fig2 = px.bar(df, x="months", y="expenses", title="Monthly Expenses")
fig3 = px.area(df, x="months", y="credits", title="Monthly Savings")
latest = get_category_distribution()
fig4 = px.pie(
names=latest["categories"],
values=latest["distribution"],
title="Latest Financial Distribution",
)
return fig2, fig3, fig4
def chatbot_respond(user_message, history):
history = history or []
if user_message:
history.append({"role": "user", "content": user_message})
try:
response = requests.post(
"https://green-smoke-labs-dev--green-smoke-labs-expensynth-api-server.modal.run/bot/query",
json={"messages": history},
)
response.raise_for_status()
bot_reply = (
response.json().get("data", {}).get("raw", "Sorry, I didn't understand.")
)
except Exception:
bot_reply = "Server unavailable. This is a mocked reply."
history.append({"role": "assistant", "content": bot_reply})
return "", history, history
def reset_chat():
"""Reset chat history and clear the chatbot text box."""
return "", [], []
def minimize_chat():
return gr.update(visible=False), gr.update(visible=True)
def restore_chat():
return gr.update(visible=True), gr.update(visible=False)
# Frontend
with gr.Blocks(
theme=Default(),
css="""
#profile-pic-wrapper {
width: 100px;
height: 100px;
border-radius: 50%;
overflow: hidden;
display: flex;
align-items: center;
justify-content: center;
margin: 0 auto 10px auto;
background: #fff;
}
#profile-icon {
width: 100%;
height: 100%;
object-fit: cover;
border-radius: 50%;
display: block;
margin: 0;
}
.header h1 {
margin: 0;
font-family: 'Arial', sans-serif;
color: #333;
}
.left-navbar {
background-color: #fff;
padding: 20px;
border-right: 1px solid #ddd;
min-height: 80vh;
}
.chat-panel {
position: fixed;
top: 20px;
right: 20px;
left: auto;
width: 350px;
height: 620px;
background: #fff;
border: 1px solid #ddd;
box-shadow: 2px 2px 10px rgba(0,0,0,0.1);
border-radius: 8px;
padding: 10px;
overflow: visible;
z-index: 1001;
}
#open-chat-btn {
position: fixed;
bottom: 20px;
right: 20px;
left: auto;
z-index: 1000;
}
#chatbot-input {
height: 150px;
}
""",
) as demo:
chat_panel = gr.Column(visible=True, elem_classes="chat-panel")
with chat_panel:
gr.Markdown("### Financial Agent", height=200)
chatbot_state = gr.State([])
chatbot_ui = gr.Chatbot(type="messages")
chatbot_input = gr.Textbox(
placeholder="Type your message...",
label="Your Message",
elem_id="chatbot-input",
)
with gr.Row():
send_btn = gr.Button("Send")
reset_btn = gr.Button("Reset Chat")
minimize_btn = gr.Button("Minimize", elem_id="minimize-chat-btn")
with gr.Row():
# gr.HTML(
# '<div id="profile-pic-wrapper"><img id="profile-icon" src="assets/profile_pic.png" /></div>'
# )
gr.Markdown("<h1>Financial Health Dashboard</h1>", elem_classes="header")
with gr.Row():
# with gr.Column(scale=1, elem_classes="left-navbar"):
# gr.Markdown("## Navigation")
# for nav in ["Dashboard", "Reports", "Analytics", "Settings"]:
# gr.Button(nav)
with gr.Column(scale=3):
with gr.Tabs():
with gr.TabItem("Charts"):
chart1 = gr.Plot(label="Monthly Expenses")
chart2 = gr.Plot(label="Monthly Savings")
chart3 = gr.Plot(label="Latest Financial Distribution")
def reload_charts():
fig2, fig3, fig4 = display_financial_charts()
return fig2, fig3, fig4
# Initial load
demo.load(reload_charts, outputs=[chart1, chart2, chart3])
# Auto-refresh every 1 hour (3600 seconds)
timer = gr.Timer(value=60, active=True)
timer.tick(fn=reload_charts, outputs=[chart1, chart2, chart3])
with gr.TabItem("Overview"):
# summary = get_financial_summary()
summary = chatbot_respond("What is the financial summary?", [])[-1][
-1
]["content"]
advice = chatbot_respond(
"What financial advice can you give? Give general advice, do not give anything specific to my transactions",
[],
)[-1][-1]["content"]
gr.Markdown(
f"""### Summary\n{summary}\n\n### Financial Advice\n{advice}"""
)
minimized_state = gr.State(False)
open_btn = gr.Button("Open Chat", elem_id="open-chat-btn", visible=False)
chatbot_input.submit(
fn=chatbot_respond,
inputs=[chatbot_input, chatbot_state],
outputs=[chatbot_input, chatbot_state, chatbot_ui],
queue=False,
)
send_btn.click(
fn=chatbot_respond,
inputs=[chatbot_input, chatbot_state],
outputs=[chatbot_input, chatbot_state, chatbot_ui],
queue=False,
)
reset_btn.click(
fn=reset_chat,
inputs=None,
outputs=[chatbot_input, chatbot_state, chatbot_ui],
queue=False,
)
minimize_btn.click(
fn=minimize_chat,
inputs=None,
outputs=[chat_panel, open_btn],
queue=False,
)
open_btn.click(
fn=restore_chat,
inputs=None,
outputs=[chat_panel, open_btn],
queue=False,
)
demo.launch()