@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/README.md CHANGED
@@ -13,7 +13,7 @@ A modern, customizable chat widget built with Lit web components. Features a cle
13
13
  - 🎯 **TypeScript Support** - Full type safety
14
14
  - 📝 **List Formatting** - Automatic rendering of bulleted and numbered lists
15
15
  - 💡 **Suggested Questions** - Clickable follow-up questions for better UX
16
- - 🔗 **Related FAQs** - Display related FAQ references
16
+ <!-- - 🔗 **Related FAQs** - Display related FAQ references (commented out) -->
17
17
  - 👋 **Customizable Welcome Message** - Set custom greeting with optional subtitle
18
18
 
19
19
  ## Quick Start
@@ -117,21 +117,28 @@ import '@a.izzuddin/ai-chat';
117
117
  session-id="user-123"
118
118
  title="AI Assistant"
119
119
  bot-avatar-url="/path/to/avatar.png"
120
+ widget-icon-url="/path/to/widget-icon.png"
120
121
  background-image-url="/path/to/background.png">
121
122
  </ai-chat>
122
123
  ```
123
124
 
125
+ **Note:** The `widget-icon-url` sets a custom icon for the floating widget button (only applies in widget mode).
126
+
124
127
  #### Custom Welcome Message
128
+ The welcome message appears as the first assistant message in the chat instead of a large empty state.
129
+
125
130
  ```html
126
131
  <ai-chat
127
132
  api-url="https://api.example.com"
128
133
  session-id="user-123"
129
134
  title="AI Assistant"
130
- welcome-message="Hello! How may I assist you?"
131
- welcome-subtitle="Ask me anything about our services">
135
+ welcome-message="Hai, bagaimana saya boleh bantu?"
136
+ welcome-subtitle="Sila tanya tentang permohonan atau apa-apa berkaitan MySTI">
132
137
  </ai-chat>
133
138
  ```
134
139
 
140
+ The welcome message and subtitle will be combined and displayed as a regular chat message from the assistant.
141
+
135
142
  ### Dark Mode
136
143
  ```html
137
144
  <ai-chat
@@ -158,9 +165,10 @@ import '@a.izzuddin/ai-chat';
158
165
  | `user-message-bg` | string | '#D6E4FF' | User message background |
159
166
  | `bot-message-bg` | string | '#F5F5F5' | Bot message background |
160
167
  | `bot-avatar-url` | string | '' | Custom bot avatar image |
168
+ | `widget-icon-url` | string | '' | Custom widget button icon (widget mode only) |
161
169
  | `background-image-url` | string | '' | Chat background image |
162
- | `welcome-message` | string | 'How can I help you today?' | Initial welcome message shown when chat is empty |
163
- | `welcome-subtitle` | string | '' | Optional subtitle text shown below welcome message |
170
+ | `welcome-message` | string | 'How can I help you today?' | Initial message from assistant (appears as first chat message) |
171
+ | `welcome-subtitle` | string | '' | Optional subtitle added to welcome message |
164
172
 
165
173
  ## API Integration
166
174
 
@@ -178,12 +186,6 @@ Expected response format:
178
186
  ```json
179
187
  {
180
188
  "response": "string",
181
- "faq_used": [
182
- {
183
- "no.": "1",
184
- "question": "What is MySTI?"
185
- }
186
- ],
187
189
  "suggested_follow_ups": [
188
190
  "What are the main objectives of the program?",
189
191
  "How can companies apply?",
@@ -192,13 +194,26 @@ Expected response format:
192
194
  }
193
195
  ```
194
196
 
197
+ <!-- FAQ functionality - commented out for now
198
+ {
199
+ "response": "string",
200
+ "faq_used": [
201
+ {
202
+ "no.": "1",
203
+ "question": "What is MySTI?"
204
+ }
205
+ ],
206
+ "suggested_follow_ups": [...]
207
+ }
208
+ -->
209
+
195
210
  **Supported field variations:**
