@a.izzuddin/ai-chat 0.2.8 → 0.2.10

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.8";
17
+ var VERSION = "0.2.10";
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(/([^\n])\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;
@@ -171,7 +269,7 @@ var AIChat = class extends LitElement {
171
269
  if (trimmedResponse.startsWith("{") || trimmedResponse.startsWith("[")) {
172
270
  console.log("\u{1F504} Detected stringified JSON, parsing...");
173
271
  try {
174
- let innerData = JSON.parse(data.response);
272
+ const innerData = JSON.parse(data.response);
175
273
  console.log("\u2705 Parsed inner data with JSON.parse");
176
274
  if (innerData && innerData.response && typeof innerData.response === "string") {
177
275
  responseText = innerData.response;
@@ -306,26 +404,14 @@ Please check your API endpoint configuration.`
306
404
  <!-- Messages Area -->
307
405
  <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
406
  <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
407
  ${repeat(this.messages, (msg) => msg.id, (msg) => html`
324
- <div class=${classMap({
408
+ <div
409
+ class=${classMap({
325
410
  message: true,
326
411
  user: msg.role === "user",
327
412
  assistant: msg.role === "assistant"
328
- })}>
413
+ })}
414
+ >
329
415
  <div class="avatar">
330
416
  ${msg.role === "user" ? "U" : this.botAvatarUrl ? html`<img src="${this.botAvatarUrl}" alt="AI" class="avatar-image" />` : "AI"}
331
417
  </div>
@@ -369,8 +455,6 @@ Please check your API endpoint configuration.`
369
455
  </div>
370
456
  </div>
371
457
  ` : ""}
372
-
373
- <div ${(el) => this.messagesEndRef = el}></div>
374
458
  </div>
375
459
  </div>
376
460
 
@@ -380,7 +464,7 @@ Please check your API endpoint configuration.`
380
464
  <input
381
465
  type="text"
382
466
  class="input-field"
383
- placeholder="Type your message..."
467
+ placeholder="Taip mesej anda..."
384
468
  .value=${this.input}
385
469
  @input=${this.handleInput}
386
470
  ?disabled=${this.isLoading}
@@ -422,7 +506,10 @@ Please check your API endpoint configuration.`
422
506
 
423
507
  <!-- Toggle Button -->
424
508
  <button
425
- class="widget-button"
509
+ class=${classMap({
510
+ "widget-button": true,
511
+ "widget-button-no-bg": !this.isOpen && !!this.widgetIconUrl
512
+ })}
426
513
  style="--primary-color: ${this.primaryColor}; --primary-color-light: ${primaryColorLight};"
427
514
  @click=${this.toggleWidget}
428
515
  aria-label=${this.isOpen ? "Close chat" : "Open chat"}
@@ -432,6 +519,8 @@ Please check your API endpoint configuration.`
432
519
  <line x1="18" y1="6" x2="6" y2="18"></line>
433
520
  <line x1="6" y1="6" x2="18" y2="18"></line>
434
521
  </svg>
522
+ ` : this.widgetIconUrl ? html`
523
+ <img src="${this.widgetIconUrl}" alt="Chat" class="widget-button-icon" />
435
524
  ` : html`
436
525
  <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
437
526
  <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 +583,29 @@ AIChat.styles = css`
494
583
  box-shadow: 0 6px 20px rgba(65, 105, 225, 0.4);
495
584
  }
496
585
 
586
+ .widget-button-no-bg {
587
+ background: transparent;
588
+ box-shadow: none;
589
+ }
590
+
591
+ .widget-button-no-bg:hover {
592
+ background: transparent;
593
+ box-shadow: none;
594
+ transform: scale(1.1);
595
+ }
596
+
497
597
  .widget-button svg {
498
598
  width: 28px;
499
599
  height: 28px;
500
600
  color: white;
501
601
  }
502
602
 
603
+ .widget-button-icon {
604
+ width: auto;
605
+ height: auto;
606
+ object-fit: cover;
607
+ }
608
+
503
609
  .widget-window {
504
610
  position: absolute;
505
611
  bottom: 80px;
@@ -961,11 +1067,13 @@ AIChat.styles = css`
961
1067
  margin: 0.5rem 0;
962
1068
  padding-left: 1.5rem;
963
1069
  white-space: normal;
1070
+ list-style-position: outside;
964
1071
  }
965
1072
 
966
1073
  .message-text li {
967
1074
  margin: 0.25rem 0;
968
1075
  white-space: normal;
1076
+ display: list-item;
969
1077
  }
970
1078
 
971
1079
  .message-text ul {
@@ -974,6 +1082,12 @@ AIChat.styles = css`
974
1082
 
975
1083
  .message-text ol {
976
1084
  list-style-type: decimal;
1085
+ counter-reset: list-counter;
1086
+ }
1087
+
1088
+ .message-text ol li {
1089
+ display: list-item;
1090
+ list-style-type: decimal;
977
1091
  }
978
1092
 
979
1093
  .faq-section {
@@ -1032,7 +1146,8 @@ AIChat.styles = css`
1032
1146
  border-color: #3f3f46;
1033
1147
  }
1034
1148
 
1035
- .faq-item-static {
1149
+ /* FAQ static item styles - commented out for now */
1150
+ /* .faq-item-static {
1036
1151
  font-size: 0.875rem;
1037
1152
  color: #6B7280;
1038
1153
  padding: 0;
@@ -1043,7 +1158,7 @@ AIChat.styles = css`
1043
1158
 
1044
1159
  :host([theme="dark"]) .faq-item-static {
1045
1160
  color: #9CA3AF;
1046
- }
1161
+ } */
1047
1162
 
1048
1163
  .loading {
1049
1164
  display: flex;
@@ -1172,6 +1287,7 @@ AIChat.properties = {
1172
1287
  mode: { type: String, reflect: true },
1173
1288
  initialMessages: { type: Array },
1174
1289
  botAvatarUrl: { type: String, attribute: "bot-avatar-url" },
1290
+ widgetIconUrl: { type: String, attribute: "widget-icon-url" },
1175
1291
  backgroundImageUrl: { type: String, attribute: "background-image-url" },
1176
1292
  widgetWidth: { type: String, attribute: "widget-width" },
1177
1293
  widgetHeight: { type: String, attribute: "widget-height" },