@salesforcedevs/docs-components 0.0.6 → 0.0.7-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.
- package/lwc.config.json +25 -2
- package/package.json +17 -6
- package/src/modules/README.md +41 -0
- package/src/modules/doc/amfModelParser/amfModelParser.ts +674 -0
- package/src/modules/doc/amfReference/amfReference.css +25 -0
- package/src/modules/doc/amfReference/amfReference.html +60 -0
- package/src/modules/doc/amfReference/amfReference.ts +1494 -0
- package/src/modules/doc/amfReference/constants.ts +76 -0
- package/src/modules/doc/amfReference/types.ts +125 -0
- package/src/modules/doc/amfTopic/amfTopic.css +21 -0
- package/src/modules/doc/amfTopic/amfTopic.html +3 -0
- package/src/modules/doc/amfTopic/amfTopic.ts +111 -0
- package/src/modules/doc/amfTopic/types.ts +56 -0
- package/src/modules/doc/amfTopic/utils.ts +136 -0
- package/src/modules/doc/breadcrumbItem/breadcrumbItem.css +51 -0
- package/src/modules/doc/breadcrumbItem/breadcrumbItem.html +5 -0
- package/src/modules/doc/breadcrumbItem/breadcrumbItem.ts +71 -0
- package/src/modules/doc/breadcrumbs/breadcrumbs.css +27 -0
- package/src/modules/doc/breadcrumbs/breadcrumbs.html +58 -0
- package/src/modules/doc/breadcrumbs/breadcrumbs.ts +183 -0
- package/src/modules/doc/chat/README.md +179 -0
- package/src/modules/doc/chat/chat.css +821 -0
- package/src/modules/doc/chat/chat.html +241 -0
- package/src/modules/doc/chat/chat.ts +586 -0
- package/src/modules/doc/componentPlayground/componentPlayground.css +22 -0
- package/src/modules/doc/componentPlayground/componentPlayground.html +20 -0
- package/src/modules/doc/componentPlayground/componentPlayground.ts +29 -0
- package/src/modules/doc/content/content.css +249 -87
- package/src/modules/doc/content/content.html +3 -2
- package/src/modules/doc/content/content.ts +272 -152
- package/src/modules/doc/contentCallout/contentCallout.css +25 -26
- package/src/modules/doc/contentCallout/contentCallout.html +13 -4
- package/src/modules/doc/contentCallout/contentCallout.ts +21 -10
- package/src/modules/doc/contentLayout/contentLayout.css +1 -0
- package/src/modules/doc/contentLayout/contentLayout.html +68 -0
- package/src/modules/doc/contentLayout/contentLayout.ts +531 -0
- package/src/modules/doc/contentMedia/contentMedia.css +49 -0
- package/src/modules/doc/contentMedia/contentMedia.html +23 -0
- package/src/modules/doc/contentMedia/contentMedia.ts +34 -0
- package/src/modules/doc/doDont/doDont.css +47 -0
- package/src/modules/doc/doDont/doDont.html +27 -0
- package/src/modules/doc/doDont/doDont.ts +17 -0
- package/src/modules/doc/editFile/editFile.css +512 -0
- package/src/modules/doc/editFile/editFile.html +164 -0
- package/src/modules/doc/editFile/editFile.ts +215 -0
- package/src/modules/doc/header/header.css +132 -0
- package/src/modules/doc/header/header.html +55 -0
- package/src/modules/doc/header/header.ts +120 -0
- package/src/modules/doc/heading/heading.css +33 -0
- package/src/modules/doc/heading/heading.html +14 -0
- package/src/modules/doc/heading/heading.ts +67 -0
- package/src/modules/doc/headingAnchor/headingAnchor.css +33 -0
- package/src/modules/doc/headingAnchor/headingAnchor.html +19 -0
- package/src/modules/doc/headingAnchor/headingAnchor.ts +43 -0
- package/src/modules/doc/headingContent/headingContent.css +53 -0
- package/src/modules/doc/headingContent/headingContent.html +13 -0
- package/src/modules/doc/headingContent/headingContent.ts +30 -0
- package/src/modules/doc/lwcContentLayout/lwcContentLayout.css +1 -0
- package/src/modules/doc/lwcContentLayout/lwcContentLayout.html +68 -0
- package/src/modules/doc/lwcContentLayout/lwcContentLayout.ts +168 -0
- package/src/modules/doc/nav/nav.css +4 -2
- package/src/modules/doc/nav/nav.ts +1 -1
- package/src/modules/doc/overview/overview.css +40 -0
- package/src/modules/doc/overview/overview.html +34 -0
- package/src/modules/doc/overview/overview.ts +12 -0
- package/src/modules/doc/phase/phase.css +70 -0
- package/src/modules/doc/phase/phase.html +38 -0
- package/src/modules/doc/phase/phase.ts +93 -0
- package/src/modules/doc/specificationContent/specificationContent.css +36 -0
- package/src/modules/doc/specificationContent/specificationContent.html +171 -0
- package/src/modules/doc/specificationContent/specificationContent.ts +127 -0
- package/src/modules/doc/sprigSurvey/sprigSurvey.html +20 -0
- package/src/modules/doc/sprigSurvey/sprigSurvey.scoped.css +16 -0
- package/src/modules/doc/sprigSurvey/sprigSurvey.ts +16 -0
- package/src/modules/doc/toc/toc.html +11 -6
- package/src/modules/doc/toc/toc.ts +1 -1
- package/src/modules/doc/toolbar/toolbar.html +8 -1
- package/src/modules/doc/versionPicker/versionPicker.css +64 -0
- package/src/modules/doc/versionPicker/versionPicker.html +38 -0
- package/src/modules/doc/versionPicker/versionPicker.ts +65 -0
- package/src/modules/doc/xmlContent/types.ts +120 -0
- package/src/modules/doc/xmlContent/utils.ts +163 -0
- package/src/modules/doc/xmlContent/xmlContent.css +54 -0
- package/src/modules/doc/xmlContent/xmlContent.html +52 -0
- package/src/modules/doc/xmlContent/xmlContent.ts +792 -0
- package/src/modules/docHelpers/amfStyle/amfStyle.css +355 -0
- package/src/modules/docHelpers/contentLayoutStyle/contentLayoutStyle.css +131 -0
- package/src/modules/docHelpers/imgStyle/imgStyle.css +59 -0
- package/src/modules/docHelpers/status/status.css +22 -0
- package/src/modules/docUtils/searchSyncer/searchSyncer.ts +86 -0
- package/src/modules/docUtils/utils/__mocks__/coveo.analytics.ts +16 -0
- package/src/modules/docUtils/utils/coveo.analytics.d.ts +10 -0
- package/src/modules/docUtils/utils/utils.ts +32 -0
- package/src/modules/doc/container/__benchmarks__/container.benchmark.js +0 -43
- package/src/modules/doc/container/__mocks__/mockAvailableLanguages.js +0 -8
- package/src/modules/doc/container/__mocks__/mockAvailableVersions.js +0 -122
- package/src/modules/doc/container/__mocks__/mockContentFetchResponse.json +0 -5
- package/src/modules/doc/container/__mocks__/mockDocContent.js +0 -29
- package/src/modules/doc/container/__mocks__/mockNavigationFetchResponse.json +0 -4061
- package/src/modules/doc/container/__mocks__/mockPageReference.js +0 -8
- package/src/modules/doc/container/__mocks__/mockPdfUrl.js +0 -1
- package/src/modules/doc/container/__mocks__/mockSelectedLanguage.js +0 -8
- package/src/modules/doc/container/__mocks__/mockSelectedVersion.js +0 -8
- package/src/modules/doc/container/__mocks__/mockToc.js +0 -146
- package/src/modules/doc/container/__tests__/container.test.ts +0 -82
- package/src/modules/doc/container/container.css +0 -34
- package/src/modules/doc/container/container.html +0 -23
- package/src/modules/doc/container/container.stories.ts +0 -18
- package/src/modules/doc/container/container.ts +0 -356
- package/src/modules/doc/content/__tests__/content.test.ts +0 -30
- package/src/modules/doc/content/__tests__/mockDocContent.ts +0 -29
- package/src/modules/doc/content/__tests__/mockPageReference.ts +0 -8
- package/src/modules/doc/contentCallout/__tests__/contentCallout.test.ts +0 -80
- package/src/modules/doc/contentCallout/__tests__/mockProps.ts +0 -14
- package/src/modules/doc/contentCallout/contentCallout.stories.ts +0 -29
- package/src/modules/doc/nav/__tests__/mockAvailableLanguages.ts +0 -8
- package/src/modules/doc/nav/__tests__/mockAvailableVersions.ts +0 -122
- package/src/modules/doc/nav/__tests__/mockPageReference.ts +0 -8
- package/src/modules/doc/nav/__tests__/mockPdfUrl.ts +0 -1
- package/src/modules/doc/nav/__tests__/mockSelectedLanguage.ts +0 -8
- package/src/modules/doc/nav/__tests__/mockSelectedVersion.ts +0 -8
- package/src/modules/doc/nav/__tests__/mockToc.ts +0 -146
- package/src/modules/doc/nav/__tests__/nav.test.ts +0 -62
- package/src/modules/doc/search/__tests__/search.test.ts +0 -20
- package/src/modules/doc/search/search.html +0 -1
- package/src/modules/doc/search/search.ts +0 -3
- package/src/modules/doc/toc/__tests__/mockPageReference.ts +0 -8
- package/src/modules/doc/toc/__tests__/mockToc.ts +0 -146
- package/src/modules/doc/toc/__tests__/toc.test.ts +0 -29
- package/src/modules/doc/toolbar/__tests__/mockAvailableLanguages.ts +0 -8
- package/src/modules/doc/toolbar/__tests__/mockAvailableVersions.ts +0 -122
- package/src/modules/doc/toolbar/__tests__/mockPdfUrl.ts +0 -1
- package/src/modules/doc/toolbar/__tests__/mockSelectedLanguage.ts +0 -8
- package/src/modules/doc/toolbar/__tests__/mockSelectedVersion.ts +0 -8
- 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
|
+
}
|