@kayro-ia/widget 1.0.0

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Kairo
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,52 @@
1
+ # @kayro-ia/widget
2
+
3
+ Embeddable AI chat widget for your product — powered by [Kairo](https://kayro.com.ar).
4
+
5
+ Add a fully functional AI assistant to your app in 3 lines of code.
6
+
7
+ ## Installation
8
+
9
+ ```bash
10
+ npm install @kayro-ia/widget
11
+ ```
12
+
13
+ ## Quick start
14
+
15
+ ```tsx
16
+ import { AIWidget } from '@kayro-ia/widget'
17
+
18
+ export default function App() {
19
+ return (
20
+ <AIWidget
21
+ apiKey="your_api_key"
22
+ orchestratorUrl="https://kayro.com.ar"
23
+ />
24
+ )
25
+ }
26
+ ```
27
+
28
+ ## Props
29
+
30
+ | Prop | Type | Required | Default | Description |
31
+ |------|------|----------|---------|-------------|
32
+ | `apiKey` | `string` | ✅ | — | Your Kairo API key |
33
+ | `orchestratorUrl` | `string` | ✅ | — | Kairo orchestrator base URL |
34
+ | `title` | `string` | | `"Asistente"` | Chat window header title |
35
+ | `placeholder` | `string` | | `"Escribí tu mensaje..."` | Input placeholder |
36
+ | `primaryColor` | `string` | | `"#6366f1"` | Accent color (bubble + user messages) |
37
+ | `position` | `"bottom-right" \| "bottom-left"` | | `"bottom-right"` | Widget position |
38
+ | `initialOpen` | `boolean` | | `false` | Open the chat window on load |
39
+ | `zIndex` | `number` | | `9999` | CSS z-index |
40
+ | `userToken` | `string` | | — | End-user auth token (forwarded to your MCP server) |
41
+ | `authHeaders` | `Record<string, string>` | | — | Extra headers forwarded to your MCP server |
42
+ | `userId` | `string` | | — | End-user ID (for analytics) |
43
+ | `userName` | `string` | | — | End-user display name |
44
+
45
+ ## Requirements
46
+
47
+ - React 18+
48
+ - Modern browser (Chrome 92+, Firefox 90+, Safari 15+)
49
+
50
+ ## License
51
+
52
+ MIT
@@ -0,0 +1,27 @@
1
+ import * as react_jsx_runtime from 'react/jsx-runtime';
2
+
3
+ interface Message {
4
+ id: string;
5
+ role: 'user' | 'assistant';
6
+ content: string;
7
+ status: 'sending' | 'streaming' | 'done' | 'error';
8
+ tokensUsed?: number;
9
+ }
10
+ interface WidgetConfig {
11
+ apiKey: string;
12
+ orchestratorUrl?: string;
13
+ userToken?: string;
14
+ authHeaders?: Record<string, string>;
15
+ userId?: string;
16
+ userName?: string;
17
+ title?: string;
18
+ placeholder?: string;
19
+ primaryColor?: string;
20
+ position?: 'bottom-right' | 'bottom-left';
21
+ initialOpen?: boolean;
22
+ zIndex?: number;
23
+ }
24
+
25
+ declare function AIWidget(props: WidgetConfig): react_jsx_runtime.JSX.Element;
26
+
27
+ export { AIWidget, type Message, type WidgetConfig };
package/dist/index.js ADDED
@@ -0,0 +1,717 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/index.tsx
21
+ var index_exports = {};
22
+ __export(index_exports, {
23
+ AIWidget: () => AIWidget
24
+ });
25
+ module.exports = __toCommonJS(index_exports);
26
+
27
+ // src/AIWidget.tsx
28
+ var import_react4 = require("react");
29
+
30
+ // src/store/chatStore.ts
31
+ var import_zustand = require("zustand");
32
+ var useChatStore = (0, import_zustand.create)((set) => ({
33
+ messages: [],
34
+ isOpen: false,
35
+ isStreaming: false,
36
+ conversationId: crypto.randomUUID(),
37
+ addMessage: (msg) => set((state) => ({ messages: [...state.messages, msg] })),
38
+ updateLastAssistantMessage: (delta) => set((state) => {
39
+ const messages = [...state.messages];
40
+ const lastIdx = messages.length - 1;
41
+ if (lastIdx < 0 || messages[lastIdx].role !== "assistant") return state;
42
+ messages[lastIdx] = {
43
+ ...messages[lastIdx],
44
+ content: messages[lastIdx].content + delta
45
+ };
46
+ return { messages };
47
+ }),
48
+ finalizeLastAssistantMessage: (tokensUsed) => set((state) => {
49
+ const messages = [...state.messages];
50
+ const lastIdx = messages.length - 1;
51
+ if (lastIdx < 0 || messages[lastIdx].role !== "assistant") return state;
52
+ messages[lastIdx] = {
53
+ ...messages[lastIdx],
54
+ status: "done",
55
+ tokensUsed
56
+ };
57
+ return { messages };
58
+ }),
59
+ setLastAssistantError: () => set((state) => {
60
+ const messages = [...state.messages];
61
+ const lastIdx = messages.length - 1;
62
+ if (lastIdx < 0 || messages[lastIdx].role !== "assistant") return state;
63
+ messages[lastIdx] = {
64
+ ...messages[lastIdx],
65
+ status: "error"
66
+ };
67
+ return { messages };
68
+ }),
69
+ setStreaming: (v) => set({ isStreaming: v }),
70
+ setOpen: (v) => set({ isOpen: v }),
71
+ clearMessages: () => set({ messages: [] })
72
+ }));
73
+
74
+ // src/components/ChatBubble.tsx
75
+ var import_jsx_runtime = (
76
+ // Close (×) icon
77
+ require("react/jsx-runtime")
78
+ );
79
+ function ChatBubble({ isOpen, onClick, primaryColor, position, zIndex }) {
80
+ const positionStyle = position === "bottom-right" ? { right: 24, bottom: 24 } : { left: 24, bottom: 24 };
81
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
82
+ "button",
83
+ {
84
+ onClick,
85
+ "aria-label": isOpen ? "Cerrar chat" : "Abrir chat",
86
+ style: {
87
+ position: "fixed",
88
+ ...positionStyle,
89
+ width: 56,
90
+ height: 56,
91
+ borderRadius: "50%",
92
+ backgroundColor: primaryColor,
93
+ border: "none",
94
+ cursor: "pointer",
95
+ display: "flex",
96
+ alignItems: "center",
97
+ justifyContent: "center",
98
+ boxShadow: "0 4px 20px rgba(0,0,0,0.2)",
99
+ zIndex,
100
+ transition: "transform 0.2s ease, box-shadow 0.2s ease",
101
+ color: "#ffffff",
102
+ padding: 0
103
+ },
104
+ onMouseEnter: (e) => {
105
+ e.currentTarget.style.transform = "scale(1.08)";
106
+ e.currentTarget.style.boxShadow = "0 6px 24px rgba(0,0,0,0.28)";
107
+ },
108
+ onMouseLeave: (e) => {
109
+ e.currentTarget.style.transform = "scale(1)";
110
+ e.currentTarget.style.boxShadow = "0 4px 20px rgba(0,0,0,0.2)";
111
+ },
112
+ children: isOpen ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
113
+ "svg",
114
+ {
115
+ width: "22",
116
+ height: "22",
117
+ viewBox: "0 0 24 24",
118
+ fill: "none",
119
+ stroke: "currentColor",
120
+ strokeWidth: "2.5",
121
+ strokeLinecap: "round",
122
+ strokeLinejoin: "round",
123
+ children: [
124
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("line", { x1: "18", y1: "6", x2: "6", y2: "18" }),
125
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("line", { x1: "6", y1: "6", x2: "18", y2: "18" })
126
+ ]
127
+ }
128
+ ) : (
129
+ // Chat bubble icon
130
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
131
+ "svg",
132
+ {
133
+ width: "24",
134
+ height: "24",
135
+ viewBox: "0 0 24 24",
136
+ fill: "currentColor",
137
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M20 2H4c-1.1 0-2 .9-2 2v18l4-4h14c1.1 0 2-.9 2-2V4c0-1.1-.9-2-2-2zm-2 12H6v-2h12v2zm0-3H6V9h12v2zm0-3H6V6h12v2z" })
138
+ }
139
+ )
140
+ )
141
+ }
142
+ );
143
+ }
144
+
145
+ // src/hooks/useChat.ts
146
+ function normalizeUrl(base) {
147
+ return base.endsWith("/") ? base.slice(0, -1) : base;
148
+ }
149
+ function useChat() {
150
+ const {
151
+ messages,
152
+ isStreaming,
153
+ conversationId,
154
+ addMessage,
155
+ updateLastAssistantMessage,
156
+ finalizeLastAssistantMessage,
157
+ setLastAssistantError,
158
+ setStreaming
159
+ } = useChatStore();
160
+ async function sendMessage(content, config) {
161
+ var _a, _b, _c;
162
+ if (isStreaming || !content.trim()) return;
163
+ const userMessage = {
164
+ id: crypto.randomUUID(),
165
+ role: "user",
166
+ content: content.trim(),
167
+ status: "done"
168
+ };
169
+ addMessage(userMessage);
170
+ const assistantMessage = {
171
+ id: crypto.randomUUID(),
172
+ role: "assistant",
173
+ content: "",
174
+ status: "streaming"
175
+ };
176
+ addMessage(assistantMessage);
177
+ setStreaming(true);
178
+ const baseUrl = normalizeUrl((_a = config.orchestratorUrl) != null ? _a : "https://app.kayro.com.ar");
179
+ const history = [...messages, userMessage].map((m) => ({
180
+ role: m.role,
181
+ content: m.content
182
+ }));
183
+ try {
184
+ const response = await fetch(`${baseUrl}/api/chat`, {
185
+ method: "POST",
186
+ headers: {
187
+ "Content-Type": "application/json",
188
+ "X-API-Key": config.apiKey
189
+ },
190
+ body: JSON.stringify({
191
+ apiKey: config.apiKey,
192
+ conversationId,
193
+ messages: history,
194
+ ...config.userToken !== void 0 && { userToken: config.userToken },
195
+ ...config.authHeaders !== void 0 && { authHeaders: config.authHeaders },
196
+ ...config.userId !== void 0 && { userId: config.userId },
197
+ ...config.userName !== void 0 && { userName: config.userName }
198
+ })
199
+ });
200
+ if (!response.ok) {
201
+ throw new Error(`HTTP ${response.status}: ${response.statusText}`);
202
+ }
203
+ if (!response.body) {
204
+ throw new Error("Response body is null");
205
+ }
206
+ const reader = response.body.getReader();
207
+ const decoder = new TextDecoder();
208
+ let buffer = "";
209
+ while (true) {
210
+ const { done, value } = await reader.read();
211
+ if (done) break;
212
+ buffer += decoder.decode(value, { stream: true });
213
+ const chunks = buffer.split("\n\n");
214
+ buffer = (_b = chunks.pop()) != null ? _b : "";
215
+ for (const chunk of chunks) {
216
+ for (const line of chunk.split("\n")) {
217
+ if (!line.startsWith("data: ")) continue;
218
+ const raw = line.slice(6).trim();
219
+ if (!raw) continue;
220
+ let data;
221
+ try {
222
+ data = JSON.parse(raw);
223
+ } catch (e) {
224
+ continue;
225
+ }
226
+ if (data.type === "text_delta" && data.delta !== void 0) {
227
+ updateLastAssistantMessage(data.delta);
228
+ } else if (data.type === "text" && data.text !== void 0) {
229
+ updateLastAssistantMessage(data.text);
230
+ } else if (data.type === "done") {
231
+ const tokensUsed = (_c = data.tokensUsed) != null ? _c : data.tokens_used;
232
+ finalizeLastAssistantMessage(tokensUsed);
233
+ } else if (data.type === "error") {
234
+ setLastAssistantError();
235
+ }
236
+ }
237
+ }
238
+ }
239
+ finalizeLastAssistantMessage();
240
+ } catch (err) {
241
+ console.error("[AIWidget] sendMessage error:", err);
242
+ setLastAssistantError();
243
+ } finally {
244
+ setStreaming(false);
245
+ }
246
+ }
247
+ return { sendMessage };
248
+ }
249
+
250
+ // src/components/MessageList.tsx
251
+ var import_react2 = require("react");
252
+
253
+ // src/components/TypingIndicator.tsx
254
+ var import_react = require("react");
255
+ var import_jsx_runtime2 = require("react/jsx-runtime");
256
+ var DOT_STYLE = {
257
+ width: 8,
258
+ height: 8,
259
+ borderRadius: "50%",
260
+ backgroundColor: "#9ca3af",
261
+ display: "inline-block",
262
+ margin: "0 2px"
263
+ };
264
+ function TypingIndicator() {
265
+ const [frame, setFrame] = (0, import_react.useState)(0);
266
+ (0, import_react.useEffect)(() => {
267
+ const interval = setInterval(() => {
268
+ setFrame((f) => (f + 1) % 3);
269
+ }, 400);
270
+ return () => clearInterval(interval);
271
+ }, []);
272
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
273
+ "div",
274
+ {
275
+ style: {
276
+ display: "flex",
277
+ alignItems: "center",
278
+ padding: "10px 14px",
279
+ backgroundColor: "#f3f4f6",
280
+ borderRadius: "16px 16px 16px 4px",
281
+ width: "fit-content",
282
+ maxWidth: 80
283
+ },
284
+ children: [0, 1, 2].map((i) => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
285
+ "span",
286
+ {
287
+ style: {
288
+ ...DOT_STYLE,
289
+ opacity: frame === i ? 1 : 0.35,
290
+ transform: frame === i ? "translateY(-2px)" : "translateY(0)",
291
+ transition: "opacity 0.2s ease, transform 0.2s ease"
292
+ }
293
+ },
294
+ i
295
+ ))
296
+ }
297
+ );
298
+ }
299
+
300
+ // src/components/MessageItem.tsx
301
+ var import_jsx_runtime3 = require("react/jsx-runtime");
302
+ function renderContent(text) {
303
+ const nodes = [];
304
+ const parts = text.split(/(\*\*[^*]+\*\*|\n)/g);
305
+ parts.forEach((part, i) => {
306
+ if (part.startsWith("**") && part.endsWith("**") && part.length > 4) {
307
+ nodes.push(/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("strong", { children: part.slice(2, -2) }, i));
308
+ } else if (part === "\n") {
309
+ nodes.push(/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("br", {}, i));
310
+ } else {
311
+ nodes.push(/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { children: part }, i));
312
+ }
313
+ });
314
+ return nodes;
315
+ }
316
+ function MessageItem({ message, primaryColor }) {
317
+ const isUser = message.role === "user";
318
+ const bubbleStyle = isUser ? {
319
+ backgroundColor: primaryColor,
320
+ color: "#ffffff",
321
+ borderRadius: "16px 16px 4px 16px",
322
+ padding: "10px 14px",
323
+ maxWidth: "75%",
324
+ wordBreak: "break-word",
325
+ fontSize: 14,
326
+ lineHeight: "1.5"
327
+ } : {
328
+ backgroundColor: "#f3f4f6",
329
+ color: "#111827",
330
+ borderRadius: "16px 16px 16px 4px",
331
+ padding: "10px 14px",
332
+ maxWidth: "75%",
333
+ wordBreak: "break-word",
334
+ fontSize: 14,
335
+ lineHeight: "1.5"
336
+ };
337
+ if (message.status === "streaming" && message.content === "") {
338
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
339
+ "div",
340
+ {
341
+ style: {
342
+ display: "flex",
343
+ justifyContent: "flex-start",
344
+ marginBottom: 8
345
+ },
346
+ children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(TypingIndicator, {})
347
+ }
348
+ );
349
+ }
350
+ if (message.status === "error") {
351
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
352
+ "div",
353
+ {
354
+ style: {
355
+ display: "flex",
356
+ justifyContent: isUser ? "flex-end" : "flex-start",
357
+ marginBottom: 8
358
+ },
359
+ children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
360
+ "div",
361
+ {
362
+ style: {
363
+ ...bubbleStyle,
364
+ backgroundColor: "#fee2e2",
365
+ color: "#dc2626",
366
+ borderRadius: "16px"
367
+ },
368
+ children: "Error al procesar el mensaje. Por favor, intent\xE1 de nuevo."
369
+ }
370
+ )
371
+ }
372
+ );
373
+ }
374
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
375
+ "div",
376
+ {
377
+ style: {
378
+ display: "flex",
379
+ justifyContent: isUser ? "flex-end" : "flex-start",
380
+ marginBottom: 8
381
+ },
382
+ children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { style: bubbleStyle, children: renderContent(message.content) })
383
+ }
384
+ );
385
+ }
386
+
387
+ // src/components/MessageList.tsx
388
+ var import_jsx_runtime4 = require("react/jsx-runtime");
389
+ function MessageList({ messages, primaryColor }) {
390
+ const bottomRef = (0, import_react2.useRef)(null);
391
+ (0, import_react2.useEffect)(() => {
392
+ var _a;
393
+ (_a = bottomRef.current) == null ? void 0 : _a.scrollIntoView({ behavior: "smooth" });
394
+ }, [messages]);
395
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
396
+ "div",
397
+ {
398
+ style: {
399
+ flex: 1,
400
+ overflowY: "auto",
401
+ padding: "16px 16px 8px",
402
+ display: "flex",
403
+ flexDirection: "column"
404
+ },
405
+ children: [
406
+ messages.length === 0 && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
407
+ "div",
408
+ {
409
+ style: {
410
+ flex: 1,
411
+ display: "flex",
412
+ alignItems: "center",
413
+ justifyContent: "center",
414
+ color: "#9ca3af",
415
+ fontSize: 14,
416
+ textAlign: "center",
417
+ padding: "0 24px"
418
+ },
419
+ children: "\xBFEn qu\xE9 te puedo ayudar?"
420
+ }
421
+ ),
422
+ messages.map((msg) => /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(MessageItem, { message: msg, primaryColor }, msg.id)),
423
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { ref: bottomRef })
424
+ ]
425
+ }
426
+ );
427
+ }
428
+
429
+ // src/components/InputBar.tsx
430
+ var import_react3 = require("react");
431
+ var import_jsx_runtime5 = require("react/jsx-runtime");
432
+ function InputBar({ onSend, isStreaming, placeholder, primaryColor }) {
433
+ const [value, setValue] = (0, import_react3.useState)("");
434
+ const textareaRef = (0, import_react3.useRef)(null);
435
+ function handleSend() {
436
+ const trimmed = value.trim();
437
+ if (!trimmed || isStreaming) return;
438
+ onSend(trimmed);
439
+ setValue("");
440
+ if (textareaRef.current) {
441
+ textareaRef.current.style.height = "auto";
442
+ }
443
+ }
444
+ function handleKeyDown(e) {
445
+ if (e.key === "Enter" && !e.shiftKey) {
446
+ e.preventDefault();
447
+ handleSend();
448
+ }
449
+ }
450
+ function handleInput() {
451
+ const el = textareaRef.current;
452
+ if (!el) return;
453
+ el.style.height = "auto";
454
+ el.style.height = `${Math.min(el.scrollHeight, 72)}px`;
455
+ }
456
+ const disabled = isStreaming || !value.trim();
457
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
458
+ "div",
459
+ {
460
+ style: {
461
+ display: "flex",
462
+ alignItems: "flex-end",
463
+ gap: 8,
464
+ padding: "12px 16px",
465
+ borderTop: "1px solid #e5e7eb",
466
+ backgroundColor: "#ffffff"
467
+ },
468
+ children: [
469
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
470
+ "textarea",
471
+ {
472
+ ref: textareaRef,
473
+ value,
474
+ onChange: (e) => setValue(e.target.value),
475
+ onKeyDown: handleKeyDown,
476
+ onInput: handleInput,
477
+ placeholder,
478
+ rows: 1,
479
+ disabled: isStreaming,
480
+ style: {
481
+ flex: 1,
482
+ resize: "none",
483
+ border: "1px solid #e5e7eb",
484
+ borderRadius: 12,
485
+ padding: "8px 12px",
486
+ fontSize: 14,
487
+ lineHeight: "24px",
488
+ outline: "none",
489
+ fontFamily: "inherit",
490
+ color: "#111827",
491
+ backgroundColor: isStreaming ? "#f9fafb" : "#ffffff",
492
+ transition: "border-color 0.15s ease",
493
+ overflowY: "hidden"
494
+ },
495
+ onFocus: (e) => {
496
+ e.currentTarget.style.borderColor = primaryColor;
497
+ },
498
+ onBlur: (e) => {
499
+ e.currentTarget.style.borderColor = "#e5e7eb";
500
+ }
501
+ }
502
+ ),
503
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
504
+ "button",
505
+ {
506
+ onClick: handleSend,
507
+ disabled,
508
+ "aria-label": "Enviar mensaje",
509
+ style: {
510
+ width: 36,
511
+ height: 36,
512
+ borderRadius: "50%",
513
+ border: "none",
514
+ cursor: disabled ? "not-allowed" : "pointer",
515
+ backgroundColor: disabled ? "#e5e7eb" : primaryColor,
516
+ color: "#ffffff",
517
+ display: "flex",
518
+ alignItems: "center",
519
+ justifyContent: "center",
520
+ flexShrink: 0,
521
+ transition: "background-color 0.15s ease",
522
+ padding: 0
523
+ },
524
+ children: /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
525
+ "svg",
526
+ {
527
+ width: "16",
528
+ height: "16",
529
+ viewBox: "0 0 24 24",
530
+ fill: "none",
531
+ stroke: "currentColor",
532
+ strokeWidth: "2",
533
+ strokeLinecap: "round",
534
+ strokeLinejoin: "round",
535
+ children: [
536
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("line", { x1: "22", y1: "2", x2: "11", y2: "13" }),
537
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("polygon", { points: "22 2 15 22 11 13 2 9 22 2" })
538
+ ]
539
+ }
540
+ )
541
+ }
542
+ )
543
+ ]
544
+ }
545
+ );
546
+ }
547
+
548
+ // src/components/ChatWindow.tsx
549
+ var import_jsx_runtime6 = require("react/jsx-runtime");
550
+ function ChatWindow({ config }) {
551
+ var _a, _b, _c, _d, _e;
552
+ const { messages, isStreaming, setOpen } = useChatStore();
553
+ const { sendMessage } = useChat();
554
+ const title = (_a = config.title) != null ? _a : "Asistente";
555
+ const placeholder = (_b = config.placeholder) != null ? _b : "Escrib\xED tu mensaje...";
556
+ const primaryColor = (_c = config.primaryColor) != null ? _c : "#6366f1";
557
+ const position = (_d = config.position) != null ? _d : "bottom-right";
558
+ const positionStyle = position === "bottom-right" ? { right: 24, bottom: 88 } : { left: 24, bottom: 88 };
559
+ function handleSend(content) {
560
+ sendMessage(content, config);
561
+ }
562
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
563
+ "div",
564
+ {
565
+ style: {
566
+ position: "fixed",
567
+ ...positionStyle,
568
+ width: 380,
569
+ height: 520,
570
+ backgroundColor: "#ffffff",
571
+ borderRadius: 16,
572
+ boxShadow: "0 20px 60px rgba(0,0,0,0.15), 0 4px 16px rgba(0,0,0,0.08)",
573
+ display: "flex",
574
+ flexDirection: "column",
575
+ overflow: "hidden",
576
+ zIndex: (_e = config.zIndex) != null ? _e : 9999,
577
+ fontFamily: "-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif"
578
+ },
579
+ children: [
580
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
581
+ "div",
582
+ {
583
+ style: {
584
+ backgroundColor: primaryColor,
585
+ color: "#ffffff",
586
+ padding: "16px 20px",
587
+ display: "flex",
588
+ alignItems: "center",
589
+ justifyContent: "space-between",
590
+ flexShrink: 0
591
+ },
592
+ children: [
593
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { style: { display: "flex", alignItems: "center", gap: 10 }, children: [
594
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
595
+ "div",
596
+ {
597
+ style: {
598
+ width: 32,
599
+ height: 32,
600
+ borderRadius: "50%",
601
+ backgroundColor: "rgba(255,255,255,0.25)",
602
+ display: "flex",
603
+ alignItems: "center",
604
+ justifyContent: "center"
605
+ },
606
+ children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
607
+ "svg",
608
+ {
609
+ width: "16",
610
+ height: "16",
611
+ viewBox: "0 0 24 24",
612
+ fill: "currentColor",
613
+ children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("path", { d: "M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-1 14H9V8h2v8zm4 0h-2V8h2v8z" })
614
+ }
615
+ )
616
+ }
617
+ ),
618
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { children: [
619
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { style: { fontWeight: 600, fontSize: 15 }, children: title }),
620
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { style: { fontSize: 12, opacity: 0.85 }, children: isStreaming ? "Escribiendo..." : "En l\xEDnea" })
621
+ ] })
622
+ ] }),
623
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
624
+ "button",
625
+ {
626
+ onClick: () => setOpen(false),
627
+ "aria-label": "Cerrar chat",
628
+ style: {
629
+ background: "none",
630
+ border: "none",
631
+ color: "#ffffff",
632
+ cursor: "pointer",
633
+ padding: 4,
634
+ borderRadius: 8,
635
+ display: "flex",
636
+ alignItems: "center",
637
+ justifyContent: "center",
638
+ opacity: 0.85,
639
+ transition: "opacity 0.15s ease"
640
+ },
641
+ onMouseEnter: (e) => {
642
+ e.currentTarget.style.opacity = "1";
643
+ },
644
+ onMouseLeave: (e) => {
645
+ e.currentTarget.style.opacity = "0.85";
646
+ },
647
+ children: /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
648
+ "svg",
649
+ {
650
+ width: "20",
651
+ height: "20",
652
+ viewBox: "0 0 24 24",
653
+ fill: "none",
654
+ stroke: "currentColor",
655
+ strokeWidth: "2.5",
656
+ strokeLinecap: "round",
657
+ strokeLinejoin: "round",
658
+ children: [
659
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("line", { x1: "18", y1: "6", x2: "6", y2: "18" }),
660
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("line", { x1: "6", y1: "6", x2: "18", y2: "18" })
661
+ ]
662
+ }
663
+ )
664
+ }
665
+ )
666
+ ]
667
+ }
668
+ ),
669
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(MessageList, { messages, primaryColor }),
670
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
671
+ InputBar,
672
+ {
673
+ onSend: handleSend,
674
+ isStreaming,
675
+ placeholder,
676
+ primaryColor
677
+ }
678
+ )
679
+ ]
680
+ }
681
+ );
682
+ }
683
+
684
+ // src/AIWidget.tsx
685
+ var import_jsx_runtime7 = require("react/jsx-runtime");
686
+ function AIWidget(props) {
687
+ var _a, _b, _c;
688
+ const { isOpen, setOpen } = useChatStore();
689
+ const primaryColor = (_a = props.primaryColor) != null ? _a : "#6366f1";
690
+ const position = (_b = props.position) != null ? _b : "bottom-right";
691
+ const zIndex = (_c = props.zIndex) != null ? _c : 9999;
692
+ (0, import_react4.useEffect)(() => {
693
+ if (props.initialOpen) {
694
+ setOpen(true);
695
+ }
696
+ }, []);
697
+ function handleBubbleClick() {
698
+ setOpen(!isOpen);
699
+ }
700
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(import_jsx_runtime7.Fragment, { children: [
701
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
702
+ ChatBubble,
703
+ {
704
+ isOpen,
705
+ onClick: handleBubbleClick,
706
+ primaryColor,
707
+ position,
708
+ zIndex
709
+ }
710
+ ),
711
+ isOpen && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(ChatWindow, { config: props })
712
+ ] });
713
+ }
714
+ // Annotate the CommonJS export names for ESM import in node:
715
+ 0 && (module.exports = {
716
+ AIWidget
717
+ });
package/package.json ADDED
@@ -0,0 +1,39 @@
1
+ {
2
+ "name": "@kayro-ia/widget",
3
+ "version": "1.0.0",
4
+ "description": "Embeddable AI chat widget for your product — powered by Kairo",
5
+ "author": "Kairo <hola@kayro.com.ar>",
6
+ "license": "MIT",
7
+ "homepage": "https://kayro.com.ar",
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "https://github.com/kairo-ia/helix-monorepo"
11
+ },
12
+ "bugs": {
13
+ "url": "https://github.com/kairo-ia/helix-monorepo/issues"
14
+ },
15
+ "keywords": [
16
+ "ai", "chatbot", "widget", "mcp", "kairo", "react", "chat", "llm", "streaming"
17
+ ],
18
+ "sideEffects": false,
19
+ "main": "./dist/index.js",
20
+ "types": "./dist/index.d.ts",
21
+ "files": ["dist"],
22
+ "scripts": {
23
+ "dev": "tsup src/index.tsx --watch",
24
+ "build": "tsup src/index.tsx --dts",
25
+ "type-check": "tsc --noEmit"
26
+ },
27
+ "peerDependencies": {
28
+ "react": ">=18.0.0",
29
+ "react-dom": ">=18.0.0"
30
+ },
31
+ "dependencies": {
32
+ "zustand": "^5.0.0"
33
+ },
34
+ "devDependencies": {
35
+ "@types/react": "^19.0.0",
36
+ "tsup": "^8.0.0",
37
+ "typescript": "^5.4.0"
38
+ }
39
+ }