tanmaivan commited on
Commit
44009db
·
1 Parent(s): f59cb4f

feat: implement dark mode, knowledge base & multi-lang UI refinements

Browse files
FrontEnd/index.html CHANGED
@@ -2,7 +2,7 @@
2
  <html lang="en">
3
  <head>
4
  <meta charset="UTF-8" />
5
- <link rel="icon" type="image/png" href="/pizzahut.png" />
6
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
7
  <title>HutMind</title>
8
  </head>
 
2
  <html lang="en">
3
  <head>
4
  <meta charset="UTF-8" />
5
+ <link rel="icon" type="image/png" href="/logo_light.png" />
6
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
7
  <title>HutMind</title>
8
  </head>
FrontEnd/public/{pizzahut.png → logo_dark.png} RENAMED
File without changes
FrontEnd/public/{vite.svg → logo_light.png} RENAMED
File without changes
FrontEnd/public/logo_light_v2.png ADDED

Git LFS Details

  • SHA256: 654f6218733502235729a5e4c5de0282efe59266e8d9d2e33301bad90f818b82
  • Pointer size: 131 Bytes
  • Size of remote file: 236 kB
FrontEnd/public/mascot.png CHANGED

Git LFS Details

  • SHA256: db1ae0453daa9dc63df925876630fe1adc06ef7b330953d1e441074b4a6381bc
  • Pointer size: 131 Bytes
  • Size of remote file: 243 kB

Git LFS Details

  • SHA256: 3b750f086e0fbb5bc55d3ec2dd2c86ae2c4b1c7e7652bae0a3717057d1f47ec7
  • Pointer size: 131 Bytes
  • Size of remote file: 244 kB
FrontEnd/public/vite.png ADDED

Git LFS Details

  • SHA256: f015b1f1c11878067744387d7ebce3068ab685ab6edfcf883b7f10b69dde2cd8
  • Pointer size: 131 Bytes
  • Size of remote file: 203 kB
FrontEnd/src/ChatBot.css CHANGED
@@ -1,10 +1,49 @@
1
- /* Reset */
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2
  *, *::before, *::after {
3
  box-sizing: border-box;
4
  margin: 0;
5
  padding: 0;
6
  }
7
 
 
 
 
 
 
 
 
 
 
 
 
 
8
  /* Container */
9
  .chatbot-container {
10
  width: 100%;
@@ -13,75 +52,110 @@
13
  margin: 0 auto;
14
  display: flex;
15
  flex-direction: column;
16
- background-color: #ffffff;
17
  position: relative;
18
  font-family: 'Inter', sans-serif;
19
- color: #374151;
 
 
20
  }
21
 
22
  /* Header */
23
  .chat-header {
 
 
 
 
24
  display: flex;
25
- justify-content: space-between;
26
  align-items: center;
27
- padding: 16px 24px;
28
- background-color: #ffffff;
29
  position: sticky;
30
  top: 0;
31
  z-index: 100;
32
  }
33
 
 
 
 
 
 
 
 
 
 
 
 
 
34
  .header-logo {
35
  display: flex;
36
  align-items: center;
37
  gap: 12px;
38
  cursor: pointer;
39
- transition: opacity 0.2s ease;
40
  }
41
 
42
- .header-logo:hover {
43
- opacity: 0.8;
44
  }
45
 
46
- .pizza-icon {
47
- color: #e31837; /* Pizza Hut Red */
 
 
 
 
48
  }
49
 
50
- .header-title h1 {
51
- font-size: 16px;
 
 
 
 
 
 
 
 
 
52
  font-weight: 600;
53
- color: #111827;
54
- line-height: 1.2;
 
 
55
  }
56
 
57
- .header-title span {
58
- font-size: 11px;
59
- color: #6b7280;
60
- text-transform: uppercase;
61
- letter-spacing: 0.5px;
62
- font-weight: 500;
 
 
63
  }
64
 
65
  .new-chat-btn {
66
  display: flex;
67
  align-items: center;
68
- gap: 6px;
69
- background-color: #ffffff;
70
- border: 1px solid #e5e7eb;
71
- color: #374151;
 
72
  font-size: 13px;
73
  font-weight: 500;
74
- padding: 10px 20px;
75
- border-radius: 24px; /* Pill-shaped */
 
76
  cursor: pointer;
77
  transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);
78
- box-shadow: 0 1px 3px rgba(0, 0, 0, 0.05);
 
79
  }
80
 
81
  .new-chat-btn:hover {
82
- background-color: #ffffff;
83
- border-color: #d1d5db;
84
- box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);
85
  transform: translateY(-1px);
86
  }
87
 
@@ -104,66 +178,124 @@
104
  width: 6px;
105
  }
106
  .chat-window::-webkit-scrollbar-thumb {
107
- background-color: #d1d5db;
108
  border-radius: 10px;
109
  }
110
 
111
  /* Welcome Screen */
112
  .welcome-container {
113
- flex: 1;
114
  display: flex;
115
  flex-direction: column;
116
  align-items: center;
117
- justify-content: center;
118
  text-align: center;
119
  padding: 40px 20px;
120
- min-height: 300px;
 
 
 
121
  }
122
 
123
  .welcome-logo {
124
  display: flex;
125
  align-items: center;
126
  justify-content: center;
127
- margin-bottom: 24px;
 
 
 
 
 
 
 
 
 
128
  }
129
 
130
  .welcome-container h2 {
131
  font-size: 24px;
132
- font-weight: 600;
133
- color: #111827;
134
  margin-bottom: 12px;
135
  }
136
 
137
  .welcome-subtitle {
138
- font-size: 15px;
139
- color: #6b7280;
140
- max-width: 400px;
141
  margin-bottom: 32px;
142
  line-height: 1.5;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
143
  }
