@menuia/react 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.
@@ -0,0 +1,48 @@
1
+ import * as react_jsx_runtime from 'react/jsx-runtime';
2
+ import { MenuIAConfig, ChatMessage, ConnectionStatus } from '@menuia/core';
3
+ export { ChatMessage, ConnectionStatus, MenuIAClient, MenuIAConfig, MessageAttachment, SendMessageOptions } from '@menuia/core';
4
+
5
+ interface MenuIAChatProps extends MenuIAConfig {
6
+ /** Chat window title */
7
+ title?: string;
8
+ /** Subtitle below title */
9
+ subtitle?: string;
10
+ /** Avatar text (initials) or URL */
11
+ avatar?: string;
12
+ /** Primary color (hex) */
13
+ primaryColor?: string;
14
+ /** Theme */
15
+ theme?: 'light' | 'dark';
16
+ /** Position of the chat button */
17
+ position?: 'bottom-right' | 'bottom-left';
18
+ /** Welcome message shown when chat is empty */
19
+ welcomeMessage?: string;
20
+ /** Input placeholder */
21
+ placeholder?: string;
22
+ /** Start with chat open */
23
+ defaultOpen?: boolean;
24
+ /** Hide the floating button (for inline usage) */
25
+ inline?: boolean;
26
+ /** Custom width */
27
+ width?: number;
28
+ /** Custom height */
29
+ height?: number;
30
+ /** Show powered by MenuIA */
31
+ showPoweredBy?: boolean;
32
+ }
33
+ declare function MenuIAChat({ title, subtitle, avatar, primaryColor, theme, position, welcomeMessage, placeholder, defaultOpen, inline, width, height, showPoweredBy, agentId, apiUrl, sessionId, customFields, metadata, channel, onMessage, onStatusChange, onTicketCreated, onError, }: MenuIAChatProps): react_jsx_runtime.JSX.Element;
34
+
35
+ interface UseMenuIAReturn {
36
+ messages: ChatMessage[];
37
+ status: ConnectionStatus;
38
+ isLoading: boolean;
39
+ isStreaming: boolean;
40
+ streamingText: string;
41
+ error: string | null;
42
+ sendMessage: (content: string) => Promise<void>;
43
+ clearChat: () => void;
44
+ ticketId: string | null;
45
+ }
46
+ declare function useMenuIA(config: MenuIAConfig): UseMenuIAReturn;
47
+
48
+ export { MenuIAChat, type MenuIAChatProps, type UseMenuIAReturn, useMenuIA };
@@ -0,0 +1,48 @@
1
+ import * as react_jsx_runtime from 'react/jsx-runtime';
2
+ import { MenuIAConfig, ChatMessage, ConnectionStatus } from '@menuia/core';
3
+ export { ChatMessage, ConnectionStatus, MenuIAClient, MenuIAConfig, MessageAttachment, SendMessageOptions } from '@menuia/core';
4
+
5
+ interface MenuIAChatProps extends MenuIAConfig {
6
+ /** Chat window title */
7
+ title?: string;
8
+ /** Subtitle below title */
9
+ subtitle?: string;
10
+ /** Avatar text (initials) or URL */
11
+ avatar?: string;
12
+ /** Primary color (hex) */
13
+ primaryColor?: string;
14
+ /** Theme */
15
+ theme?: 'light' | 'dark';
16
+ /** Position of the chat button */
17
+ position?: 'bottom-right' | 'bottom-left';
18
+ /** Welcome message shown when chat is empty */
19
+ welcomeMessage?: string;
20
+ /** Input placeholder */
21
+ placeholder?: string;
22
+ /** Start with chat open */
23
+ defaultOpen?: boolean;
24
+ /** Hide the floating button (for inline usage) */
25
+ inline?: boolean;
26
+ /** Custom width */
27
+ width?: number;
28
+ /** Custom height */
29
+ height?: number;
30
+ /** Show powered by MenuIA */
31
+ showPoweredBy?: boolean;
32
+ }
33
+ declare function MenuIAChat({ title, subtitle, avatar, primaryColor, theme, position, welcomeMessage, placeholder, defaultOpen, inline, width, height, showPoweredBy, agentId, apiUrl, sessionId, customFields, metadata, channel, onMessage, onStatusChange, onTicketCreated, onError, }: MenuIAChatProps): react_jsx_runtime.JSX.Element;
34
+
35
+ interface UseMenuIAReturn {
36
+ messages: ChatMessage[];
37
+ status: ConnectionStatus;
38
+ isLoading: boolean;
39
+ isStreaming: boolean;
40
+ streamingText: string;
41
+ error: string | null;
42
+ sendMessage: (content: string) => Promise<void>;
43
+ clearChat: () => void;
44
+ ticketId: string | null;
45
+ }
46
+ declare function useMenuIA(config: MenuIAConfig): UseMenuIAReturn;
47
+
48
+ export { MenuIAChat, type MenuIAChatProps, type UseMenuIAReturn, useMenuIA };
package/dist/index.js ADDED
@@ -0,0 +1,618 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
28
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
+
30
+ // src/index.tsx
31
+ var index_exports = {};
32
+ __export(index_exports, {
33
+ MenuIAChat: () => MenuIAChat,
34
+ MenuIAClient: () => import_core2.MenuIAClient,
35
+ useMenuIA: () => useMenuIA
36
+ });
37
+ module.exports = __toCommonJS(index_exports);
38
+
39
+ // src/MenuIAChat.tsx
40
+ var import_react2 = __toESM(require("react"));
41
+
42
+ // src/useMenuIA.ts
43
+ var import_react = require("react");
44
+ var import_core = require("@menuia/core");
45
+ function useMenuIA(config) {
46
+ const clientRef = (0, import_react.useRef)(null);
47
+ const [messages, setMessages] = (0, import_react.useState)([]);
48
+ const [status, setStatus] = (0, import_react.useState)("disconnected");
49
+ const [isLoading, setIsLoading] = (0, import_react.useState)(false);
50
+ const [isStreaming, setIsStreaming] = (0, import_react.useState)(false);
51
+ const [streamingText, setStreamingText] = (0, import_react.useState)("");
52
+ const [error, setError] = (0, import_react.useState)(null);
53
+ const [ticketId, setTicketId] = (0, import_react.useState)(null);
54
+ (0, import_react.useEffect)(() => {
55
+ const client = new import_core.MenuIAClient({
56
+ ...config,
57
+ onStatusChange: (s) => setStatus(s),
58
+ onMessage: (msg) => {
59
+ setMessages((prev) => {
60
+ if (prev.some((m) => m.id === msg.id)) return prev;
61
+ return [...prev, msg];
62
+ });
63
+ },
64
+ onTicketCreated: (id) => setTicketId(id),
65
+ onError: (err) => setError(err.message)
66
+ });
67
+ clientRef.current = client;
68
+ client.loadHistory().then((msgs) => {
69
+ if (msgs.length > 0) setMessages(msgs);
70
+ setStatus("connected");
71
+ });
72
+ client.startPolling(1e4);
73
+ return () => {
74
+ client.destroy();
75
+ clientRef.current = null;
76
+ };
77
+ }, [config.agentId, config.apiUrl]);
78
+ const sendMessage = (0, import_react.useCallback)(async (content) => {
79
+ if (!clientRef.current || !content.trim()) return;
80
+ setError(null);
81
+ setIsLoading(true);
82
+ const userMsg = {
83
+ id: `user_${Date.now()}`,
84
+ role: "user",
85
+ content: content.trim(),
86
+ createdAt: (/* @__PURE__ */ new Date()).toISOString()
87
+ };
88
+ setMessages((prev) => [...prev, userMsg]);
89
+ try {
90
+ setIsStreaming(true);
91
+ setStreamingText("");
92
+ const response = await clientRef.current.sendMessageStream(
93
+ { content: content.trim() },
94
+ (_token, fullText) => {
95
+ setStreamingText(fullText);
96
+ }
97
+ );
98
+ const finalContent = response?.data?.message || streamingText;
99
+ const assistantMsg = {
100
+ id: `assistant_${Date.now()}`,
101
+ role: "assistant",
102
+ content: finalContent,
103
+ createdAt: (/* @__PURE__ */ new Date()).toISOString()
104
+ };
105
+ setMessages((prev) => {
106
+ const filtered = prev.filter((m) => m.id !== assistantMsg.id);
107
+ return [...filtered, assistantMsg];
108
+ });
109
+ if (response?.data?.ticketId) {
110
+ setTicketId(response.data.ticketId);
111
+ }
112
+ } catch (err) {
113
+ try {
114
+ setIsStreaming(false);
115
+ setStreamingText("");
116
+ const response = await clientRef.current.sendMessage({ content: content.trim() });
117
+ const assistantMsg = {
118
+ id: `assistant_${Date.now()}_fb`,
119
+ role: "assistant",
120
+ content: response.data.message,
121
+ createdAt: (/* @__PURE__ */ new Date()).toISOString()
122
+ };
123
+ setMessages((prev) => [...prev, assistantMsg]);
124
+ } catch (fallbackErr) {
125
+ const errMsg = fallbackErr instanceof Error ? fallbackErr.message : "Erro ao enviar mensagem";
126
+ setError(errMsg);
127
+ }
128
+ } finally {
129
+ setIsLoading(false);
130
+ setIsStreaming(false);
131
+ setStreamingText("");
132
+ }
133
+ }, []);
134
+ const clearChat = (0, import_react.useCallback)(() => {
135
+ clientRef.current?.clearSession();
136
+ setMessages([]);
137
+ setTicketId(null);
138
+ setError(null);
139
+ }, []);
140
+ return {
141
+ messages,
142
+ status,
143
+ isLoading,
144
+ isStreaming,
145
+ streamingText,
146
+ error,
147
+ sendMessage,
148
+ clearChat,
149
+ ticketId
150
+ };
151
+ }
152
+
153
+ // src/styles.ts
154
+ function getStyles(primaryColor, theme) {
155
+ const isDark = theme === "dark";
156
+ const bg = isDark ? "#0a0a14" : "#ffffff";
157
+ const bgSecondary = isDark ? "#12121e" : "#f5f5f5";
158
+ const text = isDark ? "#e5e5e5" : "#1a1a1a";
159
+ const textMuted = isDark ? "#888" : "#666";
160
+ const border = isDark ? "rgba(255,255,255,0.08)" : "rgba(0,0,0,0.08)";
161
+ const userBubble = primaryColor;
162
+ const aiBubble = isDark ? "#1a1a2e" : "#f0f0f0";
163
+ return {
164
+ // Floating button
165
+ fab: {
166
+ position: "fixed",
167
+ zIndex: 9999,
168
+ width: 56,
169
+ height: 56,
170
+ borderRadius: 16,
171
+ background: `linear-gradient(135deg, ${primaryColor}, ${adjustColor(primaryColor, -20)})`,
172
+ border: "none",
173
+ cursor: "pointer",
174
+ display: "flex",
175
+ alignItems: "center",
176
+ justifyContent: "center",
177
+ boxShadow: `0 8px 32px ${primaryColor}40`,
178
+ transition: "transform 0.2s, box-shadow 0.2s"
179
+ },
180
+ // Chat window
181
+ window: {
182
+ position: "fixed",
183
+ zIndex: 9998,
184
+ width: 380,
185
+ maxWidth: "calc(100vw - 32px)",
186
+ height: 560,
187
+ maxHeight: "calc(100vh - 100px)",
188
+ borderRadius: 20,
189
+ background: bg,
190
+ border: `1px solid ${border}`,
191
+ boxShadow: `0 24px 80px rgba(0,0,0,${isDark ? "0.5" : "0.15"})`,
192
+ display: "flex",
193
+ flexDirection: "column",
194
+ overflow: "hidden",
195
+ fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif'
196
+ },
197
+ // Header
198
+ header: {
199
+ padding: "16px 20px",
200
+ background: `linear-gradient(135deg, ${primaryColor}, ${adjustColor(primaryColor, -20)})`,
201
+ display: "flex",
202
+ alignItems: "center",
203
+ gap: 12
204
+ },
205
+ headerAvatar: {
206
+ width: 40,
207
+ height: 40,
208
+ borderRadius: 12,
209
+ background: "rgba(255,255,255,0.2)",
210
+ display: "flex",
211
+ alignItems: "center",
212
+ justifyContent: "center",
213
+ fontSize: 18,
214
+ color: "white",
215
+ fontWeight: 700,
216
+ flexShrink: 0
217
+ },
218
+ headerTitle: {
219
+ color: "white",
220
+ fontSize: 16,
221
+ fontWeight: 700,
222
+ lineHeight: 1.2
223
+ },
224
+ headerSubtitle: {
225
+ color: "rgba(255,255,255,0.7)",
226
+ fontSize: 12,
227
+ marginTop: 2
228
+ },
229
+ headerClose: {
230
+ marginLeft: "auto",
231
+ background: "rgba(255,255,255,0.15)",
232
+ border: "none",
233
+ borderRadius: 8,
234
+ width: 32,
235
+ height: 32,
236
+ display: "flex",
237
+ alignItems: "center",
238
+ justifyContent: "center",
239
+ cursor: "pointer",
240
+ color: "white",
241
+ fontSize: 16
242
+ },
243
+ // Messages area
244
+ messagesArea: {
245
+ flex: 1,
246
+ overflowY: "auto",
247
+ padding: "16px 16px 8px",
248
+ display: "flex",
249
+ flexDirection: "column",
250
+ gap: 8,
251
+ background: bgSecondary
252
+ },
253
+ // Message bubble
254
+ userBubble: {
255
+ alignSelf: "flex-end",
256
+ maxWidth: "80%",
257
+ padding: "10px 14px",
258
+ borderRadius: "16px 16px 4px 16px",
259
+ background: userBubble,
260
+ color: "white",
261
+ fontSize: 14,
262
+ lineHeight: 1.5,
263
+ wordBreak: "break-word"
264
+ },
265
+ aiBubble: {
266
+ alignSelf: "flex-start",
267
+ maxWidth: "80%",
268
+ padding: "10px 14px",
269
+ borderRadius: "16px 16px 16px 4px",
270
+ background: aiBubble,
271
+ color: text,
272
+ fontSize: 14,
273
+ lineHeight: 1.5,
274
+ wordBreak: "break-word",
275
+ border: `1px solid ${border}`
276
+ },
277
+ // Streaming indicator
278
+ streaming: {
279
+ alignSelf: "flex-start",
280
+ maxWidth: "80%",
281
+ padding: "10px 14px",
282
+ borderRadius: "16px 16px 16px 4px",
283
+ background: aiBubble,
284
+ color: text,
285
+ fontSize: 14,
286
+ lineHeight: 1.5,
287
+ border: `1px solid ${primaryColor}30`,
288
+ opacity: 0.9
289
+ },
290
+ // Typing dots
291
+ typingDots: {
292
+ alignSelf: "flex-start",
293
+ padding: "12px 16px",
294
+ borderRadius: 16,
295
+ background: aiBubble,
296
+ border: `1px solid ${border}`,
297
+ display: "flex",
298
+ gap: 4
299
+ },
300
+ dot: {
301
+ width: 6,
302
+ height: 6,
303
+ borderRadius: "50%",
304
+ background: textMuted
305
+ },
306
+ // Input area
307
+ inputArea: {
308
+ padding: "12px 16px",
309
+ borderTop: `1px solid ${border}`,
310
+ display: "flex",
311
+ gap: 8,
312
+ alignItems: "flex-end",
313
+ background: bg
314
+ },
315
+ input: {
316
+ flex: 1,
317
+ padding: "10px 14px",
318
+ borderRadius: 12,
319
+ border: `1px solid ${border}`,
320
+ background: bgSecondary,
321
+ color: text,
322
+ fontSize: 14,
323
+ outline: "none",
324
+ resize: "none",
325
+ fontFamily: "inherit",
326
+ maxHeight: 100,
327
+ lineHeight: 1.4
328
+ },
329
+ sendButton: {
330
+ width: 40,
331
+ height: 40,
332
+ borderRadius: 12,
333
+ background: `linear-gradient(135deg, ${primaryColor}, ${adjustColor(primaryColor, -20)})`,
334
+ border: "none",
335
+ cursor: "pointer",
336
+ display: "flex",
337
+ alignItems: "center",
338
+ justifyContent: "center",
339
+ color: "white",
340
+ flexShrink: 0,
341
+ transition: "opacity 0.2s"
342
+ },
343
+ // Welcome message
344
+ welcome: {
345
+ textAlign: "center",
346
+ padding: "32px 24px",
347
+ color: textMuted,
348
+ fontSize: 14,
349
+ lineHeight: 1.6
350
+ },
351
+ // Error
352
+ error: {
353
+ margin: "8px 16px",
354
+ padding: "8px 12px",
355
+ borderRadius: 8,
356
+ background: isDark ? "rgba(239,68,68,0.1)" : "#fef2f2",
357
+ color: "#ef4444",
358
+ fontSize: 12,
359
+ border: "1px solid rgba(239,68,68,0.2)"
360
+ },
361
+ // Powered by
362
+ powered: {
363
+ textAlign: "center",
364
+ padding: "6px 0",
365
+ fontSize: 10,
366
+ color: textMuted,
367
+ background: bg,
368
+ borderTop: `1px solid ${border}`
369
+ },
370
+ // Timestamp
371
+ timestamp: {
372
+ fontSize: 10,
373
+ color: textMuted,
374
+ marginTop: 4
375
+ },
376
+ // Badge (unread count)
377
+ badge: {
378
+ position: "absolute",
379
+ top: -4,
380
+ right: -4,
381
+ width: 20,
382
+ height: 20,
383
+ borderRadius: "50%",
384
+ background: "#ef4444",
385
+ color: "white",
386
+ fontSize: 11,
387
+ fontWeight: 700,
388
+ display: "flex",
389
+ alignItems: "center",
390
+ justifyContent: "center"
391
+ }
392
+ };
393
+ }
394
+ function adjustColor(hex, amount) {
395
+ const num = parseInt(hex.replace("#", ""), 16);
396
+ const r = Math.max(0, Math.min(255, (num >> 16) + amount));
397
+ const g = Math.max(0, Math.min(255, (num >> 8 & 255) + amount));
398
+ const b = Math.max(0, Math.min(255, (num & 255) + amount));
399
+ return `#${(r << 16 | g << 8 | b).toString(16).padStart(6, "0")}`;
400
+ }
401
+
402
+ // src/MenuIAChat.tsx
403
+ var import_jsx_runtime = require("react/jsx-runtime");
404
+ function MenuIAChat({
405
+ title = "Assistente IA",
406
+ subtitle = "Online agora",
407
+ avatar,
408
+ primaryColor = "#7c3aed",
409
+ theme = "dark",
410
+ position = "bottom-right",
411
+ welcomeMessage = "Ola! Como posso ajudar voce?",
412
+ placeholder = "Digite sua mensagem...",
413
+ defaultOpen = false,
414
+ inline = false,
415
+ width,
416
+ height,
417
+ showPoweredBy = true,
418
+ // Core config
419
+ agentId,
420
+ apiUrl,
421
+ sessionId,
422
+ customFields,
423
+ metadata,
424
+ channel,
425
+ onMessage,
426
+ onStatusChange,
427
+ onTicketCreated,
428
+ onError
429
+ }) {
430
+ const [isOpen, setIsOpen] = (0, import_react2.useState)(defaultOpen || inline);
431
+ const [input, setInput] = (0, import_react2.useState)("");
432
+ const messagesEndRef = (0, import_react2.useRef)(null);
433
+ const inputRef = (0, import_react2.useRef)(null);
434
+ const {
435
+ messages,
436
+ isLoading,
437
+ isStreaming,
438
+ streamingText,
439
+ error,
440
+ sendMessage,
441
+ clearChat
442
+ } = useMenuIA({
443
+ agentId,
444
+ apiUrl,
445
+ sessionId,
446
+ customFields,
447
+ metadata,
448
+ channel,
449
+ onMessage,
450
+ onStatusChange,
451
+ onTicketCreated,
452
+ onError
453
+ });
454
+ const styles = getStyles(primaryColor, theme);
455
+ (0, import_react2.useEffect)(() => {
456
+ messagesEndRef.current?.scrollIntoView({ behavior: "smooth" });
457
+ }, [messages, streamingText]);
458
+ (0, import_react2.useEffect)(() => {
459
+ if (isOpen) {
460
+ setTimeout(() => inputRef.current?.focus(), 300);
461
+ }
462
+ }, [isOpen]);
463
+ const handleSend = async () => {
464
+ const text = input.trim();
465
+ if (!text || isLoading) return;
466
+ setInput("");
467
+ await sendMessage(text);
468
+ };
469
+ const handleKeyDown = (e) => {
470
+ if (e.key === "Enter" && !e.shiftKey) {
471
+ e.preventDefault();
472
+ handleSend();
473
+ }
474
+ };
475
+ const formatTime = (dateStr) => {
476
+ try {
477
+ return new Date(dateStr).toLocaleTimeString("pt-BR", { hour: "2-digit", minute: "2-digit" });
478
+ } catch {
479
+ return "";
480
+ }
481
+ };
482
+ const avatarContent = avatar?.startsWith("http") ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("img", { src: avatar, alt: "", style: { width: "100%", height: "100%", borderRadius: 12, objectFit: "cover" } }) : avatar || title.charAt(0).toUpperCase();
483
+ const positionStyle = position === "bottom-left" ? { bottom: 24, left: 24 } : { bottom: 24, right: 24 };
484
+ const windowPositionStyle = position === "bottom-left" ? { bottom: inline ? 0 : 90, left: inline ? 0 : 24 } : { bottom: inline ? 0 : 90, right: inline ? 0 : 24 };
485
+ const windowStyle = {
486
+ ...styles.window,
487
+ ...inline ? { position: "relative" } : windowPositionStyle,
488
+ ...width ? { width } : {},
489
+ ...height ? { height } : {},
490
+ ...inline ? { borderRadius: 12 } : {}
491
+ };
492
+ const chatWindow = /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: windowStyle, children: [
493
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: styles.header, children: [
494
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: styles.headerAvatar, children: avatarContent }),
495
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { children: [
496
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: styles.headerTitle, children: title }),
497
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: styles.headerSubtitle, children: subtitle })
498
+ ] }),
499
+ !inline && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { style: styles.headerClose, onClick: () => setIsOpen(false), children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2.5", strokeLinecap: "round", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M18 6L6 18M6 6l12 12" }) }) })
500
+ ] }),
501
+ error && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: styles.error, children: error }),
502
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: styles.messagesArea, className: "menuia-messages", children: [
503
+ messages.length === 0 && !isLoading && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: styles.welcome, children: [
504
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { fontSize: 32, marginBottom: 12 }, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { ...styles.headerAvatar, width: 56, height: 56, fontSize: 24, margin: "0 auto 12px", background: `${primaryColor}20`, color: primaryColor }, children: avatarContent }) }),
505
+ welcomeMessage
506
+ ] }),
507
+ messages.map((msg) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { children: [
508
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: msg.role === "user" ? styles.userBubble : styles.aiBubble, children: renderContent(msg.content) }),
509
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { ...styles.timestamp, textAlign: msg.role === "user" ? "right" : "left" }, children: formatTime(msg.createdAt) })
510
+ ] }, msg.id)),
511
+ isStreaming && streamingText && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: styles.streaming, children: [
512
+ renderContent(streamingText),
513
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: { animation: "menuia-blink 1s infinite", opacity: 0.5 }, children: "|" })
514
+ ] }),
515
+ isLoading && !isStreaming && !streamingText && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: styles.typingDots, children: [
516
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { ...styles.dot, animation: "menuia-bounce 1.4s infinite", animationDelay: "0s" } }),
517
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { ...styles.dot, animation: "menuia-bounce 1.4s infinite", animationDelay: "0.2s" } }),
518
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { ...styles.dot, animation: "menuia-bounce 1.4s infinite", animationDelay: "0.4s" } })
519
+ ] }),
520
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { ref: messagesEndRef })
521
+ ] }),
522
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: styles.inputArea, children: [
523
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
524
+ "textarea",
525
+ {
526
+ ref: inputRef,
527
+ value: input,
528
+ onChange: (e) => setInput(e.target.value),
529
+ onKeyDown: handleKeyDown,
530
+ placeholder,
531
+ rows: 1,
532
+ style: styles.input,
533
+ disabled: isLoading
534
+ }
535
+ ),
536
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
537
+ "button",
538
+ {
539
+ onClick: handleSend,
540
+ disabled: !input.trim() || isLoading,
541
+ style: { ...styles.sendButton, opacity: !input.trim() || isLoading ? 0.5 : 1 },
542
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("svg", { width: "18", height: "18", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
543
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M22 2L11 13" }),
544
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M22 2L15 22L11 13L2 9L22 2Z" })
545
+ ] })
546
+ }
547
+ )
548
+ ] }),
549
+ showPoweredBy && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: styles.powered, children: [
550
+ "Powered by ",
551
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("strong", { children: "MenuIA" })
552
+ ] }),
553
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("style", { children: `
554
+ @keyframes menuia-bounce {
555
+ 0%, 80%, 100% { transform: translateY(0); }
556
+ 40% { transform: translateY(-6px); }
557
+ }
558
+ @keyframes menuia-blink {
559
+ 0%, 100% { opacity: 1; }
560
+ 50% { opacity: 0; }
561
+ }
562
+ .menuia-messages::-webkit-scrollbar { width: 4px; }
563
+ .menuia-messages::-webkit-scrollbar-track { background: transparent; }
564
+ .menuia-messages::-webkit-scrollbar-thumb { background: ${primaryColor}30; border-radius: 2px; }
565
+ .menuia-messages::-webkit-scrollbar-thumb:hover { background: ${primaryColor}60; }
566
+ ` })
567
+ ] });
568
+ if (inline) return chatWindow;
569
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
570
+ isOpen && chatWindow,
571
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
572
+ "button",
573
+ {
574
+ style: { ...styles.fab, ...positionStyle },
575
+ onClick: () => setIsOpen(!isOpen),
576
+ onMouseEnter: (e) => {
577
+ e.currentTarget.style.transform = "scale(1.05)";
578
+ },
579
+ onMouseLeave: (e) => {
580
+ e.currentTarget.style.transform = "scale(1)";
581
+ },
582
+ children: isOpen ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("svg", { width: "24", height: "24", viewBox: "0 0 24 24", fill: "none", stroke: "white", strokeWidth: "2", strokeLinecap: "round", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M18 6L6 18M6 6l12 12" }) }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)("svg", { width: "24", height: "24", viewBox: "0 0 24 24", fill: "none", stroke: "white", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M21 15a2 2 0 01-2 2H7l-4 4V5a2 2 0 012-2h14a2 2 0 012 2z" }) })
583
+ }
584
+ )
585
+ ] });
586
+ }
587
+ function renderContent(content) {
588
+ if (!content) return null;
589
+ const paragraphs = content.split("\n\n");
590
+ if (paragraphs.length === 1 && !content.includes("\n")) {
591
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children: renderInline(content) });
592
+ }
593
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_jsx_runtime.Fragment, { children: paragraphs.map((p, i) => {
594
+ const lines = p.split("\n");
595
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { marginBottom: i < paragraphs.length - 1 ? 8 : 0 }, children: lines.map((line, j) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_react2.default.Fragment, { children: [
596
+ j > 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("br", {}),
597
+ renderInline(line)
598
+ ] }, j)) }, i);
599
+ }) });
600
+ }
601
+ function renderInline(text) {
602
+ const parts = text.split(/(\*\*[^*]+\*\*)/g);
603
+ return parts.map((part, i) => {
604
+ if (part.startsWith("**") && part.endsWith("**")) {
605
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("strong", { children: part.slice(2, -2) }, i);
606
+ }
607
+ return part;
608
+ });
609
+ }
610
+
611
+ // src/index.tsx
612
+ var import_core2 = require("@menuia/core");
613
+ // Annotate the CommonJS export names for ESM import in node:
614
+ 0 && (module.exports = {
615
+ MenuIAChat,
616
+ MenuIAClient,
617
+ useMenuIA
618
+ });
package/dist/index.mjs ADDED
@@ -0,0 +1,579 @@
1
+ // src/MenuIAChat.tsx
2
+ import React, { useState as useState2, useRef as useRef2, useEffect as useEffect2 } from "react";
3
+
4
+ // src/useMenuIA.ts
5
+ import { useState, useEffect, useRef, useCallback } from "react";
6
+ import { MenuIAClient } from "@menuia/core";
7
+ function useMenuIA(config) {
8
+ const clientRef = useRef(null);
9
+ const [messages, setMessages] = useState([]);
10
+ const [status, setStatus] = useState("disconnected");
11
+ const [isLoading, setIsLoading] = useState(false);
12
+ const [isStreaming, setIsStreaming] = useState(false);
13
+ const [streamingText, setStreamingText] = useState("");
14
+ const [error, setError] = useState(null);
15
+ const [ticketId, setTicketId] = useState(null);
16
+ useEffect(() => {
17
+ const client = new MenuIAClient({
18
+ ...config,
19
+ onStatusChange: (s) => setStatus(s),
20
+ onMessage: (msg) => {
21
+ setMessages((prev) => {
22
+ if (prev.some((m) => m.id === msg.id)) return prev;
23
+ return [...prev, msg];
24
+ });
25
+ },
26
+ onTicketCreated: (id) => setTicketId(id),
27
+ onError: (err) => setError(err.message)
28
+ });
29
+ clientRef.current = client;
30
+ client.loadHistory().then((msgs) => {
31
+ if (msgs.length > 0) setMessages(msgs);
32
+ setStatus("connected");
33
+ });
34
+ client.startPolling(1e4);
35
+ return () => {
36
+ client.destroy();
37
+ clientRef.current = null;
38
+ };
39
+ }, [config.agentId, config.apiUrl]);
40
+ const sendMessage = useCallback(async (content) => {
41
+ if (!clientRef.current || !content.trim()) return;
42
+ setError(null);
43
+ setIsLoading(true);
44
+ const userMsg = {
45
+ id: `user_${Date.now()}`,
46
+ role: "user",
47
+ content: content.trim(),
48
+ createdAt: (/* @__PURE__ */ new Date()).toISOString()
49
+ };
50
+ setMessages((prev) => [...prev, userMsg]);
51
+ try {
52
+ setIsStreaming(true);
53
+ setStreamingText("");
54
+ const response = await clientRef.current.sendMessageStream(
55
+ { content: content.trim() },
56
+ (_token, fullText) => {
57
+ setStreamingText(fullText);
58
+ }
59
+ );
60
+ const finalContent = response?.data?.message || streamingText;
61
+ const assistantMsg = {
62
+ id: `assistant_${Date.now()}`,
63
+ role: "assistant",
64
+ content: finalContent,
65
+ createdAt: (/* @__PURE__ */ new Date()).toISOString()
66
+ };
67
+ setMessages((prev) => {
68
+ const filtered = prev.filter((m) => m.id !== assistantMsg.id);
69
+ return [...filtered, assistantMsg];
70
+ });
71
+ if (response?.data?.ticketId) {
72
+ setTicketId(response.data.ticketId);
73
+ }
74
+ } catch (err) {
75
+ try {
76
+ setIsStreaming(false);
77
+ setStreamingText("");
78
+ const response = await clientRef.current.sendMessage({ content: content.trim() });
79
+ const assistantMsg = {
80
+ id: `assistant_${Date.now()}_fb`,
81
+ role: "assistant",
82
+ content: response.data.message,
83
+ createdAt: (/* @__PURE__ */ new Date()).toISOString()
84
+ };
85
+ setMessages((prev) => [...prev, assistantMsg]);
86
+ } catch (fallbackErr) {
87
+ const errMsg = fallbackErr instanceof Error ? fallbackErr.message : "Erro ao enviar mensagem";
88
+ setError(errMsg);
89
+ }
90
+ } finally {
91
+ setIsLoading(false);
92
+ setIsStreaming(false);
93
+ setStreamingText("");
94
+ }
95
+ }, []);
96
+ const clearChat = useCallback(() => {
97
+ clientRef.current?.clearSession();
98
+ setMessages([]);
99
+ setTicketId(null);
100
+ setError(null);
101
+ }, []);
102
+ return {
103
+ messages,
104
+ status,
105
+ isLoading,
106
+ isStreaming,
107
+ streamingText,
108
+ error,
109
+ sendMessage,
110
+ clearChat,
111
+ ticketId
112
+ };
113
+ }
114
+
115
+ // src/styles.ts
116
+ function getStyles(primaryColor, theme) {
117
+ const isDark = theme === "dark";
118
+ const bg = isDark ? "#0a0a14" : "#ffffff";
119
+ const bgSecondary = isDark ? "#12121e" : "#f5f5f5";
120
+ const text = isDark ? "#e5e5e5" : "#1a1a1a";
121
+ const textMuted = isDark ? "#888" : "#666";
122
+ const border = isDark ? "rgba(255,255,255,0.08)" : "rgba(0,0,0,0.08)";
123
+ const userBubble = primaryColor;
124
+ const aiBubble = isDark ? "#1a1a2e" : "#f0f0f0";
125
+ return {
126
+ // Floating button
127
+ fab: {
128
+ position: "fixed",
129
+ zIndex: 9999,
130
+ width: 56,
131
+ height: 56,
132
+ borderRadius: 16,
133
+ background: `linear-gradient(135deg, ${primaryColor}, ${adjustColor(primaryColor, -20)})`,
134
+ border: "none",
135
+ cursor: "pointer",
136
+ display: "flex",
137
+ alignItems: "center",
138
+ justifyContent: "center",
139
+ boxShadow: `0 8px 32px ${primaryColor}40`,
140
+ transition: "transform 0.2s, box-shadow 0.2s"
141
+ },
142
+ // Chat window
143
+ window: {
144
+ position: "fixed",
145
+ zIndex: 9998,
146
+ width: 380,
147
+ maxWidth: "calc(100vw - 32px)",
148
+ height: 560,
149
+ maxHeight: "calc(100vh - 100px)",
150
+ borderRadius: 20,
151
+ background: bg,
152
+ border: `1px solid ${border}`,
153
+ boxShadow: `0 24px 80px rgba(0,0,0,${isDark ? "0.5" : "0.15"})`,
154
+ display: "flex",
155
+ flexDirection: "column",
156
+ overflow: "hidden",
157
+ fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif'
158
+ },
159
+ // Header
160
+ header: {
161
+ padding: "16px 20px",
162
+ background: `linear-gradient(135deg, ${primaryColor}, ${adjustColor(primaryColor, -20)})`,
163
+ display: "flex",
164
+ alignItems: "center",
165
+ gap: 12
166
+ },
167
+ headerAvatar: {
168
+ width: 40,
169
+ height: 40,
170
+ borderRadius: 12,
171
+ background: "rgba(255,255,255,0.2)",
172
+ display: "flex",
173
+ alignItems: "center",
174
+ justifyContent: "center",
175
+ fontSize: 18,
176
+ color: "white",
177
+ fontWeight: 700,
178
+ flexShrink: 0
179
+ },
180
+ headerTitle: {
181
+ color: "white",
182
+ fontSize: 16,
183
+ fontWeight: 700,
184
+ lineHeight: 1.2
185
+ },
186
+ headerSubtitle: {
187
+ color: "rgba(255,255,255,0.7)",
188
+ fontSize: 12,
189
+ marginTop: 2
190
+ },
191
+ headerClose: {
192
+ marginLeft: "auto",
193
+ background: "rgba(255,255,255,0.15)",
194
+ border: "none",
195
+ borderRadius: 8,
196
+ width: 32,
197
+ height: 32,
198
+ display: "flex",
199
+ alignItems: "center",
200
+ justifyContent: "center",
201
+ cursor: "pointer",
202
+ color: "white",
203
+ fontSize: 16
204
+ },
205
+ // Messages area
206
+ messagesArea: {
207
+ flex: 1,
208
+ overflowY: "auto",
209
+ padding: "16px 16px 8px",
210
+ display: "flex",
211
+ flexDirection: "column",
212
+ gap: 8,
213
+ background: bgSecondary
214
+ },
215
+ // Message bubble
216
+ userBubble: {
217
+ alignSelf: "flex-end",
218
+ maxWidth: "80%",
219
+ padding: "10px 14px",
220
+ borderRadius: "16px 16px 4px 16px",
221
+ background: userBubble,
222
+ color: "white",
223
+ fontSize: 14,
224
+ lineHeight: 1.5,
225
+ wordBreak: "break-word"
226
+ },
227
+ aiBubble: {
228
+ alignSelf: "flex-start",
229
+ maxWidth: "80%",
230
+ padding: "10px 14px",
231
+ borderRadius: "16px 16px 16px 4px",
232
+ background: aiBubble,
233
+ color: text,
234
+ fontSize: 14,
235
+ lineHeight: 1.5,
236
+ wordBreak: "break-word",
237
+ border: `1px solid ${border}`
238
+ },
239
+ // Streaming indicator
240
+ streaming: {
241
+ alignSelf: "flex-start",
242
+ maxWidth: "80%",
243
+ padding: "10px 14px",
244
+ borderRadius: "16px 16px 16px 4px",
245
+ background: aiBubble,
246
+ color: text,
247
+ fontSize: 14,
248
+ lineHeight: 1.5,
249
+ border: `1px solid ${primaryColor}30`,
250
+ opacity: 0.9
251
+ },
252
+ // Typing dots
253
+ typingDots: {
254
+ alignSelf: "flex-start",
255
+ padding: "12px 16px",
256
+ borderRadius: 16,
257
+ background: aiBubble,
258
+ border: `1px solid ${border}`,
259
+ display: "flex",
260
+ gap: 4
261
+ },
262
+ dot: {
263
+ width: 6,
264
+ height: 6,
265
+ borderRadius: "50%",
266
+ background: textMuted
267
+ },
268
+ // Input area
269
+ inputArea: {
270
+ padding: "12px 16px",
271
+ borderTop: `1px solid ${border}`,
272
+ display: "flex",
273
+ gap: 8,
274
+ alignItems: "flex-end",
275
+ background: bg
276
+ },
277
+ input: {
278
+ flex: 1,
279
+ padding: "10px 14px",
280
+ borderRadius: 12,
281
+ border: `1px solid ${border}`,
282
+ background: bgSecondary,
283
+ color: text,
284
+ fontSize: 14,
285
+ outline: "none",
286
+ resize: "none",
287
+ fontFamily: "inherit",
288
+ maxHeight: 100,
289
+ lineHeight: 1.4
290
+ },
291
+ sendButton: {
292
+ width: 40,
293
+ height: 40,
294
+ borderRadius: 12,
295
+ background: `linear-gradient(135deg, ${primaryColor}, ${adjustColor(primaryColor, -20)})`,
296
+ border: "none",
297
+ cursor: "pointer",
298
+ display: "flex",
299
+ alignItems: "center",
300
+ justifyContent: "center",
301
+ color: "white",
302
+ flexShrink: 0,
303
+ transition: "opacity 0.2s"
304
+ },
305
+ // Welcome message
306
+ welcome: {
307
+ textAlign: "center",
308
+ padding: "32px 24px",
309
+ color: textMuted,
310
+ fontSize: 14,
311
+ lineHeight: 1.6
312
+ },
313
+ // Error
314
+ error: {
315
+ margin: "8px 16px",
316
+ padding: "8px 12px",
317
+ borderRadius: 8,
318
+ background: isDark ? "rgba(239,68,68,0.1)" : "#fef2f2",
319
+ color: "#ef4444",
320
+ fontSize: 12,
321
+ border: "1px solid rgba(239,68,68,0.2)"
322
+ },
323
+ // Powered by
324
+ powered: {
325
+ textAlign: "center",
326
+ padding: "6px 0",
327
+ fontSize: 10,
328
+ color: textMuted,
329
+ background: bg,
330
+ borderTop: `1px solid ${border}`
331
+ },
332
+ // Timestamp
333
+ timestamp: {
334
+ fontSize: 10,
335
+ color: textMuted,
336
+ marginTop: 4
337
+ },
338
+ // Badge (unread count)
339
+ badge: {
340
+ position: "absolute",
341
+ top: -4,
342
+ right: -4,
343
+ width: 20,
344
+ height: 20,
345
+ borderRadius: "50%",
346
+ background: "#ef4444",
347
+ color: "white",
348
+ fontSize: 11,
349
+ fontWeight: 700,
350
+ display: "flex",
351
+ alignItems: "center",
352
+ justifyContent: "center"
353
+ }
354
+ };
355
+ }
356
+ function adjustColor(hex, amount) {
357
+ const num = parseInt(hex.replace("#", ""), 16);
358
+ const r = Math.max(0, Math.min(255, (num >> 16) + amount));
359
+ const g = Math.max(0, Math.min(255, (num >> 8 & 255) + amount));
360
+ const b = Math.max(0, Math.min(255, (num & 255) + amount));
361
+ return `#${(r << 16 | g << 8 | b).toString(16).padStart(6, "0")}`;
362
+ }
363
+
364
+ // src/MenuIAChat.tsx
365
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
366
+ function MenuIAChat({
367
+ title = "Assistente IA",
368
+ subtitle = "Online agora",
369
+ avatar,
370
+ primaryColor = "#7c3aed",
371
+ theme = "dark",
372
+ position = "bottom-right",
373
+ welcomeMessage = "Ola! Como posso ajudar voce?",
374
+ placeholder = "Digite sua mensagem...",
375
+ defaultOpen = false,
376
+ inline = false,
377
+ width,
378
+ height,
379
+ showPoweredBy = true,
380
+ // Core config
381
+ agentId,
382
+ apiUrl,
383
+ sessionId,
384
+ customFields,
385
+ metadata,
386
+ channel,
387
+ onMessage,
388
+ onStatusChange,
389
+ onTicketCreated,
390
+ onError
391
+ }) {
392
+ const [isOpen, setIsOpen] = useState2(defaultOpen || inline);
393
+ const [input, setInput] = useState2("");
394
+ const messagesEndRef = useRef2(null);
395
+ const inputRef = useRef2(null);
396
+ const {
397
+ messages,
398
+ isLoading,
399
+ isStreaming,
400
+ streamingText,
401
+ error,
402
+ sendMessage,
403
+ clearChat
404
+ } = useMenuIA({
405
+ agentId,
406
+ apiUrl,
407
+ sessionId,
408
+ customFields,
409
+ metadata,
410
+ channel,
411
+ onMessage,
412
+ onStatusChange,
413
+ onTicketCreated,
414
+ onError
415
+ });
416
+ const styles = getStyles(primaryColor, theme);
417
+ useEffect2(() => {
418
+ messagesEndRef.current?.scrollIntoView({ behavior: "smooth" });
419
+ }, [messages, streamingText]);
420
+ useEffect2(() => {
421
+ if (isOpen) {
422
+ setTimeout(() => inputRef.current?.focus(), 300);
423
+ }
424
+ }, [isOpen]);
425
+ const handleSend = async () => {
426
+ const text = input.trim();
427
+ if (!text || isLoading) return;
428
+ setInput("");
429
+ await sendMessage(text);
430
+ };
431
+ const handleKeyDown = (e) => {
432
+ if (e.key === "Enter" && !e.shiftKey) {
433
+ e.preventDefault();
434
+ handleSend();
435
+ }
436
+ };
437
+ const formatTime = (dateStr) => {
438
+ try {
439
+ return new Date(dateStr).toLocaleTimeString("pt-BR", { hour: "2-digit", minute: "2-digit" });
440
+ } catch {
441
+ return "";
442
+ }
443
+ };
444
+ const avatarContent = avatar?.startsWith("http") ? /* @__PURE__ */ jsx("img", { src: avatar, alt: "", style: { width: "100%", height: "100%", borderRadius: 12, objectFit: "cover" } }) : avatar || title.charAt(0).toUpperCase();
445
+ const positionStyle = position === "bottom-left" ? { bottom: 24, left: 24 } : { bottom: 24, right: 24 };
446
+ const windowPositionStyle = position === "bottom-left" ? { bottom: inline ? 0 : 90, left: inline ? 0 : 24 } : { bottom: inline ? 0 : 90, right: inline ? 0 : 24 };
447
+ const windowStyle = {
448
+ ...styles.window,
449
+ ...inline ? { position: "relative" } : windowPositionStyle,
450
+ ...width ? { width } : {},
451
+ ...height ? { height } : {},
452
+ ...inline ? { borderRadius: 12 } : {}
453
+ };
454
+ const chatWindow = /* @__PURE__ */ jsxs("div", { style: windowStyle, children: [
455
+ /* @__PURE__ */ jsxs("div", { style: styles.header, children: [
456
+ /* @__PURE__ */ jsx("div", { style: styles.headerAvatar, children: avatarContent }),
457
+ /* @__PURE__ */ jsxs("div", { children: [
458
+ /* @__PURE__ */ jsx("div", { style: styles.headerTitle, children: title }),
459
+ /* @__PURE__ */ jsx("div", { style: styles.headerSubtitle, children: subtitle })
460
+ ] }),
461
+ !inline && /* @__PURE__ */ jsx("button", { style: styles.headerClose, onClick: () => setIsOpen(false), children: /* @__PURE__ */ jsx("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2.5", strokeLinecap: "round", children: /* @__PURE__ */ jsx("path", { d: "M18 6L6 18M6 6l12 12" }) }) })
462
+ ] }),
463
+ error && /* @__PURE__ */ jsx("div", { style: styles.error, children: error }),
464
+ /* @__PURE__ */ jsxs("div", { style: styles.messagesArea, className: "menuia-messages", children: [
465
+ messages.length === 0 && !isLoading && /* @__PURE__ */ jsxs("div", { style: styles.welcome, children: [
466
+ /* @__PURE__ */ jsx("div", { style: { fontSize: 32, marginBottom: 12 }, children: /* @__PURE__ */ jsx("div", { style: { ...styles.headerAvatar, width: 56, height: 56, fontSize: 24, margin: "0 auto 12px", background: `${primaryColor}20`, color: primaryColor }, children: avatarContent }) }),
467
+ welcomeMessage
468
+ ] }),
469
+ messages.map((msg) => /* @__PURE__ */ jsxs("div", { children: [
470
+ /* @__PURE__ */ jsx("div", { style: msg.role === "user" ? styles.userBubble : styles.aiBubble, children: renderContent(msg.content) }),
471
+ /* @__PURE__ */ jsx("div", { style: { ...styles.timestamp, textAlign: msg.role === "user" ? "right" : "left" }, children: formatTime(msg.createdAt) })
472
+ ] }, msg.id)),
473
+ isStreaming && streamingText && /* @__PURE__ */ jsxs("div", { style: styles.streaming, children: [
474
+ renderContent(streamingText),
475
+ /* @__PURE__ */ jsx("span", { style: { animation: "menuia-blink 1s infinite", opacity: 0.5 }, children: "|" })
476
+ ] }),
477
+ isLoading && !isStreaming && !streamingText && /* @__PURE__ */ jsxs("div", { style: styles.typingDots, children: [
478
+ /* @__PURE__ */ jsx("div", { style: { ...styles.dot, animation: "menuia-bounce 1.4s infinite", animationDelay: "0s" } }),
479
+ /* @__PURE__ */ jsx("div", { style: { ...styles.dot, animation: "menuia-bounce 1.4s infinite", animationDelay: "0.2s" } }),
480
+ /* @__PURE__ */ jsx("div", { style: { ...styles.dot, animation: "menuia-bounce 1.4s infinite", animationDelay: "0.4s" } })
481
+ ] }),
482
+ /* @__PURE__ */ jsx("div", { ref: messagesEndRef })
483
+ ] }),
484
+ /* @__PURE__ */ jsxs("div", { style: styles.inputArea, children: [
485
+ /* @__PURE__ */ jsx(
486
+ "textarea",
487
+ {
488
+ ref: inputRef,
489
+ value: input,
490
+ onChange: (e) => setInput(e.target.value),
491
+ onKeyDown: handleKeyDown,
492
+ placeholder,
493
+ rows: 1,
494
+ style: styles.input,
495
+ disabled: isLoading
496
+ }
497
+ ),
498
+ /* @__PURE__ */ jsx(
499
+ "button",
500
+ {
501
+ onClick: handleSend,
502
+ disabled: !input.trim() || isLoading,
503
+ style: { ...styles.sendButton, opacity: !input.trim() || isLoading ? 0.5 : 1 },
504
+ children: /* @__PURE__ */ jsxs("svg", { width: "18", height: "18", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
505
+ /* @__PURE__ */ jsx("path", { d: "M22 2L11 13" }),
506
+ /* @__PURE__ */ jsx("path", { d: "M22 2L15 22L11 13L2 9L22 2Z" })
507
+ ] })
508
+ }
509
+ )
510
+ ] }),
511
+ showPoweredBy && /* @__PURE__ */ jsxs("div", { style: styles.powered, children: [
512
+ "Powered by ",
513
+ /* @__PURE__ */ jsx("strong", { children: "MenuIA" })
514
+ ] }),
515
+ /* @__PURE__ */ jsx("style", { children: `
516
+ @keyframes menuia-bounce {
517
+ 0%, 80%, 100% { transform: translateY(0); }
518
+ 40% { transform: translateY(-6px); }
519
+ }
520
+ @keyframes menuia-blink {
521
+ 0%, 100% { opacity: 1; }
522
+ 50% { opacity: 0; }
523
+ }
524
+ .menuia-messages::-webkit-scrollbar { width: 4px; }
525
+ .menuia-messages::-webkit-scrollbar-track { background: transparent; }
526
+ .menuia-messages::-webkit-scrollbar-thumb { background: ${primaryColor}30; border-radius: 2px; }
527
+ .menuia-messages::-webkit-scrollbar-thumb:hover { background: ${primaryColor}60; }
528
+ ` })
529
+ ] });
530
+ if (inline) return chatWindow;
531
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
532
+ isOpen && chatWindow,
533
+ /* @__PURE__ */ jsx(
534
+ "button",
535
+ {
536
+ style: { ...styles.fab, ...positionStyle },
537
+ onClick: () => setIsOpen(!isOpen),
538
+ onMouseEnter: (e) => {
539
+ e.currentTarget.style.transform = "scale(1.05)";
540
+ },
541
+ onMouseLeave: (e) => {
542
+ e.currentTarget.style.transform = "scale(1)";
543
+ },
544
+ children: isOpen ? /* @__PURE__ */ jsx("svg", { width: "24", height: "24", viewBox: "0 0 24 24", fill: "none", stroke: "white", strokeWidth: "2", strokeLinecap: "round", children: /* @__PURE__ */ jsx("path", { d: "M18 6L6 18M6 6l12 12" }) }) : /* @__PURE__ */ jsx("svg", { width: "24", height: "24", viewBox: "0 0 24 24", fill: "none", stroke: "white", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: /* @__PURE__ */ jsx("path", { d: "M21 15a2 2 0 01-2 2H7l-4 4V5a2 2 0 012-2h14a2 2 0 012 2z" }) })
545
+ }
546
+ )
547
+ ] });
548
+ }
549
+ function renderContent(content) {
550
+ if (!content) return null;
551
+ const paragraphs = content.split("\n\n");
552
+ if (paragraphs.length === 1 && !content.includes("\n")) {
553
+ return /* @__PURE__ */ jsx("span", { children: renderInline(content) });
554
+ }
555
+ return /* @__PURE__ */ jsx(Fragment, { children: paragraphs.map((p, i) => {
556
+ const lines = p.split("\n");
557
+ return /* @__PURE__ */ jsx("div", { style: { marginBottom: i < paragraphs.length - 1 ? 8 : 0 }, children: lines.map((line, j) => /* @__PURE__ */ jsxs(React.Fragment, { children: [
558
+ j > 0 && /* @__PURE__ */ jsx("br", {}),
559
+ renderInline(line)
560
+ ] }, j)) }, i);
561
+ }) });
562
+ }
563
+ function renderInline(text) {
564
+ const parts = text.split(/(\*\*[^*]+\*\*)/g);
565
+ return parts.map((part, i) => {
566
+ if (part.startsWith("**") && part.endsWith("**")) {
567
+ return /* @__PURE__ */ jsx("strong", { children: part.slice(2, -2) }, i);
568
+ }
569
+ return part;
570
+ });
571
+ }
572
+
573
+ // src/index.tsx
574
+ import { MenuIAClient as MenuIAClient2 } from "@menuia/core";
575
+ export {
576
+ MenuIAChat,
577
+ MenuIAClient2 as MenuIAClient,
578
+ useMenuIA
579
+ };
package/package.json ADDED
@@ -0,0 +1,29 @@
1
+ {
2
+ "name": "@menuia/react",
3
+ "version": "1.0.0",
4
+ "description": "MenuIA Chat SDK - React Component",
5
+ "main": "dist/index.js",
6
+ "module": "dist/index.mjs",
7
+ "types": "dist/index.d.ts",
8
+ "files": ["dist"],
9
+ "scripts": {
10
+ "build": "tsup src/index.tsx --format cjs,esm --dts --clean --external react --external react-dom --external @menuia/core",
11
+ "dev": "tsup src/index.tsx --format cjs,esm --dts --watch --external react --external react-dom --external @menuia/core"
12
+ },
13
+ "peerDependencies": {
14
+ "react": ">=17.0.0",
15
+ "react-dom": ">=17.0.0"
16
+ },
17
+ "dependencies": {
18
+ "@menuia/core": "^1.0.0"
19
+ },
20
+ "devDependencies": {
21
+ "@types/react": "^18.2.0",
22
+ "@types/react-dom": "^18.2.0",
23
+ "react": "^18.2.0",
24
+ "react-dom": "^18.2.0",
25
+ "tsup": "^8.0.0",
26
+ "typescript": "^5.3.0"
27
+ },
28
+ "license": "MIT"
29
+ }