File size: 8,631 Bytes
07a989b
acee70f
43d8ca7
 
 
58996cc
d36c237
197bdd1
f1dde0e
 
 
 
2271f41
197bdd1
 
 
d36c237
 
 
 
bc61a30
 
 
338e731
bc61a30
 
 
 
c22b4e8
bc61a30
 
 
 
 
d36c237
 
e4abf2e
338e731
d36c237
bc61a30
d36c237
 
 
 
 
 
338e731
bc61a30
d36c237
bc61a30
f1dde0e
d36c237
58996cc
d36c237
 
 
 
 
 
 
 
 
 
a1c81fd
29556e3
d36c237
bc61a30
f1dde0e
d36c237
f1dde0e
 
d36c237
559482e
3d8adb6
d36c237
559482e
d36c237
6ffe729
2859381
 
d36c237
559482e
 
 
2381a32
 
d36c237
338e731
032a5a2
bc61a30
 
8003d49
 
9707347
8003d49
 
 
9707347
8003d49
 
536e36b
c22b4e8
 
 
 
 
 
 
 
 
 
 
 
 
 
acee70f
 
 
 
 
 
 
 
 
 
 
 
 
 
338e731
acee70f
 
 
 
 
 
 
e2ed6eb
acee70f
 
 
e2ed6eb
 
 
 
 
 
 
 
 
 
 
 
 
 
 
acee70f
 
 
3446e8a
 
 
 
 
 
 
 
 
 
 
 
 
 
acee70f
 
 
 
 
cb78bd2
acee70f
 
 
 
 
 
 
 
e2ed6eb
acee70f
338e731
 
acee70f
 
 
 
e2ed6eb
acee70f
e4abf2e
 
338e731
acee70f
 
 
c22b4e8
5102cf7
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
import os
import datetime
import gradio as gr
from sentence_transformers import SentenceTransformer
import torch
from groq import Groq

financialText = ""
file_names = [
    "financial_literacy_text.txt",
    "financial_aid_text.txt"
]

for path in file_names:
    with open(path, "r", encoding = "utf-8") as file:
        financialText += file.read()

model = SentenceTransformer('all-MiniLM-L6-v2')

def preprocessText(text):
    cleanedText = text.strip().split("\n")
    cleanedChunks = []
    sectionLabels = []
    currentLabel = "Knowledge Base"
    for chunk in cleanedText: 
        chunk = chunk.strip()
        if not chunk: 
            continue
        if chunk.startswith("===") and "SECTION:" in chunk:
            currentLabel = chunk.replace("===", "",).replace("SECTION:", "").strip()
            continue
        cleanedChunks.append(chunk)
        sectionLabels.append(currentLabel)
    return cleanedChunks, sectionLabels

def createEmbeddings(textChunks):
    chunkEmbeddings = model.encode(textChunks, convert_to_tensor = True)
    return chunkEmbeddings

def getTopChunks(query, chunkEmbeddings, textChunks, sectionLabels):
    queryEmbedding = model.encode(query, convert_to_tensor = True)
    queryEmbeddingNormalized = queryEmbedding / queryEmbedding.norm()
    chunkEmbeddingsNormalized = chunkEmbeddings / chunkEmbeddings.norm(dim = 1, keepdim = True)
    similarities = torch.matmul(chunkEmbeddingsNormalized, queryEmbeddingNormalized)
    topIndices = torch.topk(similarities, k=3).indices
    topChunks = [textChunks[i] for i in topIndices]
    topSections = [sectionLabels[i] for i in topIndices]
    return topChunks, topSections

cleanedChunks, sectionLabels = preprocessText(financialText)
chunkEmbeddings = createEmbeddings(cleanedChunks)

client = Groq(api_key = os.environ.get("SF_TOKEN"))

def respond(message, history):
    messages = [{"role": "system",
                "content": "You are a friendly, approachable AI assistant whose main goal is to help high school and college students to learn more about productivity, setting goals for their education, and financial literacy."
                "Keep responses between 200-300 words unless asked for more detail about your suggestions from the user."
                "Explanations should be clear with examples and always include actionable steps, following this format:"
                "User: What is the 50/30/20 rule when it comes to budgeting?"
                "AI: Great question! The 50/30/20 rule states that you should spend 50% of your income on needs, 30% of your income on wants, and 20% of your income on investing. For example, if you make $4,000 each month, you should spend $2,000 on your needs, $1,200 on your wants, and $800 on investing. That way, you can set aside money to take care of yourself while still making progress towards saving up for the things that aren't as essential."}]

    if history:
        messages.extend([{"role": h["role"], "content": h["content"]} for h in history])
        # helping grok to retain message history

    topResults, topSections = getTopChunks(message, chunkEmbeddings, cleanedChunks, sectionLabels)
    context = "\n\n".join(topResults)

    messages.append({"role": "system",
                    "content": context})

    messages.append({"role": "user",
                     "content": message})

    response = ""

    responseStream = client.chat.completions.create(
        model = "llama-3.1-8b-instant", messages = messages, stream = True, max_tokens = 600, temperature = 0.4
    )
    
    for segment in responseStream:
        token = segment.choices[0].delta.content
        if token is not None:
            response += token
            yield response

    usedSections = list(dict.fromkeys(topSections))
    citation = f"\n\n *📋 Sources: {usedSections}*"
    yield response + citation

