@salesforcedevs/docs-components 0.0.4 → 0.0.5-edit

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.
Files changed (140) hide show
  1. package/lwc.config.json +25 -2
  2. package/package.json +18 -7
  3. package/src/modules/README.md +41 -0
  4. package/src/modules/doc/amfModelParser/amfModelParser.ts +674 -0
  5. package/src/modules/doc/amfReference/amfReference.css +25 -0
  6. package/src/modules/doc/amfReference/amfReference.html +60 -0
  7. package/src/modules/doc/amfReference/amfReference.ts +1494 -0
  8. package/src/modules/doc/amfReference/constants.ts +76 -0
  9. package/src/modules/doc/amfReference/types.ts +125 -0
  10. package/src/modules/doc/amfTopic/amfTopic.css +21 -0
  11. package/src/modules/doc/amfTopic/amfTopic.html +3 -0
  12. package/src/modules/doc/amfTopic/amfTopic.ts +111 -0
  13. package/src/modules/doc/amfTopic/types.ts +56 -0
  14. package/src/modules/doc/amfTopic/utils.ts +136 -0
  15. package/src/modules/doc/breadcrumbItem/breadcrumbItem.css +51 -0
  16. package/src/modules/doc/breadcrumbItem/breadcrumbItem.html +5 -0
  17. package/src/modules/doc/breadcrumbItem/breadcrumbItem.ts +71 -0
  18. package/src/modules/doc/breadcrumbs/breadcrumbs.css +27 -0
  19. package/src/modules/doc/breadcrumbs/breadcrumbs.html +58 -0
  20. package/src/modules/doc/breadcrumbs/breadcrumbs.ts +183 -0
  21. package/src/modules/doc/chat/README.md +179 -0
  22. package/src/modules/doc/chat/chat.css +821 -0
  23. package/src/modules/doc/chat/chat.html +241 -0
  24. package/src/modules/doc/chat/chat.ts +586 -0
  25. package/src/modules/doc/componentPlayground/componentPlayground.css +22 -0
  26. package/src/modules/doc/componentPlayground/componentPlayground.html +20 -0
  27. package/src/modules/doc/componentPlayground/componentPlayground.ts +29 -0
  28. package/src/modules/doc/content/content.css +382 -6
  29. package/src/modules/doc/content/content.html +3 -2
  30. package/src/modules/doc/content/content.ts +287 -110
  31. package/src/modules/doc/contentCallout/contentCallout.css +25 -26
  32. package/src/modules/doc/contentCallout/contentCallout.html +13 -4
  33. package/src/modules/doc/contentCallout/contentCallout.ts +22 -11
  34. package/src/modules/doc/contentLayout/contentLayout.css +13 -0
  35. package/src/modules/doc/contentLayout/contentLayout.html +73 -0
  36. package/src/modules/doc/contentLayout/contentLayout.ts +531 -0
  37. package/src/modules/doc/contentMedia/contentMedia.css +49 -0
  38. package/src/modules/doc/contentMedia/contentMedia.html +23 -0
  39. package/src/modules/doc/contentMedia/contentMedia.ts +34 -0
  40. package/src/modules/doc/doDont/doDont.css +47 -0
  41. package/src/modules/doc/doDont/doDont.html +27 -0
  42. package/src/modules/doc/doDont/doDont.ts +17 -0
  43. package/src/modules/doc/editFile/editFile.css +505 -0
  44. package/src/modules/doc/editFile/editFile.html +164 -0
  45. package/src/modules/doc/editFile/editFile.ts +213 -0
  46. package/src/modules/doc/header/header.css +132 -0
  47. package/src/modules/doc/header/header.html +55 -0
  48. package/src/modules/doc/header/header.ts +120 -0
  49. package/src/modules/doc/heading/heading.css +33 -0
  50. package/src/modules/doc/heading/heading.html +14 -0
  51. package/src/modules/doc/heading/heading.ts +67 -0
  52. package/src/modules/doc/headingAnchor/headingAnchor.css +33 -0
  53. package/src/modules/doc/headingAnchor/headingAnchor.html +19 -0
  54. package/src/modules/doc/headingAnchor/headingAnchor.ts +43 -0
  55. package/src/modules/doc/headingContent/headingContent.css +53 -0
  56. package/src/modules/doc/headingContent/headingContent.html +13 -0
  57. package/src/modules/doc/headingContent/headingContent.ts +30 -0
  58. package/src/modules/doc/lwcContentLayout/lwcContentLayout.css +1 -0
  59. package/src/modules/doc/lwcContentLayout/lwcContentLayout.html +68 -0
  60. package/src/modules/doc/lwcContentLayout/lwcContentLayout.ts +168 -0
  61. package/src/modules/doc/nav/nav.css +4 -2
  62. package/src/modules/doc/nav/nav.html +8 -13
  63. package/src/modules/doc/nav/nav.ts +1 -1
  64. package/src/modules/doc/overview/overview.css +40 -0
  65. package/src/modules/doc/overview/overview.html +34 -0
  66. package/src/modules/doc/overview/overview.ts +12 -0
  67. package/src/modules/doc/phase/phase.css +70 -0
  68. package/src/modules/doc/phase/phase.html +38 -0
  69. package/src/modules/doc/phase/phase.ts +93 -0
  70. package/src/modules/doc/specificationContent/specificationContent.css +36 -0
  71. package/src/modules/doc/specificationContent/specificationContent.html +171 -0
  72. package/src/modules/doc/specificationContent/specificationContent.ts +127 -0
  73. package/src/modules/doc/sprigSurvey/sprigSurvey.html +20 -0
  74. package/src/modules/doc/sprigSurvey/sprigSurvey.scoped.css +16 -0
  75. package/src/modules/doc/sprigSurvey/sprigSurvey.ts +16 -0
  76. package/src/modules/doc/toc/toc.html +11 -6
  77. package/src/modules/doc/toc/toc.ts +2 -6
  78. package/src/modules/doc/toolbar/toolbar.html +8 -1
  79. package/src/modules/doc/toolbar/toolbar.ts +1 -1
  80. package/src/modules/doc/versionPicker/versionPicker.css +64 -0
  81. package/src/modules/doc/versionPicker/versionPicker.html +38 -0
  82. package/src/modules/doc/versionPicker/versionPicker.ts +65 -0
  83. package/src/modules/doc/xmlContent/types.ts +120 -0
  84. package/src/modules/doc/xmlContent/utils.ts +163 -0
  85. package/src/modules/doc/xmlContent/xmlContent.css +54 -0
  86. package/src/modules/doc/xmlContent/xmlContent.html +52 -0
  87. package/src/modules/doc/xmlContent/xmlContent.ts +792 -0
  88. package/src/modules/docHelpers/amfStyle/amfStyle.css +355 -0
  89. package/src/modules/docHelpers/contentLayoutStyle/contentLayoutStyle.css +131 -0
  90. package/src/modules/docHelpers/imgStyle/imgStyle.css +59 -0
  91. package/src/modules/docHelpers/status/status.css +22 -0
  92. package/src/modules/docUtils/searchSyncer/searchSyncer.ts +86 -0
  93. package/src/modules/docUtils/utils/__mocks__/coveo.analytics.ts +16 -0
  94. package/src/modules/docUtils/utils/coveo.analytics.d.ts +10 -0
  95. package/src/modules/docUtils/utils/utils.ts +32 -0
  96. package/src/modules/doc/container/__benchmarks__/container.benchmark.js +0 -43
  97. package/src/modules/doc/container/__mocks__/mockAvailableLanguages.js +0 -8
  98. package/src/modules/doc/container/__mocks__/mockAvailableVersions.js +0 -122
  99. package/src/modules/doc/container/__mocks__/mockContentFetchResponse.json +0 -5
  100. package/src/modules/doc/container/__mocks__/mockDocContent.js +0 -29
  101. package/src/modules/doc/container/__mocks__/mockNavigationFetchResponse.json +0 -4061
  102. package/src/modules/doc/container/__mocks__/mockPageReference.js +0 -8
  103. package/src/modules/doc/container/__mocks__/mockPdfUrl.js +0 -1
  104. package/src/modules/doc/container/__mocks__/mockSelectedLanguage.js +0 -8
  105. package/src/modules/doc/container/__mocks__/mockSelectedVersion.js +0 -8
  106. package/src/modules/doc/container/__mocks__/mockToc.js +0 -146
  107. package/src/modules/doc/container/__tests__/container.test.ts +0 -82
  108. package/src/modules/doc/container/container.css +0 -33
  109. package/src/modules/doc/container/container.html +0 -23
  110. package/src/modules/doc/container/container.stories.ts +0 -18
  111. package/src/modules/doc/container/container.ts +0 -360
  112. package/src/modules/doc/content/__tests__/content.test.ts +0 -30
  113. package/src/modules/doc/content/__tests__/mockDocContent.ts +0 -29
  114. package/src/modules/doc/content/__tests__/mockPageReference.ts +0 -8
  115. package/src/modules/doc/contentCallout/__tests__/contentCallout.test.ts +0 -80
  116. package/src/modules/doc/contentCallout/__tests__/mockProps.ts +0 -14
  117. package/src/modules/doc/contentCallout/contentCallout.stories.ts +0 -29
  118. package/src/modules/doc/nav/__tests__/mockAvailableLanguages.ts +0 -8
  119. package/src/modules/doc/nav/__tests__/mockAvailableVersions.ts +0 -122
  120. package/src/modules/doc/nav/__tests__/mockPageReference.ts +0 -8
  121. package/src/modules/doc/nav/__tests__/mockPdfUrl.ts +0 -1
  122. package/src/modules/doc/nav/__tests__/mockSelectedLanguage.ts +0 -8
  123. package/src/modules/doc/nav/__tests__/mockSelectedVersion.ts +0 -8
  124. package/src/modules/doc/nav/__tests__/mockToc.ts +0 -146
  125. package/src/modules/doc/nav/__tests__/nav.test.ts +0 -66
  126. package/src/modules/doc/prismcss/prismcss.css +0 -184
  127. package/src/modules/doc/prismjs/prismjs.html +0 -3
  128. package/src/modules/doc/prismjs/prismjs.ts +0 -1842
  129. package/src/modules/doc/search/__tests__/search.test.ts +0 -20
  130. package/src/modules/doc/search/search.html +0 -1
  131. package/src/modules/doc/search/search.ts +0 -3
  132. package/src/modules/doc/toc/__tests__/mockPageReference.ts +0 -8
  133. package/src/modules/doc/toc/__tests__/mockToc.ts +0 -146
  134. package/src/modules/doc/toc/__tests__/toc.test.ts +0 -29
  135. package/src/modules/doc/toolbar/__tests__/mockAvailableLanguages.ts +0 -8
  136. package/src/modules/doc/toolbar/__tests__/mockAvailableVersions.ts +0 -122
  137. package/src/modules/doc/toolbar/__tests__/mockPdfUrl.ts +0 -1
  138. package/src/modules/doc/toolbar/__tests__/mockSelectedLanguage.ts +0 -8
  139. package/src/modules/doc/toolbar/__tests__/mockSelectedVersion.ts +0 -8
  140. package/src/modules/doc/toolbar/__tests__/toolbar.test.ts +0 -44