144
 
 
145
  .suggestion-chips {
146
  display: flex;
 
 
147
  gap: 12px;
148
- flex-wrap: wrap;
149
- justify-content: center;
 
 
 
 
 
 
 
 
150
  }
151
 
152
  .suggestion-chips button {
153
- background-color: #ffffff;
154
- border: 1px solid #e5e7eb;
155
- color: #374151;
 
156
  padding: 10px 18px;
157
- border-radius: 18px;
158
- font-size: 14px;
 
159
  cursor: pointer;
160
  transition: all 0.2s ease;
161
- box-shadow: 0 1px 2px rgba(0, 0, 0, 0.04);
 
 
 
 
 
162
  }
163
 
164
  .suggestion-chips button:hover {
165
- background-color: #f9fafb;
166
- border-color: #d1d5db;
 
167
  transform: translateY(-1px);
168
  }
169
 
@@ -207,8 +339,8 @@
207
 
208
  .user-text {
209
  white-space: pre-wrap;
210
- color: #ffffff;
211
- background-color: #e31837; /* Pizza Hut Red Bubble */
212
  padding: 12px 18px;
213
  border-radius: 20px 20px 2px 20px;
214
  box-shadow: 0 2px 8px rgba(227, 24, 55, 0.15);
@@ -249,13 +381,13 @@
249
  }
250
 
251
  .copy-btn:hover {
252
- background-color: #f3f4f6; /* Light circular hover */
253
- color: #374151; /* Darker on hover */
254
  }
255
 
256
  /* React Markdown Styles */
257
  .markdown-body {
258
- color: #374151;
259
  width: 100%;
260
  }
261
 
@@ -269,7 +401,7 @@
269
 
270
  .markdown-body strong {
271
  font-weight: 600;
272
- color: #111827;
273
  }
274
 
275
  .markdown-body ul, .markdown-body ol {
@@ -282,11 +414,12 @@
282
  }
283
 
284
  .markdown-body code {
285
- background-color: #e5e7eb;
286
  padding: 2px 6px;
287
  border-radius: 4px;
288
  font-size: 14px;
289
  font-family: 'Consolas', monospace;
 
290
  }
291
 
292
  /* Typing Indicator */
@@ -300,7 +433,7 @@
300
  .dot {
301
  width: 6px;
302
  height: 6px;
303
- background-color: #9ca3af;
304
  border-radius: 50%;
305
  animation: bounce 1.4s infinite ease-in-out both;
306
  }
@@ -316,14 +449,14 @@
316
  /* Input Area */
317
  .input-area {
318
  padding: 16px 24px 24px 24px;
319
- background-color: #ffffff;
320
  }
321
 
322
  .input-container {
323
  display: flex;
324
  align-items: flex-end;
325
- background-color: #ffffff;
326
- border: 1px solid #e5e7eb;
327
  border-radius: 16px;
328
  padding: 4px;
329
  transition: all 0.2s;
@@ -341,7 +474,7 @@
341
  background: transparent;
342
  padding: 12px 16px;
343
  font-size: 16px;
344
- color: #111827;
345
  font-family: inherit;
346
  resize: none;
347
  max-height: 200px;
@@ -386,7 +519,7 @@
386
  .footer-text {
387
  text-align: center;
388
  font-size: 12px;
389
- color: #9ca3af;
390
  margin-top: 12px;
391
  }
392
 
@@ -402,30 +535,34 @@
402
  }
403
 
404
  .chat-header {
405
- padding: 12px 16px;
406
  }
407
 
408
  .header-title h1 {
409
  font-size: 14px;
410
  }
411
 
412
- .new-chat-btn span {
413
- display: none;
 
 
 
 
414
  }
415
 
416
- .new-chat-btn {
417
  padding: 8px;
418
- border-radius: 50%;
 
 
419
  }
420
 
421
  .welcome-container {
422
- padding-top: 60px;
423
- justify-content: flex-start;
424
  }
425
 
426
  .welcome-logo img {
427
- width: 140px;
428
- height: auto;
429
  }
430
 
431
  .welcome-container h2 {
@@ -457,27 +594,179 @@
457
  }
458
 
