@a.izzuddin/ai-chat 0.2.6 → 0.2.7

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
@@ -11,19 +11,27 @@ A modern, customizable chat widget built with Lit web components. Features a cle
11
11
  - 🔧 **Easy Integration** - Simple HTML attributes for configuration
12
12
  - ⚡ **Built with Lit** - Fast, lightweight web component
13
13
  - 🎯 **TypeScript Support** - Full type safety
14
+ - 📝 **List Formatting** - Automatic rendering of bulleted and numbered lists
15
+ - 💡 **Suggested Questions** - Clickable follow-up questions for better UX
16
+ - 🔗 **Related FAQs** - Display related FAQ references
17
+ - 👋 **Customizable Welcome Message** - Set custom greeting with optional subtitle
14
18
 
15
19
  ## Quick Start
16
20
 
17
21
  ### Installation
18
22
 
19
23
  ```bash
20
- npm install
21
- npm run dev
24
+ npm install @a.izzuddin/ai-chat
22
25
  ```
23
26
 
24
27
  ### Basic Usage
25
28
 
29
+ **HTML:**
26
30
  ```html
31
+ <script type="module">
32
+ import '@a.izzuddin/ai-chat';
33
+ </script>
34
+
27
35
  <ai-chat
28
36
  api-url="https://your-api-endpoint.com"
29
37
  session-id="user-123"
@@ -31,6 +39,36 @@ npm run dev
31
39
  </ai-chat>
32
40
  ```
33
41
 
42
+ **React:**
43
+ ```jsx
44
+ import '@a.izzuddin/ai-chat';
45
+
46
+ function App() {
47
+ return (
48
+ <ai-chat
49
+ api-url="https://your-api-endpoint.com"
50
+ session-id="user-123"
51
+ title="AI Assistant"
52
+ />
53
+ );
54
+ }
55
+ ```
56
+
57
+ **Vue:**
58
+ ```vue
59
+ <template>
60
+ <ai-chat
61
+ api-url="https://your-api-endpoint.com"
62
+ session-id="user-123"
63
+ title="AI Assistant">
64
+ </ai-chat>
65
+ </template>
66
+
67
+ <script setup>
68
+ import '@a.izzuddin/ai-chat';
69
+ </script>
70
+ ```
71
+
34
72
  ## Configuration
35
73
 
36
74
  ### Display Modes
@@ -83,6 +121,17 @@ npm run dev
83
121
  </ai-chat>
84
122
  ```
85
123
 
124
+ #### Custom Welcome Message
125
+ ```html
126
+ <ai-chat
127
+ api-url="https://api.example.com"
128
+ session-id="user-123"
129
+ title="AI Assistant"
130
+ welcome-message="Hello! How may I assist you?"
131
+ welcome-subtitle="Ask me anything about our services">
132
+ </ai-chat>
133
+ ```
134
+
86
135
  ### Dark Mode
87
136
  ```html
88
137
  <ai-chat
@@ -110,6 +159,8 @@ npm run dev
110
159
  | `bot-message-bg` | string | '#F5F5F5' | Bot message background |
111
160
  | `bot-avatar-url` | string | '' | Custom bot avatar image |
112
161
  | `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 |
113
164
 
114
165
  ## API Integration
115
166
 
@@ -127,15 +178,51 @@ Expected response format:
127
178
  ```json