custom_css = """
body {
    background-color: #52528C !important;
}

.gradio-container {
    background-color: #52528C !important;
}
"""

def budgetCalculator(income):
    if income is None or income <= 0:
        return "Please enter a monthly income value that is greater than 0."
    needs = income * 0.5
    wants = income * 0.3 
    savings = income * 0.2
    return (
        f"### Your 50/30/20 Budget Breakdown:\n\n"
        f"- **Needs (50%):** ${needs:,.2f}\n"
        f"- **Wants (30%):** ${wants:,.2f}\n"
        f"- **Savings (20%):** ${savings:,.2f}\n\n"
        f"Based on a monthly income of ${income:,.2f}."
    )

def goalTracker(goal, savings, targetDate):
    if goal is None or savings is None  or not targetDate:
        return "Please fill in the tracker to get started!"
    if goal <= 0:
        return "Set your goal for higher than 0!"
    if savings < 0:
        return "Savings can't be negative; start at 0 if needed!"

    try:
        target = datetime.datetime.strptime(targetDate, "%Y-%m-%d").date()
    except ValueError:
        return "Please enter the date in YYYY-MM-DD format! (ex: 2026-06-12)"

    today = datetime.date.today()
    daysLeft = (target - today).days

    if daysLeft <= 0:
        return "Try to set your goal for sometime in the future!"

    remaining = goal - savings
    if remaining <= 0:
        return "Congratulations on hitting your goal!"
        
    weeklyAmount = remaining / (daysLeft / 7)
    monthlyAmount = remaining / (daysLeft / 30.44)

    if daysLeft >= 28:
        return (
            f"### Goal Tracker Results\n\n"
            f"- **Amount Remaining:** ${remaining:,.2f}\n"
            f"- **Days Remaining:** {daysLeft}\n"
            f"- **Save this much per week:** ${weeklyAmount:,.2f}\n"
            f"- **Save this much per month:** ${monthlyAmount:,.2f}\n"
        )
    elif daysLeft >= 7: 
                return (
            f"### Goal Tracker Results\n\n"
            f"- **Amount Remaining:** ${remaining:,.2f}\n"
            f"- **Days Remaining:** {daysLeft}\n"
            f"- **Save this much per week:** ${weeklyAmount:,.2f}\n"
        )

with gr.Blocks(css=custom_css) as demo:
    gr.Image("banner_image.png", show_label=False, container=False)
    gr.HTML(
        """
    <iframe
        style="border-radius:12px"
        src="https://open.spotify.com/embed/playlist/3WimQBublpHoOkok4Vu4Dj"
        width="100%"
        height="352"
        frameBorder="0"
        allowfullscreen=""
        allow="autoplay; clipboard-write; encrypted-media; fullscreen; picture-in-picture"
        loading="lazy">
    </iframe>
    """
    )
    with gr.Tabs():
        with gr.Tab("Chatbot"):
            gr.ChatInterface(
                respond,
                title="Student Formula Bot 🔬",
                description='Welcome to the core component of \"The Student Formula\": the RAG chatbot! With the ability to act as a finance tutor, accountability buddy, and goal-setting partner all in one, it\'s designed to best suit your needs on the way to productivity and success. To get started, ask the chatbot a question or click on one of the examples!',
                examples=[
                    "How do I build and balance a budget?",
                    "Why do people write SMART goals?",
                    "What is educational investment?"
                ]
            )

        with gr.Tab("Budget Calculator"):
            gr.Markdown("# 50/30/20 Budget Calculator 📑")
            gr.Markdown("Want to see what your monthly budget might look like when following financial literacy principles? Enter your monthly income to see the suggested breakdown!")
            incomeInput = gr.Number(label = "Monthly Income ($)", minimum = 0)
            budgetOutput = gr.Markdown(label = "Monthly Budget")
            budgetButton = gr.Button("Calculate")
            budgetButton.click(fn = budgetCalculator, inputs = incomeInput, outputs = budgetOutput)

        with gr.Tab("Goal Tracker"):
            gr.Markdown("# Savings Goal Tracker 🪙")
            gr.Markdown("Saving up to buy something special? Want to figure out how long it'll take to reach that goal? Enter your goal amount, current savings, and target date to see the suggested timeline!")
            goalInput = gr.Number(label = "Savings Goal ($)", minimum = 0)
            savingsInput = gr.Number(label = "Current Savings ($)", minimum = 0)
            targetInput = gr.Textbox(label = "Target Date (YYYY-MM-DD)", placeholder = "2026-12-31")
            goalOutput =  gr.Markdown(label = "Your Savings Plan")
            goalButton = gr.Button("Calculate")
            goalButton.click(fn = goalTracker, inputs = [goalInput, savingsInput, targetInput], outputs = goalOutput)

demo.launch()