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