alaselababatunde commited on
Commit
73b40a8
·
1 Parent(s): 9461c9e
.dockerignore ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ frontend/
2
+ .git/
3
+ .venv/
4
+ __pycache__/
5
+ *.pdf
6
+ !Internet-banking-manual.pdf
7
+ !Product-manual.pdf
8
+ !UBA-Service-Charges.pdf
9
+ node_modules/
10
+ .env
.env ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ XAI_API_KEY=xai-Q6zYpn9C8ReN8bGZmp8QIgvyv8ZNee9UusbqUFpT4zPQpblvd1RTerx1mgRtu6L1n8DkdHV5lnzGi23D
2
+ REDIS_HOST=redis
3
+ REDIS_PORT=6379
.gitattributes CHANGED
@@ -33,3 +33,4 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
 
 
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
36
+ *.pdf filter=lfs diff=lfs merge=lfs -text
Dockerfile ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ FROM python:3.10-slim
2
+
3
+ WORKDIR /app
4
+
5
+ COPY requirements.txt .
6
+ RUN pip install --no-cache-dir -r requirements.txt
7
+
8
+ COPY . .
9
+
10
+ CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]
Internet-banking-manual-updated-may-15-2020.pdf ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:6a35f668a1c22565dd656305dada55c144cb8a8d13858e9c9dbfa9c21a6ad32e
3
+ size 7031504
Product-manual.pdf ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:fdfa30bc8e7a7be351142cc6b2243cbef91f00c7eb9e02ce7a50eb7b29ccf8d4
3
+ size 7869764
UBA-Service-Charges.pdf ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:d05027700ebc129cacb7486579643cab2176c6d79df9f39f4b1948a72612bdd0
3
+ size 10840699
chat_service.py ADDED
@@ -0,0 +1,70 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from fastapi import APIRouter, Depends, HTTPException, Body
2
+ from fastapi.responses import StreamingResponse
3
+ from pydantic import BaseModel
4
+ import os
5
+ import json
6
+ import asyncio
7
+ from typing import Dict, List
8
+ import uuid
9
+ import logging
10
+
11
+ router = APIRouter()
12
+
13
+ # In-memory session storage
14
+ sessions: Dict[str, List[Dict]] = {}
15
+
16
+ # Mock XAI Client if key not present
17
+ XAI_API_KEY = os.getenv("XAI_API_KEY")
18
+
19
+ class ChatRequest(BaseModel):
20
+ session_id: str
21
+ message: str
22
+
23
+ def get_session_history(session_id: str):
24
+ if session_id not in sessions:
25
+ sessions[session_id] = [
26
+ {"role": "system", "content": "You are UBA AI Support, a professional customer service agent for UBA Bank. You are helpful, calm, and professional. Never say you are an AI."}
27
+ ]
28
+ return sessions[session_id]
29
+
30
+ async def mock_xai_stream(messages):
31
+ """Mock streaming response generator"""
32
+ response = "Thank you for contacting UBA Support. I can help you with transfers, loans, and account services. How may I assist you today?"
33
+ for word in response.split():
34
+ yield f"data: {json.dumps({'content': word + ' '})}\n\n"
35
+ await asyncio.sleep(0.1)
36
+ yield "data: [DONE]\n\n"
37
+
38
+ async def real_xai_stream(messages):
39
+ """Real XAI streaming response generator (Placeholder)"""
40
+ # TODO: Implement actual XAI client call
41
+ # This is a placeholder to show where the real logic goes
42
+ yield f"data: {json.dumps({'content': 'I am connecting to XAI... (Feature coming soon)'})}\n\n"
43
+ yield "data: [DONE]\n\n"
44
+
45
+ @router.post("/chat")
46
+ async def chat_endpoint(request: ChatRequest):
47
+ history = get_session_history(request.session_id)
48
+ history.append({"role": "user", "content": request.message})
49
+
50
+ # RAG Retrieval
51
+ from rag_service import rag_service
52
+ context = rag_service.retrieve(request.message)
53
+ if context:
54
+ system_update = f"Relevant Context from UBA Database:\n{context}\n\nUse this context to answer the user if relevant."
55
+ # Append context as specialized system message or user context
56
+ history.append({"role": "system", "content": system_update})
57
+
58
+ async def event_generator():
59
+ stream_func = real_xai_stream if XAI_API_KEY else mock_xai_stream
60
+ full_response = ""
61
+ async for chunk in stream_func(history):
62
+ if "content" in chunk:
63
+ # Extract content locally to save to history (simplified)
64
+ pass
65
+ yield chunk
66
+
67
+ # Save assistant response to history (simplified)
68
+ # history.append({"role": "assistant", "content": full_response})
69
+
70
+ return StreamingResponse(event_generator(), media_type="text/event-stream")
docker-compose.yml ADDED
@@ -0,0 +1,28 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ version: '3.8'
2
+
3
+ services:
4
+ backend:
5
+ build: .
6
+ ports:
7
+ - "8000:8000"
8
+ volumes:
9
+ - ./backend:/app
10
+ environment:
11
+ - XAI_API_KEY=\${XAI_API_KEY}
12
+ networks:
13
+ - uba_network
14
+
15
+ frontend:
16
+ build:
17
+ context: ./frontend
18
+ dockerfile: Dockerfile
19
+ ports:
20
+ - "3000:80"
21
+ depends_on:
22
+ - backend
23
+ networks:
24
+ - uba_network
25
+
26
+ networks:
27
+ uba_network:
28
+ driver: bridge
frontend/.gitignore ADDED
@@ -0,0 +1,24 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Logs
2
+ logs
3
+ *.log
4
+ npm-debug.log*
5
+ yarn-debug.log*
6
+ yarn-error.log*
7
+ pnpm-debug.log*
8
+ lerna-debug.log*
9
+
10
+ node_modules
11
+ dist
12
+ dist-ssr
13
+ *.local
14
+
15
+ # Editor directories and files
16
+ .vscode/*
17
+ !.vscode/extensions.json
18
+ .idea
19
+ .DS_Store
20
+ *.suo
21
+ *.ntvs*
22
+ *.njsproj
23
+ *.sln
24
+ *.sw?
frontend/README.md ADDED
@@ -0,0 +1,16 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # React + Vite
2
+
3
+ This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules.
4
+
5
+ Currently, two official plugins are available:
6
+
7
+ - [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react) uses [Babel](https://babeljs.io/) (or [oxc](https://oxc.rs) when used in [rolldown-vite](https://vite.dev/guide/rolldown)) for Fast Refresh
8
+ - [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh
9
+
10
+ ## React Compiler
11
+
12
+ The React Compiler is not enabled on this template because of its impact on dev & build performances. To add it, see [this documentation](https://react.dev/learn/react-compiler/installation).
13
+
14
+ ## Expanding the ESLint configuration
15
+
16
+ If you are developing a production application, we recommend using TypeScript with type-aware lint rules enabled. Check out the [TS template](https://github.com/vitejs/vite/tree/main/packages/create-vite/template-react-ts) for information on how to integrate TypeScript and [`typescript-eslint`](https://typescript-eslint.io) in your project.
frontend/eslint.config.js ADDED
@@ -0,0 +1,29 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import js from '@eslint/js'
2
+ import globals from 'globals'
3
+ import reactHooks from 'eslint-plugin-react-hooks'
4
+ import reactRefresh from 'eslint-plugin-react-refresh'
5
+ import { defineConfig, globalIgnores } from 'eslint/config'
6
+
7
+ export default defineConfig([
8
+ globalIgnores(['dist']),
9
+ {
10
+ files: ['**/*.{js,jsx}'],
11
+ extends: [
12
+ js.configs.recommended,
13
+ reactHooks.configs.flat.recommended,
14
+ reactRefresh.configs.vite,
15
+ ],
16
+ languageOptions: {
17
+ ecmaVersion: 2020,
18
+ globals: globals.browser,
19
+ parserOptions: {
20
+ ecmaVersion: 'latest',
21
+ ecmaFeatures: { jsx: true },
22
+ sourceType: 'module',
23
+ },
24
+ },
25
+ rules: {
26
+ 'no-unused-vars': ['error', { varsIgnorePattern: '^[A-Z_]' }],
27
+ },
28
+ },
29
+ ])
frontend/index.html ADDED
@@ -0,0 +1,13 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <link rel="icon" type="image/svg+xml" href="/vite.svg" />
6
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
7
+ <title>frontend</title>
8
+ </head>
9
+ <body>
10
+ <div id="root"></div>
11
+ <script type="module" src="/src/main.jsx"></script>
12
+ </body>
13
+ </html>
frontend/package-lock.json ADDED
The diff for this file is too large to render. See raw diff
 
