@a.izzuddin/ai-chat 0.1.1

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 ADDED
@@ -0,0 +1,422 @@
1
+ import { css, LitElement, html } from 'lit';
2
+ import { state, customElement } from 'lit/decorators.js';
3
+ import { repeat } from 'lit/directives/repeat.js';
4
+ import { classMap } from 'lit/directives/class-map.js';
5
+
6
+ var __defProp = Object.defineProperty;
7
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
8
+ var __decorateClass = (decorators, target, key, kind) => {
9
+ var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc(target, key) : target;
10
+ for (var i = decorators.length - 1, decorator; i >= 0; i--)
11
+ if (decorator = decorators[i])
12
+ result = (kind ? decorator(target, key, result) : decorator(result)) || result;
13
+ if (kind && result) __defProp(target, key, result);
14
+ return result;
15
+ };
16
+ var AIChat = class extends LitElement {
17
+ constructor() {
18
+ super();
19
+ this.apiUrl = "";
20
+ this.sessionId = "default-session";
21
+ this.chatTitle = "My AI Agent";
22
+ this.theme = "light";
23
+ this.initialMessages = [];
24
+ this.messages = [];
25
+ this.input = "";
26
+ this.isLoading = false;
27
+ }
28
+ connectedCallback() {
29
+ super.connectedCallback();
30
+ if (this.initialMessages && this.initialMessages.length > 0) {
31
+ this.messages = [...this.initialMessages];
32
+ }
33
+ }
34
+ updated(changedProperties) {
35
+ super.updated(changedProperties);
36
+ if (changedProperties.has("messages")) {
37
+ this.scrollToBottom();
38
+ }
39
+ }
40
+ scrollToBottom() {
41
+ requestAnimationFrame(() => {
42
+ this.messagesEndRef?.scrollIntoView({ behavior: "smooth" });
43
+ });
44
+ }
45
+ handleInput(e) {
46
+ this.input = e.target.value;
47
+ }
48
+ async handleSubmit(e) {
49
+ e.preventDefault();
50
+ if (!this.input.trim() || this.isLoading || !this.apiUrl) return;
51
+ const userMessage = {
52
+ id: Date.now().toString(),
53
+ role: "user",
54
+ content: this.input.trim()
55
+ };
56
+ this.messages = [...this.messages, userMessage];
57
+ const questionText = this.input.trim();
58
+ this.input = "";
59
+ this.isLoading = true;
60
+ this.dispatchEvent(new CustomEvent("message-sent", {
61
+ detail: userMessage,
62
+ bubbles: true,
63
+ composed: true
64
+ }));
65
+ try {
66
+ const response = await fetch(`${this.apiUrl}/ask`, {
67
+ method: "POST",
68
+ headers: { "Content-Type": "application/json" },
69
+ body: JSON.stringify({
70
+ session_id: this.sessionId,
71
+ question: questionText
72
+ })
73
+ });
74
+ if (!response.ok) {
75
+ const errorText = await response.text();
76
+ throw new Error(`Backend error: ${response.status} ${errorText}`);
77
+ }
78
+ const data = await response.json();
79
+ const assistantMessage = {
80
+ id: (Date.now() + 1).toString(),
81
+ role: "assistant",
82
+ content: data.response || "No response from agent"
83
+ };
84
+ this.messages = [...this.messages, assistantMessage];
85
+ this.dispatchEvent(new CustomEvent("response-received", {
86
+ detail: assistantMessage,
87
+ bubbles: true,
88
+ composed: true
89
+ }));
90
+ } catch (err) {
91
+ console.error("Backend connection failed:", err);
92
+ const errorMessage = {
93
+ id: (Date.now() + 1).toString(),
94
+ role: "assistant",
95
+ content: `Error: ${err.message}
96
+
97
+ Please check your API endpoint configuration.`
98
+ };
99
+ this.messages = [...this.messages, errorMessage];
100
+ this.dispatchEvent(new CustomEvent("error", {
101
+ detail: err,
102
+ bubbles: true,
103
+ composed: true
104
+ }));
105
+ } finally {
106
+ this.isLoading = false;
107
+ }
108
+ }
109
+ render() {
110
+ return html`
111
+ <!-- Header -->
112
+ <div class="header">
113
+ <div class="header-content">
114
+ <h1 class="title">${this.chatTitle}</h1>
115
+ </div>
116
+ </div>
117
+
118
+ <!-- Messages Area -->
119
+ <div class="messages-area">
120
+ <div class="messages-container">
121
+ ${this.messages.length === 0 ? html`
122
+ <div class="empty-state">
123
+ <p>How can I help you today?</p>
124
+ </div>
125
+ ` : ""}
126
+
127
+ ${repeat(this.messages, (msg) => msg.id, (msg) => html`
128
+ <div class=${classMap({
129
+ message: true,
130
+ user: msg.role === "user",
131
+ assistant: msg.role === "assistant"
132
+ })}>
133
+ <div class="avatar">
134
+ ${msg.role === "user" ? "U" : "AI"}
135
+ </div>
136
+ <div class="message-content">
137
+ <p class="message-text">${msg.content}</p>
138
+ </div>
139
+ </div>
140
+ `)}
141
+
142
+ ${this.isLoading ? html`
143
+ <div class="loading">
144
+ <div class="avatar">AI</div>
145
+ <div class="message-content">
146
+ <div class="spinner"></div>
147
+ </div>
148
+ </div>
149
+ ` : ""}
150
+
151
+ <div ${(el) => this.messagesEndRef = el}></div>
152
+ </div>
153
+ </div>
154
+
155
+ <!-- Input Area -->
156
+ <div class="input-area">
157
+ <form class="input-form" @submit=${this.handleSubmit}>
158
+ <input
159
+ type="text"
160
+ class="input-field"
161
+ placeholder="Type your message..."
162
+ .value=${this.input}
163
+ @input=${this.handleInput}
164
+ ?disabled=${this.isLoading}
165
+ />
166
+ <button
167
+ type="submit"
168
+ class="send-button"
169
+ ?disabled=${this.isLoading || !this.input.trim()}
170
+ aria-label="Send message"
171
+ >
172
+ ${this.isLoading ? html`
173
+ <div class="spinner" style="border-color: #fff; border-top-color: transparent;"></div>
174
+ ` : html`
175
+ <svg class="send-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
176
+ <line x1="22" y1="2" x2="11" y2="13"></line>
177
+ <polygon points="22 2 15 22 11 13 2 9 22 2"></polygon>
178
+ </svg>
179
+ `}
180
+ </button>
181
+ </form>
182
+ </div>
183
+ `;
184
+ }
185
+ };
186
+ AIChat.styles = css`
187
+ :host {
188
+ display: flex;
189
+ flex-direction: column;
190
+ height: 100vh;
191
+ font-family: system-ui, -apple-system, sans-serif;
192
+ background: #fafafa;
193
+ color: #09090b;
194
+ }
195
+
196
+ :host([theme="dark"]) {
197
+ background: #000;
198
+ color: #fafafa;
199
+ }
200
+
201
+ .header {
202
+ border-bottom: 1px solid #e4e4e7;
203
+ background: #fff;
204
+ padding: 1rem;
205
+ }
206
+
207
+ :host([theme="dark"]) .header {
208
+ border-bottom-color: #27272a;
209
+ background: #18181b;
210
+ }
211
+
212
+ .header-content {
213
+ max-width: 56rem;
214
+ margin: 0 auto;
215
+ }
216
+
217
+ .title {
218
+ font-size: 1.25rem;
219
+ font-weight: 600;
220
+ margin: 0;
221
+ }
222
+
223
+ .messages-area {
224
+ flex: 1;
225
+ overflow-y: auto;
226
+ padding: 1.5rem 1rem;
227
+ }
228
+
229
+ .messages-container {
230
+ max-width: 56rem;
231
+ margin: 0 auto;
232
+ display: flex;
233
+ flex-direction: column;
234
+ gap: 1.5rem;
235
+ }
236
+
237
+ .empty-state {
238
+ text-align: center;
239
+ color: #71717a;
240
+ margin-top: 5rem;
241
+ }
242
+
243
+ :host([theme="dark"]) .empty-state {
244
+ color: #a1a1aa;
245
+ }
246
+
247
+ .empty-state p {
248
+ font-size: 1.5rem;
249
+ font-weight: 500;
250
+ margin: 0;
251
+ }
252
+
253
+ .message {
254
+ display: flex;
255
+ gap: 1rem;
256
+ }
257
+
258
+ .message.user {
259
+ flex-direction: row-reverse;
260
+ }
261
+
262
+ .avatar {
263
+ width: 2.5rem;
264
+ height: 2.5rem;
265
+ border-radius: 9999px;
266
+ background: #e4e4e7;
267
+ display: flex;
268
+ align-items: center;
269
+ justify-content: center;
270
+ flex-shrink: 0;
271
+ font-weight: 500;
272
+ font-size: 0.875rem;
273
+ }
274
+
275
+ :host([theme="dark"]) .avatar {
276
+ background: #3f3f46;
277
+ }
278
+
279
+ .message-content {
280
+ max-width: 36rem;
281
+ padding: 0.75rem 1rem;
282
+ border-radius: 1rem;
283
+ }
284
+
285
+ .message.user .message-content {
286
+ background: #2563eb;
287
+ color: #fff;
288
+ }
289
+
290
+ .message.assistant .message-content {
291
+ background: #fff;
292
+ border: 1px solid #e4e4e7;
293
+ color: #09090b;
294
+ }
295
+
296
+ :host([theme="dark"]) .message.assistant .message-content {
297
+ background: #27272a;
298
+ border-color: #3f3f46;
299
+ color: #fafafa;
300
+ }
301
+
302
+ .message-text {
303
+ white-space: pre-wrap;
304
+ margin: 0;
305
+ }
306
+
307
+ .loading {
308
+ display: flex;
309
+ gap: 1rem;
310
+ }
311
+
312
+ .spinner {
313
+ display: inline-block;
314
+ width: 1.25rem;
315
+ height: 1.25rem;
316
+ border: 2px solid #e4e4e7;
317
+ border-top-color: #71717a;
318
+ border-radius: 50%;
319
+ animation: spin 1s linear infinite;
320
+ }
321
+
322
+ @keyframes spin {
323
+ to { transform: rotate(360deg); }
324
+ }
325
+
326
+ .input-area {
327
+ border-top: 1px solid #e4e4e7;
328
+ background: #fff;
329
+ padding: 1rem;
330
+ }
331
+
332
+ :host([theme="dark"]) .input-area {
333
+ border-top-color: #27272a;
334
+ background: #000;
335
+ }
336
+
337
+ .input-form {
338
+ max-width: 56rem;
339
+ margin: 0 auto;
340
+ display: flex;
341
+ gap: 0.75rem;
342
+ }
343
+
344
+ .input-field {
345
+ flex: 1;
346
+ height: 3rem;
347
+ padding: 0 1rem;
348
+ border: 1px solid #e4e4e7;
349
+ border-radius: 0.5rem;
350
+ font-size: 1rem;
351
+ font-family: inherit;
352
+ background: #fff;
353
+ color: #09090b;
354
+ }
355
+
356
+ :host([theme="dark"]) .input-field {
357
+ border-color: #3f3f46;
358
+ background: #18181b;
359
+ color: #fafafa;
360
+ }
361
+
362
+ .input-field:focus {
363
+ outline: 2px solid #2563eb;
364
+ outline-offset: 2px;
365
+ }
366
+
367
+ .input-field:disabled {
368
+ opacity: 0.5;
369
+ cursor: not-allowed;
370
+ }
371
+
372
+ .send-button {
373
+ width: 3rem;
374
+ height: 3rem;
375
+ border-radius: 9999px;
376
+ border: none;
377
+ background: #2563eb;
378
+ color: #fff;
379
+ cursor: pointer;
380
+ display: flex;
381
+ align-items: center;
382
+ justify-content: center;
383
+ transition: background 0.2s;
384
+ }
385
+
386
+ .send-button:hover:not(:disabled) {
387
+ background: #1d4ed8;
388
+ }
389
+
390
+ .send-button:disabled {
391
+ opacity: 0.5;
392
+ cursor: not-allowed;
393
+ }
394
+
395
+ .send-icon {
396
+ width: 1.25rem;
397
+ height: 1.25rem;
398
+ }
399
+ `;
400
+ AIChat.properties = {
401
+ apiUrl: { type: String, attribute: "api-url" },
402
+ sessionId: { type: String, attribute: "session-id" },
403
+ chatTitle: { type: String, attribute: "title" },
404
+ theme: { type: String },
405
+ initialMessages: { type: Array }
406
+ };
407
+ __decorateClass([
408
+ state()
409
+ ], AIChat.prototype, "messages", 2);
410
+ __decorateClass([
411
+ state()
412
+ ], AIChat.prototype, "input", 2);
413
+ __decorateClass([
414
+ state()
415
+ ], AIChat.prototype, "isLoading", 2);
416
+ AIChat = __decorateClass([
417
+ customElement("ai-chat")
418
+ ], AIChat);
419
+
420
+ export { AIChat };
421
+ //# sourceMappingURL=index.mjs.map
422
+ //# sourceMappingURL=index.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/components/ai-chat.ts"],"names":[],"mappings":"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","file":"index.mjs","sourcesContent":["import { LitElement, html, css, PropertyValues } from 'lit';\nimport { customElement, property, state } from 'lit/decorators.js';\nimport { repeat } from 'lit/directives/repeat.js';\nimport { classMap } from 'lit/directives/class-map.js';\n\nexport interface Message {\n id: string;\n role: 'user' | 'assistant';\n content: string;\n}\n\n/**\n * AI Chat Web Component\n *\n * @fires message-sent - Fired when user sends a message\n * @fires response-received - Fired when AI responds\n * @fires error - Fired when an error occurs\n *\n * @example\n * ```html\n * <ai-chat\n * api-url=\"https://api.example.com\"\n * session-id=\"user-123\"\n * title=\"My AI Assistant\">\n * </ai-chat>\n * ```\n */\n@customElement('ai-chat')\nexport class AIChat extends LitElement {\n static styles = css`\n :host {\n display: flex;\n flex-direction: column;\n height: 100vh;\n font-family: system-ui, -apple-system, sans-serif;\n background: #fafafa;\n color: #09090b;\n }\n\n :host([theme=\"dark\"]) {\n background: #000;\n color: #fafafa;\n }\n\n .header {\n border-bottom: 1px solid #e4e4e7;\n background: #fff;\n padding: 1rem;\n }\n\n :host([theme=\"dark\"]) .header {\n border-bottom-color: #27272a;\n background: #18181b;\n }\n\n .header-content {\n max-width: 56rem;\n margin: 0 auto;\n }\n\n .title {\n font-size: 1.25rem;\n font-weight: 600;\n margin: 0;\n }\n\n .messages-area {\n flex: 1;\n overflow-y: auto;\n padding: 1.5rem 1rem;\n }\n\n .messages-container {\n max-width: 56rem;\n margin: 0 auto;\n display: flex;\n flex-direction: column;\n gap: 1.5rem;\n }\n\n .empty-state {\n text-align: center;\n color: #71717a;\n margin-top: 5rem;\n }\n\n :host([theme=\"dark\"]) .empty-state {\n color: #a1a1aa;\n }\n\n .empty-state p {\n font-size: 1.5rem;\n font-weight: 500;\n margin: 0;\n }\n\n .message {\n display: flex;\n gap: 1rem;\n }\n\n .message.user {\n flex-direction: row-reverse;\n }\n\n .avatar {\n width: 2.5rem;\n height: 2.5rem;\n border-radius: 9999px;\n background: #e4e4e7;\n display: flex;\n align-items: center;\n justify-content: center;\n flex-shrink: 0;\n font-weight: 500;\n font-size: 0.875rem;\n }\n\n :host([theme=\"dark\"]) .avatar {\n background: #3f3f46;\n }\n\n .message-content {\n max-width: 36rem;\n padding: 0.75rem 1rem;\n border-radius: 1rem;\n }\n\n .message.user .message-content {\n background: #2563eb;\n color: #fff;\n }\n\n .message.assistant .message-content {\n background: #fff;\n border: 1px solid #e4e4e7;\n color: #09090b;\n }\n\n :host([theme=\"dark\"]) .message.assistant .message-content {\n background: #27272a;\n border-color: #3f3f46;\n color: #fafafa;\n }\n\n .message-text {\n white-space: pre-wrap;\n margin: 0;\n }\n\n .loading {\n display: flex;\n gap: 1rem;\n }\n\n .spinner {\n display: inline-block;\n width: 1.25rem;\n height: 1.25rem;\n border: 2px solid #e4e4e7;\n border-top-color: #71717a;\n border-radius: 50%;\n animation: spin 1s linear infinite;\n }\n\n @keyframes spin {\n to { transform: rotate(360deg); }\n }\n\n .input-area {\n border-top: 1px solid #e4e4e7;\n background: #fff;\n padding: 1rem;\n }\n\n :host([theme=\"dark\"]) .input-area {\n border-top-color: #27272a;\n background: #000;\n }\n\n .input-form {\n max-width: 56rem;\n margin: 0 auto;\n display: flex;\n gap: 0.75rem;\n }\n\n .input-field {\n flex: 1;\n height: 3rem;\n padding: 0 1rem;\n border: 1px solid #e4e4e7;\n border-radius: 0.5rem;\n font-size: 1rem;\n font-family: inherit;\n background: #fff;\n color: #09090b;\n }\n\n :host([theme=\"dark\"]) .input-field {\n border-color: #3f3f46;\n background: #18181b;\n color: #fafafa;\n }\n\n .input-field:focus {\n outline: 2px solid #2563eb;\n outline-offset: 2px;\n }\n\n .input-field:disabled {\n opacity: 0.5;\n cursor: not-allowed;\n }\n\n .send-button {\n width: 3rem;\n height: 3rem;\n border-radius: 9999px;\n border: none;\n background: #2563eb;\n color: #fff;\n cursor: pointer;\n display: flex;\n align-items: center;\n justify-content: center;\n transition: background 0.2s;\n }\n\n .send-button:hover:not(:disabled) {\n background: #1d4ed8;\n }\n\n .send-button:disabled {\n opacity: 0.5;\n cursor: not-allowed;\n }\n\n .send-icon {\n width: 1.25rem;\n height: 1.25rem;\n }\n `;\n\n declare apiUrl: string;\n declare sessionId: string;\n declare chatTitle: string;\n declare theme: 'light' | 'dark';\n declare initialMessages: Message[];\n\n @state()\n private declare messages: Message[];\n\n @state()\n private declare input: string;\n\n @state()\n private declare isLoading: boolean;\n\n private messagesEndRef?: HTMLDivElement;\n\n static override properties = {\n apiUrl: { type: String, attribute: 'api-url' },\n sessionId: { type: String, attribute: 'session-id' },\n chatTitle: { type: String, attribute: 'title' },\n theme: { type: String },\n initialMessages: { type: Array },\n };\n\n constructor() {\n super();\n this.apiUrl = '';\n this.sessionId = 'default-session';\n this.chatTitle = 'My AI Agent';\n this.theme = 'light';\n this.initialMessages = [];\n this.messages = [];\n this.input = '';\n this.isLoading = false;\n }\n\n connectedCallback() {\n super.connectedCallback();\n if (this.initialMessages && this.initialMessages.length > 0) {\n this.messages = [...this.initialMessages];\n }\n }\n\n updated(changedProperties: PropertyValues) {\n super.updated(changedProperties);\n if (changedProperties.has('messages')) {\n this.scrollToBottom();\n }\n }\n\n private scrollToBottom() {\n requestAnimationFrame(() => {\n this.messagesEndRef?.scrollIntoView({ behavior: 'smooth' });\n });\n }\n\n private handleInput(e: Event) {\n this.input = (e.target as HTMLInputElement).value;\n }\n\n private async handleSubmit(e: Event) {\n e.preventDefault();\n\n if (!this.input.trim() || this.isLoading || !this.apiUrl) return;\n\n const userMessage: Message = {\n id: Date.now().toString(),\n role: 'user',\n content: this.input.trim(),\n };\n\n this.messages = [...this.messages, userMessage];\n const questionText = this.input.trim();\n this.input = '';\n this.isLoading = true;\n\n // Dispatch message-sent event\n this.dispatchEvent(new CustomEvent('message-sent', {\n detail: userMessage,\n bubbles: true,\n composed: true,\n }));\n\n try {\n const response = await fetch(`${this.apiUrl}/ask`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({\n session_id: this.sessionId,\n question: questionText,\n }),\n });\n\n if (!response.ok) {\n const errorText = await response.text();\n throw new Error(`Backend error: ${response.status} ${errorText}`);\n }\n\n const data = await response.json();\n\n const assistantMessage: Message = {\n id: (Date.now() + 1).toString(),\n role: 'assistant',\n content: data.response || 'No response from agent',\n };\n\n this.messages = [...this.messages, assistantMessage];\n\n // Dispatch response-received event\n this.dispatchEvent(new CustomEvent('response-received', {\n detail: assistantMessage,\n bubbles: true,\n composed: true,\n }));\n } catch (err: any) {\n console.error('Backend connection failed:', err);\n\n const errorMessage: Message = {\n id: (Date.now() + 1).toString(),\n role: 'assistant',\n content: `Error: ${err.message}\\n\\nPlease check your API endpoint configuration.`,\n };\n\n this.messages = [...this.messages, errorMessage];\n\n // Dispatch error event\n this.dispatchEvent(new CustomEvent('error', {\n detail: err,\n bubbles: true,\n composed: true,\n }));\n } finally {\n this.isLoading = false;\n }\n }\n\n render() {\n return html`\n <!-- Header -->\n <div class=\"header\">\n <div class=\"header-content\">\n <h1 class=\"title\">${this.chatTitle}</h1>\n </div>\n </div>\n\n <!-- Messages Area -->\n <div class=\"messages-area\">\n <div class=\"messages-container\">\n ${this.messages.length === 0 ? html`\n <div class=\"empty-state\">\n <p>How can I help you today?</p>\n </div>\n ` : ''}\n\n ${repeat(this.messages, (msg) => msg.id, (msg) => html`\n <div class=${classMap({\n message: true,\n user: msg.role === 'user',\n assistant: msg.role === 'assistant'\n })}>\n <div class=\"avatar\">\n ${msg.role === 'user' ? 'U' : 'AI'}\n </div>\n <div class=\"message-content\">\n <p class=\"message-text\">${msg.content}</p>\n </div>\n </div>\n `)}\n\n ${this.isLoading ? html`\n <div class=\"loading\">\n <div class=\"avatar\">AI</div>\n <div class=\"message-content\">\n <div class=\"spinner\"></div>\n </div>\n </div>\n ` : ''}\n\n <div ${(el: Element) => this.messagesEndRef = el as HTMLDivElement}></div>\n </div>\n </div>\n\n <!-- Input Area -->\n <div class=\"input-area\">\n <form class=\"input-form\" @submit=${this.handleSubmit}>\n <input\n type=\"text\"\n class=\"input-field\"\n placeholder=\"Type your message...\"\n .value=${this.input}\n @input=${this.handleInput}\n ?disabled=${this.isLoading}\n />\n <button\n type=\"submit\"\n class=\"send-button\"\n ?disabled=${this.isLoading || !this.input.trim()}\n aria-label=\"Send message\"\n >\n ${this.isLoading ? html`\n <div class=\"spinner\" style=\"border-color: #fff; border-top-color: transparent;\"></div>\n ` : html`\n <svg class=\"send-icon\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\">\n <line x1=\"22\" y1=\"2\" x2=\"11\" y2=\"13\"></line>\n <polygon points=\"22 2 15 22 11 13 2 9 22 2\"></polygon>\n </svg>\n `}\n </button>\n </form>\n </div>\n `;\n }\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n 'ai-chat': AIChat;\n }\n}\n"]}
package/package.json ADDED
@@ -0,0 +1,82 @@
1
+ {
2
+ "name": "@a.izzuddin/ai-chat",
3
+ "version": "0.1.1",
4
+ "description": "A framework-agnostic AI chat web component. Works with React, Vue, Svelte, Angular, and vanilla JavaScript.",
5
+ "main": "./dist/index.js",
6
+ "module": "./dist/index.mjs",
7
+ "types": "./dist/index.d.ts",
8
+ "files": [
9
+ "dist",
10
+ "custom-elements.json"
11
+ ],
12
+ "exports": {
13
+ ".": {
14
+ "types": "./dist/index.d.ts",
15
+ "import": "./dist/index.mjs",
16
+ "require": "./dist/index.js"
17
+ }
18
+ },
19
+ "customElements": "custom-elements.json",
20
+ "keywords": [
21
+ "ai",
22
+ "chat",
23
+ "chatbot",
24
+ "web-components",
25
+ "custom-elements",
26
+ "lit",
27
+ "typescript",
28
+ "framework-agnostic",
29
+ "react",
30
+ "vue",
31
+ "svelte",
32
+ "angular"
33
+ ],
34
+ "author": "a.izzuddin",
35
+ "license": "MIT",
36
+ "repository": {
37
+ "type": "git",
38
+ "url": "git+https://github.com/aiddin/ai-chat.git"
39
+ },
40
+ "scripts": {
41
+ "dev": "next dev",
42
+ "dev:demo": "next dev",
43
+ "build": "tsup && npm run analyze",
44
+ "analyze": "custom-elements-manifest analyze --litelement",
45
+ "build:demo": "next build",
46
+ "start": "next start",
47
+ "lint": "eslint",
48
+ "test": "playwright test",
49
+ "test:ui": "playwright test --ui",
50
+ "test:debug": "playwright test --debug",
51
+ "prepublishOnly": "npm run build",
52
+ "tauri": "tauri",
53
+ "tauri:dev": "tauri dev",
54
+ "tauri:build": "tauri build"
55
+ },
56
+ "dependencies": {
57
+ "lit": "^3.3.1"
58
+ },
59
+ "devDependencies": {
60
+ "@custom-elements-manifest/analyzer": "^0.11.0",
61
+ "@playwright/test": "^1.57.0",
62
+ "@shadcn/ui": "^0.0.4",
63
+ "@tailwindcss/postcss": "^4",
64
+ "@tauri-apps/cli": "^2.9.4",
65
+ "@types/node": "^20",
66
+ "@types/react": "^19.2.7",
67
+ "@types/react-dom": "^19.2.3",
68
+ "autoprefixer": "^10.4.22",
69
+ "eslint": "^9",
70
+ "eslint-config-next": "16.0.5",
71
+ "markdown-it": "^14.1.0",
72
+ "next": "16.0.5",
73
+ "playwright": "^1.57.0",
74
+ "postcss": "^8.5.6",
75
+ "react": "19.2.0",
76
+ "react-dom": "19.2.0",
77
+ "tailwindcss": "^4.1.17",
78
+ "tsup": "^8.5.1",
79
+ "tw-animate-css": "^1.4.0",
80
+ "typescript": "^5"
81
+ }
82
+ }