196
- - `faq_used` or `faqs_used` for related FAQs
211
+ <!-- - `faq_used` or `faqs_used` for related FAQs (commented out) -->
197
212
  - `suggested_follow_ups` or `suggested_questions` for clickable follow-up questions
198
213
 
199
214
  ### Response Behavior
200
215
 
201
- - **Related FAQs** - Displayed as non-clickable text references
216
+ <!-- - **Related FAQs** - Displayed as non-clickable text references (commented out) -->
202
217
  - **Suggested Questions** - Displayed as clickable buttons that send the question when clicked
203
218
  - **List Formatting** - Messages support automatic list rendering:
204
219
  - Unordered lists: Lines starting with `-`, `*`, or `•`
@@ -221,14 +221,6 @@
221
221
  "name": "Message",
222
222
  "module": "./components/ai-chat"
223
223
  }
224
- },
225
- {
226
- "kind": "js",
227
- "name": "FAQ",
228
- "declaration": {
229
- "name": "FAQ",
230
- "module": "./components/ai-chat"
231
- }
232
224
  }
233
225
  ]
234
226
  },
@@ -614,6 +606,16 @@
614
606
  "default": "''",
615
607
  "attribute": "bot-avatar-url"
616
608
  },
609
+ {
610
+ "kind": "field",
611
+ "name": "widgetIconUrl",
612
+ "privacy": "public",
613
+ "type": {
614
+ "text": "string"
615
+ },
616
+ "default": "''",
617
+ "attribute": "widget-icon-url"
618
+ },
617
619
  {
618
620
  "kind": "field",
619
621
  "name": "backgroundImageUrl",
@@ -741,17 +743,20 @@
741
743
  "default": "false"
742
744
  },
743
745
  {
744
- "kind": "field",
745
- "name": "messagesEndRef",
746
- "type": {
747
- "text": "HTMLDivElement | undefined"
748
- },
746
+ "kind": "method",
747
+ "name": "toggleWidget",
749
748
  "privacy": "private"
750
749
  },
751
750
  {
752
751
  "kind": "method",
753
- "name": "toggleWidget",
754
- "privacy": "private"
752
+ "name": "clearChat",
753
+ "privacy": "public",
754
+ "return": {
755
+ "type": {
756
+ "text": "void"
757
+ }
758
+ },
759
+ "description": "Clear all chat messages and reset to welcome message"
755
760
  },
756
761
  {
757
762
  "kind": "method",
@@ -778,6 +783,46 @@
778
783
  }
779
784
  ]
780
785
  },
786
+ {
787
+ "kind": "method",
788
+ "name": "getStorageKey",
789
+ "privacy": "private",
790
+ "return": {
791
+ "type": {
792
+ "text": "string"
793
+ }
794
+ }
795
+ },
796
+ {
797
+ "kind": "method",
798
+ "name": "saveMessagesToStorage",
799
+ "privacy": "private",
800
+ "return": {
801
+ "type": {
802
+ "text": "void"
803
+ }
804
+ }
805
+ },
806
+ {
807
+ "kind": "method",
808
+ "name": "loadMessagesFromStorage",
809
+ "privacy": "private",
810
+ "return": {
811
+ "type": {
812
+ "text": "Message[] | null"
813
+ }
814
+ }
815
+ },
816
+ {
817
+ "kind": "method",
818
+ "name": "clearMessagesFromStorage",
819
+ "privacy": "private",
820
+ "return": {
821
+ "type": {
822
+ "text": "void"
823
+ }
824
+ }
825
+ },
781
826
  {
782
827
  "kind": "method",
783
828
  "name": "formatMessageContent",
@@ -926,6 +971,14 @@
926
971
  "default": "''",
927
972
  "fieldName": "botAvatarUrl"
928
973
  },