frontend/package.json ADDED
@@ -0,0 +1,28 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "frontend",
3
+ "private": true,
4
+ "version": "0.0.0",
5
+ "type": "module",
6
+ "scripts": {
7
+ "dev": "vite",
8
+ "build": "vite build",
9
+ "lint": "eslint .",
10
+ "preview": "vite preview"
11
+ },
12
+ "dependencies": {
13
+ "react": "^19.2.0",
14
+ "react-dom": "^19.2.0",
15
+ "uuid": "^13.0.0"
16
+ },
17
+ "devDependencies": {
18
+ "@eslint/js": "^9.39.1",
19
+ "@types/react": "^19.2.5",
20
+ "@types/react-dom": "^19.2.3",
21
+ "@vitejs/plugin-react": "^5.1.1",
22
+ "eslint": "^9.39.1",
23
+ "eslint-plugin-react-hooks": "^7.0.1",
24
+ "eslint-plugin-react-refresh": "^0.4.24",
25
+ "globals": "^16.5.0",
26
+ "vite": "^7.2.4"
27
+ }
28
+ }
frontend/public/vite.svg ADDED
frontend/src/App.css ADDED
@@ -0,0 +1,42 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #root {
2
+ max-width: 1280px;
3
+ margin: 0 auto;
4
+ padding: 2rem;
5
+ text-align: center;
6
+ }
7
+
8
+ .logo {
9
+ height: 6em;
10
+ padding: 1.5em;
11
+ will-change: filter;
12
+ transition: filter 300ms;
13
+ }
14
+ .logo:hover {
15
+ filter: drop-shadow(0 0 2em #646cffaa);
16
+ }
17
+ .logo.react:hover {
18
+ filter: drop-shadow(0 0 2em #61dafbaa);
19
+ }
20
+
21
+ @keyframes logo-spin {
22
+ from {
23
+ transform: rotate(0deg);
24
+ }
25
+ to {
26
+ transform: rotate(360deg);
27
+ }
28
+ }
29
+
30
+ @media (prefers-reduced-motion: no-preference) {
31
+ a:nth-of-type(2) .logo {
32
+ animation: logo-spin infinite 20s linear;
33
+ }
34
+ }
35
+
36
+ .card {
37
+ padding: 2em;
38
+ }
39
+
40
+ .read-the-docs {
41
+ color: #888;
42
+ }
frontend/src/App.jsx ADDED
@@ -0,0 +1,134 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { useState, useRef, useEffect } from 'react';
2
+ import ubaLogo from './assets/logo.png';
3
+ import { v4 as uuidv4 } from 'uuid';
4
+
5
+ function App() {
6
+ const [messages, setMessages] = useState([
7
+ {
8
+ role: 'assistant',
9
+ content: 'Hello! Welcome to UBA Support. I can help you with account services, transfers, loans, and more. How can I assist you today?'
10
+ }
11
+ ]);
12
+ const [input, setInput] = useState('');
13
+ const [isLoading, setIsLoading] = useState(false);
14
+ const messagesEndRef = useRef(null);
15
+ const [sessionId] = useState(() => localStorage.getItem('uba_session_id') || uuidv4());
16
+
17
+ useEffect(() => {
18
+ localStorage.setItem('uba_session_id', sessionId);
19
+ }, [sessionId]);
20
+
21
+ const scrollToBottom = () => {
22
+ messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' });
23
+ };
24
+
25
+ useEffect(() => {
26
+ scrollToBottom();
27
+ }, [messages]);
28
+
29
+ const handleSubmit = async (e) => {
30
+ e.preventDefault();
31
+ if (!input.trim() || isLoading) return;
32
+
33
+ const userMessage = input.trim();
34
+ setInput('');
35
+ setMessages(prev => [...prev, { role: 'user', content: userMessage }]);
36
+ setIsLoading(true);
37
+
38
+ try {
39
+ const response = await fetch('http://localhost:8000/api/chat', {
40
+ method: 'POST',
41
+ headers: {
42
+ 'Content-Type': 'application/json',
43
+ },
44
+ body: JSON.stringify({
45
+ session_id: sessionId,
46
+ message: userMessage
47
+ }),
48
+ });
49
+
50
+ if (!response.ok) throw new Error('Network response was not ok');
51
+
52
+ const reader = response.body.getReader();
53
+ const decoder = new TextDecoder();
54
+ let assistantMessage = '';
55
+
56
+ // Add placeholder for assistant message
57
+ setMessages(prev => [...prev, { role: 'assistant', content: '' }]);
58
+
59
+ while (true) {
60
+ const { done, value } = await reader.read();
61
+ if (done) break;
62
+
63
+ const chunk = decoder.decode(value);
64
+ const lines = chunk.split('\n');
65
+
66
+ for (const line of lines) {
67
+ if (line.startsWith('data: ')) {
68
+ const data = line.slice(6);
69
+ if (data === '[DONE]') continue;
70
+
71
+ try {
72
+ const parsed = JSON.parse(data);
73
+ if (parsed.content) {
74
+ assistantMessage += parsed.content;
75
+ setMessages(prev => {
76
+ const newMessages = [...prev];
77
+ newMessages[newMessages.length - 1].content = assistantMessage;
78
+ return newMessages;
79
+ });
80
+ }
81
+ } catch (e) {
82
+ console.error('Error parsing JSON:', e);
83
+ }
84
+ }
85
+ }
86
+ }
87
+ } catch (error) {
88
+ console.error('Error:', error);
89
+ setMessages(prev => [...prev, { role: 'assistant', content: 'I apologize, but I am having trouble connecting to the server. Please check your connection and try again.' }]);
90
+ } finally {
91
+ setIsLoading(false);
92
+ }
93
+ };
94
+
95
+ return (
96
+ <div className="app-container">
97
+ <header className="uba-header">
98
+ <img src={ubaLogo} alt="UBA Logo" className="uba-logo" />
99
+ <h1>UBA AI Support</h1>
100
+ </header>
101
+
102
+ <main className="chat-container">
103
+ <div className="messages-area">
104
+ {messages.map((msg, index) => (
105
+ <div key={index} className={`message-bubble message-${msg.role}`}>
106
+ {msg.content.split('\n').map((line, i) => (
107
+ <p key={i} style={{ margin: line.trim() === '' ? '10px 0' : '5px 0' }}>
108
+ {line}
109
+ </p>
110
+ ))}
111
+ </div>
112
+ ))}
113
+ <div ref={messagesEndRef} />
114
+ </div>
115
+
116
+ <form className="input-area" onSubmit={handleSubmit}>
117
+ <input
118
+ type="text"
119
+ className="chat-input"
120
+ value={input}
121
+ onChange={(e) => setInput(e.target.value)}
122
+ placeholder="Type your message here..."
123
+ disabled={isLoading}
124
+ />
125
+ <button type="submit" className="send-button" disabled={isLoading || !input.trim()}>
126
+ {isLoading ? '...' : '➤'}
127
+ </button>
128
+ </form>
129
+ </main>
130
+ </div>
131
+ );
132
+ }
133
+
134
+ export default App;
frontend/src/assets/logo.png ADDED
frontend/src/assets/react.svg ADDED
frontend/src/index.css ADDED
@@ -0,0 +1,142 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ :root {
2
+ --uba-red: #B81104;
3
+ --uba-white: #FFFFFF;
4
+ --uba-gray: #F5F5F5;
5
+ --text-dark: #333333;
6
+ }
7
+
8
+ body {
9
+ margin: 0;
10
+ font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
11
+ -webkit-font-smoothing: antialiased;
12
+ -moz-osx-font-smoothing: grayscale;
13
+ background-color: var(--uba-gray);
14
+ color: var(--text-dark);
15
+ }
16
+
17
+ .uba-header {
18
+ background-color: var(--uba-red);
19
+ color: var(--uba-white);
20
+ padding: 1rem;
21
+ display: flex;
22
+ align-items: center;
23
+ box-shadow: 0 2px 4px rgba(0,0,0,0.1);
24
+ position: sticky;
25
+ top: 0;
26
+ z-index: 100;
27
+ }
28
+
29
+ .uba-logo {
30
+ height: 40px;
31
+ margin-right: 1rem;
32
+ background-color: white;
33
+ padding: 5px;
34
+ border-radius: 4px;
35
+ }
36
+
37
+ .chat-container {
38
+ max-width: 800px;
39
+ margin: 2rem auto;
40
+ background-color: var(--uba-white);
41
+ border-radius: 8px;
42
+ box-shadow: 0 4px 6px rgba(0,0,0,0.05);
43
+ display: flex;
44
+ flex-direction: column;
45
+ height: 80vh;
46
+ overflow: hidden;
47
+ }
48
+
49
+ .messages-area {
50
+ flex: 1;
51
+ padding: 1.5rem;
52
+ overflow-y: auto;
53
+ display: flex;
54
+ flex-direction: column;
55
+ gap: 1rem;
56
+ background-image: url('data:image/svg+xml;utf8,<svg width="100" height="100" viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg"><g fill="%23f7f7f7" fill-rule="evenodd"><path d="M11 18c3.866 0 7-3.134 7-7s-3.134-7-7-7-7 3.134-7 7 3.134 7 7 7zm48 25c3.866 0 7-3.134 7-7s-3.134-7-7-7-7 3.134-7 7 3.134 7 7 7zm-43-7c1.657 0 3-1.343 3-3s-1.343-3-3-3-3 1.343-3 3 1.343 3 3 3zm63 31c1.657 0 3-1.343 3-3s-1.343-3-3-3-3 1.343-3 3 1.343 3 3 3zM34 90c1.657 0 3-1.343 3-3s-1.343-3-3-3-3 1.343-3 3 1.343 3 3 3zm56-76c1.657 0 3-1.343 3-3s-1.343-3-3-3-3 1.343-3 3 1.343 3 3 3zM12 86c2.21 0 4-1.79 4-4s-1.79-4-4-4-4 1.79-4 4 1.79 4 4 4zm28-65c2.21 0 4-1.79 4-4s-1.79-4-4-4-4 1.79-4 4 1.79 4 4 4zm23-11c2.76 0 5-2.24 5-5s-2.24-5-5-5-5 2.24-5 5 2.24 5 5 5zm-6 60c2.21 0 4-1.79 4-4s-1.79-4-4-4-4 1.79-4 4 1.79 4 4 4zm29 22c2.76 0 5-2.24 5-5s-2.24-5-5-5-5 2.24-5 5 2.24 5 5 5zM32 63c2.76 0 5-2.24 5-5s-2.24-5-5-5-5 2.24-5 5 2.24 5 5 5zm57-13c2.76 0 5-2.24 5-5s-2.24-5-5-5-5 2.24-5 5 2.24 5 5 5zm-9-21c1.105 0 2-.895 2-2s-.895-2-2-2-2 .895-2 2 .895 2 2 2zM60 91c1.105 0 2-.895 2-2s-.895-2-2-2-2 .895-2 2 .895 2 2 2zM35 41c1.105 0 2-.895 2-2s-.895-2-2-2-2 .895-2 2 .895 2 2 2zM12 60c1.105 0 2-.895 2-2s-.895-2-2-2-2 .895-2 2 .895 2 2 2z"/></g></svg>');
57
+ }
58
+
59
+ .message-bubble {
60
+ max-width: 80%;
61
+ padding: 1rem;
62
+ border-radius: 12px;
63
+ line-height: 1.5;
64
+ position: relative;
65
+ word-wrap: break-word;
66
+ }
67
+
68
+ .message-user {
69
+ align-self: flex-end;
70
+ background-color: var(--uba-white);
71
+ color: var(--text-dark);
72
+ border: 1px solid #e0e0e0;
73
+ border-bottom-right-radius: 2px;
74
+ }
75
+
76
+ .message-assistant {
77
+ align-self: flex-start;
78
+ background-color: #FFF5F5; /* Light Red tint */
79
+ color: var(--text-dark);
80
+ border: 1px solid #ffe0e0;
81
+ border-bottom-left-radius: 2px;
82
+ }
83
+
84
+ .input-area {
85
+ padding: 1rem;
86
+ background-color: var(--uba-white);
87
+ border-top: 1px solid #eee;
88
+ display: flex;
89
+ gap: 10px;
90
+ }
91
+
92
+ .chat-input {
93
+ flex: 1;
94
+ padding: 12px;
95
+ border: 1px solid #ddd;
96
+ border-radius: 24px;
97
+ font-size: 1rem;
98
+ outline: none;
99
+ transition: border-color 0.2s;
100
+ }
101
+
102
+ .chat-input:focus {
103
+ border-color: var(--uba-red);
104
+ }
105
+
106
+ .send-button {
107
+ background-color: var(--uba-red);
108
+ color: var(--uba-white);
109
+ border: none;
110
+ width: 48px;
111
+ height: 48px;
112
+ border-radius: 50%;
113
+ cursor: pointer;
114
+ display: flex;
115
+ align-items: center;
116
+ justify-content: center;
117
+ transition: background-color 0.2s;
118
+ }
119
+
120
+ .send-button:hover {
121
+ background-color: #960e03;
122
+ }
123
+
124
+ .send-button:disabled {
125
+ background-color: #ccc;
126
+ cursor: not-allowed;
127
+ }
128
+
129
+ /* Scrollbar styling */
130
+ .messages-area::-webkit-scrollbar {
131
+ width: 8px;
132
+ }
133
+ .messages-area::-webkit-scrollbar-track {
134
+ background: #f1f1f1;
135
+ }
136
+ .messages-area::-webkit-scrollbar-thumb {
137
+ background: #ccc;
138
+ border-radius: 4px;
139
+ }
140
+ .messages-area::-webkit-scrollbar-thumb:hover {
141
+ background: #bbb;
142
+ }
frontend/src/main.jsx ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ import { StrictMode } from 'react'
2
+ import { createRoot } from 'react-dom/client'
3
+ import './index.css'
4
+ import App from './App.jsx'
5
+
6
+ createRoot(document.getElementById('root')).render(
7
+ <StrictMode>
8
+ <App />
9
+ </StrictMode>,
10
+ )
frontend/vite.config.js ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ import { defineConfig } from 'vite'
2
+ import react from '@vitejs/plugin-react'
3
+
4
+ // https://vite.dev/config/
5
+ export default defineConfig({
6
+ plugins: [react()],
7
+ })
logo.png ADDED
main.py ADDED
@@ -0,0 +1,36 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from fastapi import FastAPI, HTTPException
2
+ from fastapi.middleware.cors import CORSMiddleware
3
+ from pydantic import BaseModel
4
+ from typing import List, Optional
5
+ import os
6
+ from dotenv import load_dotenv
7
+
8
+ # Load environment variables
9
+ load_dotenv()
10
+
11
+ app = FastAPI(title="UBA AI Support", description="Backend for UBA AI Support System")
12
+
13
+ # CORS Middleware
14
+ app.add_middleware(
15
+ CORSMiddleware,
16
+ allow_origins=["*"], # In production, specify the frontend domain
17
+ allow_credentials=True,
18
+ allow_methods=["*"],
19
+ allow_headers=["*"],
20
+ )
21
+
22
+ class ChatRequest(BaseModel):
23
+ session_id: str
24
+ message: str
25
+
26
+ @app.get("/health")
27
+ async def health_check():
28
+ return {"status": "healthy"}
29
+
30
+ # Import chat router (will be created next)
31
+ from chat_service import router as chat_router
32
+ app.include_router(chat_router, prefix="/api")
33
+
34
+ if __name__ == "__main__":
35
+ import uvicorn
36
+ uvicorn.run(app, host="0.0.0.0", port=8000)
rag_service.py ADDED
@@ -0,0 +1,74 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from sentence_transformers import SentenceTransformer
2
+ import faiss
3
+ import numpy as np
4
+ import os
5
+ from pypdf import PdfReader
6
+
7
+ class RAGService:
8
+ def __init__(self):
9
+ self.documents = []
10
+ # Initialize Vector Store
11
+ self.model = SentenceTransformer('all-MiniLM-L6-v2')
12
+ self.d = 384
13
+ self.index = faiss.IndexFlatL2(self.d)
14
+
15
+ # Hardcoded knowledge base for MVP (simulating scraped content)
16
+ self.ingest_knowledge([
17
+ "UBA Group is Africa's Global Bank with presence in 20 African countries, USA, UK, UAE and France.",
18
+ "Leo is UBA's 24/7 Virtual Banker who can help with sending money, buying airtime, and paying bills.",
19
+ "You can open a UBA account online in minutes for Personal or Business banking.",
20
+ "UBA Mobile App allows you to access funds and get real-time notifications.",
21
+ "UBA offers various accounts: Savings, Current, Domiciliary, and Corporate accounts.",
22
+ "To block a card, dial *919*10# or use the Mobile App.",
23
+ "UBA's customer care email is cfc@ubagroup.com.",
24
+ "UBA code for transfer and airtime is *919#."
25
+ ])
26
+
27
+ # Load PDFs from root
28
+ self.load_pdfs()
29
+
30
+ def load_pdfs(self):
31
+ root_dir = os.path.dirname(os.path.abspath(__file__))
32
+ pdf_files = [f for f in os.listdir(root_dir) if f.lower().endswith('.pdf')]
33
+
34
+ pdf_texts = []
35
+ for pdf_file in pdf_files:
36
+ try:
37
+ path = os.path.join(root_dir, pdf_file)
38
+ reader = PdfReader(path)
39
+ text = ""
40
+ for page in reader.pages:
41
+ text += page.extract_text() + "\n"
42
+
43
+ # Chunk text roughly to avoid huge blocks
44
+ chunks = [chunk.strip() for chunk in text.split('\n\n') if chunk.strip()]
45
+ pdf_texts.extend(chunks)
46
+ print(f"Loaded {len(chunks)} chunks from {pdf_file}")
47
+ except Exception as e:
48
+ print(f"Error loading {pdf_file}: {e}")
49
+
50
+ if pdf_texts:
51
+ self.ingest_knowledge(pdf_texts)
52
+
53
+ def ingest_knowledge(self, texts: list):
54
+ if not texts:
55
+ return
56
+ embeddings = self.model.encode(texts)
57
+ self.index.add(np.array(embeddings).astype('float32'))
58
+ self.documents.extend(texts)
59
+
60
+ def retrieve(self, query: str, k: int = 3):
61
+ if self.index.ntotal == 0:
62
+ return ""
63
+
64
+ query_vector = self.model.encode([query])
65
+ D, I = self.index.search(np.array(query_vector).astype('float32'), k)
66
+
67
+ results = []
68
+ for idx in I[0]:
69
+ if idx < len(self.documents):
70
+ results.append(self.documents[idx])
71
+
72
+ return "\n".join(results)
73
+
74
+ rag_service = RAGService()
requirements.txt ADDED
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ fastapi
2
+ uvicorn
3
+ python-multipart
4
+ requests
5
+ python-dotenv
6
+ beautifulsoup4
7
+ lxml
8
+ faiss-cpu
9
+ sentence-transformers
10
+ numpy
11
+ redis
12
+ pypdf