128
179
  {
129
180
  "response": "string",
130
- "faqs_used": [
181
+ "faq_used": [
131
182
  {
132
183
  "no.": "1",
133
- "question": "Related question?"
184
+ "question": "What is MySTI?"
134
185
  }
186
+ ],
187
+ "suggested_follow_ups": [
188
+ "What are the main objectives of the program?",
189
+ "How can companies apply?",
190
+ "Who is eligible for the MySTI logo?"
135
191
  ]
136
192
  }
137
193
  ```
138
194
 
195
+ **Supported field variations:**
196
+ - `faq_used` or `faqs_used` for related FAQs
197
+ - `suggested_follow_ups` or `suggested_questions` for clickable follow-up questions
198
+
199
+ ### Response Behavior
200
+
201
+ - **Related FAQs** - Displayed as non-clickable text references
202
+ - **Suggested Questions** - Displayed as clickable buttons that send the question when clicked
203
+ - **List Formatting** - Messages support automatic list rendering:
204
+ - Unordered lists: Lines starting with `-`, `*`, or `•`
205
+ - Ordered lists: Lines starting with `1.`, `2.`, etc.
206
+
207
+ ### List Formatting Example
208
+
209
+ Your API can return text with lists:
210
+
211
+ ```
212
+ MySTI is a government initiative. Key objectives include:
213
+
214
+ 1. Stimulating local industry growth
215
+ 2. Driving technology-based economic growth
216
+ 3. Creating job opportunities
217
+
218
+ Key features:
219
+ - Platform for applicants
220
+ - Database of approved goods
221
+ - Reference for procurement
222
+ ```
223
+
224
+ This will be automatically rendered as proper HTML lists.
225
+
139
226
  ## Events
140
227
 
141
228
  The component fires custom events you can listen to:
@@ -684,6 +684,26 @@
684
684
  "default": "'#F5F5F5'",
685
685
  "attribute": "bot-message-bg"
686
686
  },
687
+ {
688
+ "kind": "field",
689
+ "name": "welcomeMessage",
690
+ "privacy": "public",
691
+ "type": {
692
+ "text": "string"
693
+ },
694
+ "default": "'How can I help you today?'",
695
+ "attribute": "welcome-message"
696
+ },
697
+ {
698
+ "kind": "field",
699
+ "name": "welcomeSubtitle",
700
+ "privacy": "public",
701
+ "type": {
702
+ "text": "string"
703
+ },
704
+ "default": "''",
705
+ "attribute": "welcome-subtitle"
706
+ },
687
707
  {
688
708
  "kind": "field",
689
709
  "name": "messages",
@@ -758,6 +778,24 @@
758
778
  }
759
779
  ]
760
780
  },
781
+ {
782
+ "kind": "method",
783
+ "name": "formatMessageContent",
784
+ "privacy": "private",
785
+ "return": {
786
+ "type": {
787
+ "text": "string"
788
+ }
789
+ },
790
+ "parameters": [
791
+ {
792
+ "name": "content",
793
+ "type": {
794
+ "text": "string"
795
+ }
796
+ }
797
+ ]
798
+ },
761
799
  {
762
800
  "kind": "method",
763
801
  "name": "scrollToBottom",
@@ -943,6 +981,22 @@
943
981
  },
944
982
  "default": "'#F5F5F5'",
945
983
  "fieldName": "botMessageBg"
984
+ },
985
+ {
986
+ "name": "welcome-message",
987
+ "type": {
988
+ "text": "string"
989
+ },
990
+ "default": "'How can I help you today?'",
991
+ "fieldName": "welcomeMessage"
992
+ },
993
+ {
994
+ "name": "welcome-subtitle",
995
+ "type": {
996
+ "text": "string"
997
+ },
998
+ "default": "''",
999
+ "fieldName": "welcomeSubtitle"
946
1000
  }
947
1001
  ],
948
1002
  "superclass": {
package/dist/index.d.mts CHANGED
@@ -67,6 +67,8 @@ declare class AIChat extends LitElement {
67
67
  primaryColorHover: string;
68
68
  userMessageBg: string;
69
69
  botMessageBg: string;
70
+ welcomeMessage: string;
71
+ welcomeSubtitle: string;
70
72
  private messages;
71
73
  private input;
72
74
  private isLoading;
@@ -127,10 +129,19 @@ declare class AIChat extends LitElement {
127
129
  type: StringConstructor;
128
130
  attribute: string;
129
131
  };
132
+ welcomeMessage: {
133
+ type: StringConstructor;
134
+ attribute: string;
135
+ };
136
+ welcomeSubtitle: {
137
+ type: StringConstructor;
138
+ attribute: string;
139
+ };
130
140
  };
131
141
  constructor();
132
142
  private toggleWidget;
133
143
  private lightenColor;
144
+ private formatMessageContent;
134
145
  connectedCallback(): void;
135
146
  updated(changedProperties: PropertyValues): void;
136
147
  private scrollToBottom;
package/dist/index.d.ts CHANGED
@@ -67,6 +67,8 @@ declare class AIChat extends LitElement {
67
67
  primaryColorHover: string;
68
68
  userMessageBg: string;
69
69
  botMessageBg: string;
70
+ welcomeMessage: string;
71
+ welcomeSubtitle: string;
70
72
  private messages;
71
73
  private input;
72
74
  private isLoading;
@@ -127,10 +129,19 @@ declare class AIChat extends LitElement {
127
129
  type: StringConstructor;
128
130
  attribute: string;
129
131
  };
132
+ welcomeMessage: {
133
+ type: StringConstructor;
134
+ attribute: string;
135
+ };
136
+ welcomeSubtitle: {
137
+ type: StringConstructor;
138
+ attribute: string;
139
+ };
130
140
  };
131
141
  constructor();
132
142
  private toggleWidget;
133
143
  private lightenColor;
144
+ private formatMessageContent;
134
145
  connectedCallback(): void;
135
146
  updated(changedProperties: PropertyValues): void;
136
147
  private scrollToBottom;
package/dist/index.js CHANGED
@@ -4,6 +4,7 @@ var lit = require('lit');
4
4
  var decorators_js = require('lit/decorators.js');
5
5
  var repeat_js = require('lit/directives/repeat.js');
6
6
  var classMap_js = require('lit/directives/class-map.js');
7
+ var unsafeHtml_js = require('lit/directives/unsafe-html.js');
7
8
 
8
9
  var __defProp = Object.defineProperty;
9
10
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
@@ -15,7 +16,7 @@ var __decorateClass = (decorators, target, key, kind) => {
15
16
  if (kind && result) __defProp(target, key, result);
16
17
  return result;
17
18
  };
18
- var VERSION = "0.2.6";
19
+ var VERSION = "0.2.7";
19
20
  exports.AIChat = class AIChat extends lit.LitElement {
20
21
  constructor() {
21
22
  super();
@@ -33,6 +34,8 @@ exports.AIChat = class AIChat extends lit.LitElement {
33
34
  this.primaryColorHover = "#3457C7";
34
35
  this.userMessageBg = "#D6E4FF";
35
36
  this.botMessageBg = "#F5F5F5";
37
+ this.welcomeMessage = "How can I help you today?";
38
+ this.welcomeSubtitle = "";
36
39
  this.messages = [];
37
40
  this.input = "";
38
41
  this.isLoading = false;
@@ -51,6 +54,57 @@ exports.AIChat = class AIChat extends lit.LitElement {
51
54
  const newB = Math.min(255, Math.round(b + (255 - b) * (percent / 100)));
52
55
  return `#${newR.toString(16).padStart(2, "0")}${newG.toString(16).padStart(2, "0")}${newB.toString(16).padStart(2, "0")}`;
53
56
  }
