banko-ai-assistant 1.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- banko_ai/__init__.py +19 -0
- banko_ai/__main__.py +10 -0
- banko_ai/ai_providers/__init__.py +18 -0
- banko_ai/ai_providers/aws_provider.py +337 -0
- banko_ai/ai_providers/base.py +175 -0
- banko_ai/ai_providers/factory.py +84 -0
- banko_ai/ai_providers/gemini_provider.py +340 -0
- banko_ai/ai_providers/openai_provider.py +295 -0
- banko_ai/ai_providers/watsonx_provider.py +591 -0
- banko_ai/cli.py +374 -0
- banko_ai/config/__init__.py +5 -0
- banko_ai/config/settings.py +216 -0
- banko_ai/static/Anallytics.png +0 -0
- banko_ai/static/Graph.png +0 -0
- banko_ai/static/Graph2.png +0 -0
- banko_ai/static/ai-status.png +0 -0
- banko_ai/static/banko-ai-assistant-watsonx.gif +0 -0
- banko_ai/static/banko-db-ops.png +0 -0
- banko_ai/static/banko-response.png +0 -0
- banko_ai/static/cache-stats.png +0 -0
- banko_ai/static/creditcard.png +0 -0
- banko_ai/static/profilepic.jpeg +0 -0
- banko_ai/static/query_watcher.png +0 -0
- banko_ai/static/roach-logo.svg +54 -0
- banko_ai/static/watsonx-icon.svg +1 -0
- banko_ai/templates/base.html +59 -0
- banko_ai/templates/dashboard.html +569 -0
- banko_ai/templates/index.html +1499 -0
- banko_ai/templates/login.html +41 -0
- banko_ai/utils/__init__.py +8 -0
- banko_ai/utils/cache_manager.py +525 -0
- banko_ai/utils/database.py +202 -0
- banko_ai/utils/migration.py +123 -0
- banko_ai/vector_search/__init__.py +18 -0
- banko_ai/vector_search/enrichment.py +278 -0
- banko_ai/vector_search/generator.py +329 -0
- banko_ai/vector_search/search.py +463 -0
- banko_ai/web/__init__.py +13 -0
- banko_ai/web/app.py +668 -0
- banko_ai/web/auth.py +73 -0
- banko_ai_assistant-1.0.0.dist-info/METADATA +414 -0
- banko_ai_assistant-1.0.0.dist-info/RECORD +46 -0
- banko_ai_assistant-1.0.0.dist-info/WHEEL +5 -0
- banko_ai_assistant-1.0.0.dist-info/entry_points.txt +2 -0
- banko_ai_assistant-1.0.0.dist-info/licenses/LICENSE +21 -0
- banko_ai_assistant-1.0.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,1499 @@
|
|
1
|
+
<!DOCTYPE html>
|
2
|
+
<html lang="en">
|
3
|
+
<head>
|
4
|
+
<meta charset="UTF-8">
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
6
|
+
<title>Banko AI Assistant - Powered by CockroachDB</title>
|
7
|
+
<link rel="icon" type="image/svg+xml" href="/static/roach-logo.svg">
|
8
|
+
<link rel="icon" type="image/png" href="/static/roach-logo.svg">
|
9
|
+
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap" rel="stylesheet">
|
10
|
+
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
|
11
|
+
<link rel="stylesheet" href="/static/css/style.css?v={{ range(1, 10000) | random }}">
|
12
|
+
<script src="https://cdn.tailwindcss.com"></script>
|
13
|
+
<script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
|
14
|
+
<script src="https://cdn.jsdelivr.net/npm/dompurify@2.4.0/dist/purify.min.js"></script>
|
15
|
+
|
16
|
+
<script>
|
17
|
+
tailwind.config = {
|
18
|
+
theme: {
|
19
|
+
extend: {
|
20
|
+
fontFamily: {
|
21
|
+
'inter': ['Inter', 'system-ui', 'sans-serif'],
|
22
|
+
},
|
23
|
+
colors: {
|
24
|
+
'cockroach': {
|
25
|
+
'primary': '#6933FF',
|
26
|
+
'primary-light': '#8B5CF6',
|
27
|
+
'primary-dark': '#5B21B6',
|
28
|
+
'secondary': '#10B981',
|
29
|
+
'accent': '#F59E0B',
|
30
|
+
'background': '#FFFFFF',
|
31
|
+
'surface': '#F8FAFC',
|
32
|
+
'border': '#E2E8F0',
|
33
|
+
'text': '#1E293B',
|
34
|
+
'text-secondary': '#64748B',
|
35
|
+
'success': '#10B981',
|
36
|
+
'warning': '#F59E0B',
|
37
|
+
'error': '#EF4444'
|
38
|
+
}
|
39
|
+
}
|
40
|
+
}
|
41
|
+
}
|
42
|
+
}
|
43
|
+
</script>
|
44
|
+
|
45
|
+
<style>
|
46
|
+
* {
|
47
|
+
font-family: 'Inter', system-ui, sans-serif;
|
48
|
+
}
|
49
|
+
|
50
|
+
body {
|
51
|
+
background-color: #F8FAFC;
|
52
|
+
color: #1E293B;
|
53
|
+
}
|
54
|
+
|
55
|
+
/* Custom scrollbar */
|
56
|
+
::-webkit-scrollbar {
|
57
|
+
width: 8px;
|
58
|
+
}
|
59
|
+
|
60
|
+
::-webkit-scrollbar-track {
|
61
|
+
background: #F1F5F9;
|
62
|
+
border-radius: 4px;
|
63
|
+
}
|
64
|
+
|
65
|
+
::-webkit-scrollbar-thumb {
|
66
|
+
background: #CBD5E1;
|
67
|
+
border-radius: 4px;
|
68
|
+
}
|
69
|
+
|
70
|
+
::-webkit-scrollbar-thumb:hover {
|
71
|
+
background: #94A3B8;
|
72
|
+
}
|
73
|
+
|
74
|
+
/* Focus improvements for accessibility */
|
75
|
+
button:focus-visible, select:focus-visible, input:focus-visible {
|
76
|
+
outline: 2px solid #6933FF;
|
77
|
+
outline-offset: 2px;
|
78
|
+
}
|
79
|
+
|
80
|
+
/* Smooth hover transitions */
|
81
|
+
.sidebar-item, .btn-modern {
|
82
|
+
transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);
|
83
|
+
}
|
84
|
+
|
85
|
+
/* Sidebar animations */
|
86
|
+
.sidebar-transition {
|
87
|
+
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
88
|
+
}
|
89
|
+
|
90
|
+
.sidebar-hidden {
|
91
|
+
transform: translateX(-100%);
|
92
|
+
}
|
93
|
+
|
94
|
+
.sidebar-visible {
|
95
|
+
transform: translateX(0);
|
96
|
+
}
|
97
|
+
|
98
|
+
/* Message animations */
|
99
|
+
.message-enter {
|
100
|
+
animation: messageSlideIn 0.3s ease-out;
|
101
|
+
}
|
102
|
+
|
103
|
+
@keyframes messageSlideIn {
|
104
|
+
from {
|
105
|
+
opacity: 0;
|
106
|
+
transform: translateY(10px);
|
107
|
+
}
|
108
|
+
to {
|
109
|
+
opacity: 1;
|
110
|
+
transform: translateY(0);
|
111
|
+
}
|
112
|
+
}
|
113
|
+
|
114
|
+
/* Voice recording animation */
|
115
|
+
.recording-pulse {
|
116
|
+
animation: recordingPulse 1.5s infinite;
|
117
|
+
}
|
118
|
+
|
119
|
+
@keyframes recordingPulse {
|
120
|
+
0%, 100% {
|
121
|
+
box-shadow: 0 0 0 0 rgba(239, 68, 68, 0.7);
|
122
|
+
}
|
123
|
+
50% {
|
124
|
+
box-shadow: 0 0 0 10px rgba(239, 68, 68, 0);
|
125
|
+
}
|
126
|
+
}
|
127
|
+
|
128
|
+
/* Modern button styles */
|
129
|
+
.btn-modern {
|
130
|
+
@apply px-4 py-2 rounded-lg font-medium transition-all duration-200;
|
131
|
+
}
|
132
|
+
|
133
|
+
.btn-primary {
|
134
|
+
@apply bg-cockroach-primary hover:bg-cockroach-primary-dark text-white shadow-lg hover:shadow-xl transform hover:-translate-y-0.5;
|
135
|
+
}
|
136
|
+
|
137
|
+
.btn-secondary {
|
138
|
+
@apply bg-white hover:bg-gray-50 text-cockroach-text border border-cockroach-border shadow-sm;
|
139
|
+
}
|
140
|
+
|
141
|
+
/* Input field styling */
|
142
|
+
.input-modern {
|
143
|
+
@apply bg-white border border-cockroach-border rounded-xl px-4 py-3 text-cockroach-text placeholder-cockroach-text-secondary focus:outline-none focus:ring-2 focus:ring-cockroach-primary focus:border-transparent transition-all duration-200 shadow-sm;
|
144
|
+
}
|
145
|
+
|
146
|
+
/* Message bubbles */
|
147
|
+
.message-bubble {
|
148
|
+
@apply rounded-2xl px-6 py-4 max-w-4xl shadow-sm;
|
149
|
+
}
|
150
|
+
|
151
|
+
.message-user {
|
152
|
+
@apply bg-cockroach-primary text-white ml-auto;
|
153
|
+
}
|
154
|
+
|
155
|
+
.message-assistant {
|
156
|
+
@apply bg-white text-cockroach-text border border-cockroach-border;
|
157
|
+
}
|
158
|
+
|
159
|
+
/* Sidebar styling */
|
160
|
+
.sidebar-item {
|
161
|
+
@apply flex items-center px-4 py-3 rounded-lg text-cockroach-text hover:bg-cockroach-surface transition-all duration-200 cursor-pointer;
|
162
|
+
}
|
163
|
+
|
164
|
+
.sidebar-item.active {
|
165
|
+
@apply bg-cockroach-primary text-white;
|
166
|
+
}
|
167
|
+
|
168
|
+
.sidebar-item:hover:not(.active) {
|
169
|
+
@apply bg-cockroach-surface;
|
170
|
+
}
|
171
|
+
|
172
|
+
/* Mobile overlay */
|
173
|
+
.mobile-overlay {
|
174
|
+
@apply fixed inset-0 bg-black bg-opacity-50 z-40 lg:hidden;
|
175
|
+
}
|
176
|
+
|
177
|
+
/* Audio controls */
|
178
|
+
.audio-control {
|
179
|
+
@apply p-3 rounded-lg bg-white hover:bg-gray-50 text-cockroach-text border border-cockroach-border transition-all duration-200 shadow-sm;
|
180
|
+
}
|
181
|
+
|
182
|
+
.audio-control.active {
|
183
|
+
@apply bg-cockroach-primary text-white border-cockroach-primary;
|
184
|
+
}
|
185
|
+
|
186
|
+
/* Language selector */
|
187
|
+
.language-selector {
|
188
|
+
@apply bg-white border border-cockroach-border rounded-lg px-3 py-2 text-cockroach-text focus:outline-none focus:ring-2 focus:ring-cockroach-primary shadow-sm;
|
189
|
+
}
|
190
|
+
|
191
|
+
/* Provider badges */
|
192
|
+
.provider-badge {
|
193
|
+
@apply inline-flex items-center px-3 py-1 rounded-full text-xs font-medium;
|
194
|
+
}
|
195
|
+
|
196
|
+
.badge-ai {
|
197
|
+
@apply bg-blue-50 text-blue-700 border border-blue-200;
|
198
|
+
}
|
199
|
+
|
200
|
+
.badge-db {
|
201
|
+
@apply bg-green-50 text-green-700 border border-green-200;
|
202
|
+
}
|
203
|
+
|
204
|
+
/* Copy button styling */
|
205
|
+
.copy-button {
|
206
|
+
@apply absolute top-3 right-3 p-2 rounded-lg bg-gray-100 hover:bg-gray-200 text-gray-600 hover:text-gray-800 transition-all duration-200 opacity-0 group-hover:opacity-100;
|
207
|
+
}
|
208
|
+
|
209
|
+
.copy-button:hover {
|
210
|
+
@apply bg-gray-200 text-gray-800;
|
211
|
+
}
|
212
|
+
|
213
|
+
.copy-button.copied {
|
214
|
+
@apply bg-green-100 text-green-600;
|
215
|
+
}
|
216
|
+
|
217
|
+
/* Message container for copy functionality */
|
218
|
+
.message-container {
|
219
|
+
@apply relative group;
|
220
|
+
}
|
221
|
+
|
222
|
+
/* Responsive adjustments */
|
223
|
+
@media (max-width: 1024px) {
|
224
|
+
.sidebar-mobile {
|
225
|
+
@apply fixed left-0 top-0 h-full z-50;
|
226
|
+
}
|
227
|
+
}
|
228
|
+
|
229
|
+
/* Chat container improvements */
|
230
|
+
.chat-container {
|
231
|
+
min-height: 400px;
|
232
|
+
max-height: calc(100vh - 300px);
|
233
|
+
}
|
234
|
+
|
235
|
+
/* Message spacing */
|
236
|
+
.message {
|
237
|
+
margin-bottom: 1.5rem;
|
238
|
+
}
|
239
|
+
|
240
|
+
/* Input area improvements */
|
241
|
+
.input-area {
|
242
|
+
background: white;
|
243
|
+
border-top: 1px solid #E2E8F0;
|
244
|
+
padding: 1.5rem;
|
245
|
+
}
|
246
|
+
|
247
|
+
/* Header improvements */
|
248
|
+
.header {
|
249
|
+
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
250
|
+
border-bottom: 1px solid #E2E8F0;
|
251
|
+
padding: 1.5rem;
|
252
|
+
}
|
253
|
+
|
254
|
+
/* Sidebar navigation improvements */
|
255
|
+
.sidebar-nav {
|
256
|
+
display: flex;
|
257
|
+
flex-direction: column;
|
258
|
+
gap: 0.5rem;
|
259
|
+
}
|
260
|
+
|
261
|
+
.sidebar-item {
|
262
|
+
display: flex;
|
263
|
+
align-items: center;
|
264
|
+
width: 100%;
|
265
|
+
text-decoration: none;
|
266
|
+
}
|
267
|
+
|
268
|
+
.sidebar-item i {
|
269
|
+
width: 1.25rem;
|
270
|
+
height: 1.25rem;
|
271
|
+
margin-right: 0.75rem;
|
272
|
+
flex-shrink: 0;
|
273
|
+
}
|
274
|
+
|
275
|
+
.sidebar-item span {
|
276
|
+
flex: 1;
|
277
|
+
}
|
278
|
+
|
279
|
+
/* Logo alignment fixes */
|
280
|
+
.provider-badges {
|
281
|
+
display: flex;
|
282
|
+
align-items: center;
|
283
|
+
gap: 0.75rem;
|
284
|
+
margin-top: 0.5rem;
|
285
|
+
}
|
286
|
+
|
287
|
+
.provider-badge {
|
288
|
+
display: inline-flex;
|
289
|
+
align-items: center;
|
290
|
+
height: 2rem;
|
291
|
+
}
|
292
|
+
|
293
|
+
.provider-badge img {
|
294
|
+
width: 1rem;
|
295
|
+
height: 1rem;
|
296
|
+
margin-right: 0.5rem;
|
297
|
+
flex-shrink: 0;
|
298
|
+
}
|
299
|
+
</style>
|
300
|
+
</head>
|
301
|
+
<body class="bg-cockroach-surface text-cockroach-text">
|
302
|
+
<div class="flex h-screen">
|
303
|
+
<!-- Sidebar -->
|
304
|
+
<aside id="sidebar" class="fixed lg:static left-0 top-0 h-full w-72 bg-white border-r border-cockroach-border flex flex-col z-50 shadow-lg transition-all duration-300">
|
305
|
+
<!-- Sidebar Header -->
|
306
|
+
<div class="p-6 border-b border-cockroach-border bg-gradient-to-br from-cockroach-primary to-cockroach-primary-light">
|
307
|
+
<div class="flex items-center justify-between">
|
308
|
+
<div>
|
309
|
+
<h1 class="text-2xl font-bold text-white">Banko</h1>
|
310
|
+
<p class="text-sm text-white/90 mt-1">AI Banking Assistant</p>
|
311
|
+
</div>
|
312
|
+
<button id="sidebar-close" class="lg:hidden text-white hover:text-white/80">
|
313
|
+
<i class="fas fa-times text-lg"></i>
|
314
|
+
</button>
|
315
|
+
</div>
|
316
|
+
</div>
|
317
|
+
|
318
|
+
<!-- Navigation -->
|
319
|
+
<nav class="flex-1 p-6">
|
320
|
+
<div class="space-y-2">
|
321
|
+
<a href="/home" class="sidebar-item {{ 'active' if current_page == 'home' else '' }}">
|
322
|
+
<i class="fas fa-home"></i>
|
323
|
+
<span>Home</span>
|
324
|
+
</a>
|
325
|
+
<a href="/savings" class="sidebar-item {{ 'active' if current_page == 'savings' else '' }}">
|
326
|
+
<i class="fas fa-piggy-bank"></i>
|
327
|
+
<span>Savings</span>
|
328
|
+
</a>
|
329
|
+
<a href="/wallet" class="sidebar-item {{ 'active' if current_page == 'wallet' else '' }}">
|
330
|
+
<i class="fas fa-wallet"></i>
|
331
|
+
<span>Wallet</span>
|
332
|
+
</a>
|
333
|
+
<a href="/credit-card" class="sidebar-item {{ 'active' if current_page == 'credit-card' else '' }}">
|
334
|
+
<i class="fas fa-credit-card"></i>
|
335
|
+
<span>Credit Card</span>
|
336
|
+
</a>
|
337
|
+
<a href="/statements" class="sidebar-item {{ 'active' if current_page == 'statements' else '' }}">
|
338
|
+
<i class="fas fa-file-alt"></i>
|
339
|
+
<span>Statements</span>
|
340
|
+
</a>
|
341
|
+
<a href="/banko" class="sidebar-item {{ 'active' if current_page == 'banko' else '' }}">
|
342
|
+
<i class="fas fa-robot"></i>
|
343
|
+
<span>Banko Assistant</span>
|
344
|
+
</a>
|
345
|
+
<a href="/benefits" class="sidebar-item {{ 'active' if current_page == 'benefits' else '' }}">
|
346
|
+
<i class="fas fa-star"></i>
|
347
|
+
<span>Benefits</span>
|
348
|
+
</a>
|
349
|
+
<a href="/settings" class="sidebar-item {{ 'active' if current_page == 'settings' else '' }}">
|
350
|
+
<i class="fas fa-cog"></i>
|
351
|
+
<span>Settings</span>
|
352
|
+
</a>
|
353
|
+
</div>
|
354
|
+
</nav>
|
355
|
+
|
356
|
+
<!-- Sidebar Footer -->
|
357
|
+
<div class="p-6 border-t border-cockroach-border bg-gray-50">
|
358
|
+
<div class="text-xs text-cockroach-text-secondary">
|
359
|
+
<p class="font-medium">Powered by CockroachDB</p>
|
360
|
+
<p class="mt-1">© 2025 Banko</p>
|
361
|
+
</div>
|
362
|
+
</div>
|
363
|
+
</aside>
|
364
|
+
|
365
|
+
<!-- Mobile Overlay -->
|
366
|
+
<div id="mobile-overlay" class="mobile-overlay hidden"></div>
|
367
|
+
|
368
|
+
<!-- Main Content -->
|
369
|
+
<main id="main-content" class="flex-1 flex flex-col transition-all duration-300">
|
370
|
+
<!-- Header -->
|
371
|
+
<header class="header flex items-center justify-between py-4">
|
372
|
+
<div class="flex items-center space-x-6 pl-6">
|
373
|
+
<button id="sidebar-toggle" class="lg:hidden btn-modern btn-secondary">
|
374
|
+
<i class="fas fa-bars"></i>
|
375
|
+
</button>
|
376
|
+
<h2 class="text-2xl font-bold text-white">Banko Assistant</h2>
|
377
|
+
<div class="flex items-center space-x-4 text-white/90 text-sm">
|
378
|
+
<span class="text-white font-medium">Powered in partnership with</span>
|
379
|
+
<div class="provider-badges flex items-center space-x-3">
|
380
|
+
<span class="provider-badge badge-ai flex items-center space-x-2 bg-white text-gray-800 px-3 py-1 rounded-full shadow-sm">
|
381
|
+
<img src="/static/watsonx-icon.svg" alt="IBM Watsonx" class="w-4 h-4" />
|
382
|
+
<span class="font-medium">IBM Watsonx</span>
|
383
|
+
</span>
|
384
|
+
<span class="text-white/70">×</span>
|
385
|
+
<span class="provider-badge badge-db flex items-center space-x-2 bg-white text-gray-800 px-3 py-1 rounded-full shadow-sm">
|
386
|
+
<img src="/static/roach-logo.svg" alt="CockroachDB" class="w-4 h-4" />
|
387
|
+
<span class="font-medium">CockroachDB</span>
|
388
|
+
</span>
|
389
|
+
</div>
|
390
|
+
</div>
|
391
|
+
</div>
|
392
|
+
<div class="flex items-center space-x-4 pr-6">
|
393
|
+
<button id="voice-toggle" class="btn-modern btn-secondary text-sm transition-all duration-300 hover:scale-105">
|
394
|
+
<i class="fas fa-microphone mr-2"></i>
|
395
|
+
<span id="voice-status">Voice ON</span>
|
396
|
+
</button>
|
397
|
+
|
398
|
+
|
399
|
+
<button id="db-toggle" class="btn-modern bg-blue-600 hover:bg-blue-700 text-white text-sm">
|
400
|
+
<i class="fas fa-database mr-2"></i>
|
401
|
+
<span>DB Ops</span>
|
402
|
+
<span id="db-indicator" class="ml-2 w-2 h-2 bg-green-400 rounded-full animate-pulse"></span>
|
403
|
+
</button>
|
404
|
+
<div class="w-12 h-12 rounded-full overflow-hidden border-2 border-cockroach-border">
|
405
|
+
<img src="/static/profilepic.jpeg" alt="Profile" class="w-full h-full object-cover">
|
406
|
+
</div>
|
407
|
+
</div>
|
408
|
+
</header>
|
409
|
+
|
410
|
+
<!-- Database Operations Panel -->
|
411
|
+
<div id="db-panel" class="hidden p-6 bg-gray-50 border-b border-cockroach-border">
|
412
|
+
<div class="flex items-center justify-between mb-4">
|
413
|
+
<h3 class="text-lg font-semibold text-cockroach-text">🔍 Database Operations</h3>
|
414
|
+
<button id="close-db-panel" class="text-cockroach-text-secondary hover:text-cockroach-text">
|
415
|
+
<i class="fas fa-times text-lg"></i>
|
416
|
+
</button>
|
417
|
+
</div>
|
418
|
+
<div id="db-operations" class="space-y-3 text-sm">
|
419
|
+
<!-- Operations will be displayed here -->
|
420
|
+
</div>
|
421
|
+
</div>
|
422
|
+
|
423
|
+
<!-- Chat Messages -->
|
424
|
+
<section id="chat-container" class="flex-1 overflow-y-auto p-6 space-y-6 chat-container">
|
425
|
+
{% for message in chat %}
|
426
|
+
{% if message.class == 'Assistant' %}
|
427
|
+
<div class="message-container message-enter" data-read-aloud="{{ message.text }}">
|
428
|
+
<div class="flex items-start space-x-4">
|
429
|
+
<div class="w-10 h-10 rounded-full bg-cockroach-primary flex items-center justify-center flex-shrink-0 shadow-md">
|
430
|
+
<i class="fas fa-robot text-white text-sm"></i>
|
431
|
+
</div>
|
432
|
+
<div class="message-bubble message-assistant flex-1">
|
433
|
+
<div class="markdown-content text-cockroach-text leading-relaxed" data-raw-content="{{ message.text }}">
|
434
|
+
<!-- Markdown content will be rendered here -->
|
435
|
+
</div>
|
436
|
+
</div>
|
437
|
+
</div>
|
438
|
+
<button class="copy-button" onclick="copyMessage(this, this.closest('.message-container').querySelector('.markdown-content').getAttribute('data-raw-content') || this.closest('.message-container').querySelector('p').textContent)" title="Copy message">
|
439
|
+
<i class="fas fa-copy"></i>
|
440
|
+
</button>
|
441
|
+
</div>
|
442
|
+
{% elif message.class == 'User' %}
|
443
|
+
<div class="message-container message-enter">
|
444
|
+
<div class="flex justify-end">
|
445
|
+
<div class="message-bubble message-user">
|
446
|
+
<p class="font-medium">{{ message.text }}</p>
|
447
|
+
</div>
|
448
|
+
<div class="w-10 h-10 rounded-full bg-cockroach-primary ml-4 flex items-center justify-center flex-shrink-0 shadow-md">
|
449
|
+
<i class="fas fa-user text-white text-sm"></i>
|
450
|
+
</div>
|
451
|
+
</div>
|
452
|
+
<button class="copy-button" onclick="copyMessage(this, this.closest('.message-container').querySelector('p').textContent)" title="Copy message">
|
453
|
+
<i class="fas fa-copy"></i>
|
454
|
+
</button>
|
455
|
+
</div>
|
456
|
+
{% endif %}
|
457
|
+
{% endfor %}
|
458
|
+
|
459
|
+
<!-- Loading Animation (ChatGPT-style) -->
|
460
|
+
<div id="loading-message" class="message-container hidden">
|
461
|
+
<div class="flex items-start space-x-4">
|
462
|
+
<div class="w-10 h-10 rounded-full bg-cockroach-primary flex items-center justify-center flex-shrink-0 shadow-md">
|
463
|
+
<i class="fas fa-robot text-white text-sm"></i>
|
464
|
+
</div>
|
465
|
+
<div class="message-bubble message-assistant flex-1">
|
466
|
+
<div class="loading-dots">
|
467
|
+
<span class="dot"></span>
|
468
|
+
<span class="dot"></span>
|
469
|
+
<span class="dot"></span>
|
470
|
+
</div>
|
471
|
+
</div>
|
472
|
+
</div>
|
473
|
+
</div>
|
474
|
+
</section>
|
475
|
+
|
476
|
+
<!-- Input Area -->
|
477
|
+
<footer class="input-area">
|
478
|
+
<div class="max-w-4xl mx-auto px-4">
|
479
|
+
<form id="chat-form" method="post" action="/banko">
|
480
|
+
<!-- Hidden field for language preference -->
|
481
|
+
<input type="hidden" name="response_language" id="response-language" value="en-US">
|
482
|
+
|
483
|
+
<div class="input-container">
|
484
|
+
<div class="input-wrapper">
|
485
|
+
<textarea
|
486
|
+
name="user_input"
|
487
|
+
id="message-input"
|
488
|
+
placeholder="Message Banko your personal finance assistant..."
|
489
|
+
class="input-modern"
|
490
|
+
rows="1"
|
491
|
+
autocomplete="off"></textarea>
|
492
|
+
|
493
|
+
<!-- Inline Controls -->
|
494
|
+
<div class="input-controls">
|
495
|
+
<div id="audio-controls" class="audio-controls">
|
496
|
+
<button type="button" id="voice-btn" class="control-btn" title="Voice Input">
|
497
|
+
<i class="fas fa-microphone" id="mic-icon"></i>
|
498
|
+
</button>
|
499
|
+
<button type="button" id="speaker-btn" class="control-btn" title="Read Aloud">
|
500
|
+
<i class="fas fa-volume-up" id="speaker-icon"></i>
|
501
|
+
</button>
|
502
|
+
<select id="language-select" class="language-select" title="Voice Language">
|
503
|
+
<option value="en-US">🇺🇸 EN</option>
|
504
|
+
<option value="es-ES">🇪🇸 ES</option>
|
505
|
+
<option value="fr-FR">🇫🇷 FR</option>
|
506
|
+
<option value="de-DE">🇩🇪 DE</option>
|
507
|
+
<option value="it-IT">🇮🇹 IT</option>
|
508
|
+
<option value="pt-PT">🇵🇹 PT</option>
|
509
|
+
<option value="ja-JP">🇯🇵 JP</option>
|
510
|
+
<option value="ko-KR">🇰🇷 KR</option>
|
511
|
+
<option value="zh-CN">🇨🇳 CN</option>
|
512
|
+
<option value="hi-IN">🇮🇳 HI</option>
|
513
|
+
</select>
|
514
|
+
</div>
|
515
|
+
<button type="submit" class="send-btn" title="Send Message">
|
516
|
+
<i class="fas fa-paper-plane"></i>
|
517
|
+
</button>
|
518
|
+
</div>
|
519
|
+
</div>
|
520
|
+
</div>
|
521
|
+
</form>
|
522
|
+
</div>
|
523
|
+
</footer>
|
524
|
+
</main>
|
525
|
+
</div>
|
526
|
+
|
527
|
+
<script>
|
528
|
+
// Configure marked for better security and formatting
|
529
|
+
marked.setOptions({
|
530
|
+
breaks: true,
|
531
|
+
gfm: true,
|
532
|
+
sanitize: false
|
533
|
+
});
|
534
|
+
|
535
|
+
|
536
|
+
|
537
|
+
|
538
|
+
|
539
|
+
// Copy functionality
|
540
|
+
function copyMessage(button, text) {
|
541
|
+
navigator.clipboard.writeText(text).then(() => {
|
542
|
+
// Visual feedback
|
543
|
+
const icon = button.querySelector('i');
|
544
|
+
const originalIcon = icon.className;
|
545
|
+
|
546
|
+
button.classList.add('copied');
|
547
|
+
icon.className = 'fas fa-check';
|
548
|
+
|
549
|
+
setTimeout(() => {
|
550
|
+
button.classList.remove('copied');
|
551
|
+
icon.className = originalIcon;
|
552
|
+
}, 2000);
|
553
|
+
}).catch(err => {
|
554
|
+
console.error('Failed to copy: ', err);
|
555
|
+
// Fallback for older browsers
|
556
|
+
const textArea = document.createElement('textarea');
|
557
|
+
textArea.value = text;
|
558
|
+
document.body.appendChild(textArea);
|
559
|
+
textArea.select();
|
560
|
+
document.execCommand('copy');
|
561
|
+
document.body.removeChild(textArea);
|
562
|
+
|
563
|
+
// Visual feedback
|
564
|
+
const icon = button.querySelector('i');
|
565
|
+
const originalIcon = icon.className;
|
566
|
+
|
567
|
+
button.classList.add('copied');
|
568
|
+
icon.className = 'fas fa-check';
|
569
|
+
|
570
|
+
setTimeout(() => {
|
571
|
+
button.classList.remove('copied');
|
572
|
+
icon.className = originalIcon;
|
573
|
+
}, 2000);
|
574
|
+
});
|
575
|
+
}
|
576
|
+
|
577
|
+
// Sidebar Management
|
578
|
+
class SidebarManager {
|
579
|
+
constructor() {
|
580
|
+
this.sidebar = document.getElementById('sidebar');
|
581
|
+
this.mainContent = document.getElementById('main-content');
|
582
|
+
this.sidebarToggle = document.getElementById('sidebar-toggle');
|
583
|
+
this.sidebarClose = document.getElementById('sidebar-close');
|
584
|
+
this.mobileOverlay = document.getElementById('mobile-overlay');
|
585
|
+
this.isOpen = true;
|
586
|
+
|
587
|
+
this.init();
|
588
|
+
}
|
589
|
+
|
590
|
+
init() {
|
591
|
+
this.sidebarToggle?.addEventListener('click', () => this.toggle());
|
592
|
+
this.sidebarClose?.addEventListener('click', () => this.close());
|
593
|
+
this.mobileOverlay?.addEventListener('click', () => this.close());
|
594
|
+
|
595
|
+
// Handle window resize
|
596
|
+
window.addEventListener('resize', () => this.handleResize());
|
597
|
+
this.handleResize();
|
598
|
+
}
|
599
|
+
|
600
|
+
toggle() {
|
601
|
+
if (this.isOpen) {
|
602
|
+
this.close();
|
603
|
+
} else {
|
604
|
+
this.open();
|
605
|
+
}
|
606
|
+
}
|
607
|
+
|
608
|
+
open() {
|
609
|
+
this.isOpen = true;
|
610
|
+
this.sidebar.classList.remove('sidebar-hidden');
|
611
|
+
this.sidebar.classList.add('sidebar-visible');
|
612
|
+
this.mobileOverlay?.classList.remove('hidden');
|
613
|
+
this.mainContent.classList.remove('lg:ml-0');
|
614
|
+
this.mainContent.classList.add('lg:ml-72');
|
615
|
+
}
|
616
|
+
|
617
|
+
close() {
|
618
|
+
this.isOpen = false;
|
619
|
+
this.sidebar.classList.add('sidebar-hidden');
|
620
|
+
this.sidebar.classList.remove('sidebar-visible');
|
621
|
+
this.mobileOverlay?.classList.add('hidden');
|
622
|
+
this.mainContent.classList.add('lg:ml-0');
|
623
|
+
this.mainContent.classList.remove('lg:ml-72');
|
624
|
+
}
|
625
|
+
|
626
|
+
handleResize() {
|
627
|
+
if (window.innerWidth >= 1024) {
|
628
|
+
// Desktop: always show sidebar
|
629
|
+
this.sidebar.classList.remove('sidebar-hidden');
|
630
|
+
this.sidebar.classList.add('sidebar-visible');
|
631
|
+
this.mobileOverlay?.classList.add('hidden');
|
632
|
+
this.mainContent.classList.remove('lg:ml-0');
|
633
|
+
this.mainContent.classList.add('lg:ml-72');
|
634
|
+
this.isOpen = true;
|
635
|
+
} else {
|
636
|
+
// Mobile: hide sidebar by default
|
637
|
+
this.sidebar.classList.add('sidebar-hidden');
|
638
|
+
this.sidebar.classList.remove('sidebar-visible');
|
639
|
+
this.mobileOverlay?.classList.add('hidden');
|
640
|
+
this.mainContent.classList.add('lg:ml-0');
|
641
|
+
this.mainContent.classList.remove('lg:ml-72');
|
642
|
+
this.isOpen = false;
|
643
|
+
}
|
644
|
+
}
|
645
|
+
}
|
646
|
+
|
647
|
+
// Voice Recognition Manager
|
648
|
+
class VoiceManager {
|
649
|
+
constructor() {
|
650
|
+
this.recognition = null;
|
651
|
+
this.isRecording = false;
|
652
|
+
this.isEnabled = true;
|
653
|
+
this.speechSynthesis = window.speechSynthesis;
|
654
|
+
this.isSpeaking = false;
|
655
|
+
|
656
|
+
this.init();
|
657
|
+
}
|
658
|
+
|
659
|
+
init() {
|
660
|
+
if ('webkitSpeechRecognition' in window || 'SpeechRecognition' in window) {
|
661
|
+
const SpeechRecognition = window.SpeechRecognition || window.webkitSpeechRecognition;
|
662
|
+
this.recognition = new SpeechRecognition();
|
663
|
+
this.recognition.continuous = false;
|
664
|
+
this.recognition.interimResults = false;
|
665
|
+
this.recognition.lang = 'en-US';
|
666
|
+
|
667
|
+
this.setupRecognitionEvents();
|
668
|
+
} else {
|
669
|
+
this.disableVoiceFeatures();
|
670
|
+
}
|
671
|
+
|
672
|
+
this.setupEventListeners();
|
673
|
+
|
674
|
+
// Initialize voice controls visibility
|
675
|
+
const audioControls = document.getElementById('audio-controls');
|
676
|
+
|
677
|
+
console.log('=== INITIALIZATION DEBUG ===');
|
678
|
+
console.log('Audio controls found:', !!audioControls);
|
679
|
+
|
680
|
+
if (audioControls) {
|
681
|
+
audioControls.style.display = 'flex';
|
682
|
+
audioControls.style.visibility = 'visible';
|
683
|
+
audioControls.style.opacity = '1';
|
684
|
+
console.log('Audio controls initialized to visible');
|
685
|
+
}
|
686
|
+
console.log('=== END INITIALIZATION ===');
|
687
|
+
}
|
688
|
+
|
689
|
+
setupRecognitionEvents() {
|
690
|
+
this.recognition.onstart = () => {
|
691
|
+
this.isRecording = true;
|
692
|
+
this.updateVoiceButton(true);
|
693
|
+
document.getElementById('message-input').placeholder = 'Listening...';
|
694
|
+
};
|
695
|
+
|
696
|
+
this.recognition.onresult = (event) => {
|
697
|
+
const transcript = event.results[0][0].transcript;
|
698
|
+
document.getElementById('message-input').value = transcript;
|
699
|
+
};
|
700
|
+
|
701
|
+
this.recognition.onend = () => {
|
702
|
+
this.isRecording = false;
|
703
|
+
this.updateVoiceButton(false);
|
704
|
+
document.getElementById('message-input').placeholder = 'Message Banko your personal finance assistant...';
|
705
|
+
};
|
706
|
+
|
707
|
+
this.recognition.onerror = (event) => {
|
708
|
+
console.error('Speech recognition error:', event.error);
|
709
|
+
this.isRecording = false;
|
710
|
+
this.updateVoiceButton(false);
|
711
|
+
document.getElementById('message-input').placeholder = 'Message Banko your personal finance assistant...';
|
712
|
+
|
713
|
+
if (event.error === 'not-allowed') {
|
714
|
+
alert('Please allow microphone access for voice input');
|
715
|
+
} else {
|
716
|
+
alert(`Voice recognition error: ${event.error}`);
|
717
|
+
}
|
718
|
+
};
|
719
|
+
}
|
720
|
+
|
721
|
+
setupEventListeners() {
|
722
|
+
console.log('Setting up voice event listeners...');
|
723
|
+
|
724
|
+
// Voice button
|
725
|
+
const voiceBtn = document.getElementById('voice-btn');
|
726
|
+
if (voiceBtn) {
|
727
|
+
voiceBtn.addEventListener('click', () => {
|
728
|
+
console.log('Voice button clicked');
|
729
|
+
if (!this.isEnabled) {
|
730
|
+
console.log('Voice is disabled');
|
731
|
+
return;
|
732
|
+
}
|
733
|
+
this.toggleRecording();
|
734
|
+
});
|
735
|
+
console.log('Voice button event listener added');
|
736
|
+
} else {
|
737
|
+
console.error('Voice button not found during setup');
|
738
|
+
}
|
739
|
+
|
740
|
+
// Speaker button
|
741
|
+
const speakerBtn = document.getElementById('speaker-btn');
|
742
|
+
if (speakerBtn) {
|
743
|
+
speakerBtn.addEventListener('click', () => {
|
744
|
+
console.log('Speaker button clicked');
|
745
|
+
if (!this.isEnabled) {
|
746
|
+
console.log('Voice is disabled');
|
747
|
+
return;
|
748
|
+
}
|
749
|
+
this.toggleSpeech();
|
750
|
+
});
|
751
|
+
console.log('Speaker button event listener added');
|
752
|
+
} else {
|
753
|
+
console.error('Speaker button not found during setup');
|
754
|
+
}
|
755
|
+
|
756
|
+
// Language selector
|
757
|
+
const languageSelect = document.getElementById('language-select');
|
758
|
+
if (languageSelect) {
|
759
|
+
// Load saved language preference
|
760
|
+
const savedLanguage = localStorage.getItem('banko-voice-language');
|
761
|
+
if (savedLanguage) {
|
762
|
+
languageSelect.value = savedLanguage;
|
763
|
+
if (this.recognition) {
|
764
|
+
this.recognition.lang = savedLanguage;
|
765
|
+
}
|
766
|
+
|
767
|
+
// Update hidden field for AI response language
|
768
|
+
const responseLanguageField = document.getElementById('response-language');
|
769
|
+
if (responseLanguageField) {
|
770
|
+
responseLanguageField.value = savedLanguage;
|
771
|
+
console.log('Set initial response language field:', savedLanguage);
|
772
|
+
}
|
773
|
+
|
774
|
+
console.log('Loaded saved language:', savedLanguage);
|
775
|
+
}
|
776
|
+
|
777
|
+
languageSelect.addEventListener('change', (e) => {
|
778
|
+
const selectedLang = e.target.value;
|
779
|
+
if (this.recognition) {
|
780
|
+
this.recognition.lang = selectedLang;
|
781
|
+
}
|
782
|
+
|
783
|
+
// Update hidden field for AI response language
|
784
|
+
const responseLanguageField = document.getElementById('response-language');
|
785
|
+
if (responseLanguageField) {
|
786
|
+
responseLanguageField.value = selectedLang;
|
787
|
+
console.log('Updated response language field:', selectedLang);
|
788
|
+
}
|
789
|
+
|
790
|
+
// Save language preference
|
791
|
+
localStorage.setItem('banko-voice-language', selectedLang);
|
792
|
+
console.log('Language changed and saved:', selectedLang);
|
793
|
+
});
|
794
|
+
console.log('Language selector event listener added');
|
795
|
+
} else {
|
796
|
+
console.error('Language selector not found during setup');
|
797
|
+
}
|
798
|
+
|
799
|
+
// Voice toggle
|
800
|
+
const voiceToggle = document.getElementById('voice-toggle');
|
801
|
+
if (voiceToggle) {
|
802
|
+
voiceToggle.addEventListener('click', () => {
|
803
|
+
console.log('Voice toggle clicked');
|
804
|
+
this.toggleVoiceFeatures();
|
805
|
+
});
|
806
|
+
console.log('Voice toggle event listener added');
|
807
|
+
} else {
|
808
|
+
console.error('Voice toggle not found during setup');
|
809
|
+
}
|
810
|
+
|
811
|
+
console.log('Voice event listeners setup complete');
|
812
|
+
}
|
813
|
+
|
814
|
+
toggleRecording() {
|
815
|
+
console.log('toggleRecording called, isEnabled:', this.isEnabled, 'recognition:', !!this.recognition);
|
816
|
+
|
817
|
+
if (!this.recognition) {
|
818
|
+
console.error('Voice recognition not available');
|
819
|
+
alert('Voice recognition is not supported in this browser');
|
820
|
+
return;
|
821
|
+
}
|
822
|
+
|
823
|
+
if (!this.isEnabled) {
|
824
|
+
console.log('Voice is disabled, cannot record');
|
825
|
+
return;
|
826
|
+
}
|
827
|
+
|
828
|
+
try {
|
829
|
+
if (this.isRecording) {
|
830
|
+
console.log('Stopping recording...');
|
831
|
+
this.recognition.stop();
|
832
|
+
this.isRecording = false;
|
833
|
+
this.updateVoiceButton(false);
|
834
|
+
} else {
|
835
|
+
console.log('Starting recording...');
|
836
|
+
// Ensure we're not already recording
|
837
|
+
if (this.recognition.state !== 'inactive') {
|
838
|
+
this.recognition.abort();
|
839
|
+
setTimeout(() => {
|
840
|
+
this.recognition.start();
|
841
|
+
}, 100);
|
842
|
+
} else {
|
843
|
+
this.recognition.start();
|
844
|
+
}
|
845
|
+
}
|
846
|
+
} catch (error) {
|
847
|
+
console.error('Error toggling recording:', error);
|
848
|
+
this.isRecording = false;
|
849
|
+
this.updateVoiceButton(false);
|
850
|
+
|
851
|
+
// More user-friendly error messages
|
852
|
+
if (error.message.includes('already started')) {
|
853
|
+
// Reset the recognition state
|
854
|
+
this.recognition.abort();
|
855
|
+
setTimeout(() => {
|
856
|
+
try {
|
857
|
+
this.recognition.start();
|
858
|
+
} catch (e) {
|
859
|
+
console.error('Failed to restart recognition:', e);
|
860
|
+
}
|
861
|
+
}, 200);
|
862
|
+
} else {
|
863
|
+
alert('Voice recognition error. Please try again.');
|
864
|
+
}
|
865
|
+
}
|
866
|
+
}
|
867
|
+
|
868
|
+
toggleSpeech() {
|
869
|
+
console.log('toggleSpeech called, isEnabled:', this.isEnabled, 'isSpeaking:', this.isSpeaking);
|
870
|
+
|
871
|
+
if (!this.isEnabled) {
|
872
|
+
console.log('Voice is disabled, cannot speak');
|
873
|
+
return;
|
874
|
+
}
|
875
|
+
|
876
|
+
// Always stop any ongoing speech first
|
877
|
+
if (this.speechSynthesis.speaking || this.isSpeaking) {
|
878
|
+
console.log('Stopping all speech...');
|
879
|
+
this.speechSynthesis.cancel();
|
880
|
+
this.isSpeaking = false;
|
881
|
+
this.updateSpeakerButton(false);
|
882
|
+
|
883
|
+
// If we were already speaking, just stop and return
|
884
|
+
if (this.isSpeaking) {
|
885
|
+
return;
|
886
|
+
}
|
887
|
+
}
|
888
|
+
|
889
|
+
try {
|
890
|
+
const botMessages = document.querySelectorAll('[data-read-aloud]');
|
891
|
+
if (botMessages.length === 0) {
|
892
|
+
console.log('No messages to read aloud');
|
893
|
+
alert('No message to read');
|
894
|
+
return;
|
895
|
+
}
|
896
|
+
|
897
|
+
const lastMessage = botMessages[botMessages.length - 1];
|
898
|
+
const textToRead = lastMessage.getAttribute('data-read-aloud');
|
899
|
+
|
900
|
+
if (textToRead) {
|
901
|
+
console.log('Reading text:', textToRead.substring(0, 50) + '...');
|
902
|
+
|
903
|
+
// Ensure speech synthesis is completely stopped before starting
|
904
|
+
this.speechSynthesis.cancel();
|
905
|
+
|
906
|
+
setTimeout(() => {
|
907
|
+
const utterance = new SpeechSynthesisUtterance(textToRead);
|
908
|
+
utterance.rate = 0.9;
|
909
|
+
utterance.pitch = 1;
|
910
|
+
utterance.volume = 0.8;
|
911
|
+
|
912
|
+
const languageSelect = document.getElementById('language-select');
|
913
|
+
if (languageSelect) {
|
914
|
+
utterance.lang = languageSelect.value;
|
915
|
+
}
|
916
|
+
|
917
|
+
// Find matching voice
|
918
|
+
const voices = this.speechSynthesis.getVoices();
|
919
|
+
const matchingVoice = voices.find(voice =>
|
920
|
+
voice.lang.startsWith(languageSelect?.value?.split('-')[0] || 'en')
|
921
|
+
);
|
922
|
+
if (matchingVoice) {
|
923
|
+
utterance.voice = matchingVoice;
|
924
|
+
}
|
925
|
+
|
926
|
+
utterance.onstart = () => {
|
927
|
+
console.log('Speech started');
|
928
|
+
this.isSpeaking = true;
|
929
|
+
this.updateSpeakerButton(true);
|
930
|
+
};
|
931
|
+
|
932
|
+
utterance.onend = () => {
|
933
|
+
console.log('Speech ended naturally');
|
934
|
+
this.isSpeaking = false;
|
935
|
+
this.updateSpeakerButton(false);
|
936
|
+
};
|
937
|
+
|
938
|
+
utterance.onerror = (event) => {
|
939
|
+
console.error('Speech synthesis error:', event.error);
|
940
|
+
this.isSpeaking = false;
|
941
|
+
this.updateSpeakerButton(false);
|
942
|
+
|
943
|
+
// Don't show alert for interrupted errors (user stopped it)
|
944
|
+
if (event.error !== 'interrupted' && event.error !== 'canceled') {
|
945
|
+
alert(`Speech synthesis error: ${event.error}`);
|
946
|
+
}
|
947
|
+
};
|
948
|
+
|
949
|
+
// Store reference to current utterance for better control
|
950
|
+
this.currentUtterance = utterance;
|
951
|
+
this.speechSynthesis.speak(utterance);
|
952
|
+
}, 100);
|
953
|
+
} else {
|
954
|
+
console.log('No text content to read');
|
955
|
+
}
|
956
|
+
} catch (error) {
|
957
|
+
console.error('Error with speech synthesis:', error);
|
958
|
+
this.isSpeaking = false;
|
959
|
+
this.updateSpeakerButton(false);
|
960
|
+
alert('Error with speech synthesis: ' + error.message);
|
961
|
+
}
|
962
|
+
}
|
963
|
+
|
964
|
+
updateVoiceButton(recording) {
|
965
|
+
const voiceBtn = document.getElementById('voice-btn');
|
966
|
+
const micIcon = document.getElementById('mic-icon');
|
967
|
+
|
968
|
+
if (recording) {
|
969
|
+
voiceBtn.classList.add('recording-pulse', 'active');
|
970
|
+
micIcon.classList.remove('fa-microphone');
|
971
|
+
micIcon.classList.add('fa-stop');
|
972
|
+
} else {
|
973
|
+
voiceBtn.classList.remove('recording-pulse', 'active');
|
974
|
+
micIcon.classList.remove('fa-stop');
|
975
|
+
micIcon.classList.add('fa-microphone');
|
976
|
+
}
|
977
|
+
}
|
978
|
+
|
979
|
+
updateSpeakerButton(speaking) {
|
980
|
+
const speakerBtn = document.getElementById('speaker-btn');
|
981
|
+
const speakerIcon = document.getElementById('speaker-icon');
|
982
|
+
|
983
|
+
if (speaking) {
|
984
|
+
speakerBtn.classList.add('active');
|
985
|
+
speakerIcon.classList.remove('fa-volume-up');
|
986
|
+
speakerIcon.classList.add('fa-stop');
|
987
|
+
} else {
|
988
|
+
speakerBtn.classList.remove('active');
|
989
|
+
speakerIcon.classList.remove('fa-stop');
|
990
|
+
speakerIcon.classList.add('fa-volume-up');
|
991
|
+
}
|
992
|
+
}
|
993
|
+
|
994
|
+
toggleVoiceFeatures() {
|
995
|
+
this.isEnabled = !this.isEnabled;
|
996
|
+
const voiceToggleBtn = document.getElementById('voice-toggle');
|
997
|
+
const audioControls = document.getElementById('audio-controls');
|
998
|
+
|
999
|
+
console.log('=== VOICE TOGGLE DEBUG ===');
|
1000
|
+
console.log('Voice toggle clicked. New state:', this.isEnabled);
|
1001
|
+
console.log('Voice toggle button found:', !!voiceToggleBtn);
|
1002
|
+
console.log('Audio controls found:', !!audioControls);
|
1003
|
+
|
1004
|
+
if (!voiceToggleBtn || !audioControls) {
|
1005
|
+
console.error('Required elements not found!');
|
1006
|
+
return;
|
1007
|
+
}
|
1008
|
+
|
1009
|
+
if (this.isEnabled) {
|
1010
|
+
console.log('Enabling voice features...');
|
1011
|
+
|
1012
|
+
// Update button appearance
|
1013
|
+
const micIcon = voiceToggleBtn.querySelector('i');
|
1014
|
+
const statusSpan = voiceToggleBtn.querySelector('#voice-status');
|
1015
|
+
if (micIcon) {
|
1016
|
+
micIcon.className = 'fas fa-microphone mr-2';
|
1017
|
+
}
|
1018
|
+
if (statusSpan) statusSpan.textContent = 'Voice ON';
|
1019
|
+
voiceToggleBtn.className = 'btn-modern btn-secondary text-sm transition-all duration-300 hover:scale-105';
|
1020
|
+
|
1021
|
+
// Show audio controls
|
1022
|
+
audioControls.style.display = 'flex';
|
1023
|
+
audioControls.style.visibility = 'visible';
|
1024
|
+
audioControls.style.opacity = '1';
|
1025
|
+
console.log('Audio controls shown');
|
1026
|
+
|
1027
|
+
} else {
|
1028
|
+
console.log('Disabling voice features...');
|
1029
|
+
|
1030
|
+
// Stop any ongoing operations FIRST
|
1031
|
+
if (this.speechSynthesis.speaking || this.isSpeaking) {
|
1032
|
+
console.log('Stopping speech synthesis...');
|
1033
|
+
this.speechSynthesis.cancel();
|
1034
|
+
this.isSpeaking = false;
|
1035
|
+
this.updateSpeakerButton(false);
|
1036
|
+
}
|
1037
|
+
if (this.isRecording && this.recognition) {
|
1038
|
+
console.log('Stopping voice recognition...');
|
1039
|
+
this.recognition.abort();
|
1040
|
+
this.isRecording = false;
|
1041
|
+
this.updateVoiceButton(false);
|
1042
|
+
}
|
1043
|
+
|
1044
|
+
// Update button appearance
|
1045
|
+
const micIcon = voiceToggleBtn.querySelector('i');
|
1046
|
+
const statusSpan = voiceToggleBtn.querySelector('#voice-status');
|
1047
|
+
if (micIcon) {
|
1048
|
+
micIcon.className = 'fas fa-microphone-slash mr-2';
|
1049
|
+
}
|
1050
|
+
if (statusSpan) statusSpan.textContent = 'Voice OFF';
|
1051
|
+
voiceToggleBtn.className = 'btn-modern bg-red-600 hover:bg-red-700 text-white text-sm transition-all duration-300 hover:scale-105';
|
1052
|
+
|
1053
|
+
// Hide audio controls
|
1054
|
+
audioControls.style.display = 'none';
|
1055
|
+
audioControls.style.visibility = 'hidden';
|
1056
|
+
audioControls.style.opacity = '0';
|
1057
|
+
console.log('Audio controls hidden');
|
1058
|
+
}
|
1059
|
+
|
1060
|
+
console.log('=== END VOICE TOGGLE DEBUG ===');
|
1061
|
+
}
|
1062
|
+
|
1063
|
+
|
1064
|
+
|
1065
|
+
disableVoiceFeatures() {
|
1066
|
+
this.isEnabled = false;
|
1067
|
+
document.getElementById('voice-btn').style.display = 'none';
|
1068
|
+
document.getElementById('speaker-btn').style.display = 'none';
|
1069
|
+
document.getElementById('language-select').style.display = 'none';
|
1070
|
+
document.getElementById('voice-toggle').innerHTML = '<i class="fas fa-microphone-slash mr-2"></i><span>Voice OFF</span>';
|
1071
|
+
document.getElementById('voice-toggle').className = 'btn-modern bg-red-600 hover:bg-red-700 text-white text-sm';
|
1072
|
+
document.getElementById('voice-status').textContent = 'Voice OFF';
|
1073
|
+
}
|
1074
|
+
}
|
1075
|
+
|
1076
|
+
// Database Operations Manager
|
1077
|
+
class DatabaseManager {
|
1078
|
+
constructor() {
|
1079
|
+
this.isVisible = false;
|
1080
|
+
this.init();
|
1081
|
+
}
|
1082
|
+
|
1083
|
+
init() {
|
1084
|
+
document.getElementById('db-toggle')?.addEventListener('click', () => this.toggle());
|
1085
|
+
document.getElementById('close-db-panel')?.addEventListener('click', () => this.hide());
|
1086
|
+
}
|
1087
|
+
|
1088
|
+
toggle() {
|
1089
|
+
if (this.isVisible) {
|
1090
|
+
this.hide();
|
1091
|
+
} else {
|
1092
|
+
this.show();
|
1093
|
+
}
|
1094
|
+
}
|
1095
|
+
|
1096
|
+
show() {
|
1097
|
+
document.getElementById('db-panel').classList.remove('hidden');
|
1098
|
+
this.isVisible = true;
|
1099
|
+
this.scrollToPanel();
|
1100
|
+
}
|
1101
|
+
|
1102
|
+
hide() {
|
1103
|
+
document.getElementById('db-panel').classList.add('hidden');
|
1104
|
+
this.isVisible = false;
|
1105
|
+
this.scrollToBottom();
|
1106
|
+
}
|
1107
|
+
|
1108
|
+
addOperation(step, description, type = 'info') {
|
1109
|
+
const operationsDiv = document.getElementById('db-operations');
|
1110
|
+
const timestamp = new Date().toLocaleTimeString();
|
1111
|
+
|
1112
|
+
const operationDiv = document.createElement('div');
|
1113
|
+
|
1114
|
+
let bgColor, textColor, borderColor = '';
|
1115
|
+
if (type === 'success') {
|
1116
|
+
bgColor = 'bg-green-50';
|
1117
|
+
textColor = 'text-green-800';
|
1118
|
+
borderColor = 'border-l-4 border-green-500';
|
1119
|
+
} else if (type === 'warning') {
|
1120
|
+
bgColor = 'bg-orange-50';
|
1121
|
+
textColor = 'text-orange-800';
|
1122
|
+
borderColor = 'border-l-4 border-orange-500';
|
1123
|
+
} else if (type === 'error') {
|
1124
|
+
bgColor = 'bg-red-50';
|
1125
|
+
textColor = 'text-red-800';
|
1126
|
+
borderColor = 'border-l-4 border-red-500';
|
1127
|
+
} else {
|
1128
|
+
bgColor = 'bg-blue-50';
|
1129
|
+
textColor = 'text-blue-800';
|
1130
|
+
borderColor = 'border-l-4 border-blue-500';
|
1131
|
+
}
|
1132
|
+
|
1133
|
+
operationDiv.className = `flex items-center space-x-3 p-4 rounded-lg ${bgColor} ${textColor} ${borderColor} transition-all duration-300`;
|
1134
|
+
|
1135
|
+
const isCacheOperation = description.includes('CACHE HIT') || description.includes('CACHE MISS');
|
1136
|
+
const cacheIcon = description.includes('CACHE HIT') ? '🎯' :
|
1137
|
+
description.includes('CACHE MISS') ? '⚡' : '';
|
1138
|
+
|
1139
|
+
operationDiv.innerHTML = `
|
1140
|
+
<span class="text-xs text-gray-500 font-mono">${timestamp}</span>
|
1141
|
+
<span class="font-semibold">${step}</span>
|
1142
|
+
${cacheIcon ? `<span class="text-lg">${cacheIcon}</span>` : ''}
|
1143
|
+
<span class="flex-1 ${isCacheOperation ? 'font-medium' : ''}">${description}</span>
|
1144
|
+
`;
|
1145
|
+
|
1146
|
+
operationsDiv.appendChild(operationDiv);
|
1147
|
+
operationsDiv.scrollTop = operationsDiv.scrollHeight;
|
1148
|
+
|
1149
|
+
if (isCacheOperation) {
|
1150
|
+
operationDiv.style.transform = 'scale(0.95)';
|
1151
|
+
setTimeout(() => {
|
1152
|
+
operationDiv.style.transform = 'scale(1)';
|
1153
|
+
}, 100);
|
1154
|
+
}
|
1155
|
+
}
|
1156
|
+
|
1157
|
+
clearOperations() {
|
1158
|
+
document.getElementById('db-operations').innerHTML = '';
|
1159
|
+
}
|
1160
|
+
|
1161
|
+
scrollToPanel() {
|
1162
|
+
const dbPanel = document.getElementById('db-panel');
|
1163
|
+
if (dbPanel) {
|
1164
|
+
dbPanel.scrollIntoView({ behavior: 'smooth', block: 'start' });
|
1165
|
+
}
|
1166
|
+
}
|
1167
|
+
|
1168
|
+
scrollToBottom() {
|
1169
|
+
setTimeout(() => {
|
1170
|
+
const chatContainer = document.getElementById('chat-container');
|
1171
|
+
if (chatContainer) {
|
1172
|
+
chatContainer.scrollTop = chatContainer.scrollHeight;
|
1173
|
+
}
|
1174
|
+
}, 100);
|
1175
|
+
}
|
1176
|
+
}
|
1177
|
+
|
1178
|
+
// Chat Manager
|
1179
|
+
class ChatManager {
|
1180
|
+
constructor() {
|
1181
|
+
this.init();
|
1182
|
+
}
|
1183
|
+
|
1184
|
+
init() {
|
1185
|
+
this.renderMarkdown();
|
1186
|
+
this.setupFormSubmission();
|
1187
|
+
this.setupKeyboardShortcuts();
|
1188
|
+
this.autoScroll();
|
1189
|
+
}
|
1190
|
+
|
1191
|
+
renderMarkdown() {
|
1192
|
+
const markdownElements = document.querySelectorAll('.markdown-content[data-raw-content]');
|
1193
|
+
markdownElements.forEach(element => {
|
1194
|
+
const rawContent = element.getAttribute('data-raw-content');
|
1195
|
+
if (rawContent) {
|
1196
|
+
const htmlContent = marked.parse(rawContent);
|
1197
|
+
const cleanContent = DOMPurify.sanitize(htmlContent);
|
1198
|
+
element.innerHTML = cleanContent;
|
1199
|
+
}
|
1200
|
+
});
|
1201
|
+
}
|
1202
|
+
|
1203
|
+
setupFormSubmission() {
|
1204
|
+
const chatManager = this;
|
1205
|
+
// Store reference globally for Enter key handler
|
1206
|
+
window.chatManager = chatManager;
|
1207
|
+
|
1208
|
+
document.getElementById('chat-form').addEventListener('submit', (e) => {
|
1209
|
+
console.log('=== FORM SUBMISSION ===');
|
1210
|
+
const messageInput = document.getElementById('message-input');
|
1211
|
+
|
1212
|
+
const message = messageInput.value.trim();
|
1213
|
+
console.log('Submitting message:', message);
|
1214
|
+
|
1215
|
+
if (!message) {
|
1216
|
+
console.log('Empty message, preventing submission');
|
1217
|
+
e.preventDefault();
|
1218
|
+
return;
|
1219
|
+
}
|
1220
|
+
|
1221
|
+
// Show loading animation immediately
|
1222
|
+
console.log('Showing loading animation...');
|
1223
|
+
chatManager.showLoadingAnimation();
|
1224
|
+
|
1225
|
+
// Show database operations if enabled
|
1226
|
+
if (window.dbManager && window.dbManager.isVisible) {
|
1227
|
+
console.log('Simulating database operations...');
|
1228
|
+
chatManager.simulateDatabaseOperations(message);
|
1229
|
+
}
|
1230
|
+
|
1231
|
+
// Clear the input field AFTER a short delay to ensure form submission
|
1232
|
+
setTimeout(() => {
|
1233
|
+
messageInput.value = '';
|
1234
|
+
messageInput.style.height = 'auto';
|
1235
|
+
}, 100);
|
1236
|
+
|
1237
|
+
console.log('Form submission proceeding to server...');
|
1238
|
+
// Let the form submit naturally to the server
|
1239
|
+
});
|
1240
|
+
|
1241
|
+
// Auto-resize textarea
|
1242
|
+
const messageInput = document.getElementById('message-input');
|
1243
|
+
if (messageInput) {
|
1244
|
+
messageInput.addEventListener('input', function() {
|
1245
|
+
this.style.height = 'auto';
|
1246
|
+
this.style.height = Math.min(this.scrollHeight, 200) + 'px';
|
1247
|
+
});
|
1248
|
+
|
1249
|
+
// Handle Enter key for submission
|
1250
|
+
messageInput.addEventListener('keydown', function(e) {
|
1251
|
+
if (e.key === 'Enter' && !e.shiftKey) {
|
1252
|
+
console.log('Enter key pressed, triggering form submission...');
|
1253
|
+
e.preventDefault();
|
1254
|
+
|
1255
|
+
const message = this.value.trim();
|
1256
|
+
if (!message) {
|
1257
|
+
console.log('Empty message, not submitting');
|
1258
|
+
return;
|
1259
|
+
}
|
1260
|
+
|
1261
|
+
// Show loading animation immediately (same as submit button)
|
1262
|
+
console.log('Showing loading animation for Enter key...');
|
1263
|
+
if (window.chatManager) {
|
1264
|
+
window.chatManager.showLoadingAnimation();
|
1265
|
+
|
1266
|
+
// Show database operations if enabled
|
1267
|
+
if (window.dbManager && window.dbManager.isVisible) {
|
1268
|
+
console.log('Simulating database operations...');
|
1269
|
+
window.chatManager.simulateDatabaseOperations(message);
|
1270
|
+
}
|
1271
|
+
}
|
1272
|
+
|
1273
|
+
// Clear the input field AFTER a short delay to ensure form submission
|
1274
|
+
setTimeout(() => {
|
1275
|
+
this.value = '';
|
1276
|
+
this.style.height = 'auto';
|
1277
|
+
}, 100);
|
1278
|
+
|
1279
|
+
// Submit the form
|
1280
|
+
const form = document.getElementById('chat-form');
|
1281
|
+
if (form) {
|
1282
|
+
form.submit();
|
1283
|
+
}
|
1284
|
+
}
|
1285
|
+
});
|
1286
|
+
}
|
1287
|
+
}
|
1288
|
+
|
1289
|
+
setupKeyboardShortcuts() {
|
1290
|
+
// Additional keyboard shortcuts can be added here
|
1291
|
+
console.log('Chat keyboard shortcuts initialized');
|
1292
|
+
}
|
1293
|
+
|
1294
|
+
showLoadingAnimation() {
|
1295
|
+
const loadingMessage = document.getElementById('loading-message');
|
1296
|
+
console.log('=== LOADING ANIMATION DEBUG ===');
|
1297
|
+
console.log('Loading message element found:', !!loadingMessage);
|
1298
|
+
if (loadingMessage) {
|
1299
|
+
console.log('Current loading message classes:', loadingMessage.className);
|
1300
|
+
console.log('Current loading message display:', loadingMessage.style.display);
|
1301
|
+
loadingMessage.classList.remove('hidden');
|
1302
|
+
loadingMessage.style.display = 'block';
|
1303
|
+
console.log('After removing hidden class:', loadingMessage.className);
|
1304
|
+
console.log('After setting display block:', loadingMessage.style.display);
|
1305
|
+
loadingMessage.scrollIntoView({ behavior: 'smooth', block: 'end' });
|
1306
|
+
console.log('Loading animation shown successfully');
|
1307
|
+
} else {
|
1308
|
+
console.error('Loading message element not found!');
|
1309
|
+
}
|
1310
|
+
console.log('=== END LOADING ANIMATION DEBUG ===');
|
1311
|
+
}
|
1312
|
+
|
1313
|
+
hideLoadingAnimation() {
|
1314
|
+
const loadingMessage = document.getElementById('loading-message');
|
1315
|
+
if (loadingMessage) {
|
1316
|
+
loadingMessage.classList.add('hidden');
|
1317
|
+
loadingMessage.style.display = 'none';
|
1318
|
+
console.log('Loading animation hidden');
|
1319
|
+
}
|
1320
|
+
}
|
1321
|
+
|
1322
|
+
simulateDatabaseOperations(message) {
|
1323
|
+
window.dbManager.clearOperations();
|
1324
|
+
window.dbManager.addOperation('📤 Query', `Processing: "${message.substring(0, 50)}${message.length > 50 ? '...' : ''}"`, 'info');
|
1325
|
+
|
1326
|
+
const cacheScenarios = [
|
1327
|
+
{ embedding: 'miss', vectorSearch: 'miss', response: 'miss', probability: 0.4 },
|
1328
|
+
{ embedding: 'hit', vectorSearch: 'miss', response: 'miss', probability: 0.3 },
|
1329
|
+
{ embedding: 'hit', vectorSearch: 'hit', response: 'miss', probability: 0.2 },
|
1330
|
+
{ embedding: 'hit', vectorSearch: 'hit', response: 'hit', probability: 0.1 }
|
1331
|
+
];
|
1332
|
+
|
1333
|
+
const random = Math.random();
|
1334
|
+
let cumulative = 0;
|
1335
|
+
let selectedScenario = cacheScenarios[0];
|
1336
|
+
for (const scenario of cacheScenarios) {
|
1337
|
+
cumulative += scenario.probability;
|
1338
|
+
if (random <= cumulative) {
|
1339
|
+
selectedScenario = scenario;
|
1340
|
+
break;
|
1341
|
+
}
|
1342
|
+
}
|
1343
|
+
|
1344
|
+
setTimeout(() => {
|
1345
|
+
const embeddingStatus = selectedScenario.embedding === 'hit' ? '✅ CACHE HIT' : '❌ CACHE MISS';
|
1346
|
+
const embeddingType = selectedScenario.embedding === 'hit' ? 'success' : 'warning';
|
1347
|
+
window.dbManager.addOperation('🔍 Embedding', `${embeddingStatus} - ${selectedScenario.embedding === 'hit' ? 'Using cached embedding' : 'Generating new embedding with SentenceTransformer'}`, embeddingType);
|
1348
|
+
}, 500);
|
1349
|
+
|
1350
|
+
setTimeout(() => {
|
1351
|
+
const vectorStatus = selectedScenario.vectorSearch === 'hit' ? '✅ CACHE HIT' : '❌ CACHE MISS';
|
1352
|
+
const vectorType = selectedScenario.vectorSearch === 'hit' ? 'success' : 'warning';
|
1353
|
+
window.dbManager.addOperation('🗄️ Vector Search', `${vectorStatus} - ${selectedScenario.vectorSearch === 'hit' ? 'Retrieved from cache' : 'Querying CockroachDB with vector similarity'}`, vectorType);
|
1354
|
+
}, 1000);
|
1355
|
+
|
1356
|
+
setTimeout(() => window.dbManager.addOperation('📊 Analysis', 'Analyzing spending patterns and categories', 'info'), 1500);
|
1357
|
+
|
1358
|
+
setTimeout(() => {
|
1359
|
+
const responseStatus = selectedScenario.response === 'hit' ? '✅ CACHE HIT' : '❌ CACHE MISS';
|
1360
|
+
const responseType = selectedScenario.response === 'hit' ? 'success' : 'warning';
|
1361
|
+
const aiProvider = 'IBM Watsonx' || 'AI Provider';
|
1362
|
+
window.dbManager.addOperation('🤖 AI Processing', `${responseStatus} - ${selectedScenario.response === 'hit' ? 'Using cached response' : `Generating new response with ${aiProvider}`}`, responseType);
|
1363
|
+
}, 2000);
|
1364
|
+
|
1365
|
+
setTimeout(() => {
|
1366
|
+
const totalHits = [selectedScenario.embedding, selectedScenario.vectorSearch, selectedScenario.response].filter(s => s === 'hit').length;
|
1367
|
+
const efficiency = Math.round((totalHits / 3) * 100);
|
1368
|
+
window.dbManager.addOperation('✅ Complete', `Response ready! Cache efficiency: ${efficiency}% (${totalHits}/3 cache hits) 🚀`, 'success');
|
1369
|
+
}, 2500);
|
1370
|
+
}
|
1371
|
+
|
1372
|
+
setupKeyboardShortcuts() {
|
1373
|
+
document.addEventListener('keydown', (e) => {
|
1374
|
+
// Ctrl/Cmd + Enter to send
|
1375
|
+
if ((e.ctrlKey || e.metaKey) && e.key === 'Enter') {
|
1376
|
+
document.getElementById('chat-form').submit();
|
1377
|
+
}
|
1378
|
+
|
1379
|
+
// Ctrl/Cmd + Shift + V for voice
|
1380
|
+
if ((e.ctrlKey || e.metaKey) && e.shiftKey && e.key === 'V') {
|
1381
|
+
e.preventDefault();
|
1382
|
+
if (window.voiceManager.isEnabled) {
|
1383
|
+
window.voiceManager.toggleRecording();
|
1384
|
+
}
|
1385
|
+
}
|
1386
|
+
|
1387
|
+
// Ctrl/Cmd + Shift + S for speech
|
1388
|
+
if ((e.ctrlKey || e.metaKey) && e.shiftKey && e.key === 'S') {
|
1389
|
+
e.preventDefault();
|
1390
|
+
if (window.voiceManager.isEnabled) {
|
1391
|
+
window.voiceManager.toggleSpeech();
|
1392
|
+
}
|
1393
|
+
}
|
1394
|
+
});
|
1395
|
+
}
|
1396
|
+
|
1397
|
+
autoScroll() {
|
1398
|
+
const chatContainer = document.getElementById('chat-container');
|
1399
|
+
if (chatContainer) {
|
1400
|
+
chatContainer.scrollTop = chatContainer.scrollHeight;
|
1401
|
+
}
|
1402
|
+
}
|
1403
|
+
}
|
1404
|
+
|
1405
|
+
// Initialize everything when DOM is loaded
|
1406
|
+
document.addEventListener('DOMContentLoaded', function() {
|
1407
|
+
console.log('Initializing Banko AI Assistant...');
|
1408
|
+
window.sidebarManager = new SidebarManager();
|
1409
|
+
window.voiceManager = new VoiceManager();
|
1410
|
+
window.dbManager = new DatabaseManager();
|
1411
|
+
window.chatManager = new ChatManager();
|
1412
|
+
console.log('Voice manager initialized:', window.voiceManager);
|
1413
|
+
console.log('Voice manager enabled state:', window.voiceManager.isEnabled);
|
1414
|
+
|
1415
|
+
// Add keyboard shortcuts
|
1416
|
+
document.addEventListener('keydown', (e) => {
|
1417
|
+
// Ctrl+M or Cmd+M for voice toggle
|
1418
|
+
if ((e.ctrlKey || e.metaKey) && e.key === 'm') {
|
1419
|
+
e.preventDefault();
|
1420
|
+
if (window.voiceManager) {
|
1421
|
+
window.voiceManager.toggleVoiceFeatures();
|
1422
|
+
console.log('Voice toggle triggered via keyboard shortcut');
|
1423
|
+
}
|
1424
|
+
}
|
1425
|
+
});
|
1426
|
+
|
1427
|
+
// Re-setup event listeners after initialization
|
1428
|
+
setTimeout(() => {
|
1429
|
+
console.log('=== RE-SETTING UP VOICE EVENT LISTENERS ===');
|
1430
|
+
window.voiceManager.setupEventListeners();
|
1431
|
+
console.log('=== VOICE CONTROLS VERIFICATION ===');
|
1432
|
+
const voiceBtn = document.getElementById('voice-btn');
|
1433
|
+
const speakerBtn = document.getElementById('speaker-btn');
|
1434
|
+
const languageSelect = document.getElementById('language-select');
|
1435
|
+
const voiceToggle = document.getElementById('voice-toggle');
|
1436
|
+
|
1437
|
+
console.log('Voice button found:', !!voiceBtn, voiceBtn);
|
1438
|
+
console.log('Speaker button found:', !!speakerBtn, speakerBtn);
|
1439
|
+
console.log('Language selector found:', !!languageSelect, languageSelect);
|
1440
|
+
console.log('Voice toggle found:', !!voiceToggle, voiceToggle);
|
1441
|
+
|
1442
|
+
if (voiceBtn) {
|
1443
|
+
console.log('Voice button display:', voiceBtn.style.display);
|
1444
|
+
console.log('Voice button visibility:', voiceBtn.style.visibility);
|
1445
|
+
console.log('Voice button opacity:', voiceBtn.style.opacity);
|
1446
|
+
}
|
1447
|
+
|
1448
|
+
if (speakerBtn) {
|
1449
|
+
console.log('Speaker button display:', speakerBtn.style.display);
|
1450
|
+
console.log('Speaker button visibility:', speakerBtn.style.visibility);
|
1451
|
+
console.log('Speaker button opacity:', speakerBtn.style.opacity);
|
1452
|
+
}
|
1453
|
+
|
1454
|
+
if (languageSelect) {
|
1455
|
+
console.log('Language selector display:', languageSelect.style.display);
|
1456
|
+
console.log('Language selector visibility:', languageSelect.style.visibility);
|
1457
|
+
console.log('Language selector opacity:', languageSelect.style.opacity);
|
1458
|
+
}
|
1459
|
+
|
1460
|
+
console.log('=== END VERIFICATION ===');
|
1461
|
+
}, 1000);
|
1462
|
+
});
|
1463
|
+
|
1464
|
+
// Auto-scroll on page load
|
1465
|
+
window.addEventListener('load', function() {
|
1466
|
+
setTimeout(() => {
|
1467
|
+
const chatContainer = document.getElementById('chat-container');
|
1468
|
+
if (chatContainer) {
|
1469
|
+
chatContainer.scrollTop = chatContainer.scrollHeight;
|
1470
|
+
}
|
1471
|
+
}, 300);
|
1472
|
+
});
|
1473
|
+
|
1474
|
+
// Auto-scroll when new messages are added
|
1475
|
+
function scrollToBottom() {
|
1476
|
+
const chatContainer = document.getElementById('chat-container');
|
1477
|
+
if (chatContainer) {
|
1478
|
+
chatContainer.scrollTop = chatContainer.scrollHeight;
|
1479
|
+
}
|
1480
|
+
}
|
1481
|
+
|
1482
|
+
// Enhanced auto-scroll functionality
|
1483
|
+
function enhancedAutoScroll() {
|
1484
|
+
const chatContainer = document.getElementById('chat-container');
|
1485
|
+
if (chatContainer) {
|
1486
|
+
// Smooth scroll to bottom
|
1487
|
+
chatContainer.scrollTo({
|
1488
|
+
top: chatContainer.scrollHeight,
|
1489
|
+
behavior: 'smooth'
|
1490
|
+
});
|
1491
|
+
}
|
1492
|
+
}
|
1493
|
+
|
1494
|
+
// Call enhanced auto-scroll after a short delay to ensure content is rendered
|
1495
|
+
setTimeout(enhancedAutoScroll, 500);
|
1496
|
+
setTimeout(enhancedAutoScroll, 1000);
|
1497
|
+
</script>
|
1498
|
+
</body>
|
1499
|
+
</html>
|