@salesforcedevs/docs-components 0.0.32-chat → 0.0.34-chat

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@salesforcedevs/docs-components",
3
- "version": "0.0.32-chat",
3
+ "version": "0.0.34-chat",
4
4
  "description": "Docs Lightning web components for DSC",
5
5
  "license": "MIT",
6
6
  "main": "index.js",
@@ -5,6 +5,7 @@
5
5
  right: 0;
6
6
  height: 100vh;
7
7
  z-index: 100000;
8
+ font-family: "Salesforce Sans", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", sans-serif;
8
9
  }
9
10
 
10
11
  /* Apply content shift to main page content when chat is open */
@@ -36,26 +37,26 @@ body.chat-closed .global-header {
36
37
  position: fixed;
37
38
  bottom: 20px;
38
39
  right: 20px;
39
- width: 60px;
40
- height: 60px;
40
+ width: 64px;
41
+ height: 64px;
41
42
  border-radius: 50%;
42
- background-color: #0066cc;
43
+ background: linear-gradient(135deg, #0176d3 0%, #005fb2 100%);
43
44
  color: white;
44
45
  border: none;
45
46
  cursor: pointer;
46
- box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
47
+ box-shadow: 0 8px 24px rgb(1 118 211 / 25%), 0 2px 8px rgb(1 118 211 / 15%);
47
48
  z-index: 1001;
48
49
  display: flex;
49
50
  align-items: center;
50
51
  justify-content: center;
51
- transition: all 0.3s ease;
52
- animation: pulse 2s infinite;
52
+ transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
53
+ animation: pulse 3s infinite;
53
54
  }
54
55
 
55
56
  .chat-trigger-button:hover {
56
- background-color: #0052a3;
57
- transform: scale(1.1);
58
- box-shadow: 0 6px 20px rgba(0, 0, 0, 0.4);
57
+ background: linear-gradient(135deg, #005fb2 0%, #003e73 100%);
58
+ transform: scale(1.05);
59
+ box-shadow: 0 12px 32px rgb(1 118 211 / 35%), 0 4px 12px rgb(1 118 211 / 25%);
59
60
  }
60
61
 
61
62
  .chat-trigger-button:active {
@@ -79,13 +80,15 @@ body.chat-closed .global-header {
79
80
 
80
81
  @keyframes pulse {
81
82
  0% {
82
- box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3), 0 0 0 0 rgba(0, 102, 204, 0.7);
83
+ box-shadow: 0 8px 24px rgb(1 118 211 / 25%), 0 2px 8px rgb(1 118 211 / 15%), 0 0 0 0 rgb(1 118 211 / 40%);
83
84
  }
85
+
84
86
  70% {
85
- box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3), 0 0 0 10px rgba(0, 102, 204, 0);
87
+ box-shadow: 0 8px 24px rgb(1 118 211 / 25%), 0 2px 8px rgb(1 118 211 / 15%), 0 0 0 16px rgb(1 118 211 / 0%);
86
88
  }
89
+
87
90
  100% {
88
- box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3), 0 0 0 0 rgba(0, 102, 204, 0);
91
+ box-shadow: 0 8px 24px rgb(1 118 211 / 25%), 0 2px 8px rgb(1 118 211 / 15%), 0 0 0 0 rgb(1 118 211 / 0%);
89
92
  }
90
93
  }
91
94
 
@@ -93,32 +96,46 @@ body.chat-closed .global-header {
93
96
  .chat-container {
94
97
  height: 100vh;
95
98
  width: 400px;
96
- background-color: #ffffff;
97
- box-shadow: -2px 0 10px rgba(0, 0, 0, 0.1);
99
+ background-color: #fff;
100
+ box-shadow: -8px 0 32px rgb(0 0 0 / 12%), -2px 0 8px rgb(0 0 0 / 8%);
98
101
  transform: translateX(100%);
99
- transition: transform 0.3s ease-in-out;
102
+ transition: transform 0.4s cubic-bezier(0.4, 0, 0.2, 1);
100
103
  display: flex;
101
104
  flex-direction: column;
102
- border-left: 1px solid #e0e0e0;
105
+ border-left: 1px solid #d8dde6;
106
+ backdrop-filter: blur(20px);
103
107
  }
104
108
 
105
- .chat-container--open {
109
+ .chat-container_open {
106
110
  transform: translateX(0);
107
111
  }
108
112
 
109
- .chat-container--disabled {
113
+ .chat-container_disabled {
110
114
  opacity: 0.6;
111
115
  pointer-events: none;
112
116
  }
113
117
 
114
118
  .chat-header {
115
- background-color: #f8f9fa;
116
- padding: 16px;
117
- border-bottom: 1px solid #e0e0e0;
119
+ background: linear-gradient(135deg, #0176d3 0%, #005fb2 100%);
120
+ padding: 18px 24px;
121
+ border-bottom: 1px solid rgb(1 118 211 / 20%);
118
122
  display: flex;
119
123
  align-items: center;
120
124
  justify-content: space-between;
121
125
  flex-shrink: 0;
126
+ backdrop-filter: blur(10px);
127
+ position: relative;
128
+ }
129
+
130
+ .chat-header::before {
131
+ content: "";
132
+ position: absolute;
133
+ top: 0;
134
+ left: 0;
135
+ right: 0;
136
+ bottom: 0;
137
+ background: linear-gradient(135deg, rgb(255 255 255 / 5%) 0%, rgb(255 255 255 / 1%) 100%);
138
+ pointer-events: none;
122
139
  }
123
140
 
124
141
  .chat-header-actions {
@@ -129,28 +146,31 @@ body.chat-closed .global-header {
129
146
 
130
147
  .chat-close-button,
131
148
  .chat-clear-button {
132
- width: 32px;
133
- height: 32px;
149
+ width: 36px;
150
+ height: 36px;
134
151
  border: none;
135
- background: none;
152
+ background: rgb(255 255 255 / 15%);
136
153
  cursor: pointer;
137
154
  display: flex;
138
155
  align-items: center;
139
156
  justify-content: center;
140
- border-radius: 4px;
141
- transition: background-color 0.2s ease;
142
- color: #666666;
157
+ border-radius: 8px;
158
+ transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);
159
+ color: white;
160
+ position: relative;
161
+ z-index: 1;
143
162
  }
144
163
 
145
164
  .chat-close-button:hover,
146
165
  .chat-clear-button:hover {
147
- background-color: #e9ecef;
148
- color: #333333;
166
+ background: rgb(255 255 255 / 25%);
167
+ transform: scale(1.05);
168
+ color: white;
149
169
  }
150
170
 
151
171
  .chat-clear-button:hover {
152
- background-color: #fee;
153
- color: #dc3545;
172
+ background: rgb(255 82 82 / 20%);
173
+ color: #ff6b6b;
154
174
  }
155
175
 
156
176
  .close-icon,
@@ -161,24 +181,30 @@ body.chat-closed .global-header {
161
181
 
162
182
  .chat-title {
163
183
  margin: 0;
164
- font-size: 18px;
165
- font-weight: 600;
166
- color: #333333;
184
+ font-size: 20px;
185
+ font-weight: 700;
186
+ color: white;
167
187
  flex: 1;
188
+ position: relative;
189
+ z-index: 1;
190
+ letter-spacing: -0.02em;
168
191
  }
169
192
 
170
193
  .chat-messages {
171
194
  flex: 1;
172
195
  overflow-y: auto;
173
- padding: 16px;
196
+ padding: 24px 20px;
174
197
  scroll-behavior: smooth;
175
198
  min-height: 0;
199
+ background: linear-gradient(180deg, #fafbfc 0%, #f7f9fb 100%);
176
200
  }
177
201
 
178
202
  .message-wrapper {
179
- margin-bottom: 16px;
203
+ margin-bottom: 20px;
180
204
  display: flex;
181
205
  align-items: flex-start;
206
+ animation: message-slide-in 0.4s cubic-bezier(0.4, 0, 0.2, 1);
207
+ gap: 12px;
182
208
  }
183
209
 
184
210
  .message-wrapper[data-sender="user"] {
@@ -189,6 +215,120 @@ body.chat-closed .global-header {
189
215
  justify-content: flex-start;
190
216
  }
191
217
 
218
+ /* Avatar styling */
219
+ .message-avatar-wrapper {
220
+ flex-shrink: 0;
221
+ display: flex;
222
+ align-items: flex-start;
223
+ margin-top: 2px;
224
+ }
225
+
226
+ /* Hide avatar for user messages */
227
+ .message-wrapper[data-sender="user"] .message-avatar-wrapper {
228
+ display: none;
229
+ }
230
+
231
+ /* Show avatar for assistant messages */
232
+ .message-wrapper[data-sender="assistant"] .message-avatar-wrapper {
233
+ display: flex;
234
+ }
235
+
236
+ .avatar-container {
237
+ width: 36px;
238
+ height: 36px;
239
+ background: linear-gradient(180deg, #fafbfc 0%, #f7f9fb 100%);
240
+ border-radius: 50%;
241
+ display: flex;
242
+ align-items: center;
243
+ justify-content: center;
244
+ box-shadow: 0 2px 8px rgb(0 0 0 / 10%);
245
+ position: relative;
246
+ overflow: hidden;
247
+ }
248
+
249
+ .avatar-container::before {
250
+ content: "";
251
+ position: absolute;
252
+ inset: 0;
253
+ background: linear-gradient(135deg, rgb(255 255 255 / 20%) 0%, rgb(255 255 255 / 10%) 100%);
254
+ border-radius: 50%;
255
+ }
256
+
257
+ .avatar-container {
258
+ animation: thinking-avatar-pulse 3s ease-in-out infinite;
259
+ }
260
+
261
+ .avatar-icon {
262
+ width: 32px;
263
+ height: 32px;
264
+ color: white;
265
+ position: relative;
266
+ z-index: 1;
267
+ filter: drop-shadow(0 1px 2px rgb(0 0 0 / 15%));
268
+ }
269
+
270
+ /* Thinking Bot SVG Animations */
271
+ .thinking-body {
272
+ animation: thinking-breathe 2s ease-in-out infinite;
273
+ transform-origin: center;
274
+ }
275
+
276
+ .thinking-smile {
277
+ animation: thinking-smile-animation 3s ease-in-out infinite;
278
+ }
279
+
280
+ .thinking-eye {
281
+ animation: think-blink 6s infinite;
282
+ transform-origin: center;
283
+ }
284
+
285
+ @keyframes thinking-avatar-pulse {
286
+ 0%, 100% {
287
+ transform: scale(1);
288
+ box-shadow: 0 2px 8px rgb(0 0 0 / 10%);
289
+ }
290
+ 50% {
291
+ transform: scale(1.08);
292
+ box-shadow: 0 4px 16px rgb(0 0 0 / 20%);
293
+ }
294
+ }
295
+
296
+ @keyframes thinking-breathe {
297
+ 0%, 100% {
298
+ transform: scale(1);
299
+ }
300
+ 50% {
301
+ transform: scale(1.05);
302
+ }
303
+ }
304
+
305
+ @keyframes thinking-smile-animation {
306
+ 0%, 100% {
307
+ transform: translateY(0px);
308
+ }
309
+ 50% {
310
+ transform: translateY(-2px);
311
+ }
312
+ }
313
+
314
+ @keyframes think-blink {
315
+ 0%, 85%, 100% {
316
+ transform: scaleY(1);
317
+ }
318
+ 90% {
319
+ transform: scaleY(0.6);
320
+ }
321
+ 92% {
322
+ transform: scaleY(0.2);
323
+ }
324
+ 94% {
325
+ transform: scaleY(0.6);
326
+ }
327
+ 96% {
328
+ transform: scaleY(1);
329
+ }
330
+ }
331
+
192
332
  .message-content {
193
333
  max-width: 80%;
194
334
  display: flex;
@@ -201,35 +341,43 @@ body.chat-closed .global-header {
201
341
  }
202
342
 
203
343
  .message-bubble {
204
- padding: 12px 16px;
205
- border-radius: 18px;
344
+ padding: 14px 18px;
345
+ border-radius: 20px;
206
346
  word-wrap: break-word;
207
347
  max-width: 100%;
348
+ position: relative;
349
+ backdrop-filter: blur(10px);
350
+ box-shadow: 0 2px 8px rgb(0 0 0 / 8%);
208
351
  }
209
352
 
210
353
  .message-wrapper[data-sender="user"] .message-bubble {
211
- background-color: #0066cc;
354
+ background: linear-gradient(135deg, #0176d3 0%, #005fb2 100%);
212
355
  color: white;
356
+ border-bottom-right-radius: 8px;
213
357
  }
214
358
 
215
359
  .message-wrapper[data-sender="assistant"] .message-bubble {
216
- background-color: #f0f0f0;
217
- color: #333333;
360
+ background: white;
361
+ color: #181818;
362
+ border: 1px solid #e5e7ea;
363
+ border-bottom-left-radius: 8px;
218
364
  }
219
365
 
220
366
  .message-text {
221
367
  margin: 0;
222
- line-height: 1.4;
223
- font-size: 14px;
368
+ line-height: 1.5;
369
+ font-size: 15px;
370
+ font-weight: 400;
224
371
  }
225
372
 
226
373
  .message-timestamp {
227
374
  font-size: 12px;
228
- color: #666666;
229
- margin-top: 4px;
375
+ color: #706e6b;
376
+ margin-top: 6px;
230
377
  display: flex;
231
378
  align-items: center;
232
379
  gap: 4px;
380
+ font-weight: 400;
233
381
  }
234
382
 
235
383
  .message-sender {
@@ -252,7 +400,7 @@ body.chat-closed .global-header {
252
400
  width: 8px;
253
401
  height: 8px;
254
402
  border-radius: 50%;
255
- background-color: #666666;
403
+ background-color: #0176d3;
256
404
  animation: typing 1.4s infinite ease-in-out both;
257
405
  }
258
406
 
@@ -265,21 +413,37 @@ body.chat-closed .global-header {
265
413
  }
266
414
 
267
415
  @keyframes typing {
268
- 0%, 80%, 100% {
416
+ 0%,
417
+ 80%,
418
+ 100% {
269
419
  transform: scale(0);
270
420
  opacity: 0.5;
271
421
  }
422
+
272
423
  40% {
273
424
  transform: scale(1);
274
425
  opacity: 1;
275
426
  }
276
427
  }
277
428
 
429
+ @keyframes message-slide-in {
430
+ from {
431
+ opacity: 0;
432
+ transform: translateY(12px);
433
+ }
434
+
435
+ to {
436
+ opacity: 1;
437
+ transform: translateY(0);
438
+ }
439
+ }
440
+
278
441
  .chat-input-area {
279
- border-top: 1px solid #e0e0e0;
280
- padding: 16px;
281
- background-color: #ffffff;
442
+ border-top: 1px solid #e5e7ea;
443
+ padding: 20px 24px;
444
+ background: white;
282
445
  flex-shrink: 0;
446
+ backdrop-filter: blur(20px);
283
447
  }
284
448
 
285
449
  .chat-input-container {
@@ -290,18 +454,22 @@ body.chat-closed .global-header {
290
454
 
291
455
  .chat-input {
292
456
  flex: 1;
293
- padding: 12px 16px;
294
- border: 1px solid #e0e0e0;
295
- border-radius: 20px;
296
- font-size: 14px;
457
+ padding: 14px 18px;
458
+ border: 2px solid #d8dde6;
459
+ border-radius: 24px;
460
+ font-size: 15px;
297
461
  outline: none;
298
- background-color: #f8f9fa;
299
- transition: border-color 0.2s ease;
462
+ background-color: #fafbfc;
463
+ transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);
464
+ font-family: inherit;
465
+ line-height: 1.4;
300
466
  }
301
467
 
302
468
  .chat-input:focus {
303
- border-color: #0066cc;
304
- background-color: #ffffff;
469
+ border-color: #0176d3;
470
+ background-color: #fff;
471
+ box-shadow: 0 0 0 3px rgb(1 118 211 / 10%);
472
+ transform: translateY(-1px);
305
473
  }
306
474
 
307
475
  .chat-input:disabled {
@@ -310,26 +478,32 @@ body.chat-closed .global-header {
310
478
  }
311
479
 
312
480
  .chat-send-button {
313
- width: 40px;
314
- height: 40px;
481
+ width: 44px;
482
+ height: 44px;
315
483
  border: none;
316
484
  border-radius: 50%;
317
- background-color: #0066cc;
485
+ background: linear-gradient(135deg, #0176d3 0%, #005fb2 100%);
318
486
  color: white;
319
487
  cursor: pointer;
320
488
  display: flex;
321
489
  align-items: center;
322
490
  justify-content: center;
323
- transition: background-color 0.2s ease;
491
+ transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);
492
+ box-shadow: 0 4px 12px rgb(1 118 211 / 25%);
493
+ margin-left: 12px;
324
494
  }
325
495
 
326
496
  .chat-send-button:hover:not(:disabled) {
327
- background-color: #0052a3;
497
+ background: linear-gradient(135deg, #005fb2 0%, #003e73 100%);
498
+ transform: scale(1.05);
499
+ box-shadow: 0 6px 16px rgb(1 118 211 / 35%);
328
500
  }
329
501
 
330
502
  .chat-send-button:disabled {
331
- background-color: #cccccc;
503
+ background: #d8dde6;
332
504
  cursor: not-allowed;
505
+ transform: none;
506
+ box-shadow: none;
333
507
  }
334
508
 
335
509
  .send-icon {
@@ -340,10 +514,10 @@ body.chat-closed .global-header {
340
514
  /* Responsive design */
341
515
  @media (max-width: 768px) {
342
516
  .chat-trigger-button {
343
- width: 50px;
344
- height: 50px;
345
- bottom: 15px;
346
- right: 15px;
517
+ width: 56px;
518
+ height: 56px;
519
+ bottom: 16px;
520
+ right: 16px;
347
521
  }
348
522
 
349
523
  .chat-icon {
@@ -362,12 +536,42 @@ body.chat-closed .global-header {
362
536
  transform: translateX(100%);
363
537
  }
364
538
 
365
- .chat-container--open {
539
+ .chat-container_open {
366
540
  transform: translateX(0);
367
541
  }
368
542
 
543
+ .chat-header {
544
+ padding: 16px 20px;
545
+ }
546
+
547
+ .chat-title {
548
+ font-size: 18px;
549
+ }
550
+
551
+ .chat-messages {
552
+ padding: 20px 16px;
553
+ }
554
+
555
+ .chat-input-area {
556
+ padding: 16px 20px;
557
+ }
558
+
369
559
  .message-content {
370
- max-width: 90%;
560
+ max-width: 85%;
561
+ }
562
+
563
+ .avatar-container {
564
+ width: 32px;
565
+ height: 32px;
566
+ }
567
+
568
+ .avatar-icon {
569
+ width: 28px;
570
+ height: 28px;
571
+ }
572
+
573
+ .message-wrapper {
574
+ gap: 10px;
371
575
  }
372
576
  }
373
577
 
@@ -378,32 +582,47 @@ body.chat-closed .global-header {
378
582
  bottom: 10px;
379
583
  right: 10px;
380
584
  }
381
-
585
+
382
586
  .chat-icon {
383
587
  width: 20px;
384
588
  height: 20px;
385
589
  }
386
-
590
+
387
591
  .chat-trigger-text {
388
592
  font-size: 7px;
389
593
  margin-top: 16px;
390
594
  }
391
-
595
+
392
596
  .chat-container {
393
597
  width: 100%;
394
598
  }
395
-
599
+
396
600
  .chat-header {
397
601
  padding: 12px;
398
602
  }
399
-
603
+
400
604
  .chat-messages {
401
605
  padding: 12px;
402
606
  }
403
-
607
+
404
608
  .chat-input-area {
405
609
  padding: 12px;
406
610
  }
611
+
612
+ .avatar-container {
613
+ width: 28px;
614
+ height: 28px;
615
+ }
616
+
617
+ .avatar-icon {
618
+ width: 24px;
619
+ height: 24px;
620
+ }
621
+
622
+ .message-wrapper {
623
+ gap: 8px;
624
+ margin-bottom: 16px;
625
+ }
407
626
  }
408
627
 
409
628
  /* Accessibility improvements */
@@ -411,30 +630,47 @@ body.chat-closed .global-header {
411
630
  .chat-trigger-button {
412
631
  animation: none;
413
632
  }
414
-
633
+
415
634
  .typing-dot {
416
635
  animation: none;
417
636
  }
418
-
637
+
419
638
  .chat-messages {
420
639
  scroll-behavior: auto;
421
640
  }
641
+
642
+ .avatar-container {
643
+ animation: none;
644
+ }
645
+
646
+ /* Disable all thinking bot animations for reduced motion users */
647
+ .thinking-body {
648
+ animation: none !important;
649
+ }
650
+
651
+ .thinking-smile {
652
+ animation: none !important;
653
+ }
654
+
655
+ .thinking-eye {
656
+ animation: none !important;
657
+ }
422
658
  }
423
659
 
424
660
  /* High contrast mode support */
425
661
  @media (prefers-contrast: high) {
426
662
  .chat-container {
427
- border: 2px solid #000000;
663
+ border: 2px solid #000;
428
664
  }
429
-
665
+
430
666
  .message-wrapper[data-sender="user"] .message-bubble {
431
- background-color: #000000;
432
- color: #ffffff;
667
+ background-color: #000;
668
+ color: #fff;
433
669
  }
434
-
670
+
435
671
  .message-wrapper[data-sender="assistant"] .message-bubble {
436
- background-color: #ffffff;
437
- color: #000000;
438
- border: 1px solid #000000;
672
+ background-color: #fff;
673
+ color: #000;
674
+ border: 1px solid #000;
439
675
  }
440
- }
676
+ }
@@ -1,13 +1,23 @@
1
1
  <template>
2
2
  <!-- Floating trigger button - visible when chat is closed -->
3
3
  <template lwc:if={showTriggerButton}>
4
- <button
4
+ <button
5
5
  class="chat-trigger-button"
6
6
  onclick={handleOpenClick}
7
7
  aria-label="Open chat"
8
8
  >
9
- <svg class="chat-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor">
10
- <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 12h.01M12 12h.01M16 12h.01M21 12c0 4.418-4.03 8-9 8a9.863 9.863 0 01-4.255-.949L3 20l1.395-3.72C3.512 15.042 3 13.574 3 12c0-4.418 4.03-8 9-8s9 3.582 9 8z"/>
9
+ <svg
10
+ class="chat-icon"
11
+ viewBox="0 0 24 24"
12
+ fill="none"
13
+ stroke="currentColor"
14
+ >
15
+ <path
16
+ stroke-linecap="round"
17
+ stroke-linejoin="round"
18
+ stroke-width="2"
19
+ d="M8 12h.01M12 12h.01M16 12h.01M21 12c0 4.418-4.03 8-9 8a9.863 9.863 0 01-4.255-.949L3 20l1.395-3.72C3.512 15.042 3 13.574 3 12c0-4.418 4.03-8 9-8s9 3.582 9 8z"
20
+ />
11
21
  </svg>
12
22
  <!-- <span class="chat-trigger-text">Chat</span> -->
13
23
  </button>
@@ -18,31 +28,94 @@
18
28
  <div class="chat-header">
19
29
  <h3 class="chat-title">{title}</h3>
20
30
  <div class="chat-header-actions">
21
- <button
31
+ <button
22
32
  class="chat-clear-button"
23
33
  onclick={handleClearClick}
24
34
  aria-label="Clear chat messages"
25
35
  title="Clear chat messages"
26
36
  >
27
- <svg class="clear-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor">
28
- <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15"/>
37
+ <svg
38
+ class="clear-icon"
39
+ viewBox="0 0 24 24"
40
+ fill="none"
41
+ stroke="currentColor"
42
+ >
43
+ <path
44
+ stroke-linecap="round"
45
+ stroke-linejoin="round"
46
+ stroke-width="2"
47
+ d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15"
48
+ />
29
49
  </svg>
30
50
  </button>
31
- <button
51
+ <button
32
52
  class="chat-close-button"
33
53
  onclick={handleCloseClick}
34
54
  aria-label="Close chat"
35
55
  >
36
- <svg class="close-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor">
37
- <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"/>
56
+ <svg
57
+ class="close-icon"
58
+ viewBox="0 0 24 24"
59
+ fill="none"
60
+ stroke="currentColor"
61
+ >
62
+ <path
63
+ stroke-linecap="round"
64
+ stroke-linejoin="round"
65
+ stroke-width="2"
66
+ d="M6 18L18 6M6 6l12 12"
67
+ />
38
68
  </svg>
39
69
  </button>
40
70
  </div>
41
71
  </div>
42
-
72
+
43
73
  <div class="chat-messages">
44
74
  <template for:each={messagesWithTyping} for:item="message">
45
- <div key={message.id} class="message-wrapper" data-sender={message.sender}>
75
+ <div
76
+ key={message.id}
77
+ class="message-wrapper"
78
+ data-sender={message.sender}
79
+ >
80
+ <!-- AI Avatar for assistant messages only -->
81
+ <div class="message-avatar-wrapper" data-sender={message.sender}>
82
+ <div class="avatar-container">
83
+ <svg
84
+ class="avatar-icon"
85
+ viewBox="0 0 300 320"
86
+ fill="none"
87
+ stroke="none"
88
+ >
89
+ <!-- Antennas -->
90
+ <g stroke="#999" stroke-width="6">
91
+ <line x1="70" y1="20" x2="70" y2="60" />
92
+ <line x1="230" y1="20" x2="230" y2="60" />
93
+ </g>
94
+ <circle cx="70" cy="20" r="10" fill="#3A98D8" />
95
+ <circle cx="230" cy="20" r="10" fill="#3A98D8" />
96
+
97
+ <!-- Robot Body -->
98
+ <g class="thinking-body">
99
+ <rect x="40" y="60" width="220" height="240" rx="110" ry="110" fill="#3A98D8" />
100
+
101
+ <!-- Forehead Dots -->
102
+ <circle cx="150" cy="90" r="6" fill="#9ED4E6" />
103
+ <rect x="135" y="100" width="30" height="10" rx="5" ry="5" fill="#9ED4E6" />
104
+
105
+ <!-- Face Panel -->
106
+ <rect x="70" y="130" width="160" height="80" rx="40" ry="40" fill="#577C86" />
107
+
108
+ <!-- Eyes (Thinking animation) -->
109
+ <circle class="thinking-eye" cx="115" cy="170" r="10" fill="#86D3BD" />
110
+ <circle class="thinking-eye" cx="185" cy="170" r="10" fill="#86D3BD" />
111
+
112
+ <!-- Thinking Smile -->
113
+ <path class="thinking-smile" d="M110 240 Q150 260 190 240" stroke="#2F435A" stroke-width="6" fill="none" stroke-linecap="round" />
114
+ </g>
115
+ </svg>
116
+ </div>
117
+ </div>
118
+
46
119
  <div class="message-content">
47
120
  <div class="message-bubble">
48
121
  <template lwc:if={message.isTyping}>
@@ -72,29 +145,39 @@
72
145
  </div>
73
146
  </template>
74
147
  </div>
75
-
148
+
76
149
  <div class="chat-input-area">
77
150
  <div class="chat-input-container">
78
- <input
79
- type="text"
80
- class="chat-input"
151
+ <input
152
+ type="text"
153
+ class="chat-input"
81
154
  placeholder={placeholder}
82
155
  value={currentMessage}
83
156
  oninput={handleInputChange}
84
157
  onkeydown={handleKeyDown}
85
158
  disabled={disabled}
86
159
  />
87
- <button
160
+ <button
88
161
  class="chat-send-button"
89
162
  onclick={handleSendClick}
90
163
  disabled={sendButtonDisabled}
91
164
  aria-label="Send message"
92
165
  >
93
- <svg class="send-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor">
94
- <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 19l9 2-9-18-9 18 9-2zm0 0v-8"/>
166
+ <svg
167
+ class="send-icon"
168
+ viewBox="0 0 24 24"
169
+ fill="none"
170
+ stroke="currentColor"
171
+ >
172
+ <path
173
+ stroke-linecap="round"
174
+ stroke-linejoin="round"
175
+ stroke-width="2"
176
+ d="M12 19l9 2-9-18-9 18 9-2zm0 0v-8"
177
+ />
95
178
  </svg>
96
179
  </button>
97
180
  </div>
98
181
  </div>
99
182
  </div>
100
- </template>
183
+ </template>
@@ -16,7 +16,7 @@ export default class Chat extends LightningElement {
16
16
  @api placeholder: string = "Type your message...";
17
17
  @api assistantName: string = "Assistant";
18
18
  @api maxHeight: string = "400px";
19
-
19
+
20
20
  @api
21
21
  get disabled() {
22
22
  return this._disabled;
@@ -47,42 +47,42 @@ export default class Chat extends LightningElement {
47
47
  @track messages: ChatMessage[] = [];
48
48
  @track currentMessage: string = "";
49
49
  @track isAssistantTyping: boolean = false;
50
-
50
+
51
51
  private _disabled: boolean = false;
52
52
  private _showTimestamp: boolean = false;
53
53
  private _isOpen: boolean = false;
54
54
  private messageIdCounter: number = 0;
55
-
55
+
56
56
  // localStorage keys for persisting messages and open state
57
- private static readonly STORAGE_KEY = 'doc-chat-messages';
58
- private static readonly OPEN_STATE_KEY = 'doc-chat-should-open';
57
+ private static readonly STORAGE_KEY = "doc-chat-messages";
58
+ private static readonly OPEN_STATE_KEY = "doc-chat-should-open";
59
59
 
60
60
  connectedCallback() {
61
61
  // Load existing messages from localStorage
62
62
  this.loadMessages();
63
-
63
+
64
64
  // Add welcome message only if no existing messages
65
65
  if (this.messages.length === 0) {
66
66
  this.addMessage("Hello! How can I help you today?", "assistant");
67
67
  }
68
-
68
+
69
69
  // Check if chat should be opened after reload
70
70
  this.checkAndOpenAfterReload();
71
-
71
+
72
72
  // Ensure body has proper class state
73
73
  this.updateBodyClass();
74
74
  }
75
75
 
76
76
  disconnectedCallback() {
77
77
  // Clean up body classes when component is destroyed
78
- document.body.classList.remove('chat-open', 'chat-closed');
78
+ document.body.classList.remove("chat-open", "chat-closed");
79
79
  }
80
80
 
81
81
  get chatContainerClass() {
82
82
  return cx(
83
83
  "chat-container",
84
- this.disabled && "chat-container--disabled",
85
- this.isOpen && "chat-container--open"
84
+ this.disabled && "chat-container_disabled",
85
+ this.isOpen && "chat-container_open"
86
86
  );
87
87
  }
88
88
 
@@ -123,7 +123,8 @@ export default class Chat extends LightningElement {
123
123
  private scrollToBottom() {
124
124
  // Use setTimeout to ensure DOM is updated
125
125
  setTimeout(() => {
126
- const messagesContainer = this.template.querySelector('.chat-messages');
126
+ const messagesContainer =
127
+ this.template.querySelector(".chat-messages");
127
128
  if (messagesContainer) {
128
129
  messagesContainer.scrollTop = messagesContainer.scrollHeight;
129
130
  }
@@ -133,26 +134,29 @@ export default class Chat extends LightningElement {
133
134
  private updateBodyClass() {
134
135
  // Update body classes to trigger content shift
135
136
  if (this.isOpen) {
136
- document.body.classList.remove('chat-closed');
137
- document.body.classList.add('chat-open');
137
+ document.body.classList.remove("chat-closed");
138
+ document.body.classList.add("chat-open");
138
139
  } else {
139
- document.body.classList.remove('chat-open');
140
- document.body.classList.add('chat-closed');
140
+ document.body.classList.remove("chat-open");
141
+ document.body.classList.add("chat-closed");
141
142
  }
142
143
  }
143
144
 
144
145
  private saveMessages() {
145
146
  try {
146
- const messagesToSave = this.messages.map(msg => ({
147
+ const messagesToSave = this.messages.map((msg) => ({
147
148
  id: msg.id,
148
149
  text: msg.text,
149
150
  timestamp: msg.timestamp.toISOString(),
150
151
  sender: msg.sender,
151
152
  formattedTime: msg.formattedTime
152
153
  }));
153
- localStorage.setItem(Chat.STORAGE_KEY, JSON.stringify(messagesToSave));
154
+ localStorage.setItem(
155
+ Chat.STORAGE_KEY,
156
+ JSON.stringify(messagesToSave)
157
+ );
154
158
  } catch (error) {
155
- console.warn('Failed to save messages to localStorage:', error);
159
+ console.warn("Failed to save messages to localStorage:", error);
156
160
  }
157
161
  }
158
162
 
@@ -168,14 +172,22 @@ export default class Chat extends LightningElement {
168
172
  sender: msg.sender,
169
173
  formattedTime: msg.formattedTime
170
174
  }));
171
-
175
+
172
176
  // Update message counter to avoid ID conflicts
173
- const lastId = this.messages.length > 0 ?
174
- Math.max(...this.messages.map(m => parseInt(m.id.replace('msg-', '')) || 0)) : 0;
177
+ const lastId =
178
+ this.messages.length > 0
179
+ ? Math.max(
180
+ ...this.messages.map(
181
+ (m) =>
182
+ parseInt(m.id.replace("msg-", ""), 10) ||
183
+ 0
184
+ )
185
+ )
186
+ : 0;
175
187
  this.messageIdCounter = lastId + 1;
176
188
  }
177
189
  } catch (error) {
178
- console.warn('Failed to load messages from localStorage:', error);
190
+ console.warn("Failed to load messages from localStorage:", error);
179
191
  this.messages = [];
180
192
  }
181
193
  }
@@ -188,27 +200,32 @@ export default class Chat extends LightningElement {
188
200
  // Add welcome message after clearing
189
201
  this.addMessage("Hello! How can I help you today?", "assistant");
190
202
  } catch (error) {
191
- console.warn('Failed to clear messages from localStorage:', error);
203
+ console.warn("Failed to clear messages from localStorage:", error);
192
204
  }
193
205
  }
194
206
 
195
207
  private checkAndOpenAfterReload() {
196
208
  try {
197
209
  const shouldOpen = localStorage.getItem(Chat.OPEN_STATE_KEY);
198
- if (shouldOpen === 'true') {
210
+ if (shouldOpen === "true") {
199
211
  // Clear the flag
200
212
  localStorage.removeItem(Chat.OPEN_STATE_KEY);
201
213
  // Open the chat
202
- this.isOpen = true;
214
+ this._isOpen = true;
203
215
  this.updateBodyClass();
204
-
216
+
205
217
  // Dispatch custom event to notify parent components
206
- this.dispatchEvent(new CustomEvent('chatopened', {
207
- detail: { opened: true }
208
- }));
218
+ this.dispatchEvent(
219
+ new CustomEvent("chatopened", {
220
+ detail: { opened: true }
221
+ })
222
+ );
209
223
  }
210
224
  } catch (error) {
211
- console.warn('Failed to check open state from localStorage:', error);
225
+ console.warn(
226
+ "Failed to check open state from localStorage:",
227
+ error
228
+ );
212
229
  }
213
230
  }
214
231
 
@@ -218,7 +235,7 @@ export default class Chat extends LightningElement {
218
235
  }
219
236
 
220
237
  handleKeyDown(event: KeyboardEvent) {
221
- if (event.key === 'Enter' && !event.shiftKey) {
238
+ if (event.key === "Enter" && !event.shiftKey) {
222
239
  event.preventDefault();
223
240
  this.sendMessage();
224
241
  }
@@ -239,14 +256,16 @@ export default class Chat extends LightningElement {
239
256
  this.currentMessage = "";
240
257
 
241
258
  // Clear input
242
- const input = this.template.querySelector('.chat-input') as HTMLInputElement;
259
+ const input = this.template.querySelector(
260
+ ".chat-input"
261
+ ) as HTMLInputElement;
243
262
  if (input) {
244
263
  input.value = "";
245
264
  }
246
265
 
247
266
  // Simulate assistant typing
248
267
  this.isAssistantTyping = true;
249
-
268
+
250
269
  // Simulate assistant response after a delay
251
270
  setTimeout(() => {
252
271
  this.isAssistantTyping = false;
@@ -257,22 +276,34 @@ export default class Chat extends LightningElement {
257
276
  private simulateAssistantResponse(userMessage: string) {
258
277
  // Simple response simulation - in a real implementation, this would call an API
259
278
  let response = `I received your message: "${userMessage}". `;
260
-
261
- if (userMessage.toLowerCase().includes("hello") || userMessage.toLowerCase().includes("hi")) {
279
+
280
+ if (
281
+ userMessage.toLowerCase().includes("hello") ||
282
+ userMessage.toLowerCase().includes("hi")
283
+ ) {
262
284
  response = "Hello! Nice to meet you. How can I assist you today?";
263
285
  } else if (userMessage.toLowerCase().includes("help")) {
264
- response = "I'm here to help! Feel free to ask me any questions about the documentation or topics you're interested in.";
265
- } else if (userMessage.toLowerCase().includes("documentation") || userMessage.toLowerCase().includes("docs")) {
266
- response = "I can help you navigate the documentation. What specific topic are you looking for?";
286
+ response =
287
+ "I'm here to help! Feel free to ask me any questions about the documentation or topics you're interested in.";
288
+ } else if (
289
+ userMessage.toLowerCase().includes("documentation") ||
290
+ userMessage.toLowerCase().includes("docs")
291
+ ) {
292
+ response =
293
+ "I can help you navigate the documentation. What specific topic are you looking for?";
267
294
  } else {
268
- response = "That's an interesting question. Let me help you with that. Could you provide more details about what you're looking for?";
295
+ response =
296
+ "That's an interesting question. Let me help you with that. Could you provide more details about what you're looking for?";
269
297
  }
270
298
 
271
299
  this.addMessage(response, "assistant");
272
300
  }
273
301
 
274
302
  formatTimestamp(timestamp: Date) {
275
- return timestamp.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });
303
+ return timestamp.toLocaleTimeString([], {
304
+ hour: "2-digit",
305
+ minute: "2-digit"
306
+ });
276
307
  }
277
308
 
278
309
  get sendButtonDisabled() {
@@ -280,32 +311,35 @@ export default class Chat extends LightningElement {
280
311
  }
281
312
 
282
313
  handleCloseClick() {
283
- this.isOpen = false;
314
+ this._isOpen = false;
284
315
  this.updateBodyClass();
285
-
316
+
286
317
  // Dispatch custom event to notify parent components
287
- this.dispatchEvent(new CustomEvent('chatclosed', {
288
- detail: { closed: true }
289
- }));
318
+ this.dispatchEvent(
319
+ new CustomEvent("chatclosed", {
320
+ detail: { closed: true }
321
+ })
322
+ );
290
323
  }
291
324
 
292
325
  handleClearClick() {
293
326
  this.clearMessages();
294
-
327
+
295
328
  // Dispatch custom event to notify parent components
296
- this.dispatchEvent(new CustomEvent('chatcleared', {
297
- detail: { cleared: true }
298
- }));
329
+ this.dispatchEvent(
330
+ new CustomEvent("chatcleared", {
331
+ detail: { cleared: true }
332
+ })
333
+ );
299
334
  }
300
335
 
301
336
  handleOpenClick() {
302
337
  try {
303
338
  // Set flag to open chat after reload
304
- localStorage.setItem(Chat.OPEN_STATE_KEY, 'true');
339
+ localStorage.setItem(Chat.OPEN_STATE_KEY, "true");
305
340
  } catch (error) {
306
- console.warn('Failed to set open state in localStorage:', error);
341
+ console.warn("Failed to set open state in localStorage:", error);
307
342
  }
308
-
309
343
  // Hard reload the page to clear cache when opening chat
310
344
  window.location.reload();
311
345
  }
@@ -313,22 +347,24 @@ export default class Chat extends LightningElement {
313
347
  openChat() {
314
348
  try {
315
349
  // Set flag to open chat after reload
316
- localStorage.setItem(Chat.OPEN_STATE_KEY, 'true');
350
+ localStorage.setItem(Chat.OPEN_STATE_KEY, "true");
317
351
  } catch (error) {
318
- console.warn('Failed to set open state in localStorage:', error);
352
+ console.warn("Failed to set open state in localStorage:", error);
319
353
  }
320
-
354
+
321
355
  // Hard reload the page to clear cache when opening chat
322
356
  window.location.reload();
323
357
  }
324
358
 
325
359
  closeChat() {
326
- this.isOpen = false;
360
+ this._isOpen = false;
327
361
  this.updateBodyClass();
328
-
362
+
329
363
  // Dispatch custom event to notify parent components
330
- this.dispatchEvent(new CustomEvent('chatclosed', {
331
- detail: { closed: true }
332
- }));
364
+ this.dispatchEvent(
365
+ new CustomEvent("chatclosed", {
366
+ detail: { closed: true }
367
+ })
368
+ );
333
369
  }
334
- }
370
+ }