974
+ {
975
+ "name": "widget-icon-url",
976
+ "type": {
977
+ "text": "string"
978
+ },
979
+ "default": "''",
980
+ "fieldName": "widgetIconUrl"
981
+ },
929
982
  {
930
983
  "name": "background-image-url",
931
984
  "type": {
@@ -1035,7 +1088,7 @@
1035
1088
  "name": "AIChat",
1036
1089
  "parameters": [
1037
1090
  {
1038
- "name": "{\n apiUrl,\n sessionId = \"default-session\",\n title = \"My AI Agent\",\n initialMessages = [],\n className,\n onMessageSent,\n onResponseReceived,\n onError,\n}",
1091
+ "name": "{\r\n apiUrl,\r\n sessionId = \"default-session\",\r\n title = \"My AI Agent\",\r\n initialMessages = [],\r\n className,\r\n onMessageSent,\r\n onResponseReceived,\r\n onError,\r\n}",
1039
1092
  "type": {
1040
1093
  "text": "AIChatProps"
1041
1094
  }
package/dist/index.d.mts CHANGED
@@ -60,6 +60,7 @@ declare class AIChat extends LitElement {
60
60
  mode: 'fullscreen' | 'widget';
61
61
  initialMessages: Message[];
62
62
  botAvatarUrl: string;
63
+ widgetIconUrl: string;
63
64
  backgroundImageUrl: string;
64
65
  widgetWidth: string;
65
66
  widgetHeight: string;
@@ -73,7 +74,6 @@ declare class AIChat extends LitElement {
73
74
  private input;
74
75
  private isLoading;
75
76
  private isOpen;
76
- private messagesEndRef?;
77
77
  static properties: {
78
78
  apiUrl: {
79
79
  type: StringConstructor;
@@ -101,6 +101,10 @@ declare class AIChat extends LitElement {
101
101
  type: StringConstructor;
102
102
  attribute: string;
103
103
  };
104
+ widgetIconUrl: {
105
+ type: StringConstructor;
106
+ attribute: string;
107
+ };
104
108
  backgroundImageUrl: {
105
109
  type: StringConstructor;
106
110
  attribute: string;
@@ -140,7 +144,16 @@ declare class AIChat extends LitElement {
140
144
  };
141
145
  constructor();
142
146
  private toggleWidget;
147
+ /**
148
+ * Clear all chat messages and reset to welcome message
149
+ * @public
150
+ */
151
+ clearChat(): void;
143
152
  private lightenColor;
153
+ private getStorageKey;
154
+ private saveMessagesToStorage;
155
+ private loadMessagesFromStorage;
156
+ private clearMessagesFromStorage;
144
157
  private formatMessageContent;
145
158
  connectedCallback(): void;
146
159
  updated(changedProperties: PropertyValues): void;
@@ -157,4 +170,4 @@ declare global {
157
170
  }
158
171
  }
159
172
 
160
- export { AIChat, type FAQ, type Message };
173
+ export { AIChat, type Message };
package/dist/index.d.ts CHANGED
@@ -60,6 +60,7 @@ declare class AIChat extends LitElement {
60
60
  mode: 'fullscreen' | 'widget';
61
61
  initialMessages: Message[];
62
62
  botAvatarUrl: string;
63
+ widgetIconUrl: string;
63
64
  backgroundImageUrl: string;
64
65
  widgetWidth: string;
65
66
  widgetHeight: string;
@@ -73,7 +74,6 @@ declare class AIChat extends LitElement {
73
74
  private input;
74
75
  private isLoading;
75
76
  private isOpen;
76
- private messagesEndRef?;
77
77
  static properties: {
78
78
  apiUrl: {
79
79
  type: StringConstructor;
@@ -101,6 +101,10 @@ declare class AIChat extends LitElement {
101
101
  type: StringConstructor;
102
102
  attribute: string;
103
103
  };
104
+ widgetIconUrl: {
105
+ type: StringConstructor;
106
+ attribute: string;
107
+ };
104
108
  backgroundImageUrl: {
105
109
  type: StringConstructor;
106
110
  attribute: string;
@@ -140,7 +144,16 @@ declare class AIChat extends LitElement {
140
144
  };
141
145
  constructor();
142
146
  private toggleWidget;
147
+ /**
148
+ * Clear all chat messages and reset to welcome message
149
+ * @public
150
+ */
151
+ clearChat(): void;
143
152
  private lightenColor;
153
+ private getStorageKey;
154
+ private saveMessagesToStorage;
155
+ private loadMessagesFromStorage;
156
+ private clearMessagesFromStorage;
144
157
  private formatMessageContent;
145
158
  connectedCallback(): void;
146
159
  updated(changedProperties: PropertyValues): void;
@@ -157,4 +170,4 @@ declare global {
157
170
  }
158
171
  }
159
172
 
160
- export { AIChat, type FAQ, type Message };
173
+ export { AIChat, type Message };
package/dist/index.js CHANGED
@@ -16,7 +16,7 @@ var __decorateClass = (decorators, target, key, kind) => {
16
16
  if (kind && result) __defProp(target, key, result);
17
17
  return result;
18
18
  };
19
- var VERSION = "0.2.8";
19
+ var VERSION = "0.2.10";
20
20
  exports.AIChat = class AIChat extends lit.LitElement {
21
21
  constructor() {
22
22
  super();
@@ -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(/([^\n])\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;
@@ -173,7 +271,7 @@ exports.AIChat = class AIChat extends lit.LitElement {
173
271
  if (trimmedResponse.startsWith("{") || trimmedResponse.startsWith("[")) {
174
272
  console.log("\u{1F504} Detected stringified JSON, parsing...");
175
273
  try {
176
- let innerData = JSON.parse(data.response);
274
+ const innerData = JSON.parse(data.response);
177
275
  console.log("\u2705 Parsed inner data with JSON.parse");
178
276
  if (innerData && innerData.response && typeof innerData.response === "string") {
179
277
  responseText = innerData.response;
@@ -308,26 +406,14 @@ Please check your API endpoint configuration.`
308
406
  <!-- Messages Area -->
309
407
  <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
408
  <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
409
  ${repeat_js.repeat(this.messages, (msg) => msg.id, (msg) => lit.html`
326
- <div class=${classMap_js.classMap({
410
+ <div
411
+ class=${classMap_js.classMap({
327
412
  message: true,
328
413
  user: msg.role === "user",
329
414
  assistant: msg.role === "assistant"
330
- })}>
415
+ })}
416
+ >
331
417
  <div class="avatar">
332
418
  ${msg.role === "user" ? "U" : this.botAvatarUrl ? lit.html`<img src="${this.botAvatarUrl}" alt="AI" class="avatar-image" />` : "AI"}
333
419
  </div>
@@ -371,8 +457,6 @@ Please check your API endpoint configuration.`
371
457
  </div>
372
458
  </div>
373
459
  ` : ""}
374
-
375
- <div ${(el) => this.messagesEndRef = el}></div>
376
460
  </div>
377
461
  </div>
378
462
 
@@ -382,7 +466,7 @@ Please check your API endpoint configuration.`
382
466
  <input
383
467
  type="text"
384
468
  class="input-field"
385
- placeholder="Type your message..."
469
+ placeholder="Taip mesej anda..."
386
470
  .value=${this.input}
387
471
  @input=${this.handleInput}
388
472
  ?disabled=${this.isLoading}
@@ -424,7 +508,10 @@ Please check your API endpoint configuration.`
424
508
 
425
509
  <!-- Toggle Button -->
426
510
  <button
427
- class="widget-button"
511
+ class=${classMap_js.classMap({
512
+ "widget-button": true,
513
+ "widget-button-no-bg": !this.isOpen && !!this.widgetIconUrl
514
+ })}
428
515
  style="--primary-color: ${this.primaryColor}; --primary-color-light: ${primaryColorLight};"
429
516
  @click=${this.toggleWidget}
430
517
  aria-label=${this.isOpen ? "Close chat" : "Open chat"}
@@ -434,6 +521,8 @@ Please check your API endpoint configuration.`
434
521
  <line x1="18" y1="6" x2="6" y2="18"></line>
435
522
  <line x1="6" y1="6" x2="18" y2="18"></line>
436
523
  </svg>
524
+ ` : this.widgetIconUrl ? lit.html`
525
+ <img src="${this.widgetIconUrl}" alt="Chat" class="widget-button-icon" />
437
526
  ` : lit.html`
438
527
  <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
439
528
  <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 +585,29 @@ exports.AIChat.styles = lit.css`
496
585
  box-shadow: 0 6px 20px rgba(65, 105, 225, 0.4);
497
586
  }
498
587
 
588
+ .widget-button-no-bg {
589
+ background: transparent;
590
+ box-shadow: none;
591
+ }
592
+
593
+ .widget-button-no-bg:hover {
594
+ background: transparent;
595
+ box-shadow: none;
596
+ transform: scale(1.1);
597
+ }
598
+
499
599
  .widget-button svg {
500
600
  width: 28px;
501
601
  height: 28px;
502
602
  color: white;
503
603
  }
504
604
 
605
+ .widget-button-icon {
606
+ width: auto;
607
+ height: auto;
608
+ object-fit: cover;
609
+ }
610
+
505
611
  .widget-window {
506
612
  position: absolute;
507
613
  bottom: 80px;
@@ -963,11 +1069,13 @@ exports.AIChat.styles = lit.css`
963
1069
  margin: 0.5rem 0;
964
1070
  padding-left: 1.5rem;
965
1071
  white-space: normal;
1072
+ list-style-position: outside;
966
1073
  }
967
1074
 
968
1075
  .message-text li {
969
1076
  margin: 0.25rem 0;
970
1077
  white-space: normal;
1078
+ display: list-item;
971
1079
  }
972
1080
 
973
1081
  .message-text ul {
@@ -976,6 +1084,12 @@ exports.AIChat.styles = lit.css`
976
1084
 
977
1085
  .message-text ol {
978
1086
  list-style-type: decimal;
1087
+ counter-reset: list-counter;
1088
+ }
1089
+
1090
+ .message-text ol li {
1091
+ display: list-item;
1092
+ list-style-type: decimal;
979
1093
  }
980
1094
 
981
1095
  .faq-section {
@@ -1034,7 +1148,8 @@ exports.AIChat.styles = lit.css`
1034
1148
  border-color: #3f3f46;
1035
1149
  }
1036
1150
 
1037
- .faq-item-static {
1151
+ /* FAQ static item styles - commented out for now */
1152
+ /* .faq-item-static {
1038
1153
  font-size: 0.875rem;
1039
1154
  color: #6B7280;
1040
1155
  padding: 0;
@@ -1045,7 +1160,7 @@ exports.AIChat.styles = lit.css`
1045
1160
 
1046
1161
  :host([theme="dark"]) .faq-item-static {
1047
1162
  color: #9CA3AF;
1048
- }
1163
+ } */
1049
1164
 
1050
1165
  .loading {
1051
1166
  display: flex;
@@ -1174,6 +1289,7 @@ exports.AIChat.properties = {
1174
1289
  mode: { type: String, reflect: true },
1175
1290
  initialMessages: { type: Array },
1176
1291
  botAvatarUrl: { type: String, attribute: "bot-avatar-url" },
1292
+ widgetIconUrl: { type: String, attribute: "widget-icon-url" },
1177
1293
  backgroundImageUrl: { type: String, attribute: "background-image-url" },
1178
1294
  widgetWidth: { type: String, attribute: "widget-width" },
1179
1295
  widgetHeight: { type: String, attribute: "widget-height" },