459
  @media (max-width: 480px) {
460
- .header-title span {
461
- display: none;
462
  }
463
 
464
  .welcome-logo img {
465
- width: 100px;
466
  }
467
 
468
  .suggestion-chips {
469
- gap: 8px;
 
 
470
  }
471
 
472
- .suggestion-chips button {
473
- padding: 8px 12px;
474
  font-size: 13px;
475
- width: 100%;
476
- text-align: left;
 
 
 
477
  }
478
 
479
- .welcome-subtitle {
480
- font-size: 14px;
481
- margin-bottom: 24px;
482
  }
483
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ :root {
2
+ /* Light Theme Variables */
3
+ --bg-primary: #ffffff;
4
+ --bg-secondary: #f9fafb;
5
+ --bg-message-user: #e31837;
6
+ --text-primary: #111827;
7
+ --text-secondary: #6b7280;
8
+ --text-on-red: #ffffff;
9
+ --border-color: #f3f4f6;
10
+ --border-input: #e5e7eb;
11
+ --shadow-sm: 0 1px 3px rgba(0, 0, 0, 0.05);
12
+ --shadow-md: 0 4px 12px rgba(0, 0, 0, 0.08);
13
+ --hover-btn: #f3f4f6;
14
+ }
15
+
16
+ .chatbot-container.dark, body.dark {
17
+ /* Dark Theme Variables */
18
+ --bg-primary: #121212; /* Slightly deeper dark */
19
+ --bg-secondary: #1e1e1e;
20
+ --bg-message-user: #e31837;
21
+ --text-primary: #ffffff;
22
+ --text-secondary: #d4d4d4; /* Much brighter for readability */
23
+ --border-color: #2a2a2a;
24
+ --border-input: #3a3a3a;
25
+ --hover-btn: #2a2a2a;
26
+ }
27
+
28
+ /* Global Resets */
29
  *, *::before, *::after {
30
  box-sizing: border-box;
31
  margin: 0;
32
  padding: 0;
33
  }
34
 
35
+ html, body {
36
+ overflow-x: hidden;
37
+ width: 100%;
38
+ }
39
+
40
+ body {
41
+ background-color: var(--bg-primary);
42
+ color: var(--text-primary);
43
+ transition: background-color 0.3s ease;
44
+ min-height: 100vh;
45
+ }
46
+
47
  /* Container */
48
  .chatbot-container {
49
  width: 100%;
 
52
  margin: 0 auto;
53
  display: flex;
54
  flex-direction: column;
55
+ background-color: var(--bg-primary);
56
  position: relative;
57
  font-family: 'Inter', sans-serif;
58
+ color: var(--text-primary);
59
+ transition: background-color 0.3s ease, color 0.3s ease;
60
+ overflow-x: hidden;
61
  }
62
 
63
  /* Header */
64
  .chat-header {
65
+ height: 70px;
66
+ padding: 0 24px;
67
+ background-color: var(--bg-primary);
68
+ border-bottom: 1px solid var(--border-color);
69
  display: flex;
 
70
  align-items: center;
71
+ justify-content: space-between;
 
72
  position: sticky;
73
  top: 0;
74
  z-index: 100;
75
  }
76
 
77
+ .header-left {
78
+ display: flex;
79
+ align-items: center;
80
+ gap: 16px;
81
+ }
82
+
83
+ .header-right {
84
+ display: flex;
85
+ align-items: center;
86
+ gap: 12px;
87
+ }
88
+
89
  .header-logo {
90
  display: flex;
91
  align-items: center;
92
  gap: 12px;
93
  cursor: pointer;
 
94
  }
95
 
96
+ .header-logo img {
97
+ object-fit: contain;
98
  }
99
 
100
+ .header-title h1 {
101
+ font-size: 18px;
102
+ font-weight: 700;
103
+ color: var(--text-primary);
104
+ letter-spacing: -0.02em;
105
+ margin: 0;
106
  }
107
 
108
+ .lang-switcher, .theme-switcher {
109
+ display: flex;
110
+ align-items: center;
111
+ justify-content: center;
112
+ gap: 6px;
113
+ background-color: transparent;
114
+ border: none;
115
+ color: var(--text-primary); /* Higher contrast */
116
+ padding: 8px;
117
+ border-radius: 50%; /* For circular hover */
118
+ font-size: 13px;
119
  font-weight: 600;
120
+ cursor: pointer;
121
+ transition: all 0.2s ease;
122
+ height: 38px;
123
+ min-width: 38px;
124
  }
125
 
126
+ .theme-switcher {
127
+ width: 38px;
128
+ padding: 0;
129
+ }
130
+
131
+ .lang-switcher:hover, .theme-switcher:hover {
132
+ background-color: var(--hover-btn);
133
+ color: var(--text-primary);
134
  }
135
 
136
  .new-chat-btn {
137
  display: flex;
138
  align-items: center;
139
+ justify-content: center;
140
+ gap: 8px;
141
+ background-color: var(--bg-primary);
142
+ border: 1px solid var(--border-input);
143
+ color: var(--text-primary);
144
  font-size: 13px;
145
  font-weight: 500;
146
+ padding: 0 20px;
147
+ height: 38px;
148
+ border-radius: 20px; /* Pill-shaped */
149
  cursor: pointer;
150
  transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);
151
+ box-shadow: var(--shadow-sm);
152
+ min-width: 140px; /* Stabilize size */
153
  }
154
 
155
  .new-chat-btn:hover {
156
+ background-color: var(--bg-primary);
157
+ border-color: #9ca3af;
158
+ box-shadow: var(--shadow-md);
159
  transform: translateY(-1px);
160
  }
161
 
 
178
  width: 6px;
179
  }
180
  .chat-window::-webkit-scrollbar-thumb {
181
+ background-color: var(--border-input);
182
  border-radius: 10px;
183
  }
184
 
185
  /* Welcome Screen */
186
  .welcome-container {
 
187
  display: flex;
188
  flex-direction: column;
189
  align-items: center;
 
190
  text-align: center;
191
  padding: 40px 20px;
192
+ margin: auto; /* Vertically and horizontally centered */
193
+ width: 100%;
194
+ max-width: 800px;
195
+ min-height: min-content;
196
  }
197
 
198
  .welcome-logo {
199
  display: flex;
200
  align-items: center;
201
  justify-content: center;
202
+ margin-bottom: 20px;
203
+ max-height: 15vh; /* Reduced height as it's smaller now */
204
+ }
205
+
206
+ .welcome-logo img {
207
+ max-height: 100%;
208
+ width: auto;
209
+ height: auto;
210
+ max-width: 100px; /* Reduced to 1/2 size as requested */
211
+ object-fit: contain;
212
  }
213
 
214
  .welcome-container h2 {
215
  font-size: 24px;
216
+ font-weight: 700;
217
+ color: var(--text-primary);
218
  margin-bottom: 12px;
219
  }
220
 
221
  .welcome-subtitle {
222
+ font-size: clamp(12px, 2vw, 14px);
223
+ color: var(--text-secondary);
 
224
  margin-bottom: 32px;
225
  line-height: 1.5;
226
+ white-space: nowrap;
227
+ width: 100%;
228
+ overflow: hidden;
229
+ text-overflow: ellipsis;
230
+ }
231
+
232
+ /* Knowledge Base Button */
233
+ .knowledge-base-btn {
234
+ display: flex;
235
+ align-items: center;
236
+ justify-content: center;
237
+ gap: 8px;
238
+ background-color: transparent;
239
+ border: 1px solid var(--border-input);
240
+ color: var(--text-primary); /* Ensure white in dark mode */
241
+ padding: 8px 16px;
242
+ border-radius: 20px;
243
+ font-size: 13px;
244
+ font-weight: 500;
245
+ cursor: pointer;
246
+ transition: all 0.2s ease;
247
+ margin-bottom: 32px;
248
+ min-width: 160px; /* Stabilize size */
249
+ }
250
+
251
+ .knowledge-base-btn:hover {
252
+ background-color: var(--hover-btn);
253
+ color: var(--text-primary);
254
+ border-color: #9ca3af;
255
+ transform: translateY(-1px);
256
  }