@@ -0,0 +1,586 @@
1
+ import { LightningElement, api, track } from "lwc";
2
+ import cx from "classnames";
3
+ import { normalizeBoolean } from "dxUtils/normalizers";
4
+
5
+ interface ChatMessage {
6
+ id: string;
7
+ text: string;
8
+ timestamp: Date;
9
+ sender: "user" | "assistant";
10
+ isTyping?: boolean;
11
+ formattedTime?: string;
12
+ isHTML?: boolean; // Flag to indicate if content should be rendered as HTML
13
+ }
14
+
15
+ export default class Chat extends LightningElement {
16
+ @api title: string = "Chat";
17
+ @api placeholder: string = "Type your message...";
18
+ @api assistantName: string = "Assistant";
19
+ @api maxHeight: string = "400px";
20
+
21
+ @api
22
+ get disabled() {
23
+ return this._disabled;
24
+ }
25
+
26
+ set disabled(value) {
27
+ this._disabled = normalizeBoolean(value);
28
+ }
29
+
30
+ @api
31
+ get showTimestamp() {
32
+ return this._showTimestamp;
33
+ }
34
+
35
+ set showTimestamp(value) {
36
+ this._showTimestamp = normalizeBoolean(value);
37
+ }
38
+
39
+ @api
40
+ get isOpen() {
41
+ return this._isOpen;
42
+ }
43
+
44
+ set isOpen(value) {
45
+ this._isOpen = normalizeBoolean(value);
46
+ }
47
+
48
+ @track messages: ChatMessage[] = [];
49
+ @track currentMessage: string = "";
50
+ @track isAssistantTyping: boolean = false;
51
+
52
+ private _disabled: boolean = false;
53
+ private _showTimestamp: boolean = false;
54
+ private _isOpen: boolean = false;
55
+ private messageIdCounter: number = 0;
56
+
57
+ // API Configuration
58
+ private static readonly API_URL =
59
+ "https://276ca7264b79.ngrok-free.app/api/llm/search";
60
+ private static readonly MAX_RESULTS = 5;
61
+
62
+ // Development mode flag - set to true if running in development
63
+ private static readonly IS_DEVELOPMENT =
64
+ window.location.hostname === "localhost" ||
65
+ window.location.hostname === "127.0.0.1";
66
+
67
+ // localStorage keys for persisting messages and open state
68
+ private static readonly STORAGE_KEY = "doc-chat-messages";
69
+ private static readonly OPEN_STATE_KEY = "doc-chat-should-open";
70
+
71
+ connectedCallback() {
72
+ console.log("---- Connected callback ------");
73
+ // Load existing messages from localStorage
74
+ this.loadMessages();
75
+
76
+ // Add welcome message only if no existing messages
77
+ if (this.messages.length === 0) {
78
+ this.addMessage("Hello! How can I help you today?", "assistant");
79
+ }
80
+
81
+ // Check if chat should be opened after reload
82
+ this.checkAndOpenAfterReload();
83
+
84
+ // Ensure body has proper class state
85
+ this.updateBodyClass();
86
+ }
87
+
88
+ disconnectedCallback() {
89
+ // Clean up body classes when component is destroyed
90
+ document.body.classList.remove("chat-open", "chat-closed");
91
+ }
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
+
129
+ get chatContainerClass() {
130
+ return cx(
131
+ "chat-container",
132
+ this.disabled && "chat-container_disabled",
133
+ this.isOpen && "chat-container_open"
134
+ );
135
+ }
136
+
137
+ get showTriggerButton() {
138
+ return !this.isOpen;
139
+ }
140
+
141
+ get messagesWithTyping() {
142
+ const messages = [...this.messages];
143
+ if (this.isAssistantTyping) {
144
+ const timestamp = new Date();
145
+ messages.push({
146
+ id: "typing",
147
+ text: "...",
148
+ timestamp,
149
+ sender: "assistant",
150
+ isTyping: true,
151
+ formattedTime: this.formatTimestamp(timestamp)
152
+ });
153
+ }
154
+ return messages;
155
+ }
156
+
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
+ ) {
170
+ const timestamp = new Date();
171
+ const message: ChatMessage = {
172
+ id: `msg-${this.messageIdCounter++}`,
173
+ text,
174
+ timestamp,
175
+ sender,
176
+ formattedTime: this.formatTimestamp(timestamp),
177
+ isHTML
178
+ };
179
+ this.messages = [...this.messages, message];
180
+ this.saveMessages();
181
+ this.scrollToBottom();
182
+ }
183
+
184
+ private scrollToBottom() {
185
+ // Use setTimeout to ensure DOM is updated
186
+ setTimeout(() => {
187
+ const messagesContainer =
188
+ this.template.querySelector(".chat-messages");
189
+ if (messagesContainer) {
190
+ messagesContainer.scrollTop = messagesContainer.scrollHeight;
191
+ }
192
+ }, 0);
193
+ }
194
+
195
+ private updateBodyClass() {
196
+ // Update body classes to trigger content shift
197
+ if (this.isOpen) {
198
+ document.body.classList.remove("chat-closed");
199
+ document.body.classList.add("chat-open");
200
+ } else {
201
+ document.body.classList.remove("chat-open");
202
+ document.body.classList.add("chat-closed");
203
+ }
204
+ }
205
+
206
+ private saveMessages() {
207
+ try {
208
+ const messagesToSave = this.messages.map((msg) => ({
209
+ id: msg.id,
210
+ text: msg.text,
211
+ timestamp: msg.timestamp.toISOString(),
212
+ sender: msg.sender,
213
+ formattedTime: msg.formattedTime,
214
+ isHTML: msg.isHTML || false
215
+ }));
216
+ localStorage.setItem(
217
+ Chat.STORAGE_KEY,
218
+ JSON.stringify(messagesToSave)
219
+ );
220
+ } catch (error) {
221
+ console.warn("Failed to save messages to localStorage:", error);
222
+ }
223
+ }
224
+
225
+ private loadMessages() {
226
+ try {
227
+ const savedMessages = localStorage.getItem(Chat.STORAGE_KEY);
228
+ if (savedMessages) {
229
+ const parsedMessages = JSON.parse(savedMessages);
230
+ this.messages = parsedMessages.map((msg: any) => ({
231
+ id: msg.id,
232
+ text: msg.text,
233
+ timestamp: new Date(msg.timestamp),
234
+ sender: msg.sender,
235
+ formattedTime: msg.formattedTime,
236
+ isHTML: msg.isHTML || false
237
+ }));
238
+
239
+ // Update message counter to avoid ID conflicts
240
+ const lastId =
241
+ this.messages.length > 0
242
+ ? Math.max(
243
+ ...this.messages.map(
244
+ (m) =>
245
+ parseInt(m.id.replace("msg-", ""), 10) ||
246
+ 0
247
+ )
248
+ )
249
+ : 0;
250
+ this.messageIdCounter = lastId + 1;
251
+ }
252
+ } catch (error) {
253
+ console.warn("Failed to load messages from localStorage:", error);
254
+ this.messages = [];
255
+ }
256
+ }
257
+
258
+ private clearMessages() {
259
+ try {
260
+ localStorage.removeItem(Chat.STORAGE_KEY);
261
+ this.messages = [];
262
+ this.messageIdCounter = 0;
263
+ // Add welcome message after clearing
264
+ this.addMessage("Hello! How can I help you today?", "assistant");
265
+ } catch (error) {
266
+ console.warn("Failed to clear messages from localStorage:", error);
267
+ }
268
+ }
269
+
270
+ private checkAndOpenAfterReload() {
271
+ try {
272
+ const shouldOpen = localStorage.getItem(Chat.OPEN_STATE_KEY);
273
+ if (shouldOpen === "true") {
274
+ // Clear the flag
275
+ localStorage.removeItem(Chat.OPEN_STATE_KEY);
276
+ // Open the chat
277
+ this._isOpen = true;
278
+ this.updateBodyClass();
279
+
280
+ // Dispatch custom event to notify parent components
281
+ this.dispatchEvent(
282
+ new CustomEvent("chatopened", {
283
+ detail: { opened: true }
284
+ })
285
+ );
286
+ }
287
+ } catch (error) {
288
+ console.warn(
289
+ "Failed to check open state from localStorage:",
290
+ error
291
+ );
292
+ }
293
+ }
294
+
295
+ handleInputChange(event: Event) {
296
+ const target = event.target as HTMLInputElement;
297
+ this.currentMessage = target.value;
298
+ }
299
+
300
+ handleKeyDown(event: KeyboardEvent) {
301
+ if (event.key === "Enter" && !event.shiftKey) {
302
+ event.preventDefault();
303
+ this.sendMessage();
304
+ }
305
+ }
306
+
307
+ handleSendClick() {
308
+ this.sendMessage();
309
+ }
310
+
311
+ private sendMessage() {
312
+ if (!this.currentMessage.trim() || this.disabled) {
313
+ return;
314
+ }
315
+
316
+ // Add user message
317
+ this.addMessage(this.currentMessage, "user");
318
+ const userMessage = this.currentMessage;
319
+ this.currentMessage = "";
320
+
321
+ // Clear input
322
+ const input = this.template.querySelector(
323
+ ".chat-input"
324
+ ) as HTMLInputElement;
325
+ if (input) {
326
+ input.value = "";
327
+ }
328
+
329
+ // Show typing indicator
330
+ this.isAssistantTyping = true;
331
+
332
+ // Get real assistant response from API
333
+ // Add a small delay to show the typing indicator
334
+ setTimeout(() => {
335
+ this.getAssistantResponse(userMessage);
336
+ }, 500);
337
+ }
338
+
339
+ private async callChatAPI(userMessage: string): Promise<string> {
340
+ try {
341
+ // Format chat history for API (exclude the current user message)
342
+ const chatHistory = this.messages.map((msg) => ({
343
+ text: msg.text,
344
+ sender: msg.sender
345
+ }));
346
+
347
+ const requestBody = {
348
+ query: userMessage,
349
+ maxResults: Chat.MAX_RESULTS,
350
+ chatHistory: chatHistory
351
+ };
352
+
353
+ console.log("Sending API request:", requestBody);
354
+
355
+ const response = await fetch(Chat.API_URL, {
356
+ method: "POST",
357
+ headers: {
358
+ "Content-Type": "application/json",
359
+ "ngrok-skip-browser-warning": "true" // Skip ngrok browser warning
360
+ },
361
+ body: JSON.stringify(requestBody)
362
+ });
363
+
364
+ if (!response.ok) {
365
+ const errorText = await response.text();
366
+ console.error("API Error Response:", errorText);
367
+ throw new Error(
368
+ `API request failed with status: ${response.status}`
369
+ );
370
+ }
371
+
372
+ const data = await response.json();
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
+
385
+ if (!responseText) {
386
+ console.warn("No response text found in API response:", data);
387
+ return "I received your message but couldn't generate a proper response.";
388
+ }
389
+
390
+ return responseText;
391
+ } catch (error) {
392
+ console.error("Chat API error:", error);
393
+
394
+ // Provide more specific error messages for CORS issues
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.";
397
+ }
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.";
405
+ }
406
+
407
+ return "I apologize, but I'm having trouble connecting to the service right now. Please try again later.";
408
+ }
409
+ }
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
+
496
+ private async getAssistantResponse(userMessage: string) {
497
+ try {
498
+ const response = await this.callChatAPI(userMessage);
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);
507
+ } catch (error) {
508
+ console.error("Error getting assistant response:", error);
509
+ this.addMessage(
510
+ "I apologize, but I encountered an error while processing your request. Please try again.",
511
+ "assistant"
512
+ );
513
+ } finally {
514
+ this.isAssistantTyping = false;
515
+ }
516
+ }
517
+
518
+ formatTimestamp(timestamp: Date) {
519
+ return timestamp.toLocaleTimeString([], {
520
+ hour: "2-digit",
521
+ minute: "2-digit"
522
+ });
523
+ }
524
+
525
+ get sendButtonDisabled() {
526
+ return this.disabled || !this.currentMessage.trim();
527
+ }
528
+
529
+ handleCloseClick() {
530
+ this._isOpen = false;
531
+ this.updateBodyClass();
532
+
533
+ // Dispatch custom event to notify parent components
534
+ this.dispatchEvent(
535
+ new CustomEvent("chatclosed", {
536
+ detail: { closed: true }
537
+ })
538
+ );
539
+ }
540
+
541
+ handleClearClick() {
542
+ this.clearMessages();
543
+
544
+ // Dispatch custom event to notify parent components
545
+ this.dispatchEvent(
546
+ new CustomEvent("chatcleared", {
547
+ detail: { cleared: true }
548
+ })
549
+ );
550
+ }
551
+
552
+ handleOpenClick() {
553
+ try {
554
+ // Set flag to open chat after reload
555
+ localStorage.setItem(Chat.OPEN_STATE_KEY, "true");
556
+ } catch (error) {
557
+ console.warn("Failed to set open state in localStorage:", error);
558
+ }
559
+ // Hard reload the page to clear cache when opening chat
560
+ window.location.reload();
561
+ }
562
+
563
+ openChat() {
564
+ try {
565
+ // Set flag to open chat after reload
566
+ localStorage.setItem(Chat.OPEN_STATE_KEY, "true");
567
+ } catch (error) {
568
+ console.warn("Failed to set open state in localStorage:", error);
569
+ }
570
+
571
+ // Hard reload the page to clear cache when opening chat
572
+ window.location.reload();
573
+ }
574
+
575
+ closeChat() {
576
+ this._isOpen = false;
577
+ this.updateBodyClass();
578
+
579
+ // Dispatch custom event to notify parent components
580
+ this.dispatchEvent(
581
+ new CustomEvent("chatclosed", {
582
+ detail: { closed: true }
583
+ })
584
+ );
585
+ }
586
+ }
@@ -0,0 +1,22 @@
1
+ @import "dxHelpers/reset";
2
+
3
+ /**
4
+ * Designs link - https://www.figma.com/design/9SalRPlJmtRDZHq03o8dL1/One-Doc-Site-Visionary-Mocks?node-id=9968-366397&m=dev
5
+ * Preview padding: 24px
6
+ * Example Selector height: 122px
7
+ * Example Preview min height: 150px
8
+ * Tab height with border: 46px(45px height and 1px border)
9
+ * Codeblock height: 264px(224px for codeblock and 40px for panel)
10
+ * Total: 606px
11
+ **/
12
+
13
+ iframe {
14
+ width: 100%;
15
+ height: 606px;
16
+ border-radius: var(--dx-g-spacing-sm);
17
+ border: 1px solid var(--dx-g-gray-90);
18
+ }
19
+
20
+ .container {
21
+ position: relative;
22
+ }
@@ -0,0 +1,20 @@
1
+ <template>
2
+ <div class="container" lwc:if={playgroundAvailable}>
3
+ <dx-spinner
4
+ size="large"
5
+ variant="brand"
6
+ if:true={isLoading}
7
+ ></dx-spinner>
8
+ <iframe
9
+ src={playgroundSrc}
10
+ onload={handleIframeLoad}
11
+ title="Component Playground"
12
+ allow="clipboard-write"
13
+ ></iframe>
14
+ </div>
15
+ <dx-error-fallback
16
+ lwc:else
17
+ title="Playground Unavailable"
18
+ description="This component's playground is currently unavailable. Please check again later."
19
+ ></dx-error-fallback>
20
+ </template>
@@ -0,0 +1,29 @@
1
+ import { LightningElement, api } from "lwc";
2
+
3
+ export default class ComponentPlayground extends LightningElement {
4
+ @api model!: string;
5
+ @api namespace!: string;
6
+ @api component!: string;
7
+ @api playgroundAppUrl!: string;
8
+
9
+ isLoading = true;
10
+
11
+ get playgroundAvailable() {
12
+ return (
13
+ this.playgroundAppUrl &&
14
+ this.model &&
15
+ this.namespace &&
16
+ this.component
17
+ );
18
+ }
19
+
20
+ get playgroundSrc(): string {
21
+ return `${this.playgroundAppUrl}/playground/${this.model}/${
22
+ this.namespace
23
+ }/${this.component.toLowerCase()}.html`;
24
+ }
25
+
26
+ handleIframeLoad() {
27
+ this.isLoading = false;
28
+ }
29
+ }