@salesforcedevs/docs-components 0.0.37-chat → 0.0.39-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
|
@@ -137,7 +137,7 @@ body.chat-closed .global-header {
|
|
|
137
137
|
/* Chat container */
|
|
138
138
|
.chat-container {
|
|
139
139
|
height: 100vh;
|
|
140
|
-
width:
|
|
140
|
+
width: 700px;
|
|
141
141
|
background-color: #fff;
|
|
142
142
|
box-shadow: -8px 0 32px rgb(0 0 0 / 12%), -2px 0 8px rgb(0 0 0 / 8%);
|
|
143
143
|
transform: translateX(100%);
|
|
@@ -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,16 @@
|
|
|
175
175
|
</div>
|
|
176
176
|
</template>
|
|
177
177
|
<template lwc:else>
|
|
178
|
-
<
|
|
178
|
+
<template lwc:if={message.isHTML}>
|
|
179
|
+
<div
|
|
180
|
+
class="message-text html-content"
|
|
181
|
+
lwc:dom="manual"
|
|
182
|
+
data-message-id={message.id}
|
|
183
|
+
></div>
|
|
184
|
+
</template>
|
|
185
|
+
<template lwc:else>
|
|
186
|
+
<p class="message-text">{message.text}</p>
|
|
187
|
+
</template>
|
|
179
188
|
</template>
|
|
180
189
|
</div>
|
|
181
190
|
<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 {
|
|
@@ -54,11 +55,14 @@ export default class Chat extends LightningElement {
|
|
|
54
55
|
private messageIdCounter: number = 0;
|
|
55
56
|
|
|
56
57
|
// API Configuration
|
|
57
|
-
private static readonly API_URL =
|
|
58
|
+
private static readonly API_URL =
|
|
59
|
+
"https://276ca7264b79.ngrok-free.app/api/llm/search";
|
|
58
60
|
private static readonly MAX_RESULTS = 5;
|
|
59
|
-
|
|
61
|
+
|
|
60
62
|
// Development mode flag - set to true if running in development
|
|
61
|
-
private static readonly IS_DEVELOPMENT =
|
|
63
|
+
private static readonly IS_DEVELOPMENT =
|
|
64
|
+
window.location.hostname === "localhost" ||
|
|
65
|
+
window.location.hostname === "127.0.0.1";
|
|
62
66
|
|
|
63
67
|
// localStorage keys for persisting messages and open state
|
|
64
68
|
private static readonly STORAGE_KEY = "doc-chat-messages";
|
|
@@ -86,6 +90,42 @@ export default class Chat extends LightningElement {
|
|
|
86
90
|
document.body.classList.remove("chat-open", "chat-closed");
|
|
87
91
|
}
|
|
88
92
|
|
|
93
|
+
renderedCallback() {
|
|
94
|
+
// Handle HTML content rendering for messages with isHTML flag
|
|
95
|
+
this.renderHTMLMessages();
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
private renderHTMLMessages() {
|
|
99
|
+
console.log("=== renderHTMLMessages called ===");
|
|
100
|
+
console.log("All messages:", this.messages);
|
|
101
|
+
console.log(
|
|
102
|
+
"HTML messages:",
|
|
103
|
+
this.messages.filter((msg) => msg.isHTML)
|
|
104
|
+
);
|
|
105
|
+
|
|
106
|
+
// Use a simpler selector approach
|
|
107
|
+
const htmlMessageElements = this.template.querySelectorAll(
|
|
108
|
+
".html-content[data-message-id]"
|
|
109
|
+
);
|
|
110
|
+
console.log("Found HTML elements:", htmlMessageElements.length);
|
|
111
|
+
|
|
112
|
+
htmlMessageElements.forEach((element) => {
|
|
113
|
+
const messageId = element.getAttribute("data-message-id");
|
|
114
|
+
console.log("Processing element with ID:", messageId);
|
|
115
|
+
|
|
116
|
+
const message = this.messages.find(
|
|
117
|
+
(msg) => msg.id === messageId && msg.isHTML
|
|
118
|
+
);
|
|
119
|
+
console.log("Found message:", message);
|
|
120
|
+
|
|
121
|
+
if (message) {
|
|
122
|
+
console.log("Setting innerHTML for message:", messageId);
|
|
123
|
+
console.log("Content:", message.text);
|
|
124
|
+
element.innerHTML = message.text;
|
|
125
|
+
}
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
|
|
89
129
|
get chatContainerClass() {
|
|
90
130
|
return cx(
|
|
91
131
|
"chat-container",
|
|
@@ -114,14 +154,27 @@ export default class Chat extends LightningElement {
|
|
|
114
154
|
return messages;
|
|
115
155
|
}
|
|
116
156
|
|
|
117
|
-
|
|
157
|
+
// Helper method to get HTML content for a message
|
|
158
|
+
getMessageHTML(messageId: string): string {
|
|
159
|
+
const message = this.messages.find(
|
|
160
|
+
(msg) => msg.id === messageId && msg.isHTML
|
|
161
|
+
);
|
|
162
|
+
return message ? message.text : "";
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
private addMessage(
|
|
166
|
+
text: string,
|
|
167
|
+
sender: "user" | "assistant",
|
|
168
|
+
isHTML: boolean = false
|
|
169
|
+
) {
|
|
118
170
|
const timestamp = new Date();
|
|
119
171
|
const message: ChatMessage = {
|
|
120
172
|
id: `msg-${this.messageIdCounter++}`,
|
|
121
173
|
text,
|
|
122
174
|
timestamp,
|
|
123
175
|
sender,
|
|
124
|
-
formattedTime: this.formatTimestamp(timestamp)
|
|
176
|
+
formattedTime: this.formatTimestamp(timestamp),
|
|
177
|
+
isHTML
|
|
125
178
|
};
|
|
126
179
|
this.messages = [...this.messages, message];
|
|
127
180
|
this.saveMessages();
|
|
@@ -157,7 +210,8 @@ export default class Chat extends LightningElement {
|
|
|
157
210
|
text: msg.text,
|
|
158
211
|
timestamp: msg.timestamp.toISOString(),
|
|
159
212
|
sender: msg.sender,
|
|
160
|
-
formattedTime: msg.formattedTime
|
|
213
|
+
formattedTime: msg.formattedTime,
|
|
214
|
+
isHTML: msg.isHTML || false
|
|
161
215
|
}));
|
|
162
216
|
localStorage.setItem(
|
|
163
217
|
Chat.STORAGE_KEY,
|
|
@@ -178,7 +232,8 @@ export default class Chat extends LightningElement {
|
|
|
178
232
|
text: msg.text,
|
|
179
233
|
timestamp: new Date(msg.timestamp),
|
|
180
234
|
sender: msg.sender,
|
|
181
|
-
formattedTime: msg.formattedTime
|
|
235
|
+
formattedTime: msg.formattedTime,
|
|
236
|
+
isHTML: msg.isHTML || false
|
|
182
237
|
}));
|
|
183
238
|
|
|
184
239
|
// Update message counter to avoid ID conflicts
|
|
@@ -284,7 +339,7 @@ export default class Chat extends LightningElement {
|
|
|
284
339
|
private async callChatAPI(userMessage: string): Promise<string> {
|
|
285
340
|
try {
|
|
286
341
|
// Format chat history for API (exclude the current user message)
|
|
287
|
-
const chatHistory = this.messages.map(msg => ({
|
|
342
|
+
const chatHistory = this.messages.map((msg) => ({
|
|
288
343
|
text: msg.text,
|
|
289
344
|
sender: msg.sender
|
|
290
345
|
}));
|
|
@@ -295,62 +350,164 @@ export default class Chat extends LightningElement {
|
|
|
295
350
|
chatHistory: chatHistory
|
|
296
351
|
};
|
|
297
352
|
|
|
298
|
-
console.log(
|
|
353
|
+
console.log("Sending API request:", requestBody);
|
|
299
354
|
|
|
300
355
|
const response = await fetch(Chat.API_URL, {
|
|
301
|
-
method:
|
|
302
|
-
mode: 'cors', // Explicitly set CORS mode
|
|
356
|
+
method: "POST",
|
|
303
357
|
headers: {
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
...(Chat.IS_DEVELOPMENT && { 'Access-Control-Allow-Origin': '*' })
|
|
358
|
+
"Content-Type": "application/json",
|
|
359
|
+
"ngrok-skip-browser-warning": "true" // Skip ngrok browser warning
|
|
307
360
|
},
|
|
308
361
|
body: JSON.stringify(requestBody)
|
|
309
362
|
});
|
|
310
363
|
|
|
311
364
|
if (!response.ok) {
|
|
312
365
|
const errorText = await response.text();
|
|
313
|
-
console.error(
|
|
314
|
-
throw new Error(
|
|
366
|
+
console.error("API Error Response:", errorText);
|
|
367
|
+
throw new Error(
|
|
368
|
+
`API request failed with status: ${response.status}`
|
|
369
|
+
);
|
|
315
370
|
}
|
|
316
371
|
|
|
317
372
|
const data = await response.json();
|
|
318
|
-
console.log(
|
|
319
|
-
|
|
320
|
-
// Handle
|
|
321
|
-
const responseText =
|
|
322
|
-
|
|
373
|
+
console.log("API Response:", data);
|
|
374
|
+
|
|
375
|
+
// Handle the new API response format with "data" field
|
|
376
|
+
const responseText =
|
|
377
|
+
data.data ||
|
|
378
|
+
data.answer ||
|
|
379
|
+
data.text ||
|
|
380
|
+
data.response ||
|
|
381
|
+
data.result ||
|
|
382
|
+
data.message;
|
|
383
|
+
console.log("Response Text:", responseText);
|
|
384
|
+
|
|
323
385
|
if (!responseText) {
|
|
324
|
-
console.warn(
|
|
325
|
-
return
|
|
386
|
+
console.warn("No response text found in API response:", data);
|
|
387
|
+
return "I received your message but couldn't generate a proper response.";
|
|
326
388
|
}
|
|
327
|
-
|
|
389
|
+
|
|
328
390
|
return responseText;
|
|
329
|
-
|
|
330
391
|
} catch (error) {
|
|
331
|
-
console.error(
|
|
332
|
-
|
|
392
|
+
console.error("Chat API error:", error);
|
|
393
|
+
|
|
333
394
|
// Provide more specific error messages for CORS issues
|
|
334
|
-
if (error instanceof TypeError && error.message.includes(
|
|
335
|
-
return
|
|
395
|
+
if (error instanceof TypeError && error.message.includes("fetch")) {
|
|
396
|
+
return "I'm having trouble connecting to the service. This might be a CORS configuration issue. Please check your internet connection and try again.";
|
|
336
397
|
}
|
|
337
|
-
|
|
338
|
-
if (
|
|
339
|
-
|
|
398
|
+
|
|
399
|
+
if (
|
|
400
|
+
error instanceof Error &&
|
|
401
|
+
(error.message.includes("CORS") ||
|
|
402
|
+
error.message.includes("cross-origin"))
|
|
403
|
+
) {
|
|
404
|
+
return "I'm having trouble connecting due to browser security restrictions. Please ask your administrator to configure CORS headers on the backend API.";
|
|
340
405
|
}
|
|
341
|
-
|
|
342
|
-
return
|
|
406
|
+
|
|
407
|
+
return "I apologize, but I'm having trouble connecting to the service right now. Please try again later.";
|
|
343
408
|
}
|
|
344
409
|
}
|
|
345
410
|
|
|
411
|
+
private parseMarkdownToHTML(text: string): string {
|
|
412
|
+
// Convert markdown-like text to HTML
|
|
413
|
+
let html = text;
|
|
414
|
+
|
|
415
|
+
console.log("Original text:", text);
|
|
416
|
+
|
|
417
|
+
// First, convert markdown links [text](url) to HTML links
|
|
418
|
+
// Make sure links are absolute (relative to root) by adding leading slash if missing
|
|
419
|
+
html = html.replace(
|
|
420
|
+
/\[([^\]]+)\]\(([^)]+)\)/g,
|
|
421
|
+
(match, linkText, url) => {
|
|
422
|
+
// If URL doesn't start with http, https, or /, make it absolute
|
|
423
|
+
let absoluteUrl = url;
|
|
424
|
+
if (!url.startsWith('http') && !url.startsWith('/')) {
|
|
425
|
+
absoluteUrl = '/' + url;
|
|
426
|
+
}
|
|
427
|
+
return `<a href="${absoluteUrl}" target="_blank" rel="noopener noreferrer" class="chat-link">${linkText}</a>`;
|
|
428
|
+
}
|
|
429
|
+
);
|
|
430
|
+
|
|
431
|
+
// Handle numbered lists with descriptions (more complex pattern)
|
|
432
|
+
// Split into lines to process each line
|
|
433
|
+
const lines = html.split("\n");
|
|
434
|
+
const processedLines = [];
|
|
435
|
+
let inList = false;
|
|
436
|
+
|
|
437
|
+
for (let i = 0; i < lines.length; i++) {
|
|
438
|
+
const line = lines[i];
|
|
439
|
+
|
|
440
|
+
// Check if this is a numbered list item (starts with number and dot)
|
|
441
|
+
if (/^\d+\.\s+/.test(line)) {
|
|
442
|
+
if (!inList) {
|
|
443
|
+
processedLines.push('<ol class="chat-list">');
|
|
444
|
+
inList = true;
|
|
445
|
+
}
|
|
446
|
+
// Extract the content after the number
|
|
447
|
+
const content = line.replace(/^\d+\.\s+/, "");
|
|
448
|
+
processedLines.push(`<li>${content}`);
|
|
449
|
+
|
|
450
|
+
// Check if next line is indented (description)
|
|
451
|
+
if (i + 1 < lines.length && /^\s{2,}/.test(lines[i + 1])) {
|
|
452
|
+
i++; // Skip to next line
|
|
453
|
+
const description = lines[i].trim();
|
|
454
|
+
processedLines.push(
|
|
455
|
+
`<br><span class="chat-description">${description}</span></li>`
|
|
456
|
+
);
|
|
457
|
+
} else {
|
|
458
|
+
processedLines.push("</li>");
|
|
459
|
+
}
|
|
460
|
+
} else if (line.trim() === "" && inList) {
|
|
461
|
+
// Empty line might end the list
|
|
462
|
+
continue;
|
|
463
|
+
} else {
|
|
464
|
+
if (inList && line.trim() !== "") {
|
|
465
|
+
processedLines.push("</ol>");
|
|
466
|
+
inList = false;
|
|
467
|
+
}
|
|
468
|
+
if (line.trim() !== "") {
|
|
469
|
+
processedLines.push(line);
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
// Close any open list
|
|
475
|
+
if (inList) {
|
|
476
|
+
processedLines.push("</ol>");
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
html = processedLines.join("<br>");
|
|
480
|
+
|
|
481
|
+
// Clean up extra breaks
|
|
482
|
+
html = html.replace(/<br><br>/g, "<br>");
|
|
483
|
+
html = html.replace(/^<br>/, "");
|
|
484
|
+
|
|
485
|
+
// Convert **bold** to <strong>
|
|
486
|
+
html = html.replace(/\*\*([^*]+)\*\*/g, "<strong>$1</strong>");
|
|
487
|
+
|
|
488
|
+
// Convert *italic* to <em>
|
|
489
|
+
html = html.replace(/\*([^*]+)\*/g, "<em>$1</em>");
|
|
490
|
+
|
|
491
|
+
console.log("Processed HTML:", html);
|
|
492
|
+
|
|
493
|
+
return html;
|
|
494
|
+
}
|
|
495
|
+
|
|
346
496
|
private async getAssistantResponse(userMessage: string) {
|
|
347
497
|
try {
|
|
348
498
|
const response = await this.callChatAPI(userMessage);
|
|
349
|
-
|
|
499
|
+
// Convert markdown to HTML for rich content display
|
|
500
|
+
const htmlContent = this.parseMarkdownToHTML(response);
|
|
501
|
+
this.addMessage(htmlContent, "assistant", true); // true indicates HTML content
|
|
502
|
+
|
|
503
|
+
// Force a re-render after adding the message
|
|
504
|
+
setTimeout(() => {
|
|
505
|
+
this.renderHTMLMessages();
|
|
506
|
+
}, 100);
|
|
350
507
|
} catch (error) {
|
|
351
|
-
console.error(
|
|
508
|
+
console.error("Error getting assistant response:", error);
|
|
352
509
|
this.addMessage(
|
|
353
|
-
|
|
510
|
+
"I apologize, but I encountered an error while processing your request. Please try again.",
|
|
354
511
|
"assistant"
|
|
355
512
|
);
|
|
356
513
|
} finally {
|