257
 
258
+ /* Suggestion Chips */
259
  .suggestion-chips {
260
  display: flex;
261
+ flex-wrap: nowrap; /* Single line as requested */
262
+ overflow-x: auto; /* Horizontal scroll if too long */
263
  gap: 12px;
264
+ justify-content: center; /* Center if enough space */
265
+ width: 100%;
266
+ max-width: 800px; /* Wider to match input area visibility better */
267
+ padding: 4px 10px;
268
+ scrollbar-width: none; /* Hide scrollbar Firefox */
269
+ -ms-overflow-style: none; /* Hide scrollbar IE/Edge */
270
+ }
271
+
272
+ .suggestion-chips::-webkit-scrollbar {
273
+ display: none; /* Hide scrollbar Chrome/Safari */
274
  }
275
 
276
  .suggestion-chips button {
277
+ flex-shrink: 0; /* Don't squash buttons */
278
+ background-color: var(--bg-primary);
279
+ border: 1px solid var(--border-input);
280
+ color: var(--text-primary); /* Use primary text (white in dark mode) */
281
  padding: 10px 18px;
282
+ border-radius: 12px;
283
+ font-size: 13px;
284
+ font-weight: 500;
285
  cursor: pointer;
286
  transition: all 0.2s ease;
287
+ box-shadow: var(--shadow-sm);
288
+ height: 44px; /* Consistent height */
289
+ display: flex;
290
+ align-items: center;
291
+ justify-content: center;
292
+ white-space: nowrap;
293
  }
294
 
295
  .suggestion-chips button:hover {
296
+ background-color: var(--hover-btn);
297
+ border-color: #9ca3af;
298
+ color: var(--text-primary);
299
  transform: translateY(-1px);
300
  }
301
 
 
339
 
340
  .user-text {
341
  white-space: pre-wrap;
342
+ color: var(--text-on-red);
343
+ background-color: var(--bg-message-user); /* Pizza Hut Red Bubble */
344
  padding: 12px 18px;
345
  border-radius: 20px 20px 2px 20px;
346
  box-shadow: 0 2px 8px rgba(227, 24, 55, 0.15);
 
381
  }
382
 
383
  .copy-btn:hover {
384
+ background-color: var(--hover-btn); /* Light circular hover */
385
+ color: var(--text-primary); /* Darker on hover */
386
  }
387
 
388
  /* React Markdown Styles */
389
  .markdown-body {
390
+ color: var(--text-primary);
391
  width: 100%;
392
  }
393
 
 
401
 
402
  .markdown-body strong {
403
  font-weight: 600;
404
+ color: var(--text-primary);
405
  }
406
 
407
  .markdown-body ul, .markdown-body ol {
 
414
  }
415
 
416
  .markdown-body code {
417
+ background-color: var(--bg-secondary);
418
  padding: 2px 6px;
419
  border-radius: 4px;
420
  font-size: 14px;
421
  font-family: 'Consolas', monospace;
422
+ color: var(--text-primary);
423
  }
424
 
425
  /* Typing Indicator */
 
433
  .dot {
434
  width: 6px;
435
  height: 6px;
436
+ background-color: var(--text-secondary);
437
  border-radius: 50%;
438
  animation: bounce 1.4s infinite ease-in-out both;
439
  }
 
449
  /* Input Area */
450
  .input-area {
451
  padding: 16px 24px 24px 24px;
452
+ background-color: var(--bg-primary);
453
  }
454
 
455
  .input-container {
456
  display: flex;
457
  align-items: flex-end;
458
+ background-color: var(--bg-primary);
459
+ border: 1px solid var(--border-input);
460
  border-radius: 16px;
461
  padding: 4px;
462
  transition: all 0.2s;
 
474
  background: transparent;
475
  padding: 12px 16px;
476
  font-size: 16px;
477
+ color: var(--text-primary);
478
  font-family: inherit;
479
  resize: none;
480
  max-height: 200px;
 
519
  .footer-text {
520
  text-align: center;
521
  font-size: 12px;
522
+ color: var(--text-secondary);
523
  margin-top: 12px;
524
  }
525
 
 
535
  }
536
 
537
  .chat-header {
538
+ padding: 0 16px;
539
  }
540
 
541
  .header-title h1 {
542
  font-size: 14px;
543
  }
544
 
545
+ .header-right {
546
+ gap: 8px; /* Tighter gap on mobile */
547
+ }
548
+
549
+ .lang-switcher span, .new-chat-btn span {
550
+ display: none; /* Hide text to save space */
551
  }
552
 
553
+ .lang-switcher, .theme-switcher, .new-chat-btn {
554
  padding: 8px;
555
+ width: 38px;
556
+ min-width: 38px;
557
+ border-radius: 50%; /* Icon only style */
558
  }
559
 
560
  .welcome-container {
561
+ padding-top: 40px;
 
562
  }
563
 
564
  .welcome-logo img {
565
+ max-width: 80px; /* Proportional reduction for mobile */
 
566
  }
567
 
568
  .welcome-container h2 {
 
594
  }
595
 
