Spaces:
Sleeping
Sleeping
mohsin-devs commited on
Commit ·
bc939ab
1
Parent(s): 4172042
Deploy BankBot AI project files
Browse files- .streamlit/config.toml +6 -0
- README.md +29 -13
- app.py +920 -0
- data/intents.json +326 -0
- ollama_integration.py +201 -0
- requirements.txt +0 -0
- users.json +1 -0
- utils.py +231 -0
.streamlit/config.toml
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
[theme]
|
| 2 |
+
base="light"
|
| 3 |
+
primaryColor="#1E40AF"
|
| 4 |
+
backgroundColor="#F8FAFC"
|
| 5 |
+
secondaryBackgroundColor="#F1F5F9"
|
| 6 |
+
textColor="#0F172A"
|
README.md
CHANGED
|
@@ -1,20 +1,36 @@
|
|
| 1 |
---
|
| 2 |
-
title:
|
| 3 |
-
emoji:
|
| 4 |
-
colorFrom:
|
| 5 |
-
colorTo:
|
| 6 |
-
sdk:
|
| 7 |
-
|
| 8 |
-
|
| 9 |
-
- streamlit
|
| 10 |
pinned: false
|
| 11 |
-
short_description: AI-powered banking assistant
|
| 12 |
license: mit
|
| 13 |
---
|
| 14 |
|
| 15 |
-
#
|
| 16 |
|
| 17 |
-
|
| 18 |
|
| 19 |
-
|
| 20 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
---
|
| 2 |
+
title: Central Bank AI
|
| 3 |
+
emoji: 🏦
|
| 4 |
+
colorFrom: blue
|
| 5 |
+
colorTo: indigo
|
| 6 |
+
sdk: streamlit
|
| 7 |
+
sdk_version: 1.54.0
|
| 8 |
+
app_file: app.py
|
|
|
|
| 9 |
pinned: false
|
|
|
|
| 10 |
license: mit
|
| 11 |
---
|
| 12 |
|
| 13 |
+
# 🏦 Central Bank AI — BankBot
|
| 14 |
|
| 15 |
+
A professional AI-powered banking assistant built with Streamlit.
|
| 16 |
|
| 17 |
+
## Features
|
| 18 |
+
- 💬 Banking chatbot powered by **Groq AI** (cloud) or **Ollama** (local)
|
| 19 |
+
- 📊 Financial dashboard with transaction history and analytics
|
| 20 |
+
- 🔐 User authentication with session management
|
| 21 |
+
- 📋 FAQ-based instant responses from a structured intents database
|
| 22 |
+
|
| 23 |
+
## AI Backend
|
| 24 |
+
- **Cloud (HF Spaces):** Uses [Groq AI](https://console.groq.com) — set `GROQ_API_KEY` as a Space Secret
|
| 25 |
+
- **Local:** Falls back to [Ollama](https://ollama.com) (llama3) automatically
|
| 26 |
+
|
| 27 |
+
## Setup (Local)
|
| 28 |
+
```bash
|
| 29 |
+
pip install -r requirements.txt
|
| 30 |
+
ollama pull llama3
|
| 31 |
+
streamlit run app.py
|
| 32 |
+
```
|
| 33 |
+
|
| 34 |
+
## Setup (Hugging Face Spaces)
|
| 35 |
+
1. Add `GROQ_API_KEY` as a **Secret** in Space Settings
|
| 36 |
+
2. The app will automatically use Groq AI
|
app.py
ADDED
|
@@ -0,0 +1,920 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import streamlit as st
|
| 2 |
+
import pandas as pd
|
| 3 |
+
import numpy as np
|
| 4 |
+
import plotly.express as px
|
| 5 |
+
import time
|
| 6 |
+
from utils import (
|
| 7 |
+
validate_email,
|
| 8 |
+
validate_password_strength,
|
| 9 |
+
format_currency,
|
| 10 |
+
get_timestamp,
|
| 11 |
+
save_chat_session,
|
| 12 |
+
load_chat_session,
|
| 13 |
+
delete_chat_session,
|
| 14 |
+
clear_all_chat_history,
|
| 15 |
+
persist_user,
|
| 16 |
+
get_persisted_users,
|
| 17 |
+
save_active_session,
|
| 18 |
+
get_active_session,
|
| 19 |
+
clear_active_session,
|
| 20 |
+
get_ai_response,
|
| 21 |
+
stream_ai_response,
|
| 22 |
+
check_ollama_connection,
|
| 23 |
+
get_active_backend,
|
| 24 |
+
get_all_chat_sessions,
|
| 25 |
+
get_faq_response,
|
| 26 |
+
rewrite_banking_response,
|
| 27 |
+
is_banking_query
|
| 28 |
+
)
|
| 29 |
+
|
| 30 |
+
st.set_page_config(
|
| 31 |
+
page_title="Central Bank AI",
|
| 32 |
+
page_icon="🏦",
|
| 33 |
+
layout="wide",
|
| 34 |
+
initial_sidebar_state="expanded"
|
| 35 |
+
)
|
| 36 |
+
|
| 37 |
+
def apply_custom_style(theme="dark"):
|
| 38 |
+
# Define color palette based on theme
|
| 39 |
+
if theme == "dark":
|
| 40 |
+
colors = {
|
| 41 |
+
"bg": "#0B1220",
|
| 42 |
+
"card_bg": "#111827",
|
| 43 |
+
"text": "#f1f5f9",
|
| 44 |
+
"text_secondary": "#94a3b8",
|
| 45 |
+
"primary": "#2563EB",
|
| 46 |
+
"secondary": "#0ea5e9",
|
| 47 |
+
"border": "#1F2937",
|
| 48 |
+
"input_bg": "rgba(30, 41, 59, 0.8)",
|
| 49 |
+
"shadow": "rgba(0, 0, 0, 0.4)",
|
| 50 |
+
"success": "#10B981",
|
| 51 |
+
"warning": "#f59e0b",
|
| 52 |
+
"danger": "#EF4444",
|
| 53 |
+
"sidebar_bg": "#0F172A",
|
| 54 |
+
"hover": "rgba(255, 255, 255, 0.05)"
|
| 55 |
+
}
|
| 56 |
+
else:
|
| 57 |
+
colors = {
|
| 58 |
+
"bg": "#F8FAFC",
|
| 59 |
+
"card_bg": "#FFFFFF",
|
| 60 |
+
"text": "#0F172A",
|
| 61 |
+
"text_secondary": "#64748B",
|
| 62 |
+
"primary": "#1E40AF",
|
| 63 |
+
"secondary": "#2563EB",
|
| 64 |
+
"border": "#E2E8F0",
|
| 65 |
+
"input_bg": "#F8FAFC",
|
| 66 |
+
"shadow": "rgba(0, 0, 0, 0.04)",
|
| 67 |
+
"success": "#10B981",
|
| 68 |
+
"warning": "#d97706",
|
| 69 |
+
"danger": "#EF4444",
|
| 70 |
+
"sidebar_bg": "#F1F5F9",
|
| 71 |
+
"hover": "#EFF6FF"
|
| 72 |
+
}
|
| 73 |
+
|
| 74 |
+
st.session_state.colors = colors
|
| 75 |
+
|
| 76 |
+
st.markdown(f"""
|
| 77 |
+
<style>
|
| 78 |
+
/* Import Fonts */
|
| 79 |
+
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=Poppins:wght@500;600;700&display=swap');
|
| 80 |
+
|
| 81 |
+
/* Global Reset & Typography */
|
| 82 |
+
.stApp {{
|
| 83 |
+
font-family: 'Inter', sans-serif;
|
| 84 |
+
color: {colors['text']};
|
| 85 |
+
background-color: {colors['bg']};
|
| 86 |
+
}}
|
| 87 |
+
|
| 88 |
+
h1, h2, h3, h4, h5, h6 {{
|
| 89 |
+
font-family: 'Poppins', sans-serif;
|
| 90 |
+
font-weight: 600;
|
| 91 |
+
color: {colors['text']};
|
| 92 |
+
margin-bottom: 0.5rem;
|
| 93 |
+
}}
|
| 94 |
+
|
| 95 |
+
h1 {{ font-size: 32px !important; }}
|
| 96 |
+
h2 {{ font-size: 24px !important; }}
|
| 97 |
+
h3 {{ font-size: 18px !important; }}
|
| 98 |
+
|
| 99 |
+
/* Layout Logic */
|
| 100 |
+
.main .block-container {{
|
| 101 |
+
padding-top: 5rem !important;
|
| 102 |
+
padding-bottom: 2rem !important;
|
| 103 |
+
max-width: 1400px;
|
| 104 |
+
margin: 0 auto;
|
| 105 |
+
}}
|
| 106 |
+
|
| 107 |
+
header[data-testid="stHeader"] {{
|
| 108 |
+
background: transparent !important;
|
| 109 |
+
box-shadow: none !important;
|
| 110 |
+
height: 0 !important;
|
| 111 |
+
}}
|
| 112 |
+
|
| 113 |
+
[data-testid="stAppToolbar"] {{
|
| 114 |
+
display: none !important;
|
| 115 |
+
}}
|
| 116 |
+
|
| 117 |
+
[data-testid="stSidebarCollapseButton"] {{
|
| 118 |
+
visibility: hidden !important;
|
| 119 |
+
opacity: 0 !important;
|
| 120 |
+
}}
|
| 121 |
+
|
| 122 |
+
/* Footer still hidden */
|
| 123 |
+
footer {{
|
| 124 |
+
display: none !important;
|
| 125 |
+
}}
|
| 126 |
+
|
| 127 |
+
/* Global Sidebar Styling */
|
| 128 |
+
/* Global Sidebar Styling - Force visibility and width */
|
| 129 |
+
section[data-testid="stSidebar"] {{
|
| 130 |
+
background: {colors.get('sidebar_bg', '#F1F5F9')} !important;
|
| 131 |
+
border-right: 1px solid {colors['border']} !important;
|
| 132 |
+
min-width: 320px !important;
|
| 133 |
+
width: 320px !important;
|
| 134 |
+
visibility: visible !important;
|
| 135 |
+
display: block !important;
|
| 136 |
+
}}
|
| 137 |
+
|
| 138 |
+
/* Ensure child containers allow visibility */
|
| 139 |
+
[data-testid="stSidebarContent"] {{
|
| 140 |
+
visibility: visible !important;
|
| 141 |
+
}}
|
| 142 |
+
|
| 143 |
+
/* Hide the 'collapsed' hamburger icon in the top left if it appears */
|
| 144 |
+
[data-testid="collapsedControl"] {{
|
| 145 |
+
display: none !important;
|
| 146 |
+
}}
|
| 147 |
+
|
| 148 |
+
/* Remove default sidebar top padding */
|
| 149 |
+
section[data-testid="stSidebar"] > div:first-child {{
|
| 150 |
+
padding-top: 2rem !important;
|
| 151 |
+
}}
|
| 152 |
+
|
| 153 |
+
/* Custom Scrollbar */
|
| 154 |
+
::-webkit-scrollbar {{
|
| 155 |
+
width: 6px;
|
| 156 |
+
}}
|
| 157 |
+
::-webkit-scrollbar-thumb {{
|
| 158 |
+
background: {colors['border']};
|
| 159 |
+
border-radius: 10px;
|
| 160 |
+
}}
|
| 161 |
+
|
| 162 |
+
/* Section Titles */
|
| 163 |
+
.section-title {{
|
| 164 |
+
font-size: 11px;
|
| 165 |
+
letter-spacing: 0.08em;
|
| 166 |
+
text-transform: uppercase;
|
| 167 |
+
color: #64748b;
|
| 168 |
+
margin-bottom: 8px;
|
| 169 |
+
margin-top: 24px;
|
| 170 |
+
}}
|
| 171 |
+
|
| 172 |
+
/* User Card */
|
| 173 |
+
.user-card {{
|
| 174 |
+
background: {colors['card_bg']};
|
| 175 |
+
padding: 14px;
|
| 176 |
+
border-radius: 12px;
|
| 177 |
+
border: 1px solid {colors['border']};
|
| 178 |
+
margin-bottom: 24px;
|
| 179 |
+
display: flex;
|
| 180 |
+
align-items: center;
|
| 181 |
+
gap: 12px;
|
| 182 |
+
transition: all 0.2s ease;
|
| 183 |
+
}}
|
| 184 |
+
.user-card:hover {{
|
| 185 |
+
transform: translateX(2px);
|
| 186 |
+
background: {colors.get('hover', 'rgba(255,255,255,0.05)')};
|
| 187 |
+
}}
|
| 188 |
+
.user-avatar {{
|
| 189 |
+
background: {colors['primary']};
|
| 190 |
+
width: 36px;
|
| 191 |
+
height: 36px;
|
| 192 |
+
border-radius: 50%;
|
| 193 |
+
display: flex;
|
| 194 |
+
align-items: center;
|
| 195 |
+
justify-content: center;
|
| 196 |
+
color: white;
|
| 197 |
+
font-weight: bold;
|
| 198 |
+
}}
|
| 199 |
+
.user-name {{
|
| 200 |
+
font-weight: 600;
|
| 201 |
+
font-size: 14px;
|
| 202 |
+
color: {colors['text']};
|
| 203 |
+
}}
|
| 204 |
+
.user-email {{
|
| 205 |
+
font-size: 12px;
|
| 206 |
+
color: {colors['text_secondary']};
|
| 207 |
+
}}
|
| 208 |
+
|
| 209 |
+
/* Navigation / Sidebar Buttons */
|
| 210 |
+
.sidebar-btn {{
|
| 211 |
+
padding: 10px 14px;
|
| 212 |
+
border-radius: 8px;
|
| 213 |
+
color: #cbd5e1;
|
| 214 |
+
transition: all 0.2s ease;
|
| 215 |
+
margin-bottom: 8px;
|
| 216 |
+
display: flex;
|
| 217 |
+
align-items: center;
|
| 218 |
+
gap: 8px;
|
| 219 |
+
cursor: pointer;
|
| 220 |
+
}}
|
| 221 |
+
.sidebar-btn:hover {{
|
| 222 |
+
background-color: {colors.get('hover', 'rgba(255,255,255,0.05)')};
|
| 223 |
+
color: white;
|
| 224 |
+
transform: translateX(2px);
|
| 225 |
+
}}
|
| 226 |
+
.sidebar-active {{
|
| 227 |
+
background-color: rgba(37, 99, 235, 0.15);
|
| 228 |
+
border-left: 3px solid {colors['primary']};
|
| 229 |
+
color: white;
|
| 230 |
+
border-radius: 0 8px 8px 0;
|
| 231 |
+
}}
|
| 232 |
+
|
| 233 |
+
/* Chat History */
|
| 234 |
+
.chat-history-container {{
|
| 235 |
+
max-height: 250px;
|
| 236 |
+
overflow-y: auto;
|
| 237 |
+
margin-bottom: 24px;
|
| 238 |
+
padding-right: 4px;
|
| 239 |
+
}}
|
| 240 |
+
.chat-item {{
|
| 241 |
+
padding: 8px 10px;
|
| 242 |
+
border-radius: 8px;
|
| 243 |
+
color: #cbd5e1;
|
| 244 |
+
transition: all 0.2s ease;
|
| 245 |
+
font-size: 0.9rem;
|
| 246 |
+
margin-bottom: 4px;
|
| 247 |
+
cursor: pointer;
|
| 248 |
+
white-space: nowrap;
|
| 249 |
+
overflow: hidden;
|
| 250 |
+
text-overflow: ellipsis;
|
| 251 |
+
}}
|
| 252 |
+
.chat-item:hover {{
|
| 253 |
+
background: {colors.get('hover', 'rgba(255,255,255,0.05)')};
|
| 254 |
+
color: white;
|
| 255 |
+
transform: translateX(2px);
|
| 256 |
+
}}
|
| 257 |
+
|
| 258 |
+
/* Logout Button */
|
| 259 |
+
.logout-btn button {{
|
| 260 |
+
background: transparent !important;
|
| 261 |
+
border: 1px solid {colors['danger']} !important;
|
| 262 |
+
color: {colors['danger']} !important;
|
| 263 |
+
border-radius: 8px !important;
|
| 264 |
+
transition: all 0.2s ease !important;
|
| 265 |
+
width: 100%;
|
| 266 |
+
padding: 8px !important;
|
| 267 |
+
}}
|
| 268 |
+
.logout-btn button:hover {{
|
| 269 |
+
background: {colors['danger']} !important;
|
| 270 |
+
color: white !important;
|
| 271 |
+
}}
|
| 272 |
+
|
| 273 |
+
/* AI Status Badge */
|
| 274 |
+
.status-badge {{
|
| 275 |
+
padding: 8px 12px;
|
| 276 |
+
border-radius: 8px;
|
| 277 |
+
font-size: 13px;
|
| 278 |
+
font-weight: 600;
|
| 279 |
+
display: flex;
|
| 280 |
+
align-items: center;
|
| 281 |
+
gap: 8px;
|
| 282 |
+
margin-top: 8px;
|
| 283 |
+
border: 1px solid {colors['border']};
|
| 284 |
+
}}
|
| 285 |
+
|
| 286 |
+
.status-online {{
|
| 287 |
+
background: rgba(16, 185, 129, 0.1);
|
| 288 |
+
color: #10b981;
|
| 289 |
+
border-color: rgba(16, 185, 129, 0.2);
|
| 290 |
+
}}
|
| 291 |
+
|
| 292 |
+
.status-offline {{
|
| 293 |
+
background: rgba(239, 68, 68, 0.1);
|
| 294 |
+
color: #ef4444;
|
| 295 |
+
border-color: rgba(239, 68, 68, 0.2);
|
| 296 |
+
}}
|
| 297 |
+
|
| 298 |
+
/* Card Component */
|
| 299 |
+
.bank-card {{
|
| 300 |
+
background-color: {colors['card_bg']};
|
| 301 |
+
border: 1px solid {colors['border']};
|
| 302 |
+
border-radius: 14px;
|
| 303 |
+
padding: 24px;
|
| 304 |
+
box-shadow: 0 4px 15px {colors['shadow']};
|
| 305 |
+
margin-bottom: 16px;
|
| 306 |
+
}}
|
| 307 |
+
|
| 308 |
+
/* Primary Buttons */
|
| 309 |
+
.stButton>button {{
|
| 310 |
+
border-radius: 8px;
|
| 311 |
+
transition: 0.3s ease;
|
| 312 |
+
}}
|
| 313 |
+
|
| 314 |
+
.stButton>button:hover {{
|
| 315 |
+
transform: translateY(-2px);
|
| 316 |
+
}}
|
| 317 |
+
|
| 318 |
+
/* Chat Interface Styling */
|
| 319 |
+
.user-bubble {{
|
| 320 |
+
background: {colors['primary']};
|
| 321 |
+
color: white;
|
| 322 |
+
padding: 12px 20px;
|
| 323 |
+
border-radius: 20px 20px 4px 20px;
|
| 324 |
+
margin-bottom: 12px;
|
| 325 |
+
max-width: 85%;
|
| 326 |
+
margin-left: auto;
|
| 327 |
+
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
|
| 328 |
+
font-size: 0.95rem;
|
| 329 |
+
line-height: 1.5;
|
| 330 |
+
}}
|
| 331 |
+
|
| 332 |
+
.ai-bubble {{
|
| 333 |
+
background: {colors['card_bg']};
|
| 334 |
+
color: {colors['text']};
|
| 335 |
+
padding: 12px 20px;
|
| 336 |
+
border-radius: 20px 20px 20px 4px;
|
| 337 |
+
margin-bottom: 12px;
|
| 338 |
+
max-width: 85%;
|
| 339 |
+
border: 1px solid {colors['border']};
|
| 340 |
+
box-shadow: 0 2px 8px rgba(0,0,0,0.05);
|
| 341 |
+
font-size: 0.95rem;
|
| 342 |
+
line-height: 1.5;
|
| 343 |
+
}}
|
| 344 |
+
|
| 345 |
+
/* Modern Chat Input Styling */
|
| 346 |
+
section[data-testid="stChatInput"] {{
|
| 347 |
+
padding-bottom: 2rem !important;
|
| 348 |
+
}}
|
| 349 |
+
|
| 350 |
+
section[data-testid="stChatInput"] > div {{
|
| 351 |
+
background-color: transparent !important;
|
| 352 |
+
}}
|
| 353 |
+
|
| 354 |
+
section[data-testid="stChatInput"] textarea {{
|
| 355 |
+
background-color: {colors['input_bg']} !important;
|
| 356 |
+
border: 1px solid {colors['border']} !important;
|
| 357 |
+
border-radius: 25px !important;
|
| 358 |
+
padding: 12px 20px !important;
|
| 359 |
+
color: {colors['text']} !important;
|
| 360 |
+
box-shadow: 0 4px 12px {colors['shadow']} !important;
|
| 361 |
+
transition: all 0.3s ease;
|
| 362 |
+
}}
|
| 363 |
+
|
| 364 |
+
section[data-testid="stChatInput"] textarea:focus {{
|
| 365 |
+
border-color: {colors['primary']} !important;
|
| 366 |
+
box-shadow: 0 4px 15px rgba(59, 130, 246, 0.3) !important;
|
| 367 |
+
}}
|
| 368 |
+
|
| 369 |
+
/* Disable default Streamlit fading on rapid updates */
|
| 370 |
+
div[data-testid="stVerticalBlock"] > div,
|
| 371 |
+
div[data-testid="stVerticalBlock"],
|
| 372 |
+
div.element-container,
|
| 373 |
+
div.stMarkdown,
|
| 374 |
+
div[data-testid="stMarkdownContainer"],
|
| 375 |
+
div[data-testid="stChatMessage"] {{
|
| 376 |
+
transition: none !important;
|
| 377 |
+
animation: none !important;
|
| 378 |
+
opacity: 1 !important;
|
| 379 |
+
}}
|
| 380 |
+
|
| 381 |
+
* {{
|
| 382 |
+
animation: none !important;
|
| 383 |
+
}}
|
| 384 |
+
|
| 385 |
+
</style>
|
| 386 |
+
""", unsafe_allow_html=True)
|
| 387 |
+
|
| 388 |
+
def init_session_state():
|
| 389 |
+
if "users" not in st.session_state:
|
| 390 |
+
st.session_state.users = get_persisted_users()
|
| 391 |
+
|
| 392 |
+
if "logged_in" not in st.session_state:
|
| 393 |
+
last_user = get_active_session()
|
| 394 |
+
if last_user and last_user in st.session_state.users:
|
| 395 |
+
st.session_state.logged_in = True
|
| 396 |
+
st.session_state.username = last_user
|
| 397 |
+
st.session_state.email = st.session_state.users[last_user]["email"]
|
| 398 |
+
else:
|
| 399 |
+
st.session_state.logged_in = False
|
| 400 |
+
|
| 401 |
+
if "theme" not in st.session_state:
|
| 402 |
+
st.session_state.theme = "light"
|
| 403 |
+
|
| 404 |
+
apply_custom_style(st.session_state.theme)
|
| 405 |
+
|
| 406 |
+
if "username" not in st.session_state:
|
| 407 |
+
st.session_state.username = ""
|
| 408 |
+
if "email" not in st.session_state:
|
| 409 |
+
st.session_state.email = ""
|
| 410 |
+
if "current_page" not in st.session_state:
|
| 411 |
+
st.session_state.current_page = "login"
|
| 412 |
+
if "chat_sessions" not in st.session_state:
|
| 413 |
+
if st.session_state.logged_in and st.session_state.username:
|
| 414 |
+
st.session_state.chat_sessions = get_all_chat_sessions(st.session_state.username)
|
| 415 |
+
else:
|
| 416 |
+
st.session_state.chat_sessions = []
|
| 417 |
+
if "current_chat_id" not in st.session_state:
|
| 418 |
+
st.session_state.current_chat_id = None
|
| 419 |
+
if "messages" not in st.session_state:
|
| 420 |
+
st.session_state.messages = []
|
| 421 |
+
if "balance" not in st.session_state:
|
| 422 |
+
st.session_state.balance = 850000.00
|
| 423 |
+
if "interest_rate" not in st.session_state:
|
| 424 |
+
st.session_state.interest_rate = 6.5
|
| 425 |
+
if "accrued_interest" not in st.session_state:
|
| 426 |
+
st.session_state.accrued_interest = 55000.00
|
| 427 |
+
if "active_loans" not in st.session_state:
|
| 428 |
+
st.session_state.active_loans = 2
|
| 429 |
+
if "total_loan_amount" not in st.session_state:
|
| 430 |
+
st.session_state.total_loan_amount = 3500000.00
|
| 431 |
+
|
| 432 |
+
init_session_state()
|
| 433 |
+
|
| 434 |
+
def login(username, password):
|
| 435 |
+
if username in st.session_state.users:
|
| 436 |
+
if st.session_state.users[username]["password"] == password:
|
| 437 |
+
st.session_state.logged_in = True
|
| 438 |
+
st.session_state.username = username
|
| 439 |
+
st.session_state.email = st.session_state.users[username]["email"]
|
| 440 |
+
st.session_state.current_page = "dashboard"
|
| 441 |
+
st.session_state.chat_sessions = get_all_chat_sessions(username)
|
| 442 |
+
save_active_session(username)
|
| 443 |
+
return True
|
| 444 |
+
return False
|
| 445 |
+
|
| 446 |
+
def signup(username, email, password):
|
| 447 |
+
if username in st.session_state.users:
|
| 448 |
+
return False, "Username already exists"
|
| 449 |
+
|
| 450 |
+
st.session_state.users[username] = {
|
| 451 |
+
"email": email,
|
| 452 |
+
"password": password
|
| 453 |
+
}
|
| 454 |
+
persist_user(username, email, password)
|
| 455 |
+
return True, "Account created successfully!"
|
| 456 |
+
|
| 457 |
+
def logout():
|
| 458 |
+
st.session_state.logged_in = False
|
| 459 |
+
st.session_state.username = ""
|
| 460 |
+
st.session_state.email = ""
|
| 461 |
+
st.session_state.current_page = "login"
|
| 462 |
+
st.session_state.messages = []
|
| 463 |
+
st.session_state.current_chat_id = None
|
| 464 |
+
clear_active_session()
|
| 465 |
+
|
| 466 |
+
def get_mock_transactions():
|
| 467 |
+
dates = pd.date_range(end=pd.Timestamp.now(), periods=30, freq='D')
|
| 468 |
+
|
| 469 |
+
types = []
|
| 470 |
+
amounts = []
|
| 471 |
+
cats = []
|
| 472 |
+
for _ in range(30):
|
| 473 |
+
if np.random.random() > 0.8:
|
| 474 |
+
types.append("Income")
|
| 475 |
+
cats.append(np.random.choice(["Salary", "Investment", "Refund"]))
|
| 476 |
+
amounts.append(round(float(np.random.uniform(5000, 25000)), 2))
|
| 477 |
+
else:
|
| 478 |
+
types.append("Expense")
|
| 479 |
+
cats.append(np.random.choice(["Food", "Rent", "Shopping", "Transport", "Entertainment", "Utilities"]))
|
| 480 |
+
amounts.append(round(float(np.random.uniform(100, 5000)), 2))
|
| 481 |
+
|
| 482 |
+
data = {"Date": dates, "Category": cats, "Type": types, "Amount": amounts}
|
| 483 |
+
return pd.DataFrame(data)
|
| 484 |
+
|
| 485 |
+
def show_login_page():
|
| 486 |
+
col1, col2, col3 = st.columns([1, 2, 1])
|
| 487 |
+
with col2:
|
| 488 |
+
st.title("🏦 Central Bank AI")
|
| 489 |
+
st.subheader("Login")
|
| 490 |
+
st.divider()
|
| 491 |
+
|
| 492 |
+
with st.form("login_form"):
|
| 493 |
+
username = st.text_input("Username", placeholder="Enter your username")
|
| 494 |
+
password = st.text_input("Password", type="password", placeholder="Enter your password")
|
| 495 |
+
submit = st.form_submit_button("Login", use_container_width=True, type="primary")
|
| 496 |
+
|
| 497 |
+
if submit:
|
| 498 |
+
if login(username, password):
|
| 499 |
+
st.success("Login successful!")
|
| 500 |
+
st.rerun()
|
| 501 |
+
else:
|
| 502 |
+
st.error("Invalid username or password")
|
| 503 |
+
|
| 504 |
+
st.divider()
|
| 505 |
+
if st.button("Don't have an account? Sign Up", use_container_width=True):
|
| 506 |
+
st.session_state.current_page = "signup"
|
| 507 |
+
st.rerun()
|
| 508 |
+
|
| 509 |
+
def show_signup_page():
|
| 510 |
+
col1, col2, col3 = st.columns([1, 2, 1])
|
| 511 |
+
with col2:
|
| 512 |
+
st.title("🏦 Central Bank AI")
|
| 513 |
+
st.subheader("Create Account")
|
| 514 |
+
st.divider()
|
| 515 |
+
|
| 516 |
+
with st.form("signup_form"):
|
| 517 |
+
username = st.text_input("Username", placeholder="Choose a username")
|
| 518 |
+
email = st.text_input("Email", placeholder="Enter your email")
|
| 519 |
+
password = st.text_input("Password", type="password", placeholder="Create a password")
|
| 520 |
+
confirm_password = st.text_input("Confirm Password", type="password", placeholder="Re-enter your password")
|
| 521 |
+
submit = st.form_submit_button("Create Account", use_container_width=True, type="primary")
|
| 522 |
+
|
| 523 |
+
if submit:
|
| 524 |
+
if not username or not email or not password or not confirm_password:
|
| 525 |
+
st.error("All fields are required")
|
| 526 |
+
elif password != confirm_password:
|
| 527 |
+
st.error("Passwords do not match")
|
| 528 |
+
else:
|
| 529 |
+
success, msg = signup(username, email, password)
|
| 530 |
+
if success:
|
| 531 |
+
st.success(msg)
|
| 532 |
+
st.info("Please login with your credentials")
|
| 533 |
+
st.session_state.current_page = "login"
|
| 534 |
+
time.sleep(1)
|
| 535 |
+
st.rerun()
|
| 536 |
+
else:
|
| 537 |
+
st.error(msg)
|
| 538 |
+
|
| 539 |
+
st.divider()
|
| 540 |
+
if st.button("Already have an account? Login", use_container_width=True):
|
| 541 |
+
st.session_state.current_page = "login"
|
| 542 |
+
st.rerun()
|
| 543 |
+
|
| 544 |
+
def show_dashboard():
|
| 545 |
+
with st.sidebar:
|
| 546 |
+
st.markdown(f"""
|
| 547 |
+
<div style="padding: 0 0 1rem 0; text-align: center;">
|
| 548 |
+
<div style="font-size: 2.5rem; margin-bottom: 0.5rem;">🏦</div>
|
| 549 |
+
<h2 style="margin: 0; font-size: 1.3rem !important; font-family: 'Poppins', sans-serif;">Central Bank AI</h2>
|
| 550 |
+
</div>
|
| 551 |
+
""", unsafe_allow_html=True)
|
| 552 |
+
|
| 553 |
+
# User Info Section (New CSS Class)
|
| 554 |
+
st.markdown(f"""
|
| 555 |
+
<div class="user-card">
|
| 556 |
+
<div class="user-avatar">
|
| 557 |
+
{st.session_state.username[0].upper() if st.session_state.username else 'U'}
|
| 558 |
+
</div>
|
| 559 |
+
<div>
|
| 560 |
+
<div class="user-name">{st.session_state.username}</div>
|
| 561 |
+
<div class="user-email">{st.session_state.email}</div>
|
| 562 |
+
</div>
|
| 563 |
+
</div>
|
| 564 |
+
""", unsafe_allow_html=True)
|
| 565 |
+
|
| 566 |
+
if "current_tab" not in st.session_state:
|
| 567 |
+
st.session_state.current_tab = "Dashboard"
|
| 568 |
+
|
| 569 |
+
st.markdown("<div class='section-title'>Navigation</div>", unsafe_allow_html=True)
|
| 570 |
+
|
| 571 |
+
nav_btn_style1 = "primary" if st.session_state.current_tab == "Dashboard" else "secondary"
|
| 572 |
+
nav_btn_style2 = "primary" if st.session_state.current_tab == "Banking Assistant" else "secondary"
|
| 573 |
+
|
| 574 |
+
if st.button("📊 Dashboard", use_container_width=True, type=nav_btn_style1):
|
| 575 |
+
st.session_state.current_tab = "Dashboard"
|
| 576 |
+
st.rerun()
|
| 577 |
+
|
| 578 |
+
if st.button("� Banking Assistant", use_container_width=True, type=nav_btn_style2):
|
| 579 |
+
st.session_state.current_tab = "Banking Assistant"
|
| 580 |
+
st.rerun()
|
| 581 |
+
|
| 582 |
+
page = st.session_state.current_tab
|
| 583 |
+
|
| 584 |
+
st.markdown("<div style='margin-bottom: 24px;'></div>", unsafe_allow_html=True)
|
| 585 |
+
|
| 586 |
+
st.markdown("<div class='logout-btn'>", unsafe_allow_html=True)
|
| 587 |
+
if st.button("Logout", use_container_width=True):
|
| 588 |
+
logout()
|
| 589 |
+
st.rerun()
|
| 590 |
+
st.markdown("</div>", unsafe_allow_html=True)
|
| 591 |
+
|
| 592 |
+
# Push Chat History to the bottom
|
| 593 |
+
st.markdown("<div style='flex-grow: 1; min-height: 40px;'></div>", unsafe_allow_html=True)
|
| 594 |
+
|
| 595 |
+
st.markdown("<div class='section-title'>Recent Chats</div>", unsafe_allow_html=True)
|
| 596 |
+
|
| 597 |
+
new_col, clear_col = st.columns([1, 1])
|
| 598 |
+
with new_col:
|
| 599 |
+
if st.button("➕ New Chat", use_container_width=True):
|
| 600 |
+
st.session_state.messages = []
|
| 601 |
+
st.session_state.current_chat_id = None
|
| 602 |
+
st.rerun()
|
| 603 |
+
with clear_col:
|
| 604 |
+
if st.session_state.chat_sessions and st.button("🗑️ Clear All", use_container_width=True):
|
| 605 |
+
clear_all_chat_history(st.session_state.username, st.session_state)
|
| 606 |
+
st.session_state.messages = []
|
| 607 |
+
st.session_state.current_chat_id = None
|
| 608 |
+
st.rerun()
|
| 609 |
+
|
| 610 |
+
# Chat Sessions
|
| 611 |
+
st.markdown("<div class='chat-history-container'>", unsafe_allow_html=True)
|
| 612 |
+
if st.session_state.chat_sessions:
|
| 613 |
+
# Display only top 5 initially, or all if "show_all_chats" is True
|
| 614 |
+
if "show_all_chats" not in st.session_state:
|
| 615 |
+
st.session_state.show_all_chats = False
|
| 616 |
+
|
| 617 |
+
display_chats = st.session_state.chat_sessions if st.session_state.show_all_chats else st.session_state.chat_sessions[:5]
|
| 618 |
+
|
| 619 |
+
for chat in display_chats:
|
| 620 |
+
preview = chat.get('preview', 'No messages')
|
| 621 |
+
chat_id = chat['session_id']
|
| 622 |
+
|
| 623 |
+
chat_col1, chat_col2 = st.columns([4, 1])
|
| 624 |
+
with chat_col1:
|
| 625 |
+
if st.button(f"📄 {preview}", key=f"chat_{chat_id}", use_container_width=True):
|
| 626 |
+
st.session_state.messages = chat['messages']
|
| 627 |
+
st.session_state.current_chat_id = chat_id
|
| 628 |
+
st.rerun()
|
| 629 |
+
with chat_col2:
|
| 630 |
+
if st.button("❌", key=f"del_{chat_id}", use_container_width=True):
|
| 631 |
+
delete_chat_session(st.session_state.username, st.session_state, chat_id)
|
| 632 |
+
if st.session_state.current_chat_id == chat_id:
|
| 633 |
+
st.session_state.messages = []
|
| 634 |
+
st.session_state.current_chat_id = None
|
| 635 |
+
st.rerun()
|
| 636 |
+
|
| 637 |
+
# Show "See all" button if there are more than 5 chats
|
| 638 |
+
if len(st.session_state.chat_sessions) > 5:
|
| 639 |
+
if st.session_state.show_all_chats:
|
| 640 |
+
if st.button("See Less", use_container_width=True):
|
| 641 |
+
st.session_state.show_all_chats = False
|
| 642 |
+
st.rerun()
|
| 643 |
+
else:
|
| 644 |
+
if st.button(f"See All ({len(st.session_state.chat_sessions)})", use_container_width=True):
|
| 645 |
+
st.session_state.show_all_chats = True
|
| 646 |
+
st.rerun()
|
| 647 |
+
else:
|
| 648 |
+
st.caption("No recent chats")
|
| 649 |
+
st.markdown("</div>", unsafe_allow_html=True)
|
| 650 |
+
|
| 651 |
+
st.title("Dashboard" if page == "Dashboard" else "Banking Assistant")
|
| 652 |
+
|
| 653 |
+
if page == "Dashboard":
|
| 654 |
+
st.markdown("## 📊 Dashboard Overview")
|
| 655 |
+
|
| 656 |
+
# Custom Metric Cards
|
| 657 |
+
st.markdown(f"""
|
| 658 |
+
<div style="display: flex; gap: 20px; margin-bottom: 2rem;">
|
| 659 |
+
<div class="bank-card" style="flex: 1; text-align: center;">
|
| 660 |
+
<div style="color: {st.session_state.colors['text_secondary']}; font-size: 0.9rem; margin-bottom: 8px;">Account Balance</div>
|
| 661 |
+
<div style="font-size: 1.8rem; font-weight: 700;">{format_currency(st.session_state.balance)}</div>
|
| 662 |
+
</div>
|
| 663 |
+
<div class="bank-card" style="flex: 1; text-align: center;">
|
| 664 |
+
<div style="color: {st.session_state.colors['text_secondary']}; font-size: 0.9rem; margin-bottom: 8px;">Interest Rate</div>
|
| 665 |
+
<div style="font-size: 1.8rem; font-weight: 700;">{st.session_state.interest_rate}%</div>
|
| 666 |
+
</div>
|
| 667 |
+
<div class="bank-card" style="flex: 1; text-align: center;">
|
| 668 |
+
<div style="color: {st.session_state.colors['text_secondary']}; font-size: 0.9rem; margin-bottom: 8px;">Active Loans</div>
|
| 669 |
+
<div style="font-size: 1.8rem; font-size: 1.8rem; font-weight: 700;">{st.session_state.active_loans}</div>
|
| 670 |
+
</div>
|
| 671 |
+
</div>
|
| 672 |
+
""", unsafe_allow_html=True)
|
| 673 |
+
|
| 674 |
+
st.markdown("<div style='margin-bottom: 2rem;'></div>", unsafe_allow_html=True)
|
| 675 |
+
|
| 676 |
+
# 2 & 3. Insights & Health Score
|
| 677 |
+
col_health, col_insights = st.columns(2)
|
| 678 |
+
with col_health:
|
| 679 |
+
st.markdown(f"""
|
| 680 |
+
<div class="bank-card" style="height: 100%;">
|
| 681 |
+
<h3 style="margin-top:0;">🟢 Financial Health Score</h3>
|
| 682 |
+
<div style="font-size: 2.5rem; font-weight: 700; color: {st.session_state.colors['primary']};">78 <span style="font-size: 1rem; color: {st.session_state.colors['text_secondary']};">/ 100</span></div>
|
| 683 |
+
<div style="margin-top: 10px; font-size: 0.95rem; color: {st.session_state.colors['text_secondary']};">
|
| 684 |
+
<div style="margin-bottom: 4px;">✓ Good savings ratio</div>
|
| 685 |
+
<div style="margin-bottom: 4px;">✓ Low EMI burden</div>
|
| 686 |
+
<div>✓ Stable spending</div>
|
| 687 |
+
</div>
|
| 688 |
+
</div>
|
| 689 |
+
""", unsafe_allow_html=True)
|
| 690 |
+
|
| 691 |
+
with col_insights:
|
| 692 |
+
st.markdown(f"""
|
| 693 |
+
<div class="bank-card" style="height: 100%;">
|
| 694 |
+
<h3 style="margin-top:0;">💡 Smart Insights</h3>
|
| 695 |
+
<div style="margin-top: 15px; font-size: 0.95rem; line-height: 1.6;">
|
| 696 |
+
<div style="margin-bottom: 8px;">📈 This month your spending increased by <b>12%</b> compared to last month.</div>
|
| 697 |
+
<div style="margin-bottom: 8px;">🛍️ Most spending category: <b>Shopping</b>.</div>
|
| 698 |
+
<div>⚠️ EMI due in <b>5 days</b>.</div>
|
| 699 |
+
</div>
|
| 700 |
+
</div>
|
| 701 |
+
""", unsafe_allow_html=True)
|
| 702 |
+
|
| 703 |
+
st.markdown("<div style='margin-bottom: 1rem;'></div>", unsafe_allow_html=True)
|
| 704 |
+
|
| 705 |
+
# 4 & 5. Net Worth & Upcoming Dues
|
| 706 |
+
col_nw, col_dues = st.columns(2)
|
| 707 |
+
with col_nw:
|
| 708 |
+
st.markdown(f"""
|
| 709 |
+
<div class="bank-card" style="height: 100%;">
|
| 710 |
+
<h3 style="margin-top:0;">💎 Net Worth</h3>
|
| 711 |
+
<div style="display: flex; justify-content: space-between; margin-bottom: 8px; font-size: 1rem;">
|
| 712 |
+
<span style="color: {st.session_state.colors['text_secondary']};">Assets (Savings + FD + Investments)</span>
|
| 713 |
+
<span style="font-weight: 600; color: {st.session_state.colors['success']};">{format_currency(st.session_state.balance + 3500000)}</span>
|
| 714 |
+
</div>
|
| 715 |
+
<div style="display: flex; justify-content: space-between; margin-bottom: 16px; border-bottom: 1px solid {st.session_state.colors['border']}; padding-bottom: 12px; font-size: 1rem;">
|
| 716 |
+
<span style="color: {st.session_state.colors['text_secondary']};">Liabilities (Loans + Credit Dues)</span>
|
| 717 |
+
<span style="font-weight: 600; color: {st.session_state.colors['danger']};">{format_currency(st.session_state.total_loan_amount)}</span>
|
| 718 |
+
</div>
|
| 719 |
+
<div style="display: flex; justify-content: space-between; align-items: center;">
|
| 720 |
+
<span style="font-weight: 600; font-size: 1.1rem;">Total Net Worth</span>
|
| 721 |
+
<span style="font-size: 1.5rem; font-weight: 700; color: {st.session_state.colors['primary']};">{format_currency(st.session_state.balance + 3500000 - st.session_state.total_loan_amount)}</span>
|
| 722 |
+
</div>
|
| 723 |
+
</div>
|
| 724 |
+
""", unsafe_allow_html=True)
|
| 725 |
+
|
| 726 |
+
with col_dues:
|
| 727 |
+
st.markdown(f"""
|
| 728 |
+
<div class="bank-card" style="height: 100%;">
|
| 729 |
+
<h3 style="margin-top:0;">📅 Upcoming Payments</h3>
|
| 730 |
+
<div style="margin-top: 15px; font-size: 1rem; line-height: 1.6;">
|
| 731 |
+
<div style="display: flex; justify-content: space-between; margin-bottom: 12px;">
|
| 732 |
+
<span>Home Loan EMI</span>
|
| 733 |
+
<div><span style="font-weight: 600;">₹12,000</span> <span style="font-size: 0.85rem; color: {st.session_state.colors['text_secondary']};">due 5 Mar</span></div>
|
| 734 |
+
</div>
|
| 735 |
+
<div style="display: flex; justify-content: space-between; margin-bottom: 12px;">
|
| 736 |
+
<span>Credit Card Bill</span>
|
| 737 |
+
<div><span style="font-weight: 600;">₹8,400</span> <span style="font-size: 0.85rem; color: {st.session_state.colors['text_secondary']};">due 9 Mar</span></div>
|
| 738 |
+
</div>
|
| 739 |
+
<div style="display: flex; justify-content: space-between;">
|
| 740 |
+
<span>Electricity Bill</span>
|
| 741 |
+
<div><span style="font-weight: 600;">₹1,500</span> <span style="font-size: 0.85rem; color: {st.session_state.colors['text_secondary']};">due 2 Mar</span></div>
|
| 742 |
+
</div>
|
| 743 |
+
</div>
|
| 744 |
+
</div>
|
| 745 |
+
""", unsafe_allow_html=True)
|
| 746 |
+
|
| 747 |
+
st.divider()
|
| 748 |
+
|
| 749 |
+
# Visualizations
|
| 750 |
+
col_left, col_right = st.columns([2, 1])
|
| 751 |
+
df = get_mock_transactions()
|
| 752 |
+
|
| 753 |
+
with col_left:
|
| 754 |
+
st.write("### 📉 Income vs Expenses")
|
| 755 |
+
daily_data = df.groupby(['Date', 'Type'])['Amount'].sum().reset_index()
|
| 756 |
+
fig_bar = px.bar(
|
| 757 |
+
daily_data,
|
| 758 |
+
x='Date',
|
| 759 |
+
y='Amount',
|
| 760 |
+
color='Type',
|
| 761 |
+
barmode='group',
|
| 762 |
+
color_discrete_map={"Income": st.session_state.colors['success'], "Expense": st.session_state.colors['danger']}
|
| 763 |
+
)
|
| 764 |
+
fig_bar.update_layout(margin=dict(t=0, b=0, l=0, r=0), height=300, paper_bgcolor="rgba(0,0,0,0)", plot_bgcolor="rgba(0,0,0,0)", showlegend=True, legend=dict(orientation="h", yanchor="bottom", y=1.02, xanchor="right", x=1))
|
| 765 |
+
if st.session_state.theme == "dark":
|
| 766 |
+
fig_bar.update_layout(font_color="white")
|
| 767 |
+
else:
|
| 768 |
+
fig_bar.update_layout(font_color="black")
|
| 769 |
+
st.plotly_chart(fig_bar, use_container_width=True)
|
| 770 |
+
|
| 771 |
+
with col_right:
|
| 772 |
+
st.write("### 🍰 Expenses Breakdown")
|
| 773 |
+
expense_df = df[df['Type'] == 'Expense']
|
| 774 |
+
if expense_df.empty:
|
| 775 |
+
st.info("No expenses recorded yet.")
|
| 776 |
+
else:
|
| 777 |
+
category_data = expense_df.groupby('Category')['Amount'].sum().reset_index()
|
| 778 |
+
fig = px.pie(
|
| 779 |
+
category_data,
|
| 780 |
+
values='Amount',
|
| 781 |
+
names='Category',
|
| 782 |
+
hole=0.4,
|
| 783 |
+
color_discrete_sequence=[st.session_state.colors['primary'], st.session_state.colors['secondary'], '#38bdf8', '#818cf8', '#a78bfa', '#f472b6']
|
| 784 |
+
)
|
| 785 |
+
fig.update_layout(
|
| 786 |
+
margin=dict(t=0, b=0, l=0, r=0),
|
| 787 |
+
height=300,
|
| 788 |
+
showlegend=False
|
| 789 |
+
)
|
| 790 |
+
# Ensure transparent background for the chart
|
| 791 |
+
fig.update_layout(paper_bgcolor="rgba(0,0,0,0)", plot_bgcolor="rgba(0,0,0,0)")
|
| 792 |
+
if st.session_state.theme == "dark":
|
| 793 |
+
fig.update_layout(font_color="white")
|
| 794 |
+
else:
|
| 795 |
+
fig.update_layout(font_color="black")
|
| 796 |
+
|
| 797 |
+
st.plotly_chart(fig, use_container_width=True)
|
| 798 |
+
|
| 799 |
+
st.divider()
|
| 800 |
+
|
| 801 |
+
# Consolidated Transactions
|
| 802 |
+
st.markdown("### 📝 Recent Transaction History")
|
| 803 |
+
st.dataframe(
|
| 804 |
+
df.sort_values(by="Date", ascending=False),
|
| 805 |
+
use_container_width=True,
|
| 806 |
+
hide_index=True
|
| 807 |
+
)
|
| 808 |
+
|
| 809 |
+
else:
|
| 810 |
+
is_connected = check_ollama_connection()
|
| 811 |
+
col_h1, col_h2 = st.columns([4, 1])
|
| 812 |
+
with col_h2:
|
| 813 |
+
backend = get_active_backend()
|
| 814 |
+
if is_connected:
|
| 815 |
+
label = "☁️ Groq AI" if backend == "groq" else "🟢 Ollama"
|
| 816 |
+
st.markdown(f'<div class="status-badge status-online"><span>●</span> {label}</div>', unsafe_allow_html=True)
|
| 817 |
+
else:
|
| 818 |
+
st.markdown('<div class="status-badge status-offline"><span>○</span> Offline</div>', unsafe_allow_html=True)
|
| 819 |
+
|
| 820 |
+
st.markdown("<div style='margin-top: 1rem;'></div>", unsafe_allow_html=True)
|
| 821 |
+
|
| 822 |
+
# FAQ Suggestions
|
| 823 |
+
st.markdown("<div style='margin-bottom: 10px; font-size: 0.9rem; color: #64748b;'><strong>Popular Questions:</strong></div>", unsafe_allow_html=True)
|
| 824 |
+
|
| 825 |
+
faq_row1_col1, faq_row1_col2, faq_row1_col3 = st.columns(3)
|
| 826 |
+
with faq_row1_col1:
|
| 827 |
+
if st.button("💰 Balance?", use_container_width=True):
|
| 828 |
+
st.session_state.faq_trigger = "What is my balance?"
|
| 829 |
+
st.rerun()
|
| 830 |
+
with faq_row1_col2:
|
| 831 |
+
if st.button("📈 Interest?", use_container_width=True):
|
| 832 |
+
st.session_state.faq_trigger = "What are the current interest rates?"
|
| 833 |
+
st.rerun()
|
| 834 |
+
with faq_row1_col3:
|
| 835 |
+
if st.button("📞 Support", use_container_width=True):
|
| 836 |
+
st.session_state.faq_trigger = "How do I contact customer care?"
|
| 837 |
+
st.rerun()
|
| 838 |
+
|
| 839 |
+
faq_row2_col1, faq_row2_col2, faq_row2_col3 = st.columns(3)
|
| 840 |
+
with faq_row2_col1:
|
| 841 |
+
if st.button("🕒 Hours", use_container_width=True):
|
| 842 |
+
st.session_state.faq_trigger = "What are the working hours?"
|
| 843 |
+
st.rerun()
|
| 844 |
+
with faq_row2_col2:
|
| 845 |
+
if st.button("🏦 Min Bal", use_container_width=True):
|
| 846 |
+
st.session_state.faq_trigger = "What is the minimum balance?"
|
| 847 |
+
st.rerun()
|
| 848 |
+
with faq_row2_col3:
|
| 849 |
+
if st.button("📋 FD Rates", use_container_width=True):
|
| 850 |
+
st.session_state.faq_trigger = "What are the FD rates?"
|
| 851 |
+
st.rerun()
|
| 852 |
+
|
| 853 |
+
chat_container = st.container(height=400, border=False)
|
| 854 |
+
|
| 855 |
+
with chat_container:
|
| 856 |
+
for message in st.session_state.messages:
|
| 857 |
+
role = message["role"]
|
| 858 |
+
if role == "user":
|
| 859 |
+
st.markdown(f'<div class="user-bubble">{message["content"]}</div>', unsafe_allow_html=True)
|
| 860 |
+
else:
|
| 861 |
+
st.markdown(f'<div class="ai-bubble">{message["content"]}</div>', unsafe_allow_html=True)
|
| 862 |
+
|
| 863 |
+
prompt = st.chat_input("Ask about your finances or banking services...")
|
| 864 |
+
|
| 865 |
+
if getattr(st.session_state, 'faq_trigger', None):
|
| 866 |
+
prompt = st.session_state.faq_trigger
|
| 867 |
+
st.session_state.faq_trigger = None
|
| 868 |
+
|
| 869 |
+
if prompt:
|
| 870 |
+
st.session_state.messages.append({"role": "user", "content": prompt})
|
| 871 |
+
|
| 872 |
+
with chat_container:
|
| 873 |
+
st.markdown(f'<div class="user-bubble">{prompt}</div>', unsafe_allow_html=True)
|
| 874 |
+
|
| 875 |
+
faq_response = get_faq_response(prompt)
|
| 876 |
+
|
| 877 |
+
res_box = st.empty()
|
| 878 |
+
full_response = ""
|
| 879 |
+
|
| 880 |
+
if faq_response:
|
| 881 |
+
full_response = faq_response
|
| 882 |
+
res_box.markdown(f'<div class="ai-bubble">{full_response}</div>', unsafe_allow_html=True)
|
| 883 |
+
elif is_banking_query(prompt):
|
| 884 |
+
if check_ollama_connection():
|
| 885 |
+
last_update_time = time.time()
|
| 886 |
+
for chunk in stream_ai_response(prompt, history=st.session_state.messages[:-1]):
|
| 887 |
+
if chunk:
|
| 888 |
+
full_response += chunk
|
| 889 |
+
current_time = time.time()
|
| 890 |
+
if current_time - last_update_time > 0.05:
|
| 891 |
+
res_box.markdown(f'<div class="ai-bubble">{full_response}▌</div>', unsafe_allow_html=True)
|
| 892 |
+
last_update_time = current_time
|
| 893 |
+
|
| 894 |
+
res_box.markdown(f'<div class="ai-bubble">{full_response}</div>', unsafe_allow_html=True)
|
| 895 |
+
else:
|
| 896 |
+
full_response = "I'm having trouble reaching the AI engine right now. However, I can still help with basic queries like your balance or interest rates. How can I assist you?"
|
| 897 |
+
res_box.markdown(f'<div class="ai-bubble">{full_response}</div>', unsafe_allow_html=True)
|
| 898 |
+
else:
|
| 899 |
+
# Non-banking refusal
|
| 900 |
+
full_response = "I am a banking assistant and can only answer banking-related queries. Please feel free to ask about accounts, loans, cards, or other financial services."
|
| 901 |
+
res_box.markdown(f'<div class="ai-bubble">{full_response}</div>', unsafe_allow_html=True)
|
| 902 |
+
|
| 903 |
+
if not full_response:
|
| 904 |
+
full_response = "I'm having trouble reaching the main AI engine right now. However, I can still help with basic queries like your balance or interest rates. How can I assist you?"
|
| 905 |
+
|
| 906 |
+
st.session_state.messages.append({"role": "assistant", "content": full_response})
|
| 907 |
+
# Save using the persistent utility
|
| 908 |
+
new_id = save_chat_session(st.session_state.username, st.session_state, st.session_state.messages, st.session_state.current_chat_id)
|
| 909 |
+
if not st.session_state.current_chat_id:
|
| 910 |
+
st.session_state.current_chat_id = new_id
|
| 911 |
+
|
| 912 |
+
st.rerun()
|
| 913 |
+
|
| 914 |
+
if not st.session_state.logged_in:
|
| 915 |
+
if st.session_state.current_page == "login":
|
| 916 |
+
show_login_page()
|
| 917 |
+
elif st.session_state.current_page == "signup":
|
| 918 |
+
show_signup_page()
|
| 919 |
+
else:
|
| 920 |
+
show_dashboard()
|
data/intents.json
ADDED
|
@@ -0,0 +1,326 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"intents": [
|
| 3 |
+
{
|
| 4 |
+
"tag": "greetings",
|
| 5 |
+
"patterns": [
|
| 6 |
+
"hi",
|
| 7 |
+
"hello",
|
| 8 |
+
"hey",
|
| 9 |
+
"good morning",
|
| 10 |
+
"good afternoon",
|
| 11 |
+
"good evening",
|
| 12 |
+
"is anyone there?",
|
| 13 |
+
"hello central bank ai"
|
| 14 |
+
],
|
| 15 |
+
"responses": [
|
| 16 |
+
"Hello! Welcome to Central Bank AI. How can I assist you with your banking needs today?",
|
| 17 |
+
"Hi there! I'm your Central Bank virtual assistant. What can I do for you?"
|
| 18 |
+
]
|
| 19 |
+
},
|
| 20 |
+
{
|
| 21 |
+
"tag": "goodbye",
|
| 22 |
+
"patterns": [
|
| 23 |
+
"bye",
|
| 24 |
+
"goodbye",
|
| 25 |
+
"see you later",
|
| 26 |
+
"thanks for the help",
|
| 27 |
+
"thank you",
|
| 28 |
+
"that's all"
|
| 29 |
+
],
|
| 30 |
+
"responses": [
|
| 31 |
+
"Thank you for choosing Central Bank. Have a great day!",
|
| 32 |
+
"Goodbye! Feel free to reach out if you have more questions."
|
| 33 |
+
]
|
| 34 |
+
},
|
| 35 |
+
{
|
| 36 |
+
"tag": "account_opening",
|
| 37 |
+
"patterns": [
|
| 38 |
+
"how to open account",
|
| 39 |
+
"create new bank account",
|
| 40 |
+
"open savings account",
|
| 41 |
+
"steps to open account",
|
| 42 |
+
"account opening procedure",
|
| 43 |
+
"documents for account",
|
| 44 |
+
"required documents",
|
| 45 |
+
"what documents do i need"
|
| 46 |
+
],
|
| 47 |
+
"responses": [
|
| 48 |
+
"You can open a savings account online via our website or mobile app by providing your Aadhaar and PAN. Alternatively, visit any branch with your ID and address proof.",
|
| 49 |
+
"To open an account, you will need a valid ID (Aadhaar/Passport), PAN card, and proof of residence."
|
| 50 |
+
]
|
| 51 |
+
},
|
| 52 |
+
{
|
| 53 |
+
"tag": "balance_enquiry",
|
| 54 |
+
"patterns": [
|
| 55 |
+
"balance",
|
| 56 |
+
"check balance",
|
| 57 |
+
"check my balance",
|
| 58 |
+
"what is my balance",
|
| 59 |
+
"account balance",
|
| 60 |
+
"how much money in my account",
|
| 61 |
+
"view balance"
|
| 62 |
+
],
|
| 63 |
+
"responses": [
|
| 64 |
+
"Your current account balance is displayed on your dashboard. You can also check it via SMS banking or by calling our toll-free number.",
|
| 65 |
+
"To view your balance, simply log in to your dashboard or use our mobile banking app."
|
| 66 |
+
]
|
| 67 |
+
},
|
| 68 |
+
{
|
| 69 |
+
"tag": "fund_transfer",
|
| 70 |
+
"patterns": [
|
| 71 |
+
"transfer money",
|
| 72 |
+
"send funds",
|
| 73 |
+
"how to transfer money",
|
| 74 |
+
"make a payment",
|
| 75 |
+
"wire transfer",
|
| 76 |
+
"neft rtgs imps"
|
| 77 |
+
],
|
| 78 |
+
"responses": [
|
| 79 |
+
"You can transfer funds via NEFT, RTGS, or IMPS through the 'Transfer Money' section in your dashboard.",
|
| 80 |
+
"For instant transfers, use IMPS or UPI. For larger amounts, NEFT or RTGS are recommended."
|
| 81 |
+
]
|
| 82 |
+
},
|
| 83 |
+
{
|
| 84 |
+
"tag": "debit_card",
|
| 85 |
+
"patterns": [
|
| 86 |
+
"debit card",
|
| 87 |
+
"apply for debit card",
|
| 88 |
+
"new debit card",
|
| 89 |
+
"block debit card",
|
| 90 |
+
"lost card"
|
| 91 |
+
],
|
| 92 |
+
"responses": [
|
| 93 |
+
"You can manage your debit card, including blocking it or requesting a new one, under the 'Cards' section of your dashboard.",
|
| 94 |
+
"If your debit card is lost or stolen, please block it immediately via the app or call 1800-111-2222."
|
| 95 |
+
]
|
| 96 |
+
},
|
| 97 |
+
{
|
| 98 |
+
"tag": "credit_card",
|
| 99 |
+
"patterns": [
|
| 100 |
+
"credit card",
|
| 101 |
+
"apply for credit card",
|
| 102 |
+
"credit card limit",
|
| 103 |
+
"pay credit card bill",
|
| 104 |
+
"credit card offers"
|
| 105 |
+
],
|
| 106 |
+
"responses": [
|
| 107 |
+
"Apply for various credit cards through our 'Cards' portal. You can also view your statements and pay bills there.",
|
| 108 |
+
"Check your eligibility for a credit card increase in the 'Card Services' section."
|
| 109 |
+
]
|
| 110 |
+
},
|
| 111 |
+
{
|
| 112 |
+
"tag": "atm_issues",
|
| 113 |
+
"patterns": [
|
| 114 |
+
"atm problem",
|
| 115 |
+
"money not dispensed",
|
| 116 |
+
"atm card stuck",
|
| 117 |
+
"atm pin reset",
|
| 118 |
+
"atm transaction failure"
|
| 119 |
+
],
|
| 120 |
+
"responses": [
|
| 121 |
+
"If cash was not dispensed but debited, it will usually be reversed within 24-48 hours. If not, please lodge a complaint in the app.",
|
| 122 |
+
"You can reset your ATM PIN at any Central Bank ATM or via Internet Banking."
|
| 123 |
+
]
|
| 124 |
+
},
|
| 125 |
+
{
|
| 126 |
+
"tag": "internet_banking",
|
| 127 |
+
"patterns": [
|
| 128 |
+
"internet banking",
|
| 129 |
+
"net banking",
|
| 130 |
+
"how to register for net banking",
|
| 131 |
+
"net banking login issues"
|
| 132 |
+
],
|
| 133 |
+
"responses": [
|
| 134 |
+
"Register for Internet Banking online using your debit card and account details on our official website.",
|
| 135 |
+
"Ensure you never share your Internet Banking password or OTP with anyone."
|
| 136 |
+
]
|
| 137 |
+
},
|
| 138 |
+
{
|
| 139 |
+
"tag": "mobile_banking",
|
| 140 |
+
"patterns": [
|
| 141 |
+
"mobile banking",
|
| 142 |
+
"banking app",
|
| 143 |
+
"how to use app",
|
| 144 |
+
"mobile app support"
|
| 145 |
+
],
|
| 146 |
+
"responses": [
|
| 147 |
+
"Download our official mobile banking app from the Play Store or App Store for secure on-the-go banking.",
|
| 148 |
+
"Our mobile app allows you to manage accounts, pay bills, and transfer funds instantly."
|
| 149 |
+
]
|
| 150 |
+
},
|
| 151 |
+
{
|
| 152 |
+
"tag": "loan_information",
|
| 153 |
+
"patterns": [
|
| 154 |
+
"loan",
|
| 155 |
+
"personal loan",
|
| 156 |
+
"home loan",
|
| 157 |
+
"car loan",
|
| 158 |
+
"loan eligibility",
|
| 159 |
+
"apply for loan"
|
| 160 |
+
],
|
| 161 |
+
"responses": [
|
| 162 |
+
"We offer a range of loans including Home, Personal, and Education loans at competitive interest rates. Apply online via the 'Loans' tab.",
|
| 163 |
+
"Check your loan eligibility and required documents in the 'Loan Center' section of your dashboard."
|
| 164 |
+
]
|
| 165 |
+
},
|
| 166 |
+
{
|
| 167 |
+
"tag": "emi_calculation",
|
| 168 |
+
"patterns": [
|
| 169 |
+
"emi",
|
| 170 |
+
"calculate emi",
|
| 171 |
+
"loan calculator",
|
| 172 |
+
"monthly installment"
|
| 173 |
+
],
|
| 174 |
+
"responses": [
|
| 175 |
+
"Use our EMI Calculator on the website to estimate your monthly installments based on loan amount, interest, and tenure.",
|
| 176 |
+
"For a ₹10 lakh loan at 9% for 5 years, your approximate EMI would be around ₹20,758."
|
| 177 |
+
]
|
| 178 |
+
},
|
| 179 |
+
{
|
| 180 |
+
"tag": "fixed_deposit",
|
| 181 |
+
"patterns": [
|
| 182 |
+
"fd",
|
| 183 |
+
"fixed deposit",
|
| 184 |
+
"fd interest rates",
|
| 185 |
+
"open fd",
|
| 186 |
+
"recurring deposit"
|
| 187 |
+
],
|
| 188 |
+
"responses": [
|
| 189 |
+
"Open a Fixed Deposit (FD) instantly via Net Banking. Current interest rates are up to 7.5% per annum.",
|
| 190 |
+
"The minimum amount to open an FD is ₹5,000, and you can choose tenures from 7 days to 10 years."
|
| 191 |
+
]
|
| 192 |
+
},
|
| 193 |
+
{
|
| 194 |
+
"tag": "kyc_update",
|
| 195 |
+
"patterns": [
|
| 196 |
+
"kyc",
|
| 197 |
+
"update kyc",
|
| 198 |
+
"kyc documents",
|
| 199 |
+
"re-kyc"
|
| 200 |
+
],
|
| 201 |
+
"responses": [
|
| 202 |
+
"Update your KYC details by uploading self-attested documents like Aadhaar and PAN via our 'Profile' section.",
|
| 203 |
+
"KYC updates are mandatory every few years. You can complete it digitally without visiting the branch."
|
| 204 |
+
]
|
| 205 |
+
},
|
| 206 |
+
{
|
| 207 |
+
"tag": "password_reset",
|
| 208 |
+
"patterns": [
|
| 209 |
+
"reset password",
|
| 210 |
+
"forgot password",
|
| 211 |
+
"change login password",
|
| 212 |
+
"password recovery"
|
| 213 |
+
],
|
| 214 |
+
"responses": [
|
| 215 |
+
"Reset your password using the 'Forgot Password' link on the login page. You'll need your registered email/mobile.",
|
| 216 |
+
"For security, we recommend changing your banking password every 90 days."
|
| 217 |
+
]
|
| 218 |
+
},
|
| 219 |
+
{
|
| 220 |
+
"tag": "upi_issues",
|
| 221 |
+
"patterns": [
|
| 222 |
+
"upi problem",
|
| 223 |
+
"upi payment failed",
|
| 224 |
+
"upi id",
|
| 225 |
+
"wrong upi transfer"
|
| 226 |
+
],
|
| 227 |
+
"responses": [
|
| 228 |
+
"If a UPI transaction fails, the amount is usually refunded within 3-5 business days. Check the transaction status in your UPI app.",
|
| 229 |
+
"You can create or manage your UPI ID in the 'Payments' section of our mobile app."
|
| 230 |
+
]
|
| 231 |
+
},
|
| 232 |
+
{
|
| 233 |
+
"tag": "transaction_failure",
|
| 234 |
+
"patterns": [
|
| 235 |
+
"transaction failed",
|
| 236 |
+
"payment declined",
|
| 237 |
+
"failed payment",
|
| 238 |
+
"money debited but not received"
|
| 239 |
+
],
|
| 240 |
+
"responses": [
|
| 241 |
+
"Payment failures can occur due to network issues or insufficient funds. Check your 'Transaction History' for details.",
|
| 242 |
+
"If money was debited for a failed transaction, it will be automatically credited back within 48-72 hours."
|
| 243 |
+
]
|
| 244 |
+
},
|
| 245 |
+
{
|
| 246 |
+
"tag": "charges_fees",
|
| 247 |
+
"patterns": [
|
| 248 |
+
"charges",
|
| 249 |
+
"bank fees",
|
| 250 |
+
"service charges",
|
| 251 |
+
"minimum balance penalty",
|
| 252 |
+
"transaction fees"
|
| 253 |
+
],
|
| 254 |
+
"responses": [
|
| 255 |
+
"View our full schedule of charges on the website under 'Rates and Services'.",
|
| 256 |
+
"Maintain a minimum average balance of ₹10,000 to avoid non-maintenance charges."
|
| 257 |
+
]
|
| 258 |
+
},
|
| 259 |
+
{
|
| 260 |
+
"tag": "cheque_book_request",
|
| 261 |
+
"patterns": [
|
| 262 |
+
"cheque book",
|
| 263 |
+
"order cheque book",
|
| 264 |
+
"stop cheque",
|
| 265 |
+
"cheque status"
|
| 266 |
+
],
|
| 267 |
+
"responses": [
|
| 268 |
+
"You can request a new cheque book through the 'Services' tab. It will be delivered within 5 working days.",
|
| 269 |
+
"To stop a cheque payment, log in to Net Banking and go to the Cheque Services section."
|
| 270 |
+
]
|
| 271 |
+
},
|
| 272 |
+
{
|
| 273 |
+
"tag": "branch_locator",
|
| 274 |
+
"patterns": [
|
| 275 |
+
"find branch",
|
| 276 |
+
"branch near me",
|
| 277 |
+
"ifsc code",
|
| 278 |
+
"bank address",
|
| 279 |
+
"branch timing"
|
| 280 |
+
],
|
| 281 |
+
"responses": [
|
| 282 |
+
"Use the 'Branch Locator' on our website to find the nearest branch and its IFSC code.",
|
| 283 |
+
"Our branches are typically open from 9:30 AM to 4:30 PM on weekdays."
|
| 284 |
+
]
|
| 285 |
+
},
|
| 286 |
+
{
|
| 287 |
+
"tag": "interest_rates",
|
| 288 |
+
"patterns": [
|
| 289 |
+
"interest",
|
| 290 |
+
"interest rates",
|
| 291 |
+
"current interest rates",
|
| 292 |
+
"savings interest",
|
| 293 |
+
"loan interest"
|
| 294 |
+
],
|
| 295 |
+
"responses": [
|
| 296 |
+
"Our current savings account interest rate is 4.0% p.a., while Fixed Deposits offer up to 7.5% p.a. depending on the tenure.",
|
| 297 |
+
"Interest rates vary by product. Savings accounts currently offer 4.0%, and personal loans start at 10.5% p.a."
|
| 298 |
+
]
|
| 299 |
+
},
|
| 300 |
+
{
|
| 301 |
+
"tag": "customer_support",
|
| 302 |
+
"patterns": [
|
| 303 |
+
"customer care",
|
| 304 |
+
"contact bank",
|
| 305 |
+
"helpline",
|
| 306 |
+
"support number",
|
| 307 |
+
"email support",
|
| 308 |
+
"call number",
|
| 309 |
+
"helpline number",
|
| 310 |
+
"support call no"
|
| 311 |
+
],
|
| 312 |
+
"responses": [
|
| 313 |
+
"Reach our 24/7 customer support at 1800-444-5555 or email us at customercare@centralbank.com.",
|
| 314 |
+
"Our help desks at branches are available during business hours for in-person assistance."
|
| 315 |
+
]
|
| 316 |
+
},
|
| 317 |
+
{
|
| 318 |
+
"tag": "fallback",
|
| 319 |
+
"patterns": [],
|
| 320 |
+
"responses": [
|
| 321 |
+
"I'm sorry, I didn't quite understand that. Could you please rephrase your banking-related question?",
|
| 322 |
+
"I'm not sure how to help with that. As a banking assistant, I can help you with accounts, loans, cards, and more. What would you like to know?"
|
| 323 |
+
]
|
| 324 |
+
}
|
| 325 |
+
]
|
| 326 |
+
}
|
ollama_integration.py
ADDED
|
@@ -0,0 +1,201 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
import requests
|
| 3 |
+
import json
|
| 4 |
+
import time
|
| 5 |
+
|
| 6 |
+
# Auto-detect backend: Groq if API key is set, otherwise Ollama
|
| 7 |
+
GROQ_API_KEY = os.environ.get("GROQ_API_KEY", "")
|
| 8 |
+
USE_GROQ = bool(GROQ_API_KEY)
|
| 9 |
+
|
| 10 |
+
BANKING_KEYWORDS = [
|
| 11 |
+
"account", "loan", "card", "balance",
|
| 12 |
+
"transfer", "bank", "interest", "emi",
|
| 13 |
+
"credit", "debit", "kyc", "upi", "cheque",
|
| 14 |
+
"deposit", "fd", "rd", "branch", "ifsc",
|
| 15 |
+
"transaction", "payment", "savings", "checking",
|
| 16 |
+
"mortgage", "investment", "fintech", "wallet",
|
| 17 |
+
"rate", "rates", "support", "customer", "care",
|
| 18 |
+
"help", "contact", "helpline", "number", "call",
|
| 19 |
+
"document", "required", "identity", "proof", "open"
|
| 20 |
+
]
|
| 21 |
+
|
| 22 |
+
SYSTEM_PROMPT = """You are BankBot, a professional banking assistant.
|
| 23 |
+
You ONLY answer banking-related questions.
|
| 24 |
+
If the question is not related to banking, politely refuse.
|
| 25 |
+
Never answer questions about politics, sports, entertainment, coding, or personal advice.
|
| 26 |
+
Please provide clear, professional, and helpful responses."""
|
| 27 |
+
|
| 28 |
+
|
| 29 |
+
def is_banking_query(user_input):
|
| 30 |
+
input_lower = user_input.lower()
|
| 31 |
+
return any(word in input_lower for word in BANKING_KEYWORDS)
|
| 32 |
+
|
| 33 |
+
|
| 34 |
+
def get_active_backend():
|
| 35 |
+
"""Returns 'groq' if GROQ_API_KEY is set, otherwise 'ollama'."""
|
| 36 |
+
return "groq" if USE_GROQ else "ollama"
|
| 37 |
+
|
| 38 |
+
|
| 39 |
+
# ─── Groq AI Functions ────────────────────────────────────────────────────────
|
| 40 |
+
|
| 41 |
+
def get_groq_response(prompt, history=None, model="llama-3.3-70b-versatile"):
|
| 42 |
+
"""Fetches a response from Groq AI API."""
|
| 43 |
+
try:
|
| 44 |
+
from groq import Groq
|
| 45 |
+
client = Groq(api_key=GROQ_API_KEY)
|
| 46 |
+
|
| 47 |
+
messages = [{"role": "system", "content": SYSTEM_PROMPT}]
|
| 48 |
+
|
| 49 |
+
if history:
|
| 50 |
+
for msg in history[-5:]:
|
| 51 |
+
if msg.get("role") and msg.get("content"):
|
| 52 |
+
messages.append({"role": msg["role"], "content": msg["content"]})
|
| 53 |
+
|
| 54 |
+
messages.append({"role": "user", "content": prompt})
|
| 55 |
+
|
| 56 |
+
response = client.chat.completions.create(
|
| 57 |
+
model=model,
|
| 58 |
+
messages=messages,
|
| 59 |
+
temperature=0.1,
|
| 60 |
+
max_tokens=1000,
|
| 61 |
+
)
|
| 62 |
+
return response.choices[0].message.content
|
| 63 |
+
except Exception as e:
|
| 64 |
+
print(f"Groq Error: {e}")
|
| 65 |
+
return None
|
| 66 |
+
|
| 67 |
+
|
| 68 |
+
def stream_groq_response(prompt, history=None, model="llama-3.3-70b-versatile"):
|
| 69 |
+
"""Yields streamed response chunks from Groq AI API."""
|
| 70 |
+
try:
|
| 71 |
+
from groq import Groq
|
| 72 |
+
client = Groq(api_key=GROQ_API_KEY)
|
| 73 |
+
|
| 74 |
+
messages = [{"role": "system", "content": SYSTEM_PROMPT}]
|
| 75 |
+
|
| 76 |
+
if history:
|
| 77 |
+
for msg in history[-5:]:
|
| 78 |
+
if msg.get("role") and msg.get("content"):
|
| 79 |
+
messages.append({"role": msg["role"], "content": msg["content"]})
|
| 80 |
+
|
| 81 |
+
messages.append({"role": "user", "content": prompt})
|
| 82 |
+
|
| 83 |
+
stream = client.chat.completions.create(
|
| 84 |
+
model=model,
|
| 85 |
+
messages=messages,
|
| 86 |
+
temperature=0.1,
|
| 87 |
+
max_tokens=1000,
|
| 88 |
+
stream=True,
|
| 89 |
+
)
|
| 90 |
+
for chunk in stream:
|
| 91 |
+
content = chunk.choices[0].delta.content
|
| 92 |
+
if content:
|
| 93 |
+
yield content
|
| 94 |
+
except Exception as e:
|
| 95 |
+
print(f"Groq Stream Error: {e}")
|
| 96 |
+
yield None
|
| 97 |
+
|
| 98 |
+
|
| 99 |
+
# ─── Ollama Functions ─────────────────────────────────────────────────────────
|
| 100 |
+
|
| 101 |
+
def get_ollama_response(prompt, history=None, model="llama3:latest"):
|
| 102 |
+
"""Fetches a response from a local Ollama instance."""
|
| 103 |
+
url = "http://127.0.0.1:11434/api/chat"
|
| 104 |
+
messages = [{"role": "system", "content": SYSTEM_PROMPT}]
|
| 105 |
+
|
| 106 |
+
if history:
|
| 107 |
+
for msg in history[-5:]:
|
| 108 |
+
if msg.get("role") and msg.get("content"):
|
| 109 |
+
messages.append({"role": msg["role"], "content": msg["content"]})
|
| 110 |
+
|
| 111 |
+
messages.append({"role": "user", "content": prompt})
|
| 112 |
+
|
| 113 |
+
payload = {
|
| 114 |
+
"model": model,
|
| 115 |
+
"messages": messages,
|
| 116 |
+
"stream": False,
|
| 117 |
+
"options": {"temperature": 0.1, "top_p": 0.9, "num_predict": 1000}
|
| 118 |
+
}
|
| 119 |
+
|
| 120 |
+
try:
|
| 121 |
+
response = requests.post(url, json=payload, timeout=90)
|
| 122 |
+
response.raise_for_status()
|
| 123 |
+
data = response.json()
|
| 124 |
+
return data.get("message", {}).get("content", "")
|
| 125 |
+
except Exception as e:
|
| 126 |
+
print(f"Ollama Error: {e}")
|
| 127 |
+
if model == "llama3:latest":
|
| 128 |
+
return get_ollama_response(prompt, history, model="llama3")
|
| 129 |
+
return None
|
| 130 |
+
|
| 131 |
+
|
| 132 |
+
def stream_ollama_response(prompt, history=None, model="llama3:latest"):
|
| 133 |
+
"""Yields chunks of the response from a local Ollama instance for streaming."""
|
| 134 |
+
url = "http://127.0.0.1:11434/api/chat"
|
| 135 |
+
messages = [{"role": "system", "content": SYSTEM_PROMPT}]
|
| 136 |
+
|
| 137 |
+
if history:
|
| 138 |
+
for msg in history[-5:]:
|
| 139 |
+
if msg.get("role") and msg.get("content"):
|
| 140 |
+
messages.append({"role": msg["role"], "content": msg["content"]})
|
| 141 |
+
|
| 142 |
+
messages.append({"role": "user", "content": prompt})
|
| 143 |
+
|
| 144 |
+
payload = {
|
| 145 |
+
"model": model,
|
| 146 |
+
"messages": messages,
|
| 147 |
+
"stream": True,
|
| 148 |
+
"options": {"temperature": 0.1, "top_p": 0.9, "num_predict": 1000}
|
| 149 |
+
}
|
| 150 |
+
|
| 151 |
+
try:
|
| 152 |
+
response = requests.post(url, json=payload, timeout=90, stream=True)
|
| 153 |
+
response.raise_for_status()
|
| 154 |
+
|
| 155 |
+
for line in response.iter_lines():
|
| 156 |
+
if line:
|
| 157 |
+
chunk = json.loads(line)
|
| 158 |
+
if 'message' in chunk and 'content' in chunk['message']:
|
| 159 |
+
yield chunk['message']['content']
|
| 160 |
+
if chunk.get('done'):
|
| 161 |
+
break
|
| 162 |
+
except Exception as e:
|
| 163 |
+
print(f"Ollama Stream Error: {e}")
|
| 164 |
+
if model == "llama3:latest":
|
| 165 |
+
yield from stream_ollama_response(prompt, history, model="llama3")
|
| 166 |
+
else:
|
| 167 |
+
yield None
|
| 168 |
+
|
| 169 |
+
|
| 170 |
+
# ─── Unified Wrapper Functions ────────────────────────────────────────────────
|
| 171 |
+
|
| 172 |
+
def get_ai_response(prompt, history=None):
|
| 173 |
+
"""Auto-selects Groq or Ollama based on environment."""
|
| 174 |
+
if USE_GROQ:
|
| 175 |
+
return get_groq_response(prompt, history)
|
| 176 |
+
return get_ollama_response(prompt, history)
|
| 177 |
+
|
| 178 |
+
|
| 179 |
+
def stream_ai_response(prompt, history=None):
|
| 180 |
+
"""Auto-selects streaming from Groq or Ollama based on environment."""
|
| 181 |
+
if USE_GROQ:
|
| 182 |
+
yield from stream_groq_response(prompt, history)
|
| 183 |
+
else:
|
| 184 |
+
yield from stream_ollama_response(prompt, history)
|
| 185 |
+
|
| 186 |
+
|
| 187 |
+
def check_ollama_connection():
|
| 188 |
+
"""Checks if the local Ollama server is running."""
|
| 189 |
+
if USE_GROQ:
|
| 190 |
+
return True
|
| 191 |
+
try:
|
| 192 |
+
response = requests.get("http://127.0.0.1:11434/", timeout=2)
|
| 193 |
+
return response.status_code == 200
|
| 194 |
+
except:
|
| 195 |
+
return False
|
| 196 |
+
|
| 197 |
+
|
| 198 |
+
def rewrite_banking_response(predefined_answer):
|
| 199 |
+
"""Uses the active AI backend to rewrite a predefined FAQ response."""
|
| 200 |
+
prompt = f"Rewrite this banking answer to be complete and detailed according to all formal rules:\n\n{predefined_answer}"
|
| 201 |
+
return get_ai_response(prompt)
|
requirements.txt
CHANGED
|
Binary files a/requirements.txt and b/requirements.txt differ
|
|
|
users.json
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
{"admin": {"email": "admin@gmail.com", "password": "Admin@123"}, "mohsin": {"email": "mohsin@gmail.com", "password": "Mohsin@123"}}
|
utils.py
ADDED
|
@@ -0,0 +1,231 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import re
|
| 2 |
+
import requests
|
| 3 |
+
from datetime import datetime
|
| 4 |
+
import uuid
|
| 5 |
+
import json
|
| 6 |
+
import os
|
| 7 |
+
import random
|
| 8 |
+
import streamlit as st
|
| 9 |
+
from ollama_integration import (
|
| 10 |
+
get_ollama_response,
|
| 11 |
+
stream_ollama_response,
|
| 12 |
+
get_ai_response,
|
| 13 |
+
stream_ai_response,
|
| 14 |
+
get_active_backend,
|
| 15 |
+
rewrite_banking_response,
|
| 16 |
+
is_banking_query
|
| 17 |
+
)
|
| 18 |
+
|
| 19 |
+
USER_FILE = "users.json"
|
| 20 |
+
SESSION_FILE = "session.json"
|
| 21 |
+
HISTORY_FILE = "chat_history.json"
|
| 22 |
+
INTENTS_FILE = os.path.join("data", "intents.json")
|
| 23 |
+
|
| 24 |
+
@st.cache_data
|
| 25 |
+
def load_intents():
|
| 26 |
+
if not os.path.exists(INTENTS_FILE):
|
| 27 |
+
return {"intents": []}
|
| 28 |
+
try:
|
| 29 |
+
with open(INTENTS_FILE, "r") as f:
|
| 30 |
+
return json.load(f)
|
| 31 |
+
except Exception as e:
|
| 32 |
+
print(f"Error loading intents: {e}")
|
| 33 |
+
return {"intents": []}
|
| 34 |
+
|
| 35 |
+
# Global intents data, initialized from cached function
|
| 36 |
+
intents_data = load_intents()
|
| 37 |
+
|
| 38 |
+
def persist_user(username, email, password):
|
| 39 |
+
users = get_persisted_users()
|
| 40 |
+
users[username] = {"email": email, "password": password}
|
| 41 |
+
with open(USER_FILE, "w") as f:
|
| 42 |
+
json.dump(users, f)
|
| 43 |
+
|
| 44 |
+
def get_persisted_users():
|
| 45 |
+
if not os.path.exists(USER_FILE):
|
| 46 |
+
return {}
|
| 47 |
+
try:
|
| 48 |
+
with open(USER_FILE, "r") as f:
|
| 49 |
+
return json.load(f)
|
| 50 |
+
except:
|
| 51 |
+
return {}
|
| 52 |
+
|
| 53 |
+
def save_active_session(username):
|
| 54 |
+
with open(SESSION_FILE, "w") as f:
|
| 55 |
+
json.dump({"username": username}, f)
|
| 56 |
+
|
| 57 |
+
def get_active_session():
|
| 58 |
+
if not os.path.exists(SESSION_FILE):
|
| 59 |
+
return None
|
| 60 |
+
try:
|
| 61 |
+
with open(SESSION_FILE, "r") as f:
|
| 62 |
+
data = json.load(f)
|
| 63 |
+
return data.get("username")
|
| 64 |
+
except:
|
| 65 |
+
return None
|
| 66 |
+
|
| 67 |
+
def clear_active_session():
|
| 68 |
+
if os.path.exists(SESSION_FILE):
|
| 69 |
+
os.remove(SESSION_FILE)
|
| 70 |
+
|
| 71 |
+
def validate_email(email):
|
| 72 |
+
pattern = r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$'
|
| 73 |
+
return re.match(pattern, email) is not None
|
| 74 |
+
|
| 75 |
+
def validate_password_strength(password):
|
| 76 |
+
if len(password) < 8:
|
| 77 |
+
return False, "Password must be at least 8 characters long"
|
| 78 |
+
|
| 79 |
+
if not re.search(r'[A-Z]', password):
|
| 80 |
+
return False, "Password must contain at least one uppercase letter"
|
| 81 |
+
|
| 82 |
+
if not re.search(r'[a-z]', password):
|
| 83 |
+
return False, "Password must contain at least one lowercase letter"
|
| 84 |
+
|
| 85 |
+
if not re.search(r'\d', password):
|
| 86 |
+
return False, "Password must contain at least one number"
|
| 87 |
+
|
| 88 |
+
if not re.search(r'[!@#$%^&*(),.?":{}|<>]', password):
|
| 89 |
+
return False, "Password must contain at least one special character"
|
| 90 |
+
|
| 91 |
+
return True, "Password is strong"
|
| 92 |
+
|
| 93 |
+
def format_currency(amount):
|
| 94 |
+
return f"₹{amount:,.2f}"
|
| 95 |
+
|
| 96 |
+
def get_timestamp():
|
| 97 |
+
return datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
| 98 |
+
|
| 99 |
+
def generate_session_id():
|
| 100 |
+
return str(uuid.uuid4())
|
| 101 |
+
|
| 102 |
+
def get_chat_preview(messages, max_length=50):
|
| 103 |
+
if not messages:
|
| 104 |
+
return "Empty chat"
|
| 105 |
+
|
| 106 |
+
for msg in messages:
|
| 107 |
+
if msg["role"] == "user":
|
| 108 |
+
content = msg["content"]
|
| 109 |
+
if len(content) > max_length:
|
| 110 |
+
return content[:max_length] + "..."
|
| 111 |
+
return content
|
| 112 |
+
|
| 113 |
+
return "No user messages"
|
| 114 |
+
|
| 115 |
+
@st.cache_data(ttl=30)
|
| 116 |
+
def load_history_file():
|
| 117 |
+
if not os.path.exists(HISTORY_FILE):
|
| 118 |
+
return {}
|
| 119 |
+
try:
|
| 120 |
+
with open(HISTORY_FILE, "r") as f:
|
| 121 |
+
return json.load(f)
|
| 122 |
+
except:
|
| 123 |
+
return {}
|
| 124 |
+
|
| 125 |
+
def save_history_file(history):
|
| 126 |
+
with open(HISTORY_FILE, "w") as f:
|
| 127 |
+
json.dump(history, f, indent=4)
|
| 128 |
+
|
| 129 |
+
def get_all_chat_sessions(username):
|
| 130 |
+
history = load_history_file()
|
| 131 |
+
return history.get(username, [])
|
| 132 |
+
|
| 133 |
+
def save_chat_session(username, session_state, messages, session_id=None):
|
| 134 |
+
if not messages or len(messages) == 0:
|
| 135 |
+
return None
|
| 136 |
+
|
| 137 |
+
history = load_history_file()
|
| 138 |
+
user_sessions = history.get(username, [])
|
| 139 |
+
|
| 140 |
+
if session_id:
|
| 141 |
+
# Update existing session
|
| 142 |
+
found = False
|
| 143 |
+
for session in user_sessions:
|
| 144 |
+
if session["session_id"] == session_id:
|
| 145 |
+
session["messages"] = messages
|
| 146 |
+
session["preview"] = get_chat_preview(messages)
|
| 147 |
+
session["timestamp"] = get_timestamp()
|
| 148 |
+
found = True
|
| 149 |
+
break
|
| 150 |
+
|
| 151 |
+
# Also update in-memory session_state for immediate UI feedback
|
| 152 |
+
for session in session_state.chat_sessions:
|
| 153 |
+
if session["session_id"] == session_id:
|
| 154 |
+
session["messages"] = messages
|
| 155 |
+
session["preview"] = get_chat_preview(messages)
|
| 156 |
+
session["timestamp"] = get_timestamp()
|
| 157 |
+
break
|
| 158 |
+
else:
|
| 159 |
+
# Create new session
|
| 160 |
+
session_id = generate_session_id()
|
| 161 |
+
new_session = {
|
| 162 |
+
"session_id": session_id,
|
| 163 |
+
"timestamp": get_timestamp(),
|
| 164 |
+
"messages": messages,
|
| 165 |
+
"preview": get_chat_preview(messages)
|
| 166 |
+
}
|
| 167 |
+
user_sessions.insert(0, new_session)
|
| 168 |
+
|
| 169 |
+
if "chat_sessions" not in session_state:
|
| 170 |
+
session_state.chat_sessions = []
|
| 171 |
+
session_state.chat_sessions.insert(0, new_session)
|
| 172 |
+
|
| 173 |
+
history[username] = user_sessions
|
| 174 |
+
save_history_file(history)
|
| 175 |
+
return session_id
|
| 176 |
+
|
| 177 |
+
def load_chat_session(username, session_id):
|
| 178 |
+
user_sessions = get_all_chat_sessions(username)
|
| 179 |
+
for session in user_sessions:
|
| 180 |
+
if session["session_id"] == session_id:
|
| 181 |
+
return session["messages"]
|
| 182 |
+
return None
|
| 183 |
+
|
| 184 |
+
def delete_chat_session(username, session_state, session_id):
|
| 185 |
+
history = load_history_file()
|
| 186 |
+
user_sessions = history.get(username, [])
|
| 187 |
+
|
| 188 |
+
user_sessions = [s for s in user_sessions if s["session_id"] != session_id]
|
| 189 |
+
history[username] = user_sessions
|
| 190 |
+
save_history_file(history)
|
| 191 |
+
|
| 192 |
+
if "chat_sessions" in session_state:
|
| 193 |
+
session_state.chat_sessions = [s for s in session_state.chat_sessions if s["session_id"] != session_id]
|
| 194 |
+
return True
|
| 195 |
+
|
| 196 |
+
def clear_all_chat_history(username, session_state):
|
| 197 |
+
history = load_history_file()
|
| 198 |
+
history[username] = []
|
| 199 |
+
save_history_file(history)
|
| 200 |
+
|
| 201 |
+
session_state.chat_sessions = []
|
| 202 |
+
return True
|
| 203 |
+
|
| 204 |
+
@st.cache_data(ttl=10)
|
| 205 |
+
def check_ollama_connection():
|
| 206 |
+
from ollama_integration import check_ollama_connection as _check
|
| 207 |
+
return _check()
|
| 208 |
+
|
| 209 |
+
def get_faq_response(prompt):
|
| 210 |
+
"""
|
| 211 |
+
Checks if the user's prompt matches any common frequently asked questions
|
| 212 |
+
using the structured intents.json data.
|
| 213 |
+
"""
|
| 214 |
+
prompt_lower = prompt.lower().strip()
|
| 215 |
+
|
| 216 |
+
if not intents_data or "intents" not in intents_data:
|
| 217 |
+
return None
|
| 218 |
+
|
| 219 |
+
# Iterate through intents to find a matching pattern
|
| 220 |
+
for intent in intents_data["intents"]:
|
| 221 |
+
for pattern in intent["patterns"]:
|
| 222 |
+
p_lower = pattern.lower()
|
| 223 |
+
# For short patterns (like 'hi'), use word boundary check
|
| 224 |
+
if len(p_lower) <= 3:
|
| 225 |
+
if re.search(rf"\b{re.escape(p_lower)}\b", prompt_lower):
|
| 226 |
+
return random.choice(intent["responses"])
|
| 227 |
+
# For longer patterns, substring match is usually fine and more flexible
|
| 228 |
+
elif p_lower in prompt_lower:
|
| 229 |
+
return random.choice(intent["responses"])
|
| 230 |
+
|
| 231 |
+
return None
|