57
+ formatMessageContent(content) {
58
+ const escapeHtml = (text) => {
59
+ const div = document.createElement("div");
60
+ div.textContent = text;
61
+ return div.innerHTML;
62
+ };
63
+ let processedContent = content.replace(/(\d+\.\s+[^0-9]+?)(?=\s+\d+\.\s+|\s*$)/g, "$1\n");
64
+ processedContent = processedContent.replace(/(-\s+[^-]+?)(?=\s+-\s+|\s*$)/g, "$1\n");
65
+ const lines = processedContent.split("\n");
66
+ let formattedContent = "";
67
+ let inList = false;
68
+ let listType = null;
69
+ for (let i = 0; i < lines.length; i++) {
70
+ const line = lines[i];
71
+ const trimmedLine = line.trim();
72
+ const unorderedMatch = trimmedLine.match(/^[-*•]\s+(.+)$/);
73
+ const orderedMatch = trimmedLine.match(/^\d+\.\s+(.+)$/);
74
+ if (unorderedMatch) {
75
+ if (!inList || listType !== "ul") {
76
+ if (inList) formattedContent += listType === "ol" ? "</ol>" : "</ul>";
77
+ formattedContent += "<ul>";
78
+ inList = true;
79
+ listType = "ul";
80
+ }
81
+ formattedContent += `<li>${escapeHtml(unorderedMatch[1])}</li>`;
82
+ } else if (orderedMatch) {
83
+ if (!inList || listType !== "ol") {
84
+ if (inList) formattedContent += listType === "ol" ? "</ol>" : "</ul>";
85
+ formattedContent += "<ol>";
86
+ inList = true;
87
+ listType = "ol";
88
+ }
89
+ formattedContent += `<li>${escapeHtml(orderedMatch[1])}</li>`;
90
+ } else {
91
+ if (inList) {
92
+ formattedContent += listType === "ol" ? "</ol>" : "</ul>";
93
+ inList = false;
94
+ listType = null;
95
+ }
96
+ if (trimmedLine === "") {
97
+ formattedContent += "<br>";
98
+ } else {
99
+ formattedContent += escapeHtml(line) + "\n";
100
+ }
101
+ }
102
+ }
103
+ if (inList) {
104
+ formattedContent += listType === "ol" ? "</ol>" : "</ul>";
105
+ }
106
+ return formattedContent;
107
+ }
54
108
  connectedCallback() {
55
109
  super.connectedCallback();
56
110
  if (this.initialMessages && this.initialMessages.length > 0) {
@@ -256,7 +310,15 @@ Please check your API endpoint configuration.`
256
310
  <div class="messages-container">
257
311
  ${this.messages.length === 0 ? lit.html`
258
312
  <div class="empty-state">
259
- <p>How can I help you today?</p>
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>
260
322
  </div>
261
323
  ` : ""}
262
324
 
@@ -270,13 +332,13 @@ Please check your API endpoint configuration.`
270
332
  ${msg.role === "user" ? "U" : this.botAvatarUrl ? lit.html`<img src="${this.botAvatarUrl}" alt="AI" class="avatar-image" />` : "AI"}
271
333
  </div>
272
334
  <div class="message-content">
273
- <p class="message-text">${msg.content}</p>
335
+ <div class="message-text">${unsafeHtml_js.unsafeHTML(this.formatMessageContent(msg.content))}</div>
274
336
  ${msg.role === "assistant" && msg.faqs && msg.faqs.length > 0 ? lit.html`
275
337
  <div class="faq-section">
276
338
  <p class="faq-title">Related FAQs:</p>
277
339
  <ul class="faq-list">
278
340
  ${msg.faqs.map((faq) => lit.html`
279
- <li class="faq-item" @click=${() => this.handleFAQClick(faq.question)}>
341
+ <li class="faq-item-static">
280
342
  ${faq.question}
281
343
  </li>
282
344
  `)}
@@ -576,7 +638,7 @@ exports.AIChat.styles = lit.css`
576
638
 
577
639
  .faq-item {
578
640
  font-size: 0.8125rem;
579
- padding: 0.375rem;
641
+ padding: 0;
580
642
  }
581
643
 
582
644
  .input-area {
@@ -756,18 +818,74 @@ exports.AIChat.styles = lit.css`
756
818
 
757
819
  .empty-state {
758
820
  text-align: center;
759
- color: #9ca3af;
760
821
  margin-top: 5rem;
822
+ display: flex;
823
+ flex-direction: column;
824
+ align-items: center;
825
+ gap: 1.5rem;
761
826
  }
762
827
 
763
828
  :host([theme="dark"]) .empty-state {
764
829
  color: #a1a1aa;
765
830
  }
766
831
 
767
- .empty-state p {
768
- font-size: 1.5rem;
769
- font-weight: 500;
832
+ .empty-state-avatar {
833
+ width: 5rem;
834
+ height: 5rem;
835
+ border-radius: 50%;
836
+ background: #E5E7EB;
837
+ display: flex;
838
+ align-items: center;
839
+ justify-content: center;
840
+ overflow: hidden;
841
+ }
842
+
843
+ :host([theme="dark"]) .empty-state-avatar {
844
+ background: #3f3f46;
845
+ }
846
+
847
+ .empty-state-avatar-image {
848
+ width: 100%;
849
+ height: 100%;
850
+ object-fit: cover;
851
+ }
852
+
853
+ .empty-state-avatar svg {
854
+ width: 3rem;
855
+ height: 3rem;
856
+ color: #9ca3af;
857
+ }
858
+
859
+ :host([theme="dark"]) .empty-state-avatar svg {
860
+ color: #6b7280;
861
+ }
862
+
863
+ .empty-state-content {
864
+ display: flex;
865
+ flex-direction: column;
866
+ gap: 0.5rem;
867
+ }
868
+
869
+ .empty-state-message {
870
+ font-size: 1.25rem;
871
+ font-weight: 600;
872
+ margin: 0;
873
+ color: #374151;
874
+ }
875
+
876
+ :host([theme="dark"]) .empty-state-message {
877
+ color: #f3f4f6;
878
+ }
879
+
880
+ .empty-state-subtitle {
881
+ font-size: 0.9375rem;
770
882
  margin: 0;
883
+ color: #6b7280;
884
+ max-width: 24rem;
885
+ }
886
+
887
+ :host([theme="dark"]) .empty-state-subtitle {
888
+ color: #9ca3af;
771
889
  }
772
890
 
773
891
  .message {
@@ -837,11 +955,32 @@ exports.AIChat.styles = lit.css`
837
955
  .message-text {
838
956
  white-space: pre-wrap;
839
957
  margin: 0;
958
+ word-wrap: break-word;
959
+ }
960
+
961
+ .message-text ul,
962
+ .message-text ol {
963
+ margin: 0.5rem 0;
964
+ padding-left: 1.5rem;
965
+ white-space: normal;
966
+ }
967
+
968
+ .message-text li {
969
+ margin: 0.25rem 0;
970
+ white-space: normal;
971
+ }
972
+
973
+ .message-text ul {
974
+ list-style-type: disc;
975
+ }
976
+
977
+ .message-text ol {
978
+ list-style-type: decimal;
840
979
  }
841
980
 
842
981
  .faq-section {
843
- margin-top: 1rem;
844
- padding-top: 1rem;
982
+ margin-top: 0.75rem;
983
+ padding-top: 0.75rem;
845
984
  border-top: 1px solid #d1d5db;
846
985
  }
847
986
 
@@ -853,7 +992,7 @@ exports.AIChat.styles = lit.css`
853
992
  font-size: 0.875rem;
854
993
  font-weight: 600;
855
994
  color: var(--primary-color, #3681D3);
856
- margin: 0 0 0.5rem 0;
995
+ margin: 0 0 0.375rem 0;
857
996
  }
858
997
 
859
998
  :host([theme="dark"]) .faq-title {
@@ -866,13 +1005,13 @@ exports.AIChat.styles = lit.css`
866
1005
  margin: 0;
867
1006
  display: flex;
868
1007
  flex-direction: column;
869
- gap: 0.5rem;
1008
+ gap: 0.375rem;
870
1009
  }
871
1010
 
872
1011
  .faq-item {
873
1012
  font-size: 0.875rem;
874
1013
  color: var(--primary-color, #3681D3);
875
- padding: 0.5rem 0.75rem;
1014
+ padding: 0;
876
1015
  border-radius: 0.5rem;
877
1016
  cursor: pointer;
878
1017
  transition: background-color 0.2s, color 0.2s;
@@ -895,6 +1034,19 @@ exports.AIChat.styles = lit.css`
895
1034
  border-color: #3f3f46;
896
1035
  }
897
1036
 
1037
+ .faq-item-static {
1038
+ font-size: 0.875rem;
1039
+ color: #6B7280;
1040
+ padding: 0;
1041
+ border-radius: 0.5rem;
1042
+ cursor: default;
1043
+ border: 1px solid transparent;
1044
+ }
1045
+
1046
+ :host([theme="dark"]) .faq-item-static {
1047
+ color: #9CA3AF;
1048
+ }
1049
+
898
1050
  .loading {
899
1051
  display: flex;
900
1052
  gap: 1rem;
@@ -1028,7 +1180,9 @@ exports.AIChat.properties = {
1028
1180
  primaryColor: { type: String, attribute: "primary-color" },
1029
1181
  primaryColorHover: { type: String, attribute: "primary-color-hover" },
1030
1182
  userMessageBg: { type: String, attribute: "user-message-bg" },
1031
- botMessageBg: { type: String, attribute: "bot-message-bg" }
1183
+ botMessageBg: { type: String, attribute: "bot-message-bg" },
1184
+ welcomeMessage: { type: String, attribute: "welcome-message" },
1185
+ welcomeSubtitle: { type: String, attribute: "welcome-subtitle" }
1032
1186
  };
1033
1187
  __decorateClass([
1034
1188
  decorators_js.state()