@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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@salesforcedevs/docs-components",
3
- "version": "0.0.36-chat",
3
+ "version": "0.0.38-chat",
4
4
  "description": "Docs Lightning web components for DSC",
5
5
  "license": "MIT",
6
6
  "main": "index.js",
@@ -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, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", sans-serif;
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(135deg, rgb(255 255 255 / 5%) 0%, rgb(255 255 255 / 1%) 100%);
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(135deg, rgb(255 255 255 / 20%) 0%, rgb(255 255 255 / 10%) 100%);
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%, 100% {
273
- transform: scale(1);
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%, 100% {
285
- transform: scale(1);
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%, 100% {
295
- transform: translateY(0);
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%, 85%, 100% {
305
- transform: scaleY(1);
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 class="message-avatar-wrapper" data-sender={message.sender}>
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 cx="230" cy="20" r="10" fill="#3A98D8" />
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 x="40" y="60" width="220" height="240" rx="110" ry="110" fill="#3A98D8" />
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 cx="150" cy="90" r="6" fill="#9ED4E6" />
95
- <rect x="135" y="100" width="30" height="10" rx="5" ry="5" fill="#9ED4E6" />
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 x="70" y="130" width="160" height="80" rx="40" ry="40" fill="#577C86" />
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 class="thinking-eye" cx="115" cy="170" r="10" fill="#86D3BD" />
102
- <circle class="thinking-eye" cx="185" cy="170" r="10" fill="#86D3BD" />
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 class="thinking-smile" d="M110 240 Q150 260 190 240" stroke="#2F435A" stroke-width="6" fill="none" stroke-linecap="round" />
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
- <p class="message-text">{message.text}</p>
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('---- Connected callback ------');
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
- private addMessage(text: string, sender: "user" | "assistant") {
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
- // Simulate assistant typing
313
+ // Show typing indicator
268
314
  this.isAssistantTyping = true;
269
315
 
270
- // Simulate assistant response after a delay
316
+ // Get real assistant response from API
317
+ // Add a small delay to show the typing indicator
271
318
  setTimeout(() => {
272
- this.isAssistantTyping = false;
273
- this.simulateAssistantResponse(userMessage);
274
- }, 1000 + Math.random() * 2000);
275
- }
276
-
277
- private simulateAssistantResponse(userMessage: string) {
278
- // Simple response simulation - in a real implementation, this would call an API
279
- let response = `I received your message: "${userMessage}". `;
280
-
281
- if (
282
- userMessage.toLowerCase().includes("hello") ||
283
- userMessage.toLowerCase().includes("hi")
284
- ) {
285
- response = "Hello! Nice to meet you. How can I assist you today?";
286
- } else if (userMessage.toLowerCase().includes("help")) {
287
- response =
288
- "I'm here to help! Feel free to ask me any questions about the documentation or topics you're interested in.";
289
- } else if (
290
- userMessage.toLowerCase().includes("documentation") ||
291
- userMessage.toLowerCase().includes("docs")
292
- ) {
293
- response =
294
- "I can help you navigate the documentation. What specific topic are you looking for?";
295
- } else {
296
- response =
297
- "That's an interesting question. Let me help you with that. Could you provide more details about what you're looking for?";
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
- this.addMessage(response, "assistant");
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) {