@salesforcedevs/docs-components 0.0.36-chat → 0.0.38-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 +1 -1
- package/src/modules/doc/chat/chat.css +195 -36
- package/src/modules/doc/chat/chat.html +72 -10
- package/src/modules/doc/chat/chat.ts +209 -34
package/package.json
CHANGED
|
@@ -5,7 +5,9 @@
|
|
|
5
5
|
right: 0;
|
|
6
6
|
height: 100vh;
|
|
7
7
|
z-index: 100000;
|
|
8
|
-
font-family: "Salesforce Sans", -apple-system, BlinkMacSystemFont,
|
|
8
|
+
font-family: "Salesforce Sans", -apple-system, BlinkMacSystemFont,
|
|
9
|
+
"Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue",
|
|
10
|
+
sans-serif;
|
|
9
11
|
}
|
|
10
12
|
|
|
11
13
|
/* Apply content shift to main page content when chat is open */
|
|
@@ -50,6 +52,59 @@ body.chat-closed .global-header {
|
|
|
50
52
|
padding: 0;
|
|
51
53
|
}
|
|
52
54
|
|
|
55
|
+
/* Tooltip styling */
|
|
56
|
+
.chat-trigger-button::before {
|
|
57
|
+
content: attr(data-tooltip);
|
|
58
|
+
position: absolute;
|
|
59
|
+
right: 100%;
|
|
60
|
+
top: 50%;
|
|
61
|
+
transform: translateY(-50%);
|
|
62
|
+
background: linear-gradient(135deg, #0176d3 0%, #005fb2 100%);
|
|
63
|
+
color: white;
|
|
64
|
+
padding: 12px 16px;
|
|
65
|
+
border-radius: 12px;
|
|
66
|
+
font-size: 14px;
|
|
67
|
+
font-weight: 500;
|
|
68
|
+
white-space: nowrap;
|
|
69
|
+
opacity: 0;
|
|
70
|
+
visibility: hidden;
|
|
71
|
+
margin-right: 16px;
|
|
72
|
+
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
|
73
|
+
box-shadow: 0 8px 24px rgb(1 118 211 / 25%), 0 2px 8px rgb(1 118 211 / 15%);
|
|
74
|
+
backdrop-filter: blur(10px);
|
|
75
|
+
z-index: 1002;
|
|
76
|
+
pointer-events: none;
|
|
77
|
+
letter-spacing: 0.02em;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/* Tooltip arrow */
|
|
81
|
+
.chat-trigger-button::after {
|
|
82
|
+
content: "";
|
|
83
|
+
position: absolute;
|
|
84
|
+
right: 100%;
|
|
85
|
+
top: 50%;
|
|
86
|
+
transform: translateY(-50%);
|
|
87
|
+
margin-right: 8px;
|
|
88
|
+
width: 0;
|
|
89
|
+
height: 0;
|
|
90
|
+
border-left: 8px solid #0176d3;
|
|
91
|
+
border-top: 8px solid transparent;
|
|
92
|
+
border-bottom: 8px solid transparent;
|
|
93
|
+
opacity: 0;
|
|
94
|
+
visibility: hidden;
|
|
95
|
+
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
|
96
|
+
z-index: 1002;
|
|
97
|
+
pointer-events: none;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/* Show tooltip on hover */
|
|
101
|
+
.chat-trigger-button:hover::before,
|
|
102
|
+
.chat-trigger-button:hover::after {
|
|
103
|
+
opacity: 1;
|
|
104
|
+
visibility: visible;
|
|
105
|
+
transform: translateY(-50%) translateX(-4px);
|
|
106
|
+
}
|
|
107
|
+
|
|
53
108
|
.chat-trigger-button:hover .chat-gif {
|
|
54
109
|
transform: scale(1.1);
|
|
55
110
|
}
|
|
@@ -65,8 +120,6 @@ body.chat-closed .global-header {
|
|
|
65
120
|
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
|
66
121
|
}
|
|
67
122
|
|
|
68
|
-
|
|
69
|
-
|
|
70
123
|
@keyframes pulse {
|
|
71
124
|
0% {
|
|
72
125
|
transform: scale(1);
|
|
@@ -123,7 +176,11 @@ body.chat-closed .global-header {
|
|
|
123
176
|
left: 0;
|
|
124
177
|
right: 0;
|
|
125
178
|
bottom: 0;
|
|
126
|
-
background: linear-gradient(
|
|
179
|
+
background: linear-gradient(
|
|
180
|
+
135deg,
|
|
181
|
+
rgb(255 255 255 / 5%) 0%,
|
|
182
|
+
rgb(255 255 255 / 1%) 100%
|
|
183
|
+
);
|
|
127
184
|
pointer-events: none;
|
|
128
185
|
}
|
|
129
186
|
|
|
@@ -240,7 +297,11 @@ body.chat-closed .global-header {
|
|
|
240
297
|
content: "";
|
|
241
298
|
position: absolute;
|
|
242
299
|
inset: 0;
|
|
243
|
-
background: linear-gradient(
|
|
300
|
+
background: linear-gradient(
|
|
301
|
+
135deg,
|
|
302
|
+
rgb(255 255 255 / 20%) 0%,
|
|
303
|
+
rgb(255 255 255 / 10%) 100%
|
|
304
|
+
);
|
|
244
305
|
border-radius: 50%;
|
|
245
306
|
}
|
|
246
307
|
|
|
@@ -269,56 +330,61 @@ body.chat-closed .global-header {
|
|
|
269
330
|
}
|
|
270
331
|
|
|
271
332
|
@keyframes thinking-avatar-pulse {
|
|
272
|
-
0%,
|
|
273
|
-
|
|
333
|
+
0%,
|
|
334
|
+
100% {
|
|
335
|
+
transform: scale(1);
|
|
274
336
|
box-shadow: 0 2px 8px rgb(0 0 0 / 10%);
|
|
275
337
|
}
|
|
276
338
|
|
|
277
|
-
50% {
|
|
278
|
-
transform: scale(1.08);
|
|
339
|
+
50% {
|
|
340
|
+
transform: scale(1.08);
|
|
279
341
|
box-shadow: 0 4px 16px rgb(0 0 0 / 20%);
|
|
280
342
|
}
|
|
281
343
|
}
|
|
282
344
|
|
|
283
345
|
@keyframes thinking-breathe {
|
|
284
|
-
0%,
|
|
285
|
-
|
|
346
|
+
0%,
|
|
347
|
+
100% {
|
|
348
|
+
transform: scale(1);
|
|
286
349
|
}
|
|
287
350
|
|
|
288
|
-
50% {
|
|
289
|
-
transform: scale(1.05);
|
|
351
|
+
50% {
|
|
352
|
+
transform: scale(1.05);
|
|
290
353
|
}
|
|
291
354
|
}
|
|
292
355
|
|
|
293
356
|
@keyframes thinking-smile-animation {
|
|
294
|
-
0%,
|
|
295
|
-
|
|
357
|
+
0%,
|
|
358
|
+
100% {
|
|
359
|
+
transform: translateY(0);
|
|
296
360
|
}
|
|
297
361
|
|
|
298
|
-
50% {
|
|
299
|
-
transform: translateY(-2px);
|
|
362
|
+
50% {
|
|
363
|
+
transform: translateY(-2px);
|
|
300
364
|
}
|
|
301
365
|
}
|
|
302
366
|
|
|
303
367
|
@keyframes think-blink {
|
|
304
|
-
0%,
|
|
305
|
-
|
|
368
|
+
0%,
|
|
369
|
+
85%,
|
|
370
|
+
100% {
|
|
371
|
+
transform: scaleY(1);
|
|
306
372
|
}
|
|
307
373
|
|
|
308
|
-
90% {
|
|
309
|
-
transform: scaleY(0.6);
|
|
374
|
+
90% {
|
|
375
|
+
transform: scaleY(0.6);
|
|
310
376
|
}
|
|
311
377
|
|
|
312
|
-
92% {
|
|
313
|
-
transform: scaleY(0.2);
|
|
378
|
+
92% {
|
|
379
|
+
transform: scaleY(0.2);
|
|
314
380
|
}
|
|
315
381
|
|
|
316
|
-
94% {
|
|
317
|
-
transform: scaleY(0.6);
|
|
382
|
+
94% {
|
|
383
|
+
transform: scaleY(0.6);
|
|
318
384
|
}
|
|
319
385
|
|
|
320
|
-
96% {
|
|
321
|
-
transform: scaleY(1);
|
|
386
|
+
96% {
|
|
387
|
+
transform: scaleY(1);
|
|
322
388
|
}
|
|
323
389
|
}
|
|
324
390
|
|
|
@@ -363,6 +429,68 @@ body.chat-closed .global-header {
|
|
|
363
429
|
font-weight: 400;
|
|
364
430
|
}
|
|
365
431
|
|
|
432
|
+
/* HTML content styling for rich messages */
|
|
433
|
+
.message-text .chat-link {
|
|
434
|
+
color: #0176d3;
|
|
435
|
+
text-decoration: none;
|
|
436
|
+
border-bottom: 1px solid transparent;
|
|
437
|
+
transition: all 0.2s ease;
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
.message-text .chat-link:hover {
|
|
441
|
+
color: #005fb2;
|
|
442
|
+
border-bottom-color: #005fb2;
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
.message-text .chat-list {
|
|
446
|
+
margin: 12px 0;
|
|
447
|
+
padding-left: 20px;
|
|
448
|
+
counter-reset: none;
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
.message-text .chat-list li {
|
|
452
|
+
margin: 12px 0;
|
|
453
|
+
line-height: 1.6;
|
|
454
|
+
font-weight: 500;
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
.message-text .chat-description {
|
|
458
|
+
font-weight: 400;
|
|
459
|
+
color: #666;
|
|
460
|
+
font-size: 14px;
|
|
461
|
+
line-height: 1.5;
|
|
462
|
+
margin-top: 4px;
|
|
463
|
+
display: block;
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
.message-text strong {
|
|
467
|
+
font-weight: 600;
|
|
468
|
+
color: #181818;
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
.message-text em {
|
|
472
|
+
font-style: italic;
|
|
473
|
+
color: #555;
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
/* Style for line breaks in HTML content */
|
|
477
|
+
.message-text br {
|
|
478
|
+
margin: 4px 0;
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
/* Ensure proper spacing for HTML content */
|
|
482
|
+
.message-text p {
|
|
483
|
+
margin: 8px 0;
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
.message-text p:first-child {
|
|
487
|
+
margin-top: 0;
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
.message-text p:last-child {
|
|
491
|
+
margin-bottom: 0;
|
|
492
|
+
}
|
|
493
|
+
|
|
366
494
|
.message-timestamp {
|
|
367
495
|
font-size: 12px;
|
|
368
496
|
color: #706e6b;
|
|
@@ -510,40 +638,52 @@ body.chat-closed .global-header {
|
|
|
510
638
|
bottom: 16px;
|
|
511
639
|
right: 16px;
|
|
512
640
|
}
|
|
513
|
-
|
|
641
|
+
|
|
514
642
|
.chat-gif {
|
|
515
643
|
width: 56px;
|
|
516
644
|
height: 56px;
|
|
517
645
|
}
|
|
518
|
-
|
|
519
646
|
|
|
520
|
-
|
|
647
|
+
/* Adjust tooltip for mobile */
|
|
648
|
+
.chat-trigger-button::before {
|
|
649
|
+
font-size: 13px;
|
|
650
|
+
padding: 10px 14px;
|
|
651
|
+
margin-right: 12px;
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
.chat-trigger-button::after {
|
|
655
|
+
margin-right: 6px;
|
|
656
|
+
border-left-width: 6px;
|
|
657
|
+
border-top-width: 6px;
|
|
658
|
+
border-bottom-width: 6px;
|
|
659
|
+
}
|
|
660
|
+
|
|
521
661
|
.chat-container {
|
|
522
662
|
width: 100%;
|
|
523
663
|
right: 0;
|
|
524
664
|
transform: translateX(100%);
|
|
525
665
|
}
|
|
526
|
-
|
|
666
|
+
|
|
527
667
|
.chat-container_open {
|
|
528
668
|
transform: translateX(0);
|
|
529
669
|
}
|
|
530
|
-
|
|
670
|
+
|
|
531
671
|
.chat-header {
|
|
532
672
|
padding: 16px 20px;
|
|
533
673
|
}
|
|
534
|
-
|
|
674
|
+
|
|
535
675
|
.chat-title {
|
|
536
676
|
font-size: 18px;
|
|
537
677
|
}
|
|
538
|
-
|
|
678
|
+
|
|
539
679
|
.chat-messages {
|
|
540
680
|
padding: 20px 16px;
|
|
541
681
|
}
|
|
542
|
-
|
|
682
|
+
|
|
543
683
|
.chat-input-area {
|
|
544
684
|
padding: 16px 20px;
|
|
545
685
|
}
|
|
546
|
-
|
|
686
|
+
|
|
547
687
|
.message-content {
|
|
548
688
|
max-width: 85%;
|
|
549
689
|
}
|
|
@@ -574,7 +714,20 @@ body.chat-closed .global-header {
|
|
|
574
714
|
height: 48px;
|
|
575
715
|
}
|
|
576
716
|
|
|
717
|
+
/* Smaller tooltip for small screens */
|
|
718
|
+
.chat-trigger-button::before {
|
|
719
|
+
font-size: 12px;
|
|
720
|
+
padding: 8px 12px;
|
|
721
|
+
margin-right: 10px;
|
|
722
|
+
border-radius: 8px;
|
|
723
|
+
}
|
|
577
724
|
|
|
725
|
+
.chat-trigger-button::after {
|
|
726
|
+
margin-right: 4px;
|
|
727
|
+
border-left-width: 5px;
|
|
728
|
+
border-top-width: 5px;
|
|
729
|
+
border-bottom-width: 5px;
|
|
730
|
+
}
|
|
578
731
|
|
|
579
732
|
.chat-container {
|
|
580
733
|
width: 100%;
|
|
@@ -638,6 +791,12 @@ body.chat-closed .global-header {
|
|
|
638
791
|
.thinking-eye {
|
|
639
792
|
animation: none !important;
|
|
640
793
|
}
|
|
794
|
+
|
|
795
|
+
/* Disable tooltip animations for reduced motion users */
|
|
796
|
+
.chat-trigger-button::before,
|
|
797
|
+
.chat-trigger-button::after {
|
|
798
|
+
transition: none;
|
|
799
|
+
}
|
|
641
800
|
}
|
|
642
801
|
|
|
643
802
|
/* High contrast mode support */
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
class="chat-trigger-button"
|
|
6
6
|
onclick={handleOpenClick}
|
|
7
7
|
aria-label="Open chat"
|
|
8
|
+
data-tooltip="Chat with us!"
|
|
8
9
|
>
|
|
9
10
|
<img
|
|
10
11
|
class="chat-gif"
|
|
@@ -70,7 +71,10 @@
|
|
|
70
71
|
data-sender={message.sender}
|
|
71
72
|
>
|
|
72
73
|
<!-- AI Avatar for assistant messages only -->
|
|
73
|
-
<div
|
|
74
|
+
<div
|
|
75
|
+
class="message-avatar-wrapper"
|
|
76
|
+
data-sender={message.sender}
|
|
77
|
+
>
|
|
74
78
|
<div class="avatar-container">
|
|
75
79
|
<svg
|
|
76
80
|
class="avatar-icon"
|
|
@@ -84,25 +88,78 @@
|
|
|
84
88
|
<line x1="230" y1="20" x2="230" y2="60" />
|
|
85
89
|
</g>
|
|
86
90
|
<circle cx="70" cy="20" r="10" fill="#3A98D8" />
|
|
87
|
-
<circle
|
|
91
|
+
<circle
|
|
92
|
+
cx="230"
|
|
93
|
+
cy="20"
|
|
94
|
+
r="10"
|
|
95
|
+
fill="#3A98D8"
|
|
96
|
+
/>
|
|
88
97
|
|
|
89
98
|
<!-- Robot Body -->
|
|
90
99
|
<g class="thinking-body">
|
|
91
|
-
<rect
|
|
100
|
+
<rect
|
|
101
|
+
x="40"
|
|
102
|
+
y="60"
|
|
103
|
+
width="220"
|
|
104
|
+
height="240"
|
|
105
|
+
rx="110"
|
|
106
|
+
ry="110"
|
|
107
|
+
fill="#3A98D8"
|
|
108
|
+
/>
|
|
92
109
|
|
|
93
110
|
<!-- Forehead Dots -->
|
|
94
|
-
<circle
|
|
95
|
-
|
|
111
|
+
<circle
|
|
112
|
+
cx="150"
|
|
113
|
+
cy="90"
|
|
114
|
+
r="6"
|
|
115
|
+
fill="#9ED4E6"
|
|
116
|
+
/>
|
|
117
|
+
<rect
|
|
118
|
+
x="135"
|
|
119
|
+
y="100"
|
|
120
|
+
width="30"
|
|
121
|
+
height="10"
|
|
122
|
+
rx="5"
|
|
123
|
+
ry="5"
|
|
124
|
+
fill="#9ED4E6"
|
|
125
|
+
/>
|
|
96
126
|
|
|
97
127
|
<!-- Face Panel -->
|
|
98
|
-
<rect
|
|
128
|
+
<rect
|
|
129
|
+
x="70"
|
|
130
|
+
y="130"
|
|
131
|
+
width="160"
|
|
132
|
+
height="80"
|
|
133
|
+
rx="40"
|
|
134
|
+
ry="40"
|
|
135
|
+
fill="#577C86"
|
|
136
|
+
/>
|
|
99
137
|
|
|
100
138
|
<!-- Eyes (Thinking animation) -->
|
|
101
|
-
<circle
|
|
102
|
-
|
|
139
|
+
<circle
|
|
140
|
+
class="thinking-eye"
|
|
141
|
+
cx="115"
|
|
142
|
+
cy="170"
|
|
143
|
+
r="10"
|
|
144
|
+
fill="#86D3BD"
|
|
145
|
+
/>
|
|
146
|
+
<circle
|
|
147
|
+
class="thinking-eye"
|
|
148
|
+
cx="185"
|
|
149
|
+
cy="170"
|
|
150
|
+
r="10"
|
|
151
|
+
fill="#86D3BD"
|
|
152
|
+
/>
|
|
103
153
|
|
|
104
154
|
<!-- Thinking Smile -->
|
|
105
|
-
<path
|
|
155
|
+
<path
|
|
156
|
+
class="thinking-smile"
|
|
157
|
+
d="M110 240 Q150 260 190 240"
|
|
158
|
+
stroke="#2F435A"
|
|
159
|
+
stroke-width="6"
|
|
160
|
+
fill="none"
|
|
161
|
+
stroke-linecap="round"
|
|
162
|
+
/>
|
|
106
163
|
</g>
|
|
107
164
|
</svg>
|
|
108
165
|
</div>
|
|
@@ -118,7 +175,12 @@
|
|
|
118
175
|
</div>
|
|
119
176
|
</template>
|
|
120
177
|
<template lwc:else>
|
|
121
|
-
<
|
|
178
|
+
<template lwc:if={message.isHTML}>
|
|
179
|
+
<div class="message-text html-content" lwc:dom="manual" data-message-id={message.id}></div>
|
|
180
|
+
</template>
|
|
181
|
+
<template lwc:else>
|
|
182
|
+
<p class="message-text">{message.text}</p>
|
|
183
|
+
</template>
|
|
122
184
|
</template>
|
|
123
185
|
</div>
|
|
124
186
|
<template lwc:if={showTimestamp}>
|
|
@@ -9,6 +9,7 @@ interface ChatMessage {
|
|
|
9
9
|
sender: "user" | "assistant";
|
|
10
10
|
isTyping?: boolean;
|
|
11
11
|
formattedTime?: string;
|
|
12
|
+
isHTML?: boolean; // Flag to indicate if content should be rendered as HTML
|
|
12
13
|
}
|
|
13
14
|
|
|
14
15
|
export default class Chat extends LightningElement {
|
|
@@ -53,12 +54,19 @@ export default class Chat extends LightningElement {
|
|
|
53
54
|
private _isOpen: boolean = false;
|
|
54
55
|
private messageIdCounter: number = 0;
|
|
55
56
|
|
|
57
|
+
// API Configuration
|
|
58
|
+
private static readonly API_URL = "https://276ca7264b79.ngrok-free.app/api/llm/search";
|
|
59
|
+
private static readonly MAX_RESULTS = 5;
|
|
60
|
+
|
|
61
|
+
// Development mode flag - set to true if running in development
|
|
62
|
+
private static readonly IS_DEVELOPMENT = window.location.hostname === 'localhost' || window.location.hostname === '127.0.0.1';
|
|
63
|
+
|
|
56
64
|
// localStorage keys for persisting messages and open state
|
|
57
65
|
private static readonly STORAGE_KEY = "doc-chat-messages";
|
|
58
66
|
private static readonly OPEN_STATE_KEY = "doc-chat-should-open";
|
|
59
67
|
|
|
60
68
|
connectedCallback() {
|
|
61
|
-
console.log(
|
|
69
|
+
console.log("---- Connected callback ------");
|
|
62
70
|
// Load existing messages from localStorage
|
|
63
71
|
this.loadMessages();
|
|
64
72
|
|
|
@@ -79,6 +87,35 @@ export default class Chat extends LightningElement {
|
|
|
79
87
|
document.body.classList.remove("chat-open", "chat-closed");
|
|
80
88
|
}
|
|
81
89
|
|
|
90
|
+
renderedCallback() {
|
|
91
|
+
// Handle HTML content rendering for messages with isHTML flag
|
|
92
|
+
this.renderHTMLMessages();
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
private renderHTMLMessages() {
|
|
96
|
+
console.log('=== renderHTMLMessages called ===');
|
|
97
|
+
console.log('All messages:', this.messages);
|
|
98
|
+
console.log('HTML messages:', this.messages.filter(msg => msg.isHTML));
|
|
99
|
+
|
|
100
|
+
// Use a simpler selector approach
|
|
101
|
+
const htmlMessageElements = this.template.querySelectorAll('.html-content[data-message-id]');
|
|
102
|
+
console.log('Found HTML elements:', htmlMessageElements.length);
|
|
103
|
+
|
|
104
|
+
htmlMessageElements.forEach((element) => {
|
|
105
|
+
const messageId = element.getAttribute('data-message-id');
|
|
106
|
+
console.log('Processing element with ID:', messageId);
|
|
107
|
+
|
|
108
|
+
const message = this.messages.find(msg => msg.id === messageId && msg.isHTML);
|
|
109
|
+
console.log('Found message:', message);
|
|
110
|
+
|
|
111
|
+
if (message) {
|
|
112
|
+
console.log('Setting innerHTML for message:', messageId);
|
|
113
|
+
console.log('Content:', message.text);
|
|
114
|
+
element.innerHTML = message.text;
|
|
115
|
+
}
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
|
|
82
119
|
get chatContainerClass() {
|
|
83
120
|
return cx(
|
|
84
121
|
"chat-container",
|
|
@@ -107,14 +144,21 @@ export default class Chat extends LightningElement {
|
|
|
107
144
|
return messages;
|
|
108
145
|
}
|
|
109
146
|
|
|
110
|
-
|
|
147
|
+
// Helper method to get HTML content for a message
|
|
148
|
+
getMessageHTML(messageId: string): string {
|
|
149
|
+
const message = this.messages.find(msg => msg.id === messageId && msg.isHTML);
|
|
150
|
+
return message ? message.text : '';
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
private addMessage(text: string, sender: "user" | "assistant", isHTML: boolean = false) {
|
|
111
154
|
const timestamp = new Date();
|
|
112
155
|
const message: ChatMessage = {
|
|
113
156
|
id: `msg-${this.messageIdCounter++}`,
|
|
114
157
|
text,
|
|
115
158
|
timestamp,
|
|
116
159
|
sender,
|
|
117
|
-
formattedTime: this.formatTimestamp(timestamp)
|
|
160
|
+
formattedTime: this.formatTimestamp(timestamp),
|
|
161
|
+
isHTML
|
|
118
162
|
};
|
|
119
163
|
this.messages = [...this.messages, message];
|
|
120
164
|
this.saveMessages();
|
|
@@ -150,7 +194,8 @@ export default class Chat extends LightningElement {
|
|
|
150
194
|
text: msg.text,
|
|
151
195
|
timestamp: msg.timestamp.toISOString(),
|
|
152
196
|
sender: msg.sender,
|
|
153
|
-
formattedTime: msg.formattedTime
|
|
197
|
+
formattedTime: msg.formattedTime,
|
|
198
|
+
isHTML: msg.isHTML || false
|
|
154
199
|
}));
|
|
155
200
|
localStorage.setItem(
|
|
156
201
|
Chat.STORAGE_KEY,
|
|
@@ -171,7 +216,8 @@ export default class Chat extends LightningElement {
|
|
|
171
216
|
text: msg.text,
|
|
172
217
|
timestamp: new Date(msg.timestamp),
|
|
173
218
|
sender: msg.sender,
|
|
174
|
-
formattedTime: msg.formattedTime
|
|
219
|
+
formattedTime: msg.formattedTime,
|
|
220
|
+
isHTML: msg.isHTML || false
|
|
175
221
|
}));
|
|
176
222
|
|
|
177
223
|
// Update message counter to avoid ID conflicts
|
|
@@ -264,40 +310,169 @@ export default class Chat extends LightningElement {
|
|
|
264
310
|
input.value = "";
|
|
265
311
|
}
|
|
266
312
|
|
|
267
|
-
//
|
|
313
|
+
// Show typing indicator
|
|
268
314
|
this.isAssistantTyping = true;
|
|
269
315
|
|
|
270
|
-
//
|
|
316
|
+
// Get real assistant response from API
|
|
317
|
+
// Add a small delay to show the typing indicator
|
|
271
318
|
setTimeout(() => {
|
|
272
|
-
this.
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
319
|
+
this.getAssistantResponse(userMessage);
|
|
320
|
+
}, 500);
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
private async callChatAPI(userMessage: string): Promise<string> {
|
|
324
|
+
try {
|
|
325
|
+
// Format chat history for API (exclude the current user message)
|
|
326
|
+
const chatHistory = this.messages.map(msg => ({
|
|
327
|
+
text: msg.text,
|
|
328
|
+
sender: msg.sender
|
|
329
|
+
}));
|
|
330
|
+
|
|
331
|
+
const requestBody = {
|
|
332
|
+
query: userMessage,
|
|
333
|
+
maxResults: Chat.MAX_RESULTS,
|
|
334
|
+
chatHistory: chatHistory
|
|
335
|
+
};
|
|
336
|
+
|
|
337
|
+
console.log('Sending API request:', requestBody);
|
|
338
|
+
|
|
339
|
+
const response = await fetch(Chat.API_URL, {
|
|
340
|
+
method: 'POST',
|
|
341
|
+
headers: {
|
|
342
|
+
'Content-Type': 'application/json',
|
|
343
|
+
'ngrok-skip-browser-warning': 'true', // Skip ngrok browser warning
|
|
344
|
+
},
|
|
345
|
+
body: JSON.stringify(requestBody)
|
|
346
|
+
});
|
|
347
|
+
|
|
348
|
+
if (!response.ok) {
|
|
349
|
+
const errorText = await response.text();
|
|
350
|
+
console.error('API Error Response:', errorText);
|
|
351
|
+
throw new Error(`API request failed with status: ${response.status}`);
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
const data = await response.json();
|
|
355
|
+
console.log('API Response:', data);
|
|
356
|
+
|
|
357
|
+
// Handle the new API response format with "data" field
|
|
358
|
+
const responseText = data.data || data.answer || data.text || data.response || data.result || data.message;
|
|
359
|
+
console.log('Response Text:', responseText);
|
|
360
|
+
|
|
361
|
+
if (!responseText) {
|
|
362
|
+
console.warn('No response text found in API response:', data);
|
|
363
|
+
return 'I received your message but couldn\'t generate a proper response.';
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
return responseText;
|
|
367
|
+
|
|
368
|
+
} catch (error) {
|
|
369
|
+
console.error('Chat API error:', error);
|
|
370
|
+
|
|
371
|
+
// Provide more specific error messages for CORS issues
|
|
372
|
+
if (error instanceof TypeError && error.message.includes('fetch')) {
|
|
373
|
+
return 'I\'m having trouble connecting to the service. This might be a CORS configuration issue. Please check your internet connection and try again.';
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
if (error instanceof Error && (error.message.includes('CORS') || error.message.includes('cross-origin'))) {
|
|
377
|
+
return 'I\'m having trouble connecting due to browser security restrictions. Please ask your administrator to configure CORS headers on the backend API.';
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
return 'I apologize, but I\'m having trouble connecting to the service right now. Please try again later.';
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
private parseMarkdownToHTML(text: string): string {
|
|
385
|
+
// Convert markdown-like text to HTML
|
|
386
|
+
let html = text;
|
|
387
|
+
|
|
388
|
+
console.log('Original text:', text);
|
|
389
|
+
|
|
390
|
+
// First, convert markdown links [text](url) to HTML links
|
|
391
|
+
html = html.replace(/\[([^\]]+)\]\(([^)]+)\)/g, '<a href="$2" target="_blank" rel="noopener noreferrer" class="chat-link">$1</a>');
|
|
392
|
+
|
|
393
|
+
// Handle numbered lists with descriptions (more complex pattern)
|
|
394
|
+
// Split into lines to process each line
|
|
395
|
+
const lines = html.split('\n');
|
|
396
|
+
const processedLines = [];
|
|
397
|
+
let inList = false;
|
|
398
|
+
|
|
399
|
+
for (let i = 0; i < lines.length; i++) {
|
|
400
|
+
const line = lines[i];
|
|
401
|
+
|
|
402
|
+
// Check if this is a numbered list item (starts with number and dot)
|
|
403
|
+
if (/^\d+\.\s+/.test(line)) {
|
|
404
|
+
if (!inList) {
|
|
405
|
+
processedLines.push('<ol class="chat-list">');
|
|
406
|
+
inList = true;
|
|
407
|
+
}
|
|
408
|
+
// Extract the content after the number
|
|
409
|
+
const content = line.replace(/^\d+\.\s+/, '');
|
|
410
|
+
processedLines.push(`<li>${content}`);
|
|
411
|
+
|
|
412
|
+
// Check if next line is indented (description)
|
|
413
|
+
if (i + 1 < lines.length && /^\s{2,}/.test(lines[i + 1])) {
|
|
414
|
+
i++; // Skip to next line
|
|
415
|
+
const description = lines[i].trim();
|
|
416
|
+
processedLines.push(`<br><span class="chat-description">${description}</span></li>`);
|
|
417
|
+
} else {
|
|
418
|
+
processedLines.push('</li>');
|
|
419
|
+
}
|
|
420
|
+
} else if (line.trim() === '' && inList) {
|
|
421
|
+
// Empty line might end the list
|
|
422
|
+
continue;
|
|
423
|
+
} else {
|
|
424
|
+
if (inList && line.trim() !== '') {
|
|
425
|
+
processedLines.push('</ol>');
|
|
426
|
+
inList = false;
|
|
427
|
+
}
|
|
428
|
+
if (line.trim() !== '') {
|
|
429
|
+
processedLines.push(line);
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
// Close any open list
|
|
435
|
+
if (inList) {
|
|
436
|
+
processedLines.push('</ol>');
|
|
298
437
|
}
|
|
438
|
+
|
|
439
|
+
html = processedLines.join('<br>');
|
|
440
|
+
|
|
441
|
+
// Clean up extra breaks
|
|
442
|
+
html = html.replace(/<br><br>/g, '<br>');
|
|
443
|
+
html = html.replace(/^<br>/, '');
|
|
444
|
+
|
|
445
|
+
// Convert **bold** to <strong>
|
|
446
|
+
html = html.replace(/\*\*([^*]+)\*\*/g, '<strong>$1</strong>');
|
|
447
|
+
|
|
448
|
+
// Convert *italic* to <em>
|
|
449
|
+
html = html.replace(/\*([^*]+)\*/g, '<em>$1</em>');
|
|
450
|
+
|
|
451
|
+
console.log('Processed HTML:', html);
|
|
452
|
+
|
|
453
|
+
return html;
|
|
454
|
+
}
|
|
299
455
|
|
|
300
|
-
|
|
456
|
+
private async getAssistantResponse(userMessage: string) {
|
|
457
|
+
try {
|
|
458
|
+
const response = await this.callChatAPI(userMessage);
|
|
459
|
+
// Convert markdown to HTML for rich content display
|
|
460
|
+
const htmlContent = this.parseMarkdownToHTML(response);
|
|
461
|
+
this.addMessage(htmlContent, "assistant", true); // true indicates HTML content
|
|
462
|
+
|
|
463
|
+
// Force a re-render after adding the message
|
|
464
|
+
setTimeout(() => {
|
|
465
|
+
this.renderHTMLMessages();
|
|
466
|
+
}, 100);
|
|
467
|
+
} catch (error) {
|
|
468
|
+
console.error('Error getting assistant response:', error);
|
|
469
|
+
this.addMessage(
|
|
470
|
+
'I apologize, but I encountered an error while processing your request. Please try again.',
|
|
471
|
+
"assistant"
|
|
472
|
+
);
|
|
473
|
+
} finally {
|
|
474
|
+
this.isAssistantTyping = false;
|
|
475
|
+
}
|
|
301
476
|
}
|
|
302
477
|
|
|
303
478
|
formatTimestamp(timestamp: Date) {
|