@a.izzuddin/ai-chat 0.2.8 → 0.2.9

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/dist/index.js CHANGED
@@ -27,6 +27,7 @@ exports.AIChat = class AIChat extends lit.LitElement {
27
27
  this.mode = "fullscreen";
28
28
  this.initialMessages = [];
29
29
  this.botAvatarUrl = "";
30
+ this.widgetIconUrl = "";
30
31
  this.backgroundImageUrl = "";
31
32
  this.widgetWidth = "380px";
32
33
  this.widgetHeight = "600px";
@@ -44,6 +45,25 @@ exports.AIChat = class AIChat extends lit.LitElement {
44
45
  toggleWidget() {
45
46
  this.isOpen = !this.isOpen;
46
47
  }
48
+ /**
49
+ * Clear all chat messages and reset to welcome message
50
+ * @public
51
+ */
52
+ clearChat() {
53
+ this.clearMessagesFromStorage();
54
+ if (this.welcomeMessage) {
55
+ const welcomeText = this.welcomeSubtitle ? `${this.welcomeMessage}
56
+
57
+ ${this.welcomeSubtitle}` : this.welcomeMessage;
58
+ this.messages = [{
59
+ id: "welcome-" + Date.now(),
60
+ role: "assistant",
61
+ content: welcomeText
62
+ }];
63
+ } else {
64
+ this.messages = [];
65
+ }
66
+ }
47
67
  lightenColor(hex, percent = 15) {
48
68
  hex = hex.replace("#", "");
49
69
  const r = parseInt(hex.substring(0, 2), 16);
@@ -54,23 +74,66 @@ exports.AIChat = class AIChat extends lit.LitElement {
54
74
  const newB = Math.min(255, Math.round(b + (255 - b) * (percent / 100)));
55
75
  return `#${newR.toString(16).padStart(2, "0")}${newG.toString(16).padStart(2, "0")}${newB.toString(16).padStart(2, "0")}`;
56
76
  }
77
+ getStorageKey() {
78
+ return `ai-chat-messages-${this.sessionId}`;
79
+ }
80
+ saveMessagesToStorage() {
81
+ try {
82
+ const storageKey = this.getStorageKey();
83
+ localStorage.setItem(storageKey, JSON.stringify(this.messages));
84
+ } catch (error) {
85
+ console.warn("Failed to save messages to localStorage:", error);
86
+ }
87
+ }
88
+ loadMessagesFromStorage() {
89
+ try {
90
+ const storageKey = this.getStorageKey();
91
+ const saved = localStorage.getItem(storageKey);
92
+ if (saved) {
93
+ return JSON.parse(saved);
94
+ }
95
+ } catch (error) {
96
+ console.warn("Failed to load messages from localStorage:", error);
97
+ }
98
+ return null;
99
+ }
100
+ clearMessagesFromStorage() {
101
+ try {
102
+ const storageKey = this.getStorageKey();
103
+ localStorage.removeItem(storageKey);
104
+ } catch (error) {
105
+ console.warn("Failed to clear messages from localStorage:", error);
106
+ }
107
+ }
57
108
  formatMessageContent(content) {
58
109
  const escapeHtml = (text) => {
59
110
  const div = document.createElement("div");
60
111
  div.textContent = text;
61
112
  return div.innerHTML;
62
113
  };
63
- let processedContent = content.replace(/(\d+\.\s+[^0-9]+?)(?=\s+\d+\.\s+|\s*$)/g, "$1\n");
114
+ let processedContent = content.replace(/([:\w])\s*(\d+\.\s+)/g, "$1\n$2");
115
+ processedContent = processedContent.replace(/(\d+\.\s+[^0-9]+?)(?=\s+\d+\.\s+|\s*$)/g, "$1\n");
64
116
  processedContent = processedContent.replace(/(-\s+[^-]+?)(?=\s+-\s+|\s*$)/g, "$1\n");
65
117
  const lines = processedContent.split("\n");
66
118
  let formattedContent = "";
67
119
  let inList = false;
68
120
  let listType = null;
121
+ let orderedListCounter = 1;
122
+ const getNextListType = (startIndex) => {
123
+ for (let j = startIndex + 1; j < lines.length; j++) {
124
+ const nextLine = lines[j].trim();
125
+ if (nextLine === "") continue;
126
+ if (nextLine.match(/^[-*•]\s+/)) return "ul";
127
+ if (nextLine.match(/^\d+\.\s+/)) return "ol";
128
+ return null;
129
+ }
130
+ return null;
131
+ };
69
132
  for (let i = 0; i < lines.length; i++) {
70
133
  const line = lines[i];
71
134
  const trimmedLine = line.trim();
72
135
  const unorderedMatch = trimmedLine.match(/^[-*•]\s+(.+)$/);
73
- const orderedMatch = trimmedLine.match(/^\d+\.\s+(.+)$/);
136
+ const orderedMatch = trimmedLine.match(/^(\d+)\.\s+(.+)$/);
74
137
  if (unorderedMatch) {
75
138
  if (!inList || listType !== "ul") {
76
139
  if (inList) formattedContent += listType === "ol" ? "</ol>" : "</ul>";
@@ -80,22 +143,40 @@ exports.AIChat = class AIChat extends lit.LitElement {
80
143
  }
81
144
  formattedContent += `<li>${escapeHtml(unorderedMatch[1])}</li>`;
82
145
  } else if (orderedMatch) {
146
+ const itemNumber = parseInt(orderedMatch[1], 10);
147
+ const itemText = orderedMatch[2];
83
148
  if (!inList || listType !== "ol") {
84
149
  if (inList) formattedContent += listType === "ol" ? "</ol>" : "</ul>";
85
- formattedContent += "<ol>";
150
+ if (itemNumber === 1) {
151
+ orderedListCounter = 1;
152
+ formattedContent += "<ol>";
153
+ } else {
154
+ formattedContent += `<ol start="${orderedListCounter}">`;
155
+ }
86
156
  inList = true;
87
157
  listType = "ol";
88
158
  }
89
- formattedContent += `<li>${escapeHtml(orderedMatch[1])}</li>`;
159
+ formattedContent += `<li value="${itemNumber}">${escapeHtml(itemText)}</li>`;
160
+ orderedListCounter = itemNumber + 1;
90
161
  } else {
91
- if (inList) {
92
- formattedContent += listType === "ol" ? "</ol>" : "</ul>";
93
- inList = false;
94
- listType = null;
95
- }
96
162
  if (trimmedLine === "") {
97
- formattedContent += "<br>";
163
+ const nextListType = getNextListType(i);
164
+ if (inList && nextListType === listType) {
165
+ formattedContent += '<li style="list-style: none; height: 0.5em;"></li>';
166
+ } else {
167
+ if (inList) {
168
+ formattedContent += listType === "ol" ? "</ol>" : "</ul>";
169
+ inList = false;
170
+ listType = null;
171
+ }
172
+ formattedContent += "<br>";
173
+ }
98
174
  } else {
175
+ if (inList) {
176
+ formattedContent += listType === "ol" ? "</ol>" : "</ul>";
177
+ inList = false;
178
+ listType = null;
179
+ }
99
180
  formattedContent += escapeHtml(line) + "\n";
100
181
  }
101
182
  }
@@ -107,20 +188,37 @@ exports.AIChat = class AIChat extends lit.LitElement {
107
188
  }
108
189
  connectedCallback() {
109
190
  super.connectedCallback();
191
+ const savedMessages = this.loadMessagesFromStorage();
110
192
  if (this.initialMessages && this.initialMessages.length > 0) {
111
193
  this.messages = [...this.initialMessages];
194
+ } else if (savedMessages && savedMessages.length > 0) {
195
+ this.messages = savedMessages;
196
+ } else if (this.welcomeMessage) {
197
+ const welcomeText = this.welcomeSubtitle ? `${this.welcomeMessage}
198
+
199
+ ${this.welcomeSubtitle}` : this.welcomeMessage;
200
+ this.messages = [{
201
+ id: "welcome-" + Date.now(),
202
+ role: "assistant",
203
+ content: welcomeText
204
+ }];
112
205
  }
113
206
  }
114
207
  updated(changedProperties) {
115
208
  super.updated(changedProperties);
116
209
  if (changedProperties.has("messages")) {
117
210
  this.scrollToBottom();
211
+ this.saveMessagesToStorage();
118
212
  }
119
213
  }
120
214
  scrollToBottom() {
121
- requestAnimationFrame(() => {
122
- this.messagesEndRef?.scrollIntoView({ behavior: "smooth" });
123
- });
215
+ setTimeout(() => {
216
+ const userMessages = this.shadowRoot?.querySelectorAll(".message.user");
217
+ if (userMessages && userMessages.length > 0) {
218
+ const lastUserMessage = userMessages[userMessages.length - 1];
219
+ lastUserMessage.scrollIntoView({ behavior: "smooth", block: "start" });
220
+ }
221
+ }, 100);
124
222
  }
125
223
  handleInput(e) {
126
224
  this.input = e.target.value;
@@ -164,7 +262,6 @@ exports.AIChat = class AIChat extends lit.LitElement {
164
262
  const data = await response.json();
165
263
  console.log("\u{1F50D} Raw API response:", data);
166
264
  let responseText = "No response from agent";
167
- let faqs = void 0;
168
265
  let suggestedQuestions = void 0;
169
266
  if (data && typeof data === "object" && data.response && typeof data.response === "string") {
170
267
  console.log("\u{1F4DD} data.response type:", typeof data.response);
@@ -173,18 +270,15 @@ exports.AIChat = class AIChat extends lit.LitElement {
173
270
  if (trimmedResponse.startsWith("{") || trimmedResponse.startsWith("[")) {
174
271
  console.log("\u{1F504} Detected stringified JSON, parsing...");
175
272
  try {
176
- let innerData = JSON.parse(data.response);
273
+ const innerData = JSON.parse(data.response);
177
274
  console.log("\u2705 Parsed inner data with JSON.parse");
178
275
  if (innerData && innerData.response && typeof innerData.response === "string") {
179
276
  responseText = innerData.response;
180
- faqs = innerData.faq_used || innerData.faqs_used || void 0;
181
277
  suggestedQuestions = innerData.suggested_follow_ups || innerData.suggested_questions || void 0;
182
278
  console.log("\u2705 Extracted text length:", responseText.length);
183
- console.log("\u2705 Extracted FAQs count:", faqs?.length || 0);
184
279
  console.log("\u2705 Extracted suggested questions count:", suggestedQuestions?.length || 0);
185
280
  } else {
186
281
  responseText = data.response;
187
- faqs = data.faq_used || data.faqs_used || void 0;
188
282
  suggestedQuestions = data.suggested_follow_ups || data.suggested_questions || void 0;
189
283
  }
190
284
  } catch (parseError) {
@@ -198,26 +292,6 @@ exports.AIChat = class AIChat extends lit.LitElement {
198
292
  console.error("\u274C Could not extract response");
199
293
  responseText = "Error: Could not parse response";
200
294
  }
201
- const faqsPattern = /"(?:faq_used|faqs_used)"\s*:\s*(\[[^\]]*\])/s;
202
- const faqsMatch = data.response.match(faqsPattern);
203
- if (faqsMatch) {
204
- try {
205
- faqs = JSON.parse(faqsMatch[1]);
206
- console.log("\u2705 Extracted FAQs, count:", faqs?.length || 0);
207
- } catch {
208
- console.log("\u26A0\uFE0F Could not parse FAQs, trying multiline...");
209
- const faqsMultiPattern = /"(?:faq_used|faqs_used)"\s*:\s*(\[[\s\S]*?\n\s*\])/;
210
- const faqsMultiMatch = data.response.match(faqsMultiPattern);
211
- if (faqsMultiMatch) {
212
- try {
213
- faqs = JSON.parse(faqsMultiMatch[1]);
214
- console.log("\u2705 Extracted multi-line FAQs, count:", faqs?.length || 0);
215
- } catch {
216
- faqs = void 0;
217
- }
218
- }
219
- }
220
- }
221
295
  const suggestedPattern = /"(?:suggested_follow_ups|suggested_questions)"\s*:\s*(\[[^\]]*\])/s;
222
296
  const suggestedMatch = data.response.match(suggestedPattern);
223
297
  if (suggestedMatch) {
@@ -242,7 +316,6 @@ exports.AIChat = class AIChat extends lit.LitElement {
242
316
  } else {
243
317
  console.log("\u{1F4C4} Direct text response (not JSON)");
244
318
  responseText = data.response;
245
- faqs = data.faq_used || data.faqs_used || void 0;
246
319
  suggestedQuestions = data.suggested_follow_ups || data.suggested_questions || void 0;
247
320
  }
248
321
  } else if (typeof data === "string") {
@@ -251,18 +324,16 @@ exports.AIChat = class AIChat extends lit.LitElement {
251
324
  } else if (data && typeof data === "object") {
252
325
  console.warn("\u26A0\uFE0F Unexpected format, using fallback");
253
326
  responseText = data.message || data.answer || "Error: Unexpected response format";
254
- faqs = data.faq_used || data.faqs_used || void 0;
255
327
  suggestedQuestions = data.suggested_follow_ups || data.suggested_questions || void 0;
256
328
  }
257
329
  console.log("\u{1F3AF} Final responseText length:", responseText.length);
258
330
  console.log("\u{1F3AF} Final responseText preview:", responseText.substring(0, 100));
259
- console.log("\u{1F3AF} Final FAQs:", faqs);
260
331
  console.log("\u{1F3AF} Final suggested questions:", suggestedQuestions);
261
332
  const assistantMessage = {
262
333
  id: (Date.now() + 1).toString(),
263
334
  role: "assistant",
264
335
  content: responseText,
265
- faqs,
336
+ // faqs: faqs, // Commented out - FAQ functionality disabled
266
337
  suggestedQuestions
267
338
  };
268
339
  this.messages = [...this.messages, assistantMessage];
@@ -308,32 +379,21 @@ Please check your API endpoint configuration.`
308
379
  <!-- Messages Area -->
309
380
  <div class="messages-area" style="--user-message-bg: ${this.userMessageBg}; --bot-message-bg: ${this.botMessageBg}; --primary-color: ${this.primaryColor}; --primary-color-light: ${primaryColorLight}; --primary-color-hover: ${this.primaryColorHover}; ${this.backgroundImageUrl ? `--background-image-url: url('${this.backgroundImageUrl}');` : ""}">
310
381
  <div class="messages-container">
311
- ${this.messages.length === 0 ? lit.html`
312
- <div class="empty-state">
313
- <div class="empty-state-avatar">
314
- ${this.botAvatarUrl ? lit.html`<img src="${this.botAvatarUrl}" alt="Bot" class="empty-state-avatar-image" />` : lit.html`<svg viewBox="0 0 24 24" fill="none" stroke="#9ca3af" stroke-width="2">
315
- <path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"></path>
316
- </svg>`}
317
- </div>
318
- <div class="empty-state-content">
319
- <p class="empty-state-message">${this.welcomeMessage}</p>
320
- ${this.welcomeSubtitle ? lit.html`<p class="empty-state-subtitle">${this.welcomeSubtitle}</p>` : ""}
321
- </div>
322
- </div>
323
- ` : ""}
324
-
325
382
  ${repeat_js.repeat(this.messages, (msg) => msg.id, (msg) => lit.html`
326
- <div class=${classMap_js.classMap({
383
+ <div
384
+ class=${classMap_js.classMap({
327
385
  message: true,
328
386
  user: msg.role === "user",
329
387
  assistant: msg.role === "assistant"
330
- })}>
388
+ })}
389
+ >
331
390
  <div class="avatar">
332
391
  ${msg.role === "user" ? "U" : this.botAvatarUrl ? lit.html`<img src="${this.botAvatarUrl}" alt="AI" class="avatar-image" />` : "AI"}
333
392
  </div>
334
393
  <div class="message-content">
335
394
  <div class="message-text">${unsafeHtml_js.unsafeHTML(this.formatMessageContent(msg.content))}</div>
336
- ${msg.role === "assistant" && msg.faqs && msg.faqs.length > 0 ? lit.html`
395
+ <!-- FAQ section - commented out for now -->
396
+ <!-- ${msg.role === "assistant" && msg.faqs && msg.faqs.length > 0 ? lit.html`
337
397
  <div class="faq-section">
338
398
  <p class="faq-title">Related FAQs:</p>
339
399
  <ul class="faq-list">
@@ -344,7 +404,7 @@ Please check your API endpoint configuration.`
344
404
  `)}
345
405
  </ul>
346
406
  </div>
347
- ` : ""}
407
+ ` : ""} -->
348
408
  ${msg.role === "assistant" && msg.suggestedQuestions && msg.suggestedQuestions.length > 0 ? lit.html`
349
409
  <div class="faq-section">
350
410
  <p class="faq-title">Suggested Questions:</p>
@@ -371,8 +431,6 @@ Please check your API endpoint configuration.`
371
431
  </div>
372
432
  </div>
373
433
  ` : ""}
374
-
375
- <div ${(el) => this.messagesEndRef = el}></div>
376
434
  </div>
377
435
  </div>
378
436
 
@@ -382,7 +440,7 @@ Please check your API endpoint configuration.`
382
440
  <input
383
441
  type="text"
384
442
  class="input-field"
385
- placeholder="Type your message..."
443
+ placeholder="Taip mesej anda..."
386
444
  .value=${this.input}
387
445
  @input=${this.handleInput}
388
446
  ?disabled=${this.isLoading}
@@ -424,7 +482,10 @@ Please check your API endpoint configuration.`
424
482
 
425
483
  <!-- Toggle Button -->
426
484
  <button
427
- class="widget-button"
485
+ class=${classMap_js.classMap({
486
+ "widget-button": true,
487
+ "widget-button-no-bg": !this.isOpen && !!this.widgetIconUrl
488
+ })}
428
489
  style="--primary-color: ${this.primaryColor}; --primary-color-light: ${primaryColorLight};"
429
490
  @click=${this.toggleWidget}
430
491
  aria-label=${this.isOpen ? "Close chat" : "Open chat"}
@@ -434,6 +495,8 @@ Please check your API endpoint configuration.`
434
495
  <line x1="18" y1="6" x2="6" y2="18"></line>
435
496
  <line x1="6" y1="6" x2="18" y2="18"></line>
436
497
  </svg>
498
+ ` : this.widgetIconUrl ? lit.html`
499
+ <img src="${this.widgetIconUrl}" alt="Chat" class="widget-button-icon" />
437
500
  ` : lit.html`
438
501
  <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
439
502
  <path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"></path>
@@ -496,12 +559,29 @@ exports.AIChat.styles = lit.css`
496
559
  box-shadow: 0 6px 20px rgba(65, 105, 225, 0.4);
497
560
  }
498
561
 
562
+ .widget-button-no-bg {
563
+ background: transparent;
564
+ box-shadow: none;
565
+ }
566
+
567
+ .widget-button-no-bg:hover {
568
+ background: transparent;
569
+ box-shadow: none;
570
+ transform: scale(1.1);
571
+ }
572
+
499
573
  .widget-button svg {
500
574
  width: 28px;
501
575
  height: 28px;
502
576
  color: white;
503
577
  }
504
578
 
579
+ .widget-button-icon {
580
+ width: auto;
581
+ height: auto;
582
+ object-fit: cover;
583
+ }
584
+
505
585
  .widget-window {
506
586
  position: absolute;
507
587
  bottom: 80px;
@@ -963,11 +1043,13 @@ exports.AIChat.styles = lit.css`
963
1043
  margin: 0.5rem 0;
964
1044
  padding-left: 1.5rem;
965
1045
  white-space: normal;
1046
+ list-style-position: outside;
966
1047
  }
967
1048
 
968
1049
  .message-text li {
969
1050
  margin: 0.25rem 0;
970
1051
  white-space: normal;
1052
+ display: list-item;
971
1053
  }
972
1054
 
973
1055
  .message-text ul {
@@ -976,6 +1058,12 @@ exports.AIChat.styles = lit.css`
976
1058
 
977
1059
  .message-text ol {
978
1060
  list-style-type: decimal;
1061
+ counter-reset: list-counter;
1062
+ }
1063
+
1064
+ .message-text ol li {
1065
+ display: list-item;
1066
+ list-style-type: decimal;
979
1067
  }
980
1068
 
981
1069
  .faq-section {
@@ -1034,7 +1122,8 @@ exports.AIChat.styles = lit.css`
1034
1122
  border-color: #3f3f46;
1035
1123
  }
1036
1124
 
1037
- .faq-item-static {
1125
+ /* FAQ static item styles - commented out for now */
1126
+ /* .faq-item-static {
1038
1127
  font-size: 0.875rem;
1039
1128
  color: #6B7280;
1040
1129
  padding: 0;
@@ -1045,7 +1134,7 @@ exports.AIChat.styles = lit.css`
1045
1134
 
1046
1135
  :host([theme="dark"]) .faq-item-static {
1047
1136
  color: #9CA3AF;
1048
- }
1137
+ } */
1049
1138
 
1050
1139
  .loading {
1051
1140
  display: flex;
@@ -1174,6 +1263,7 @@ exports.AIChat.properties = {
1174
1263
  mode: { type: String, reflect: true },
1175
1264
  initialMessages: { type: Array },
1176
1265
  botAvatarUrl: { type: String, attribute: "bot-avatar-url" },
1266
+ widgetIconUrl: { type: String, attribute: "widget-icon-url" },
1177
1267
  backgroundImageUrl: { type: String, attribute: "background-image-url" },
1178
1268
  widgetWidth: { type: String, attribute: "widget-width" },
1179
1269
  widgetHeight: { type: String, attribute: "widget-height" },