Mohammad Wasil commited on
Commit
62a2bc4
·
1 Parent(s): 266eec2

updating frontend

Browse files
Files changed (3) hide show
  1. css/styles.css +387 -0
  2. css/variables.css +39 -0
  3. js/app.js +427 -0
css/styles.css ADDED
@@ -0,0 +1,387 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ @import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap');
2
+ @import url('./variables.css');
3
+
4
+ /* Reset & Base */
5
+ * {
6
+ margin: 0;
7
+ padding: 0;
8
+ box-sizing: border-box;
9
+ }
10
+
11
+ body {
12
+ font-family: var(--font-primary);
13
+ font-size: var(--font-size-base);
14
+ background-color: var(--color-bg);
15
+ color: var(--color-text);
16
+ line-height: 1.6;
17
+ overflow: hidden; /* Prevent scroll on mobile */
18
+ }
19
+
20
+ /* Chat Container */
21
+ .chat-container {
22
+ position: relative;
23
+ display: flex;
24
+ flex-direction: column;
25
+ height: 100vh;
26
+ max-width: 1200px;
27
+ margin: 0 auto;
28
+ background: var(--color-surface);
29
+ box-shadow: var(--shadow-lg);
30
+ }
31
+
32
+ /* Header */
33
+ .chat-header {
34
+ background: linear-gradient(135deg, var(--color-primary), var(--color-secondary));
35
+ color: white;
36
+ padding: var(--spacing-md) var(--spacing-lg);
37
+ text-align: center;
38
+ box-shadow: var(--shadow-sm);
39
+ }
40
+
41
+ .chat-header h1 {
42
+ font-weight: 600;
43
+ font-size: 1.5rem;
44
+ margin-bottom: var(--spacing-xs);
45
+ }
46
+
47
+ .chat-header p {
48
+ font-size: var(--font-size-sm);
49
+ opacity: 0.9;
50
+ }
51
+
52
+
53
+ /* message indentation of the Agent Output */
54
+ /* Ensure the list markers actually show up */
55
+ .message-content ul {
56
+ list-style-type: disc !important; /* Forces bullet points */
57
+ margin: 10px 0 10px 20px;
58
+ padding-left: 10px;
59
+ }
60
+
61
+ .message-content ol {
62
+ list-style-type: decimal !important; /* Forces numbers */
63
+ margin: 10px 0 10px 20px;
64
+ padding-left: 10px;
65
+ }
66
+
67
+ .message-content li {
68
+ margin-bottom: 5px;
69
+ display: list-item; /* Ensures it behaves like a list line */
70
+ }
71
+
72
+ /* Headers styling */
73
+ .message-content h3 {
74
+ margin-top: 15px;
75
+ margin-bottom: 5px;
76
+ font-weight: bold;
77
+ }
78
+
79
+
80
+
81
+
82
+ /* Messages Area */
83
+ .messages-area {
84
+ flex: 1;
85
+ overflow-y: auto;
86
+ padding: var(--spacing-lg);
87
+ display: flex;
88
+ flex-direction: column;
89
+ gap: var(--spacing-md);
90
+
91
+ /* Custom Scrollbar */
92
+ scrollbar-width: thin;
93
+ scrollbar-color: var(--color-border) transparent;
94
+ }
95
+
96
+ .messages-area::-webkit-scrollbar {
97
+ width: 8px;
98
+ }
99
+
100
+ .messages-area::-webkit-scrollbar-track {
101
+ background: var(--color-bg);
102
+ }
103
+
104
+ .messages-area::-webkit-scrollbar-thumb {
105
+ background: var(--color-border);
106
+ border-radius: var(--radius-sm);
107
+ }
108
+
109
+ /* Message Bubbles */
110
+ .message {
111
+ max-width: 75%;
112
+ padding: var(--spacing-sm) var(--spacing-md);
113
+ border-radius: var(--radius-lg);
114
+ animation: fadeIn 0.3s ease-out;
115
+ word-wrap: break-word;
116
+ }
117
+
118
+ @keyframes fadeIn {
119
+ from {
120
+ opacity: 0;
121
+ transform: translateY(10px);
122
+ }
123
+ to {
124
+ opacity: 1;
125
+ transform: translateY(0);
126
+ }
127
+ }
128
+
129
+ .message.user {
130
+ align-self: flex-end;
131
+ background: var(--color-user-msg);
132
+ border-bottom-right-radius: var(--radius-sm);
133
+ }
134
+
135
+ .message.bot {
136
+ align-self: flex-start;
137
+ background: var(--color-bot-msg);
138
+ border-bottom-left-radius: var(--radius-sm);
139
+ }
140
+
141
+ .message-content {
142
+ margin-bottom: var(--spacing-xs);
143
+ }
144
+
145
+ .message-timestamp {
146
+ font-size: 11px;
147
+ color: var(--color-text-light);
148
+ text-align: right;
149
+ }
150
+
151
+ /* Loading Indicator */
152
+ .loading {
153
+ display: none; /* Hidden by default */
154
+ align-self: flex-start;
155
+ padding: var(--spacing-sm) var(--spacing-md);
156
+ background: var(--color-accent);
157
+ border-radius: var(--radius-md);
158
+ font-style: italic;
159
+ color: var(--color-text-light);
160
+ }
161
+
162
+ .loading.active {
163
+ display: block;
164
+ animation: pulse 1.5s infinite;
165
+ }
166
+
167
+ @keyframes pulse {
168
+ 0%, 100% { opacity: 0.6; }
169
+ 50% { opacity: 1; }
170
+ }
171
+
172
+ /* Input Area */
173
+ .input-area {
174
+ padding: var(--spacing-md) var(--spacing-lg);
175
+ border-top: 2px solid var(--color-border);
176
+ background: var(--color-surface);
177
+ display: flex;
178
+ gap: var(--spacing-sm);
179
+ align-items: center;
180
+ }
181
+
182
+ #messageInput {
183
+ flex: 1;
184
+ padding: var(--spacing-sm) var(--spacing-md);
185
+ border: 2px solid var(--color-border);
186
+ border-radius: var(--radius-xl);
187
+ font-size: var(--font-size-base);
188
+ outline: none;
189
+ transition: all 0.3s ease;
190
+ background: var(--color-bg);
191
+ }
192
+
193
+ #messageInput:focus {
194
+ border-color: var(--color-primary);
195
+ box-shadow: 0 0 0 3px rgba(255, 107, 107, 0.1);
196
+ }
197
+
198
+ #messageInput:disabled {
199
+ background: var(--color-border);
200
+ cursor: not-allowed;
201
+ }
202
+
203
+ .send-button {
204
+ background: var(--color-primary);
205
+ color: white;
206
+ border: none;
207
+ border-radius: 50%;
208
+ width: 48px;
209
+ height: 48px;
210
+ display: flex;
211
+ align-items: center;
212
+ justify-content: center;
213
+ cursor: pointer;
214
+ transition: all 0.3s ease;
215
+ box-shadow: var(--shadow-sm);
216
+ }
217
+
218
+ .send-button:hover {
219
+ background: #FF5252; /* Darker coral */
220
+ transform: scale(1.05);
221
+ box-shadow: var(--shadow-md);
222
+ }
223
+
224
+ .send-button:active {
225
+ transform: scale(0.95);
226
+ }
227
+
228
+ .send-button:disabled {
229
+ background: var(--color-text-light);
230
+ cursor: not-allowed;
231
+ transform: none;
232
+ }
233
+
234
+ .send-icon {
235
+ width: 20px;
236
+ height: 20px;
237
+ fill: currentColor;
238
+ }
239
+
240
+ /* Error Banner */
241
+ .error-banner {
242
+ display: none;
243
+ background: var(--color-error);
244
+ color: white;
245
+ padding: var(--spacing-sm) var(--spacing-md);
246
+ text-align: center;
247
+ font-size: var(--font-size-sm);
248
+ animation: slideDown 0.3s ease-out;
249
+ }
250
+
251
+ .error-banner.active {
252
+ display: block;
253
+ }
254
+
255
+ /* Feedback Container */
256
+ .message-feedback {
257
+ position: relative;
258
+ z-index: 10;
259
+ display: flex; /* Ensures buttons stay side-by-side */
260
+ gap: 10px; /* Adds space between thumbs */
261
+ margin-top: 10px; /* Pushes it away from the text */
262
+ min-height: 24px; /* Force a minimum height so they don't collapse */
263
+ opacity: 1; /* Changed from 0.8 to 1 for testing visibility */
264
+ visibility: visible;
265
+ /* opacity: 0.8; */
266
+ /* transition: opacity 0.2s; */
267
+ }
268
+
269
+ .message-feedback:hover {
270
+ opacity: 1;
271
+ }
272
+
273
+ /* Feedback Buttons */
274
+ .message-feedback button {
275
+ background: #f0f0f0;
276
+ border: 1px solid #ddd;
277
+ border-radius: 4px;
278
+ padding: 2px 8px;
279
+ cursor: pointer;
280
+ font-size: 14px;
281
+ transition: all 0.2s ease;
282
+ display: flex;
283
+ align-items: center;
284
+ justify-content: center;
285
+ }
286
+
287
+ .message-feedback button:hover {
288
+ background-color: #e0e0e0;
289
+ transform: scale(1.1);
290
+ }
291
+
292
+ .message-feedback button:active {
293
+ background-color: #d0d0d0;
294
+ transform: scale(0.95);
295
+ }
296
+
297
+ /* Optional: Highlight after click */
298
+ .message-feedback button.clicked {
299
+ background-color: #FF5252;
300
+ color: white;
301
+ pointer-events: none;
302
+ opacity: 0.5;
303
+ }
304
+
305
+
306
+
307
+
308
+ @keyframes slideDown {
309
+ from {
310
+ transform: translateY(-100%);
311
+ opacity: 0;
312
+ }
313
+ to {
314
+ transform: translateY(0);
315
+ opacity: 1;
316
+ }
317
+ }
318
+
319
+ /* Connection Status */
320
+ .connection-status {
321
+ position: absolute;
322
+ margin-right: 30px;
323
+ margin-top: 10px;
324
+ top: var(--spacing-sm);
325
+ right: var(--spacing-sm);
326
+ padding: var(--spacing-xs) var(--spacing-sm);
327
+ border-radius: var(--radius-sm);
328
+ font-size: 12px;
329
+ font-weight: 500;
330
+ transition: opacity 0.5s ease;
331
+ pointer-events: auto;
332
+ z-index: 100;
333
+ }
334
+
335
+ .connection-status.connected {
336
+ background: var(--color-success);
337
+ color: white;
338
+ }
339
+
340
+ .connection-status.disconnected {
341
+ background: var(--color-error);
342
+ color: white;
343
+ }
344
+
345
+ /* Mobile Responsiveness */
346
+ @media (max-width: 768px) {
347
+ .chat-container {
348
+ box-shadow: none;
349
+ }
350
+
351
+ .chat-header h1 {
352
+ font-size: 1.2rem;
353
+ }
354
+
355
+ .messages-area {
356
+ padding: var(--spacing-md);
357
+ }
358
+
359
+ .message {
360
+ max-width: 85%;
361
+ }
362
+
363
+ .input-area {
364
+ padding: var(--spacing-sm) var(--spacing-md);
365
+ }
366
+
367
+ .send-button {
368
+ width: 44px;
369
+ height: 44px;
370
+ }
371
+ }
372
+
373
+ /* Accessibility */
374
+ @media (prefers-reduced-motion: reduce) {
375
+ * {
376
+ animation-duration: 0.01ms !important;
377
+ animation-iteration-count: 1 !important;
378
+ transition-duration: 0.01ms !important;
379
+ }
380
+ }
381
+
382
+ /* Focus indicators for keyboard navigation */
383
+ button:focus,
384
+ #messageInput:focus {
385
+ outline: 2px solid var(--color-primary);
386
+ outline-offset: 2px;
387
+ }
css/variables.css ADDED
@@ -0,0 +1,39 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ :root {
2
+ /* Warm Professional Palette */
3
+ --color-primary: #FF6B6B; /* Coral - energetic but friendly */
4
+ --color-secondary: #4ECDC4; /* Teal - calming accent */
5
+ --color-accent: #FFE66D; /* Warm Yellow - highlights */
6
+ --color-bg: #FFF8F5; /* Cream - soft background */
7
+ --color-surface: #FFFFFF; /* White - cards/messages */
8
+ --color-text: #2D3436; /* Dark Gray - primary text */
9
+ --color-text-light: #636E72; /* Medium Gray - secondary text */
10
+ --color-border: #FFD3B6; /* Light Coral - borders */
11
+ --color-success: #00B894; /* Green - success states */
12
+ --color-error: #E17055; /* Warm Red - errors */
13
+ --color-user-msg: #FFF0EC; /* Very Light Coral - user messages */
14
+ --color-bot-msg: #F1F2F6; /* Light Gray - bot messages */
15
+
16
+ /* Spacing */
17
+ --spacing-xs: 0.5rem;
18
+ --spacing-sm: 1rem;
19
+ --spacing-md: 1.5rem;
20
+ --spacing-lg: 2rem;
21
+ --spacing-xl: 3rem;
22
+
23
+ /* Typography */
24
+ --font-primary: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
25
+ --font-size-base: 16px;
26
+ --font-size-sm: 14px;
27
+ --font-size-lg: 18px;
28
+
29
+ /* Shadows */
30
+ --shadow-sm: 0 2px 4px rgba(255, 107, 107, 0.1);
31
+ --shadow-md: 0 4px 12px rgba(255, 107, 107, 0.15);
32
+ --shadow-lg: 0 8px 24px rgba(255, 107, 107, 0.2);
33
+
34
+ /* Border Radius */
35
+ --radius-sm: 8px;
36
+ --radius-md: 12px;
37
+ --radius-lg: 16px;
38
+ --radius-xl: 24px;
39
+ }
js/app.js ADDED
@@ -0,0 +1,427 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // Configuration
2
+ const CONFIG = {
3
+ API_BASE_URL: 'http://localhost:8000',
4
+ API_ENDPOINT: '/api/v1/chat/sync',
5
+ MESSAGE_BATCH_SIZE: 10,
6
+ MAX_MESSAGE_LENGTH: 500,
7
+ RETRY_ATTEMPTS: 3,
8
+ TIMEOUT_MS: 600000
9
+ };
10
+
11
+ // State Management
12
+ const state = {
13
+ isConnected: false,
14
+ isLoading: false,
15
+ sessionId: localStorage.getItem('sessionId') || `user_${Date.now()}`,
16
+ messages: JSON.parse(localStorage.getItem('chatHistory') || '[]'),
17
+ lastActivity: Date.now()
18
+ };
19
+
20
+ // DOM Elements
21
+ const elements = {
22
+ messagesArea: document.getElementById('messagesArea'),
23
+ messageInput: document.getElementById('messageInput'),
24
+ sendButton: document.getElementById('sendButton'),
25
+ loadingIndicator: document.getElementById('loadingIndicator'),
26
+ errorBanner: document.getElementById('errorBanner'),
27
+ connectionStatus: document.getElementById('connectionStatus'),
28
+ welcomeTimestamp: document.getElementById('welcomeTimestamp')
29
+ };
30
+
31
+ // Initialize
32
+ document.addEventListener('DOMContentLoaded', () => {
33
+ initializeApp();
34
+ });
35
+
36
+ function initializeApp() {
37
+ elements.welcomeTimestamp.textContent = formatTimestamp(Date.now());
38
+ loadChatHistory();
39
+ setupEventListeners();
40
+ checkConnection();
41
+ elements.messageInput.focus();
42
+ localStorage.setItem('sessionId', state.sessionId);
43
+ }
44
+
45
+ function setupEventListeners() {
46
+ // Send button click
47
+ elements.sendButton.addEventListener('click', (e) => {
48
+ e.preventDefault();
49
+ sendMessage();
50
+ });
51
+
52
+ // Input field events
53
+ elements.messageInput.addEventListener('keypress', (e) => {
54
+ if (e.key === 'Enter' && !e.shiftKey) {
55
+ e.preventDefault();
56
+ sendMessage();
57
+ }
58
+ });
59
+
60
+ elements.messageInput.addEventListener('input', (e) => {
61
+ const value = e.target.value;
62
+ if (value.length > CONFIG.MAX_MESSAGE_LENGTH) {
63
+ showError(`Message too long (max ${CONFIG.MAX_MESSAGE_LENGTH} chars)`);
64
+ e.target.value = value.slice(0, CONFIG.MAX_MESSAGE_LENGTH);
65
+ }
66
+ updateSendButton();
67
+ });
68
+
69
+ // Periodic checks
70
+ setInterval(checkConnection, 5000);
71
+ setInterval(() => {
72
+ if (Date.now() - state.lastActivity > 30 * 60 * 1000) {
73
+ clearChatHistory();
74
+ }
75
+ }, 60000);
76
+ }
77
+
78
+ async function checkConnection() {
79
+ try {
80
+ const response = await fetch(`${CONFIG.API_BASE_URL}/health`);
81
+ const isConnected = response.ok;
82
+ updateConnectionStatus(isConnected);
83
+ state.isConnected = isConnected;
84
+ } catch (error) {
85
+ console.error('Connection check failed:', error);
86
+ updateConnectionStatus(false);
87
+ state.isConnected = false;
88
+ }
89
+ }
90
+
91
+ // 1. variable to track the LAST known state
92
+ let lastStatus = null;
93
+ let statusTimeout;
94
+
95
+ function updateConnectionStatus(isConnected) {
96
+ const status = elements.connectionStatus;
97
+
98
+ // This stops the constant "re-appearing" loop.
99
+ if (isConnected === lastStatus) return;
100
+
101
+ // Update the tracker
102
+ lastStatus = isConnected;
103
+
104
+ // Clear any old timers
105
+ if (statusTimeout) clearTimeout(statusTimeout);
106
+
107
+ if (isConnected) {
108
+ status.textContent = 'Connected';
109
+ status.className = 'connection-status connected';
110
+ status.style.opacity = '1';
111
+
112
+ // Hide after 3 seconds and NEVER show again unless we disconnect first
113
+ statusTimeout = setTimeout(() => {
114
+ status.style.opacity = '0';
115
+ status.style.pointerEvents = 'none';
116
+ }, 3000);
117
+ } else {
118
+ // If disconnected, show it and KEEP it visible
119
+ status.textContent = 'Disconnected';
120
+ status.className = 'connection-status disconnected';
121
+ status.style.opacity = '1';
122
+ status.style.pointerEvents = 'auto';
123
+ }
124
+ }
125
+
126
+
127
+
128
+ async function sendMessage() {
129
+ const question = elements.messageInput.value.trim();
130
+ console.log('Sending message:', question); // Debug log
131
+
132
+ if (!question || state.isLoading) {
133
+ console.log('Message not sent - empty or loading:', {question, isLoading: state.isLoading});
134
+ return;
135
+ }
136
+
137
+ if (!state.isConnected) {
138
+ showError('Not connected to server. Please check your connection.');
139
+ return;
140
+ }
141
+
142
+ hideError();
143
+ addMessageToUI(question, 'user');
144
+ elements.messageInput.value = '';
145
+ updateSendButton();
146
+ showLoading();
147
+
148
+ await callSupportAgent(question);
149
+ }
150
+
151
+ async function callSupportAgent(question) {
152
+ const payload = {
153
+ question: question,
154
+ session_id: state.sessionId
155
+ };
156
+
157
+ console.log('Sending request to:', `${CONFIG.API_BASE_URL}${CONFIG.API_ENDPOINT}`); // Debug log
158
+ console.log('Payload:', payload); // Debug log
159
+
160
+ try {
161
+ const controller = new AbortController();
162
+ const timeoutId = setTimeout(() => controller.abort(), CONFIG.TIMEOUT_MS);
163
+
164
+ const response = await fetch(`${CONFIG.API_BASE_URL}${CONFIG.API_ENDPOINT}`, {
165
+ method: 'POST',
166
+ headers: {'Content-Type': 'application/json'},
167
+ body: JSON.stringify(payload),
168
+ signal: controller.signal
169
+ });
170
+
171
+ clearTimeout(timeoutId);
172
+
173
+ if (!response.ok) {
174
+ throw new Error(`HTTP ${response.status}: ${response.statusText}`);
175
+ }
176
+
177
+ const result = await response.json();
178
+ console.log('Response received:', result); // Debug log
179
+ addMessageToUI(result.answer, 'bot', result.sources);
180
+ saveToHistory(question, result.answer);
181
+
182
+ } catch (error) {
183
+ console.error('API call failed:', error);
184
+ const errorMessage = 'Sorry, I encountered an error. Please try again.';
185
+ addMessageToUI(errorMessage, 'bot');
186
+ showError(errorMessage);
187
+ } finally {
188
+ hideLoading();
189
+ state.lastActivity = Date.now();
190
+ }
191
+ }
192
+ function escapeHtml(unsafe) {
193
+ return unsafe
194
+ .replace(/&/g, "&")
195
+ .replace(/</g, "&lt;")
196
+ .replace(/>/g, "&gt;")
197
+ .replace(/"/g, "&quot;")
198
+ .replace(/'/g, "&#039;");
199
+ }
200
+
201
+ // function renderContent(content) {
202
+ // // sanitize, then convert simple markdown + lists + newlines to HTML
203
+ // let html = escapeHtml(content);
204
+
205
+ // // bold **text**
206
+ // html = html.replace(/\*\*(.+?)\*\*/g, "<strong>$1</strong>");
207
+
208
+ // // split into lines and convert to paragraphs / lists
209
+ // const lines = html.split(/\r?\n/);
210
+ // let out = "";
211
+ // let inUl = false;
212
+ // let inOl = false;
213
+
214
+ // for (let rawLine of lines) {
215
+ // const line = rawLine.trim();
216
+ // if (line === "") {
217
+ // // blank line -> close lists and add spacing
218
+ // if (inUl) { out += "</ul>"; inUl = false; }
219
+ // if (inOl) { out += "</ol>"; inOl = false; }
220
+ // out += "<p></p>";
221
+ // continue;
222
+ // }
223
+
224
+ // if (/^[-•]\s+/.test(line)) {
225
+ // if (!inUl) { if (inOl) { out += "</ol>"; inOl = false; } out += "<ul>"; inUl = true; }
226
+ // out += `<li>${line.replace(/^[-•]\s+/, "")}</li>`;
227
+ // continue;
228
+ // }
229
+
230
+ // if (/^\d+\.\s+/.test(line)) {
231
+ // if (!inOl) { if (inUl) { out += "</ul>"; inUl = false; } out += "<ol>"; inOl = true; }
232
+ // out += `<li>${line.replace(/^\d+\.\s+/, "")}</li>`;
233
+ // continue;
234
+ // }
235
+
236
+ // // normal paragraph line
237
+ // if (inUl) { out += "</ul>"; inUl = false; }
238
+ // if (inOl) { out += "</ol>"; inOl = false; }
239
+ // out += `<p>${line}</p>`;
240
+ // }
241
+
242
+ // if (inUl) out += "</ul>";
243
+ // if (inOl) out += "</ol>";
244
+ // return out;
245
+ // }
246
+
247
+ function renderContent(content) {
248
+ let html = escapeHtml(content);
249
+ html = html.replace(/\*\*(.+?)\*\*/g, "<strong>$1</strong>");
250
+
251
+ const lines = html.split(/\r?\n/);
252
+ let out = "";
253
+ let inUl = false;
254
+ let inOl = false;
255
+
256
+ for (let rawLine of lines) {
257
+ const line = rawLine.trim();
258
+
259
+ // Updated Regex: Now includes '*' and handles different spacing
260
+ const isUl = /^[*•-]\s+/.test(line);
261
+ const isOl = /^\d+[.)]\s+/.test(line);
262
+
263
+ if (line === "") {
264
+ if (inUl) { out += "</ul>"; inUl = false; }
265
+ if (inOl) { out += "</ol>"; inOl = false; }
266
+ continue;
267
+ }
268
+
269
+ if (line.startsWith('###')) {
270
+ if (inUl) { out += "</ul>"; inUl = false; }
271
+ if (inOl) { out += "</ol>"; inOl = false; }
272
+ out += `<h3>${line.replace(/^###\s*/, '')}</h3>`;
273
+ } else if (isUl) {
274
+ if (!inUl) { out += "<ul>"; inUl = true; }
275
+ out += `<li>${line.replace(/^[*•-]\s+/, "")}</li>`;
276
+ } else if (isOl) {
277
+ if (!inOl) { out += "<ol>"; inOl = true; }
278
+ out += `<li>${line.replace(/^\d+[.)]\s+/, "")}</li>`;
279
+ } else {
280
+ if (inUl) { out += "</ul>"; inUl = false; }
281
+ if (inOl) { out += "</ol>"; inOl = false; }
282
+ out += `<p>${line}</p>`;
283
+ }
284
+ }
285
+ return out;
286
+ }
287
+
288
+
289
+
290
+
291
+ function addMessageToUI(content, sender, sources = null) {
292
+ const messageDiv = document.createElement('div');
293
+ messageDiv.className = `message ${sender}`;
294
+
295
+ const contentDiv = document.createElement('div');
296
+ contentDiv.className = 'message-content';
297
+
298
+ // Render content safely: support **bold**, numbered lists, bullet lists and line breaks.
299
+ contentDiv.innerHTML = renderContent(content);
300
+
301
+ const timestampDiv = document.createElement('div');
302
+ timestampDiv.className = 'message-timestamp';
303
+ timestampDiv.textContent = formatTimestamp(Date.now());
304
+
305
+ messageDiv.appendChild(contentDiv);
306
+ messageDiv.appendChild(timestampDiv);
307
+
308
+ if (sender === 'bot' && sources && sources.length > 0) {
309
+ const sourcesDiv = document.createElement('div');
310
+ sourcesDiv.className = 'message-sources';
311
+ sourcesDiv.textContent = `Source: ${sources.join(', ')}`;
312
+ messageDiv.appendChild(sourcesDiv);
313
+ }
314
+
315
+ if (sender === 'bot') {
316
+ console.log("Bot message detected, adding buttons..."); // DEBUG LOG
317
+ const feedbackDiv = document.createElement('div');
318
+ feedbackDiv.className = 'message-feedback';
319
+
320
+ const thumbsUp = document.createElement('button');
321
+ thumbsUp.type = 'button';
322
+ thumbsUp.innerHTML = '👍';
323
+ thumbsUp.title = 'Helpful';
324
+ thumbsUp.onclick = () => {
325
+ thumbsUp.classList.add('clicked');
326
+ thumbsDown.disabled = true;
327
+ recordFeedback('thumbs_up', state.sessionId);
328
+ };
329
+
330
+ const thumbsDown = document.createElement('button');
331
+ thumbsDown.innerHTML = '👎';
332
+ thumbsDown.title = 'Not helpful';
333
+ thumbsDown.onclick = () => {
334
+ thumbsDown.classList.add('clicked');
335
+ thumbsUp.disabled = true;
336
+ recordFeedback('thumbs_down', state.sessionId);
337
+ };
338
+
339
+ feedbackDiv.appendChild(thumbsUp);
340
+ feedbackDiv.appendChild(thumbsDown);
341
+ messageDiv.appendChild(feedbackDiv);
342
+ }
343
+
344
+ elements.messagesArea.appendChild(messageDiv);
345
+ scrollToBottom();
346
+ }
347
+
348
+ async function recordFeedback(rating, sessionId) {
349
+ try {
350
+ console.log('Recording feedback:', {rating, sessionId}); // Debug log
351
+ const response = await fetch(`${CONFIG.API_BASE_URL}/api/v1/feedback`, {
352
+ method: 'POST',
353
+ headers: {'Content-Type': 'application/json'},
354
+ body: JSON.stringify({ rating, session_id: sessionId })
355
+ });
356
+
357
+ if (!response.ok) {
358
+ console.error('Feedback recording failed:', response.status);
359
+ } else {
360
+ console.log('Feedback recorded successfully:', rating);
361
+ }
362
+ } catch (e) {
363
+ console.error('Feedback error:', e);
364
+ }
365
+ }
366
+
367
+ function formatTimestamp(ts) {
368
+ return new Date(ts).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });
369
+ }
370
+
371
+ function scrollToBottom() {
372
+ elements.messagesArea.scrollTop = elements.messagesArea.scrollHeight;
373
+ }
374
+
375
+ function showLoading() {
376
+ state.isLoading = true;
377
+ elements.loadingIndicator.classList.add('active');
378
+ elements.loadingIndicator.textContent = "Agent is thinking...";
379
+ elements.messageInput.disabled = true;
380
+ elements.sendButton.disabled = true;
381
+ updateSendButton(); // Ensure button state is updated
382
+ }
383
+
384
+ function hideLoading() {
385
+ state.isLoading = false;
386
+ elements.loadingIndicator.classList.remove('active');
387
+ elements.messageInput.disabled = false;
388
+ elements.sendButton.disabled = false;
389
+ updateSendButton(); // Ensure button state is updated
390
+ elements.messageInput.focus();
391
+ }
392
+
393
+ function updateSendButton() {
394
+ const hasText = elements.messageInput.value.trim() !== '';
395
+ const canSend = hasText && !state.isLoading && state.isConnected;
396
+ elements.sendButton.disabled = !canSend;
397
+ console.log('Button state updated:', {hasText, isLoading: state.isLoading, isConnected: state.isConnected, canSend}); // Debug log
398
+ }
399
+
400
+ function showError(msg) {
401
+ console.error('Error:', msg); // Debug log
402
+ elements.errorBanner.textContent = msg;
403
+ elements.errorBanner.classList.add('active');
404
+ setTimeout(hideError, 5000);
405
+ }
406
+
407
+ function hideError() {
408
+ elements.errorBanner.classList.remove('active');
409
+ }
410
+
411
+ function saveToHistory(q, a) {
412
+ state.messages.push({ question: q, answer: a, timestamp: Date.now() });
413
+ if (state.messages.length > 50) state.messages.shift();
414
+ localStorage.setItem('chatHistory', JSON.stringify(state.messages));
415
+ }
416
+
417
+ function loadChatHistory() {
418
+ state.messages.slice(-10).forEach(m => {
419
+ addMessageToUI(m.question, 'user');
420
+ addMessageToUI(m.answer, 'bot');
421
+ });
422
+ }
423
+
424
+ function clearChatHistory() {
425
+ localStorage.clear();
426
+ location.reload();
427
+ }