@kayro-ia/widget 1.0.0 → 1.0.2

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