@salesforcedevs/docs-components 0.0.37-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.37-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",
@@ -429,6 +429,68 @@ body.chat-closed .global-header {
429
429
  font-weight: 400;
430
430
  }
431
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
+
432
494
  .message-timestamp {
433
495
  font-size: 12px;
434
496
  color: #706e6b;
@@ -175,7 +175,12 @@
175
175
  </div>
176
176
  </template>
177
177
  <template lwc:else>
178
- <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>
179
184
  </template>
180
185
  </div>
181
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 {
@@ -86,6 +87,35 @@ export default class Chat extends LightningElement {
86
87
  document.body.classList.remove("chat-open", "chat-closed");
87
88
  }
88
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
+
89
119
  get chatContainerClass() {
90
120
  return cx(
91
121
  "chat-container",
@@ -114,14 +144,21 @@ export default class Chat extends LightningElement {
114
144
  return messages;
115
145
  }
116
146
 
117
- 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) {
118
154
  const timestamp = new Date();
119
155
  const message: ChatMessage = {
120
156
  id: `msg-${this.messageIdCounter++}`,
121
157
  text,
122
158
  timestamp,
123
159
  sender,
124
- formattedTime: this.formatTimestamp(timestamp)
160
+ formattedTime: this.formatTimestamp(timestamp),
161
+ isHTML
125
162
  };
126
163
  this.messages = [...this.messages, message];
127
164
  this.saveMessages();
@@ -157,7 +194,8 @@ export default class Chat extends LightningElement {
157
194
  text: msg.text,
158
195
  timestamp: msg.timestamp.toISOString(),
159
196
  sender: msg.sender,
160
- formattedTime: msg.formattedTime
197
+ formattedTime: msg.formattedTime,
198
+ isHTML: msg.isHTML || false
161
199
  }));
162
200
  localStorage.setItem(
163
201
  Chat.STORAGE_KEY,
@@ -178,7 +216,8 @@ export default class Chat extends LightningElement {
178
216
  text: msg.text,
179
217
  timestamp: new Date(msg.timestamp),
180
218
  sender: msg.sender,
181
- formattedTime: msg.formattedTime
219
+ formattedTime: msg.formattedTime,
220
+ isHTML: msg.isHTML || false
182
221
  }));
183
222
 
184
223
  // Update message counter to avoid ID conflicts
@@ -299,11 +338,9 @@ export default class Chat extends LightningElement {
299
338
 
300
339
  const response = await fetch(Chat.API_URL, {
301
340
  method: 'POST',
302
- mode: 'cors', // Explicitly set CORS mode
303
341
  headers: {
304
342
  'Content-Type': 'application/json',
305
343
  'ngrok-skip-browser-warning': 'true', // Skip ngrok browser warning
306
- ...(Chat.IS_DEVELOPMENT && { 'Access-Control-Allow-Origin': '*' })
307
344
  },
308
345
  body: JSON.stringify(requestBody)
309
346
  });
@@ -317,8 +354,9 @@ export default class Chat extends LightningElement {
317
354
  const data = await response.json();
318
355
  console.log('API Response:', data);
319
356
 
320
- // Handle different possible response formats
321
- const responseText = data.answer || data.text || data.response || data.result || data.message;
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);
322
360
 
323
361
  if (!responseText) {
324
362
  console.warn('No response text found in API response:', data);
@@ -343,10 +381,89 @@ export default class Chat extends LightningElement {
343
381
  }
344
382
  }
345
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>');
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
+ }
455
+
346
456
  private async getAssistantResponse(userMessage: string) {
347
457
  try {
348
458
  const response = await this.callChatAPI(userMessage);
349
- this.addMessage(response, "assistant");
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);
350
467
  } catch (error) {
351
468
  console.error('Error getting assistant response:', error);
352
469
  this.addMessage(