596
  @media (max-width: 480px) {
597
+ .header-title {
598
+ display: none; /* Only logo on very small screens if needed */
599
  }
600
 
601
  .welcome-logo img {
602
+ max-width: 70px; /* Even smaller for very small screens */
603
  }
604
 
605
  .suggestion-chips {
606
+ justify-content: flex-start; /* Ensure scrolling starts from left */
607
+ padding-left: 16px;
608
+ padding-right: 16px;
609
  }
610
 
611
+ .welcome-subtitle {
 
612
  font-size: 13px;
613
+ margin-bottom: 24px;
614
+ padding: 0 10px;
615
+ white-space: normal; /* Allow wrap on very small screens to avoid horizontal scroll of plain text */
616
+ text-overflow: clip;
617
+ overflow: visible;
618
  }
619
 
620
+ .modal-content {
621
+ margin: 20px; /* Prevent touching edges on mobile */
622
+ max-height: 85vh;
623
  }
624
  }
625
+
626
+ /* Knowledge Base Button */
627
+ .knowledge-base-btn {
628
+ display: flex;
629
+ align-items: center;
630
+ gap: 8px;
631
+ background-color: transparent;
632
+ border: 1px solid #e5e7eb;
633
+ color: #6b7280;
634
+ padding: 8px 16px;
635
+ border-radius: 20px;
636
+ font-size: 13px;
637
+ font-weight: 500;
638
+ cursor: pointer;
639
+ transition: all 0.2s ease;
640
+ margin-bottom: 32px;
641
+ }
642
+
643
+ .knowledge-base-btn:hover {
644
+ background-color: #f9fafb;
645
+ color: #374151;
646
+ border-color: #d1d5db;
647
+ transform: translateY(-1px);
648
+ }
649
+
650
+ /* Modal Styles */
651
+ .modal-overlay {
652
+ position: fixed;
653
+ top: 0;
654
+ left: 0;
655
+ right: 0;
656
+ bottom: 0;
657
+ background-color: rgba(0, 0, 0, 0.6); /* Darker for better contrast */
658
+ backdrop-filter: blur(8px); /* Stronger blur */
659
+ display: flex;
660
+ align-items: center;
661
+ justify-content: center;
662
+ z-index: 1000;
663
+ animation: fadeIn 0.2s ease-out;
664
+ }
665
+
666
+ .modal-content {
667
+ background-color: var(--bg-primary);
668
+ width: 90%;
669
+ max-width: 500px;
670
+ max-height: 90vh; /* Keep within viewport */
671
+ display: flex;
672
+ flex-direction: column;
673
+ border-radius: 20px;
674
+ box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04);
675
+ overflow: hidden;
676
+ animation: slideUp 0.3s cubic-bezier(0.16, 1, 0.3, 1);
677
+ border: 1px solid var(--border-color);
678
+ color: var(--text-primary);
679
+ }
680
+
681
+ .modal-header {
682
+ padding: 20px 24px;
683
+ border-bottom: 1px solid var(--border-color);
684
+ display: flex;
685
+ justify-content: space-between;
686
+ align-items: center;
687
+ background-color: var(--bg-primary);
688
+ flex-shrink: 0; /* Header stays at the top */
689
+ }
690
+
691
+ .modal-header h3 {
692
+ font-size: 18px;
693
+ font-weight: 600;
694
+ color: var(--text-primary);
695
+ margin: 0;
696
+ }
697
+
698
+ .close-modal {
699
+ background: transparent;
700
+ border: none;
701
+ color: #9ca3af;
702
+ cursor: pointer;
703
+ padding: 4px;
704
+ font-size: 18px;
705
+ display: flex;
706
+ transition: color 0.2s;
707
+ }
708
+
709
+ .close-modal:hover {
710
+ color: #374151;
711
+ }
712
+
713
+ .modal-body {
714
+ padding: 24px;
715
+ overflow-y: auto; /* Contents scrollable */
716
+ flex: 1;
717
+ }
718
+
719
+ .modal-description {
720
+ font-size: 14px;
721
+ color: var(--text-secondary);
722
+ margin-bottom: 20px;
723
+ line-height: 1.5;
724
+ }
725
+
726
+ .sources-list {
727
+ display: flex;
728
+ flex-direction: column;
729
+ gap: 12px;
730
+ }
731
+
732
+ .source-item {
733
+ display: flex;
734
+ align-items: center;
735
+ gap: 12px;
736
+ padding: 12px 16px;
737
+ background-color: var(--bg-secondary);
738
+ border-radius: 12px;
739
+ transition: background-color 0.2s;
740
+ border: 1px solid var(--border-color);
741
+ }
742
+
743
+ .source-item:hover {
744
+ background-color: var(--hover-btn);
745
+ }
746
+
747
+ .source-icon {
748
+ font-size: 16px;
749
+ flex-shrink: 0;
750
+ }
751
+
752
+ .source-icon.pdf { color: #ef4444; }
753
+ .source-icon.word { color: #3b82f6; }
754
+ .source-icon.db { color: #10b981; }
755
+
756
+ .source-name {
757
+ font-size: 14px;
758
+ color: var(--text-primary);
759
+ font-weight: 500;
760
+ line-height: 1.4;
761
+ }
762
+
763
+ @keyframes fadeIn {
764
+ from { opacity: 0; }
765
+ to { opacity: 1; }
766
+ }
767
+
768
+ @keyframes slideUp {
769
+ from { transform: translateY(20px); opacity: 0; }
770
+ to { transform: translateY(0); opacity: 1; }
771
+ }
772
+
FrontEnd/src/ChatBot.jsx CHANGED
@@ -1,19 +1,107 @@
1
  import { useState, useEffect, useRef } from "react";
2
- import { FaUser, FaRobot, FaPaperPlane, FaCircle, FaPlus, FaPizzaSlice } from "react-icons/fa";
3
  import { MdOutlineContentCopy, MdCheck } from "react-icons/md";
4
  import ReactMarkdown from "react-markdown";
5
  import "./ChatBot.css";
6
 
7
  const BASE_URL = "https://tanmaivan-hutmind.hf.space";
8
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
9
  const ChatBot = () => {
10
  const [messages, setMessages] = useState([]);
11
  const [userInput, setUserInput] = useState("");
12
  const [isLoading, setIsLoading] = useState(false);
13
  const [copiedIndex, setCopiedIndex] = useState(null);
 
 
 
14
  const chatWindowRef = useRef(null);
15
  const textareaRef = useRef(null);
16
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
17
  // Auto scroll
18
  useEffect(() => {
19
  if (chatWindowRef.current) {
@@ -55,7 +143,7 @@ const ChatBot = () => {
55
  });
56
 
57
  if (!response.ok) {
58
- throw new Error("Lỗi khi gửi yêu cầu");
59
  }
60
 
61
  const reader = response.body.getReader();
@@ -91,7 +179,7 @@ const ChatBot = () => {
91
  console.error("Lỗi:", error);
92
  setMessages((prevMessages) => [
93
  ...prevMessages.filter((msg) => msg.text !== "..."),
94
- { sender: "bot", text: "Đã xảy ra lỗi khi kết nối với chatbot. Vui lòng thử lại sau." },
95
  ]);
96
  } finally {
97
  setIsLoading(false);
@@ -107,7 +195,7 @@ const ChatBot = () => {
107
  });
108
 
109
  if (!response.ok) {
110
- throw new Error("Lỗi khi khởi tạo lại chatbot");
111
  }
112
 
113
  const data = await response.json();
@@ -124,46 +212,86 @@ const ChatBot = () => {
124
  });
125
  };
126
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
127
  return (
128
- <div className="chatbot-container">
129
  <div className="chat-header">
130
- <div className="header-logo" onClick={handleNewChat} title="Quay về trang chủ">
131
- <img src="/pizzahut.png" alt="Pizza Hut Logo" width={50} height={50} />
132
- <div className="header-title">
133
- <h1>HutMind</h1>
 
 
 
 
 
 
 
134
  </div>
135
  </div>
136
- <button className="new-chat-btn" onClick={handleNewChat} title="Cuộc trò chuyện mới">
137
- <FaPlus size={16} />
138
- <span>New Chat</span>
139
- </button>
 
 
 
 
 
 
 
 
 
 
 
 
140
  </div>
141
 
142
  <div className="chat-window" ref={chatWindowRef}>
143
  {messages.length === 0 && (
144
  <div className="welcome-container">
145
  <div className="welcome-logo">
146
- <img src="/mascot.png" alt="Pizza Hut Mascot" width={200} height={250} />
147
  </div>
148
- <h2>Tôi có thể giúp gì cho bạn?</h2>
149
- <p className="welcome-subtitle">
150
- Hãy hỏi tôi về chính sách JRG, thông tin hệ thống Pizza Hut VN hoặc về Data Team.
151
- </p>
 
 
 
 
152
  <div className="suggestion-chips">
153
- <button onClick={() => setUserInput("Giới thiệu về team data, ban quản lý là ai?")}>
154
- Giới thiệu Team Data
155
  </button>
156
 
157
- <button onClick={() => setUserInput("Kể cho tôi nghe lịch sử hình thành của Pizza Hut!")}>
158
- Lịch sử Pizza Hut
159
  </button>
160
 
161
- <button onClick={() => setUserInput("Team Data có ai làm việc ở nước ngoài không?")}>
162
- Team Data nước ngoài
163
  </button>
164
 
165
- <button onClick={() => setUserInput("Tập đoàn JRG đang quản lý thương hiệu Pizza Hut ở những nước nào?")}>
166
- Quy mô JRG toàn cầu
167
  </button>
168
  </div>
169
  </div>
@@ -196,7 +324,7 @@ const ChatBot = () => {
196
  <button
197
  className="copy-btn"
198
  onClick={() => copyToClipboard(message.text, index)}
199
- title="Sao chép"
200
  >
201
  {copiedIndex === index ? <MdCheck size={18} /> : <MdOutlineContentCopy size={18} />}
202
  </button>
@@ -218,7 +346,7 @@ const ChatBot = () => {
218
  ref={textareaRef}
219
  value={userInput}
220
  onChange={(e) => setUserInput(e.target.value)}
221
- placeholder="Gửi tin nhắn cho HutMind..."
222
  rows={1}
223
  onKeyDown={(e) => {
224
  if (e.key === "Enter" && !e.shiftKey) {
@@ -237,10 +365,32 @@ const ChatBot = () => {
237
  <FaPaperPlane size={16} />
238
  </button>
239
  </div>
240
- <div className="footer-text">
241
- HutMind có thể mắc lỗi. Vui lòng kiểm tra lại các thông tin quan trọng.
242
- </div>
243
  </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
244
  </div>
245
  );
246
  };
 
1
  import { useState, useEffect, useRef } from "react";
2
+ import { FaUser, FaRobot, FaPaperPlane, FaCircle, FaPlus, FaPizzaSlice, FaInfoCircle, FaTimes, FaFilePdf, FaFileWord, FaDatabase, FaGlobe, FaSun, FaMoon } from "react-icons/fa";
3
  import { MdOutlineContentCopy, MdCheck } from "react-icons/md";
4
  import ReactMarkdown from "react-markdown";
5
  import "./ChatBot.css";
6
 
7
  const BASE_URL = "https://tanmaivan-hutmind.hf.space";
8
 
9
+ const TRANSLATIONS = {
10
+ vi: {
11
+ newChat: "Trò chuyện mới",
12
+ welcomeTitle: "Tôi có thể giúp gì cho bạn?",
13
+ welcomeSubtitle: "Hãy hỏi tôi về chính sách JRG, thông tin hệ thống Pizza Hut VN hoặc về Data Analytics Team.",
14
+ knowledgeBaseBtn: "Dữ liệu thông tin",
15
+ suggestion1: "Giới thiệu về team data analytics, ban quản lý là ai?",
16
+ suggestion1Chip: "Giới thiệu Data Analytics",
17
+ suggestion2: "Kể cho tôi nghe lịch sử hình thành của Pizza Hut!",
18
+ suggestion2Chip: "Lịch sử Pizza Hut",
19
+ suggestion3: "Data Analytics Team có ai làm việc ở nước ngoài không?",
20
+ suggestion3Chip: "Data Analytics nước ngoài",
21
+ suggestion4: "Tập đoàn JRG đang quản lý thương hiệu Pizza Hut ở những nước nào?",
22
+ suggestion4Chip: "Quy mô JRG toàn cầu",
23
+ inputPlaceholder: "Gửi tin nhắn cho HutMind...",
24
+ footerDisclaimer: "HutMind có thể mắc lỗi. Vui lòng kiểm tra lại các thông tin quan trọng.",
25
+ modalTitle: "Dữ liệu tham khảo",
26
+ modalDescription: "Dưới đây là các tài liệu và nguồn dữ liệu hiện đang được HutMind sử dụng để trả lời câu hỏi của bạn:",
27
+ errorConnection: "Đã xảy ra lỗi khi kết nối với chatbot. Vui lòng thử lại sau.",
28
+ errorInit: "Lỗi khi khởi tạo lại chatbot",
29
+ copyTooltip: "Sao chép",
30
+ themeLight: "Chế độ sáng",
31
+ themeDark: "Chế độ tối",
32
+ sources: [
33
+ { name: "Thông tin về Data Analytics Team (JRGVN)", type: "database" },
34
+ { name: "Hướng dẫn Chính sách Bảo mật Thông tin (2024)", type: "pdf" },
35
+ { name: "Quy định Nghỉ phép và Nghỉ bệnh (PSL)", type: "pdf" },
36
+ { name: "Chính sách Công tác và Tiếp khách nước ngoài (2025)", type: "pdf" },
37
+ { name: "Chính sách Bảo mật Dữ liệu PHV (2023)", type: "word" },
38
+ { name: "Chỉ thị Ứng phó Sự cố CNTT (DRP)", type: "pdf" },
39
+ { name: "Danh mục từ viết tắt và Thuật ngữ chuyên môn", type: "word" },
40
+ { name: "Quy trình và Chính sách Phần mềm", type: "pdf" },
41
+ ]
42
+ },
43
+ en: {
44
+ newChat: "New Chat",
45
+ welcomeTitle: "How can I help you today?",
46
+ welcomeSubtitle: "Ask me about JRG policies, Pizza Hut VN systems, or the Data Analytics Team.",
47
+ knowledgeBaseBtn: "Knowledge Base",
48
+ suggestion1: "Intro to Data Analytics Team, who's the management?",
49
+ suggestion1Chip: "Data Analytics Intro",
50
+ suggestion2: "Tell me about the history of Pizza Hut!",
51
+ suggestion2Chip: "Pizza Hut History",
52
+ suggestion3: "Does anyone in the Data Analytics Team work overseas?",
53
+ suggestion3Chip: "Overseas Data Analytics Team",
54
+ suggestion4: "Which countries does JRG manage the Pizza Hut brand in?",
55
+ suggestion4Chip: "JRG Global Scale",
56
+ inputPlaceholder: "Message HutMind...",
57
+ footerDisclaimer: "HutMind can make mistakes. Check important info.",
58
+ modalTitle: "Reference Data",
59
+ modalDescription: "Below are the documents and data sources currently being used by HutMind to answer your questions:",
60
+ errorConnection: "An error occurred connecting to the chatbot. Please try again later.",
61
+ errorInit: "Error re-initializing chatbot",
62
+ copyTooltip: "Copy",
63
+ themeLight: "Light mode",
64
+ themeDark: "Dark mode",
65
+ sources: [
66
+ { name: "Data Analytics Team Info (JRGVN)", type: "database" },
67
+ { name: "Info Security Policy Guidelines (2024)", type: "pdf" },
68
+ { name: "Leave & Sick Leave Regulations (PSL)", type: "pdf" },
69
+ { name: "Overseas Travel & Entertainment Policy (2025)", type: "pdf" },
70
+ { name: "PHV Data Privacy Policy (2023)", type: "word" },
71
+ { name: "IT Disaster Recovery Directive (DRP)", type: "pdf" },
72
+ { name: "Abbreviations & Technical Terms", type: "word" },
73
+ { name: "Software Policy & Procedure", type: "pdf" },
74
+ ]
75
+ }
76
+ };
77
+
78
  const ChatBot = () => {
79
  const [messages, setMessages] = useState([]);
80
  const [userInput, setUserInput] = useState("");
81
  const [isLoading, setIsLoading] = useState(false);
82
  const [copiedIndex, setCopiedIndex] = useState(null);
83
+ const [showKnowledgeModal, setShowKnowledgeModal] = useState(false);
84
+ const [language, setLanguage] = useState(() => localStorage.getItem("hutmind-lang") || "vi");
85
+ const [theme, setTheme] = useState(() => localStorage.getItem("hutmind-theme") || "light");
86
  const chatWindowRef = useRef(null);
87
  const textareaRef = useRef(null);
88
 
89
+ const t = (key) => TRANSLATIONS[language][key];
90
+
91
+ // Persist settings
92
+ useEffect(() => {
93
+ localStorage.setItem("hutmind-lang", language);
94
+ }, [language]);
95
+
96
+ useEffect(() => {
97
+ localStorage.setItem("hutmind-theme", theme);
98
+ if (theme === 'dark') {
99
+ document.body.classList.add('dark');
100
+ } else {
101
+ document.body.classList.remove('dark');
102
+ }
103
+ }, [theme]);
104
+
105
  // Auto scroll
106
  useEffect(() => {
107
  if (chatWindowRef.current) {
 
143
  });
144
 
145
  if (!response.ok) {
146
+ throw new Error(t("errorConnection"));
147
  }
148
 
149
  const reader = response.body.getReader();
 
179
  console.error("Lỗi:", error);
180
  setMessages((prevMessages) => [
181
  ...prevMessages.filter((msg) => msg.text !== "..."),
182
+ { sender: "bot", text: t("errorConnection") },
183
  ]);
184
  } finally {
185
  setIsLoading(false);
 
195
  });
196
 
197
  if (!response.ok) {
198
+ throw new Error(t("errorInit"));
199
  }
200
 
201
  const data = await response.json();
 
212
  });
213
  };
214
 
215
+ const getSourceIcon = (type) => {
216
+ switch (type) {
217
+ case "pdf": return <FaFilePdf className="source-icon pdf" />;
218
+ case "word": return <FaFileWord className="source-icon word" />;
219
+ case "database": return <FaDatabase className="source-icon db" />;
220
+ default: return <FaInfoCircle className="source-icon" />;
221
+ }
222
+ };
223
+
224
+ const toggleLanguage = () => {
225
+ setLanguage(prev => prev === "vi" ? "en" : "vi");
226
+ };
227
+
228
+ const toggleTheme = () => {
229
+ setTheme(prev => prev === "light" ? "dark" : "light");
230
+ };
231
+
232
  return (
233
+ <div className={`chatbot-container ${theme}`}>
234
  <div className="chat-header">
235
+ <div className="header-left">
236
+ <div className="header-logo" onClick={handleNewChat} title="HutMind">
237
+ <img
238
+ src={theme === 'light' ? "/logo_light.png" : "/logo_dark.png"}
239
+ alt="Logo"
240
+ width={50}
241
+ height={50}
242
+ />
243
+ <div className="header-title">
244
+ <h1>HutMind</h1>
245
+ </div>
246
  </div>
247
  </div>
248
+
249
+ <div className="header-right">
250
+ <button className="theme-switcher" onClick={toggleTheme} title={theme === 'light' ? t("themeDark") : t("themeLight")}>
251
+ {theme === 'light' ? <FaMoon size={16} /> : <FaSun size={16} />}
252
+ </button>
253
+
254
+ <button className="lang-switcher" onClick={toggleLanguage} title="Switch Language">
255
+ <FaGlobe size={14} />
256
+ <span>{language.toUpperCase()}</span>
257
+ </button>
258
+
259
+ <button className="new-chat-btn" onClick={handleNewChat} title={t("newChat")}>
260
+ <FaPlus size={16} />
261
+ <span>{t("newChat")}</span>
262
+ </button>
263
+ </div>
264
  </div>
265
 
266
  <div className="chat-window" ref={chatWindowRef}>
267
  {messages.length === 0 && (
268
  <div className="welcome-container">
269
  <div className="welcome-logo">
270
+ <img src="/mascot.png" alt="Mascot" width={200} height={250} />
271
  </div>
272
+ <h2>{t("welcomeTitle")}</h2>
273
+ <p className="welcome-subtitle">{t("welcomeSubtitle")}</p>
274
+
275
+ <button className="knowledge-base-btn" onClick={() => setShowKnowledgeModal(true)}>
276
+ <FaInfoCircle size={14} />
277
+ <span>{t("knowledgeBaseBtn")}</span>
278
+ </button>
279
+
280
  <div className="suggestion-chips">
281
+ <button onClick={() => setUserInput(t("suggestion1"))}>
282
+ {t("suggestion1Chip")}
283
  </button>
284
 
285
+ <button onClick={() => setUserInput(t("suggestion2"))}>
286
+ {t("suggestion2Chip")}
287
  </button>
288
 
289
+ <button onClick={() => setUserInput(t("suggestion3"))}>
290
+ {t("suggestion3Chip")}
291
  </button>
292
 
293
+ <button onClick={() => setUserInput(t("suggestion4"))}>
294
+ {t("suggestion4Chip")}
295
  </button>
296
  </div>
297
  </div>
 
324
  <button
325
  className="copy-btn"
326
  onClick={() => copyToClipboard(message.text, index)}
327
+ title={t("copyTooltip")}
328
  >
329
  {copiedIndex === index ? <MdCheck size={18} /> : <MdOutlineContentCopy size={18} />}
330
  </button>
 
346
  ref={textareaRef}
347
  value={userInput}
348
  onChange={(e) => setUserInput(e.target.value)}
349
+ placeholder={t("inputPlaceholder")}
350
  rows={1}
351
  onKeyDown={(e) => {
352
  if (e.key === "Enter" && !e.shiftKey) {
 
365
  <FaPaperPlane size={16} />
366
  </button>
367
  </div>
368
+ <div className="footer-text">{t("footerDisclaimer")}</div>
 
 
369
  </div>
370
+
371
+ {showKnowledgeModal && (
372
+ <div className="modal-overlay" onClick={() => setShowKnowledgeModal(false)}>
373
+ <div className="modal-content" onClick={(e) => e.stopPropagation()}>
374
+ <div className="modal-header">
375
+ <h3>{t("modalTitle")}</h3>
376
+ <button className="close-modal" onClick={() => setShowKnowledgeModal(false)}>
377
+ <FaTimes />
378
+ </button>
379
+ </div>
380
+ <div className="modal-body">
381
+ <p className="modal-description">{t("modalDescription")}</p>
382
+ <div className="sources-list">
383
+ {t("sources").map((source, idx) => (
384
+ <div key={idx} className="source-item">
385
+ {getSourceIcon(source.type)}
386
+ <span className="source-name">{source.name}</span>
387
+ </div>
388
+ ))}
389
+ </div>
390
+ </div>
391
+ </div>
392
+ </div>
393
+ )}
394
  </div>
395
  );
396
  };