@avatarfirst/react 0.1.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/dist/index.js ADDED
@@ -0,0 +1,1674 @@
1
+ // src/context/AvatarProvider.tsx
2
+ import { useEffect, useState, useMemo } from "react";
3
+
4
+ // src/context/AvatarContext.ts
5
+ import { createContext } from "react";
6
+ var AvatarContext = createContext(null);
7
+
8
+ // src/context/AvatarProvider.tsx
9
+ import { jsx } from "react/jsx-runtime";
10
+ function AvatarProvider({ agent, autoStart = false, children }) {
11
+ const [state, setState] = useState(agent.getState());
12
+ const [overlays, setOverlays] = useState(agent.getVisibleOverlays());
13
+ useEffect(() => {
14
+ const unsubState = agent.on("state:changed", ({ to }) => {
15
+ setState(to);
16
+ });
17
+ const unsubOverlay = agent.overlays.events.on("overlay:change", (visible) => {
18
+ setOverlays([...visible]);
19
+ });
20
+ if (autoStart) {
21
+ agent.start();
22
+ }
23
+ return () => {
24
+ unsubState();
25
+ unsubOverlay();
26
+ };
27
+ }, [agent, autoStart]);
28
+ const isConnected = state === "connected" || state === "speaking" || state === "listening" || state === "thinking";
29
+ const value = useMemo(
30
+ () => ({ agent, state, isConnected, overlays }),
31
+ [agent, state, isConnected, overlays]
32
+ );
33
+ return /* @__PURE__ */ jsx(AvatarContext.Provider, { value, children });
34
+ }
35
+
36
+ // src/hooks/useAvatar.ts
37
+ import { useContext, useCallback as useCallback2 } from "react";
38
+ function useAvatar() {
39
+ const ctx = useContext(AvatarContext);
40
+ if (!ctx) {
41
+ throw new Error("useAvatar must be used within an AvatarProvider");
42
+ }
43
+ const { agent, state, isConnected } = ctx;
44
+ const start = useCallback2(async () => {
45
+ await agent?.start();
46
+ }, [agent]);
47
+ const stop = useCallback2(async () => {
48
+ await agent?.stop();
49
+ }, [agent]);
50
+ const speak = useCallback2(
51
+ async (text) => {
52
+ await agent?.speak(text);
53
+ },
54
+ [agent]
55
+ );
56
+ const message = useCallback2(
57
+ async (text) => {
58
+ await agent?.message(text);
59
+ },
60
+ [agent]
61
+ );
62
+ const interrupt = useCallback2(async () => {
63
+ await agent?.interrupt();
64
+ }, [agent]);
65
+ const startListening = useCallback2(() => {
66
+ agent?.startListening();
67
+ }, [agent]);
68
+ const stopListening = useCallback2(() => {
69
+ agent?.stopListening();
70
+ }, [agent]);
71
+ const attachVideoElement = useCallback2(
72
+ (el) => {
73
+ agent?.attachVideoElement(el);
74
+ },
75
+ [agent]
76
+ );
77
+ return {
78
+ agent,
79
+ state,
80
+ isConnected,
81
+ start,
82
+ stop,
83
+ speak,
84
+ message,
85
+ interrupt,
86
+ startListening,
87
+ stopListening,
88
+ attachVideoElement
89
+ };
90
+ }
91
+
92
+ // src/hooks/useOverlay.ts
93
+ import { useContext as useContext2, useCallback as useCallback3, useMemo as useMemo2 } from "react";
94
+ function useOverlay(overlayId) {
95
+ const ctx = useContext2(AvatarContext);
96
+ if (!ctx) {
97
+ throw new Error("useOverlay must be used within an AvatarProvider");
98
+ }
99
+ const { agent, overlays } = ctx;
100
+ const isVisible = useMemo2(() => {
101
+ if (!overlayId) return false;
102
+ return overlays.some((o) => o.id === overlayId);
103
+ }, [overlayId, overlays]);
104
+ const overlayData = useMemo2(() => {
105
+ if (!overlayId) return void 0;
106
+ return overlays.find((o) => o.id === overlayId);
107
+ }, [overlayId, overlays]);
108
+ const show = useCallback3(
109
+ (config) => {
110
+ agent?.showOverlay(config);
111
+ },
112
+ [agent]
113
+ );
114
+ const hide = useCallback3(
115
+ (id) => {
116
+ agent?.hideOverlay(id ?? overlayId ?? "");
117
+ },
118
+ [agent, overlayId]
119
+ );
120
+ return {
121
+ overlays,
122
+ isVisible,
123
+ overlayData,
124
+ show,
125
+ hide
126
+ };
127
+ }
128
+
129
+ // src/hooks/useAvatarEvent.ts
130
+ import { useContext as useContext3, useEffect as useEffect2 } from "react";
131
+ function useAvatarEvent(event, handler) {
132
+ const ctx = useContext3(AvatarContext);
133
+ if (!ctx) {
134
+ throw new Error("useAvatarEvent must be used within an AvatarProvider");
135
+ }
136
+ useEffect2(() => {
137
+ if (!ctx.agent) return;
138
+ const unsub = ctx.agent.on(event, handler);
139
+ return unsub;
140
+ }, [ctx.agent, event, handler]);
141
+ }
142
+
143
+ // src/hooks/useAction.ts
144
+ import { useContext as useContext4, useCallback as useCallback4 } from "react";
145
+ function useAction() {
146
+ const ctx = useContext4(AvatarContext);
147
+ if (!ctx) {
148
+ throw new Error("useAction must be used within an AvatarProvider");
149
+ }
150
+ const trigger = useCallback4(
151
+ (actionId) => {
152
+ ctx.agent?.triggerAction(actionId);
153
+ },
154
+ [ctx.agent]
155
+ );
156
+ return { trigger };
157
+ }
158
+
159
+ // src/hooks/useAvatarFirst.ts
160
+ import { useState as useState2, useCallback as useCallback5, useRef, useEffect as useEffect3 } from "react";
161
+ import {
162
+ AvatarFirstClient
163
+ } from "@avatarfirst/core";
164
+ function useAvatarFirst(options) {
165
+ const [state, setState] = useState2("idle");
166
+ const [sessionId, setSessionId] = useState2(null);
167
+ const [greeting, setGreeting] = useState2(null);
168
+ const [avatarToken, setAvatarToken] = useState2(null);
169
+ const [overlays, setOverlays] = useState2([]);
170
+ const [isProcessing, setIsProcessing] = useState2(false);
171
+ const [error, setError] = useState2(null);
172
+ const clientRef = useRef(null);
173
+ const optionsRef = useRef(options);
174
+ optionsRef.current = options;
175
+ useEffect3(() => {
176
+ clientRef.current = new AvatarFirstClient({
177
+ apiKey: options.apiKey,
178
+ baseUrl: options.baseUrl
179
+ });
180
+ }, [options.apiKey, options.baseUrl]);
181
+ const connect = useCallback5(async () => {
182
+ if (!clientRef.current) return null;
183
+ setState("connecting");
184
+ setError(null);
185
+ optionsRef.current.onStateChange?.("connecting");
186
+ try {
187
+ const session = await clientRef.current.createSession();
188
+ setSessionId(session.sessionId);
189
+ setGreeting(session.greeting);
190
+ setAvatarToken(session.avatar ?? null);
191
+ setState("connected");
192
+ optionsRef.current.onStateChange?.("connected");
193
+ return session;
194
+ } catch (err) {
195
+ const msg = err.message;
196
+ setError(msg);
197
+ setState("error");
198
+ optionsRef.current.onStateChange?.("error");
199
+ return null;
200
+ }
201
+ }, []);
202
+ const sendMessage = useCallback5(async (text) => {
203
+ if (!clientRef.current || !sessionId || isProcessing) return null;
204
+ setIsProcessing(true);
205
+ try {
206
+ const response = await clientRef.current.sendMessage(sessionId, text);
207
+ if (response.overlay) {
208
+ setOverlays((prev) => [...prev, response.overlay]);
209
+ optionsRef.current.onOverlay?.(response.overlay);
210
+ }
211
+ if (response.actions) {
212
+ for (const action of response.actions) {
213
+ optionsRef.current.onAction?.(action);
214
+ }
215
+ }
216
+ setIsProcessing(false);
217
+ return response;
218
+ } catch (err) {
219
+ setError(err.message);
220
+ setIsProcessing(false);
221
+ return null;
222
+ }
223
+ }, [sessionId, isProcessing]);
224
+ const disconnect = useCallback5(() => {
225
+ if (clientRef.current && sessionId) {
226
+ clientRef.current.endSession(sessionId);
227
+ }
228
+ setSessionId(null);
229
+ setGreeting(null);
230
+ setAvatarToken(null);
231
+ setOverlays([]);
232
+ setState("idle");
233
+ optionsRef.current.onStateChange?.("idle");
234
+ }, [sessionId]);
235
+ return {
236
+ state,
237
+ sessionId,
238
+ greeting,
239
+ avatarToken,
240
+ overlays,
241
+ isProcessing,
242
+ error,
243
+ connect,
244
+ sendMessage,
245
+ disconnect
246
+ };
247
+ }
248
+
249
+ // src/components/overlays/OverlayContainer.tsx
250
+ import { AnimatePresence, motion } from "framer-motion";
251
+
252
+ // src/animations/overlayAnimations.ts
253
+ function getOverlayVariants(animation = "fade") {
254
+ if (typeof animation === "object") {
255
+ return {
256
+ initial: animation.enter,
257
+ animate: animation.enter,
258
+ exit: animation.exit
259
+ };
260
+ }
261
+ switch (animation) {
262
+ case "fade":
263
+ return {
264
+ initial: { opacity: 0 },
265
+ animate: { opacity: 1, transition: { duration: 0.3 } },
266
+ exit: { opacity: 0, transition: { duration: 0.2 } }
267
+ };
268
+ case "slide-up":
269
+ return {
270
+ initial: { opacity: 0, y: 40 },
271
+ animate: { opacity: 1, y: 0, transition: { type: "spring", stiffness: 300, damping: 25 } },
272
+ exit: { opacity: 0, y: 40, transition: { duration: 0.2 } }
273
+ };
274
+ case "slide-down":
275
+ return {
276
+ initial: { opacity: 0, y: -40 },
277
+ animate: { opacity: 1, y: 0, transition: { type: "spring", stiffness: 300, damping: 25 } },
278
+ exit: { opacity: 0, y: -40, transition: { duration: 0.2 } }
279
+ };
280
+ case "slide-left":
281
+ return {
282
+ initial: { opacity: 0, x: 40 },
283
+ animate: { opacity: 1, x: 0, transition: { type: "spring", stiffness: 300, damping: 25 } },
284
+ exit: { opacity: 0, x: 40, transition: { duration: 0.2 } }
285
+ };
286
+ case "slide-right":
287
+ return {
288
+ initial: { opacity: 0, x: -40 },
289
+ animate: { opacity: 1, x: 0, transition: { type: "spring", stiffness: 300, damping: 25 } },
290
+ exit: { opacity: 0, x: -40, transition: { duration: 0.2 } }
291
+ };
292
+ case "scale":
293
+ return {
294
+ initial: { opacity: 0, scale: 0.85 },
295
+ animate: { opacity: 1, scale: 1, transition: { type: "spring", stiffness: 300, damping: 25 } },
296
+ exit: { opacity: 0, scale: 0.85, transition: { duration: 0.2 } }
297
+ };
298
+ case "none":
299
+ return {
300
+ initial: {},
301
+ animate: {},
302
+ exit: {}
303
+ };
304
+ default:
305
+ return {
306
+ initial: { opacity: 0 },
307
+ animate: { opacity: 1 },
308
+ exit: { opacity: 0 }
309
+ };
310
+ }
311
+ }
312
+
313
+ // src/components/overlays/OverlayContainer.tsx
314
+ import { jsx as jsx2 } from "react/jsx-runtime";
315
+ function getPositionStyles(position = "bottom-center") {
316
+ if (typeof position === "object") {
317
+ return { position: "absolute", left: position.x, top: position.y };
318
+ }
319
+ const base = { position: "absolute" };
320
+ switch (position) {
321
+ case "center":
322
+ return { ...base, top: "50%", left: "50%", transform: "translate(-50%, -50%)" };
323
+ case "bottom-center":
324
+ return { ...base, bottom: "2rem", left: "50%", transform: "translateX(-50%)" };
325
+ case "bottom-right":
326
+ return { ...base, bottom: "2rem", right: "2rem" };
327
+ case "bottom-left":
328
+ return { ...base, bottom: "2rem", left: "2rem" };
329
+ case "top-center":
330
+ return { ...base, top: "2rem", left: "50%", transform: "translateX(-50%)" };
331
+ case "right-center":
332
+ return { ...base, top: "50%", right: "2rem", transform: "translateY(-50%)" };
333
+ case "fullscreen":
334
+ return { ...base, inset: 0 };
335
+ default:
336
+ return { ...base, bottom: "2rem", left: "50%", transform: "translateX(-50%)" };
337
+ }
338
+ }
339
+ function OverlayContainer({ components }) {
340
+ const { overlays, hide } = useOverlay();
341
+ return /* @__PURE__ */ jsx2("div", { className: "fixed inset-0 pointer-events-none z-40", children: /* @__PURE__ */ jsx2(AnimatePresence, { children: overlays.map((overlay) => {
342
+ const Component = components[overlay.component];
343
+ if (!Component) return null;
344
+ const variants = getOverlayVariants(overlay.animation);
345
+ const positionStyle = getPositionStyles(overlay.position);
346
+ return /* @__PURE__ */ jsx2(
347
+ motion.div,
348
+ {
349
+ variants,
350
+ initial: "initial",
351
+ animate: "animate",
352
+ exit: "exit",
353
+ style: positionStyle,
354
+ className: "pointer-events-auto",
355
+ onClick: overlay.dismissOnClickOutside ? (e) => {
356
+ if (e.target === e.currentTarget) hide(overlay.id);
357
+ } : void 0,
358
+ children: /* @__PURE__ */ jsx2(
359
+ Component,
360
+ {
361
+ data: overlay.data,
362
+ overlayId: overlay.id,
363
+ onDismiss: () => hide(overlay.id)
364
+ }
365
+ )
366
+ },
367
+ overlay.id
368
+ );
369
+ }) }) });
370
+ }
371
+
372
+ // src/components/AvatarAgent.tsx
373
+ import { jsx as jsx3, jsxs } from "react/jsx-runtime";
374
+ function AvatarAgent({
375
+ config,
376
+ overlayComponents = {},
377
+ autoStart = false,
378
+ children
379
+ }) {
380
+ return /* @__PURE__ */ jsxs(AvatarProvider, { agent: config, autoStart, children: [
381
+ children,
382
+ /* @__PURE__ */ jsx3(OverlayContainer, { components: overlayComponents })
383
+ ] });
384
+ }
385
+
386
+ // src/components/AvatarFirst.tsx
387
+ import { useState as useState7, useEffect as useEffect5, useCallback as useCallback7, useMemo as useMemo5, useRef as useRef4 } from "react";
388
+ import {
389
+ AvatarFirstClient as AvatarFirstClient2,
390
+ HeyGenAdapter,
391
+ createAvatarAgent
392
+ } from "@avatarfirst/core";
393
+
394
+ // src/components/AvatarView.tsx
395
+ import { useRef as useRef2, useEffect as useEffect4 } from "react";
396
+ import { motion as motion3 } from "framer-motion";
397
+
398
+ // src/components/AvatarFallback.tsx
399
+ import { motion as motion2 } from "framer-motion";
400
+ import { jsx as jsx4, jsxs as jsxs2 } from "react/jsx-runtime";
401
+ function AvatarFallback({ size, state }) {
402
+ const isSmall = size === "small";
403
+ const isMedium = size === "medium";
404
+ const faceSize = isSmall ? "w-12 h-12" : isMedium ? "w-24 h-24" : "w-full h-full";
405
+ const eyeSize = isSmall ? 4 : isMedium ? 6 : 12;
406
+ const mouthWidth = isSmall ? 15 : isMedium ? 25 : 50;
407
+ if (state === "connecting") {
408
+ return /* @__PURE__ */ jsxs2("div", { className: "relative w-full h-full flex items-center justify-center", children: [
409
+ /* @__PURE__ */ jsx4(
410
+ motion2.div,
411
+ {
412
+ className: "absolute inset-4 rounded-full border-4 border-blue-500/30",
413
+ animate: { scale: [1, 1.2, 1], opacity: [0.5, 0, 0.5] },
414
+ transition: { duration: 2, repeat: Infinity }
415
+ }
416
+ ),
417
+ /* @__PURE__ */ jsx4(
418
+ motion2.div,
419
+ {
420
+ className: "absolute inset-8 rounded-full border-4 border-purple-500/30",
421
+ animate: { scale: [1, 1.3, 1], opacity: [0.5, 0, 0.5] },
422
+ transition: { duration: 2, repeat: Infinity, delay: 0.3 }
423
+ }
424
+ ),
425
+ /* @__PURE__ */ jsx4(
426
+ motion2.div,
427
+ {
428
+ className: "w-10 h-10 border-4 border-white/30 border-t-white rounded-full",
429
+ animate: { rotate: 360 },
430
+ transition: { duration: 1, repeat: Infinity, ease: "linear" }
431
+ }
432
+ )
433
+ ] });
434
+ }
435
+ return /* @__PURE__ */ jsxs2("div", { className: `relative ${faceSize}`, children: [
436
+ /* @__PURE__ */ jsx4(
437
+ motion2.div,
438
+ {
439
+ className: "absolute inset-0 rounded-full bg-gradient-to-br from-blue-400 via-purple-500 to-pink-500",
440
+ animate: {
441
+ background: [
442
+ "linear-gradient(135deg, #60a5fa, #a855f7, #ec4899)",
443
+ "linear-gradient(135deg, #a855f7, #ec4899, #60a5fa)",
444
+ "linear-gradient(135deg, #ec4899, #60a5fa, #a855f7)",
445
+ "linear-gradient(135deg, #60a5fa, #a855f7, #ec4899)"
446
+ ]
447
+ },
448
+ transition: { duration: 8, repeat: Infinity, ease: "linear" }
449
+ }
450
+ ),
451
+ /* @__PURE__ */ jsx4("div", { className: "absolute inset-2 rounded-full bg-gradient-to-br from-white/30 to-transparent" }),
452
+ /* @__PURE__ */ jsxs2("svg", { viewBox: "0 0 100 100", className: "absolute inset-0 w-full h-full", children: [
453
+ /* @__PURE__ */ jsx4(
454
+ motion2.ellipse,
455
+ {
456
+ cx: "35",
457
+ cy: "42",
458
+ rx: eyeSize,
459
+ ry: eyeSize,
460
+ fill: "white",
461
+ animate: { ry: [eyeSize, eyeSize * 0.1, eyeSize] },
462
+ transition: { duration: 0.15, repeat: Infinity, repeatDelay: 4, times: [0, 0.5, 1] }
463
+ }
464
+ ),
465
+ /* @__PURE__ */ jsx4(
466
+ motion2.circle,
467
+ {
468
+ cx: "35",
469
+ cy: "42",
470
+ r: eyeSize * 0.5,
471
+ fill: "#1e293b",
472
+ animate: { cx: [35, 37, 35, 33, 35] },
473
+ transition: { duration: 3, repeat: Infinity, ease: "easeInOut" }
474
+ }
475
+ ),
476
+ /* @__PURE__ */ jsx4("circle", { cx: "37", cy: "40", r: eyeSize * 0.2, fill: "white", opacity: "0.8" }),
477
+ /* @__PURE__ */ jsx4(
478
+ motion2.ellipse,
479
+ {
480
+ cx: "65",
481
+ cy: "42",
482
+ rx: eyeSize,
483
+ ry: eyeSize,
484
+ fill: "white",
485
+ animate: { ry: [eyeSize, eyeSize * 0.1, eyeSize] },
486
+ transition: { duration: 0.15, repeat: Infinity, repeatDelay: 4, times: [0, 0.5, 1] }
487
+ }
488
+ ),
489
+ /* @__PURE__ */ jsx4(
490
+ motion2.circle,
491
+ {
492
+ cx: "65",
493
+ cy: "42",
494
+ r: eyeSize * 0.5,
495
+ fill: "#1e293b",
496
+ animate: { cx: [65, 67, 65, 63, 65] },
497
+ transition: { duration: 3, repeat: Infinity, ease: "easeInOut" }
498
+ }
499
+ ),
500
+ /* @__PURE__ */ jsx4("circle", { cx: "67", cy: "40", r: eyeSize * 0.2, fill: "white", opacity: "0.8" }),
501
+ /* @__PURE__ */ jsx4(
502
+ motion2.path,
503
+ {
504
+ d: state === "speaking" ? `M ${50 - mouthWidth / 2} 62 Q 50 75 ${50 + mouthWidth / 2} 62` : `M ${50 - mouthWidth / 2} 65 Q 50 72 ${50 + mouthWidth / 2} 65`,
505
+ stroke: "white",
506
+ strokeWidth: isSmall ? 2 : isMedium ? 3 : 4,
507
+ fill: state === "speaking" ? "rgba(255,255,255,0.3)" : "none",
508
+ strokeLinecap: "round",
509
+ animate: state === "speaking" ? {
510
+ d: [
511
+ `M ${50 - mouthWidth / 2} 62 Q 50 75 ${50 + mouthWidth / 2} 62`,
512
+ `M ${50 - mouthWidth / 2} 62 Q 50 68 ${50 + mouthWidth / 2} 62`,
513
+ `M ${50 - mouthWidth / 2} 62 Q 50 78 ${50 + mouthWidth / 2} 62`,
514
+ `M ${50 - mouthWidth / 2} 62 Q 50 70 ${50 + mouthWidth / 2} 62`
515
+ ]
516
+ } : {},
517
+ transition: { duration: 0.3, repeat: Infinity, ease: "easeInOut" }
518
+ }
519
+ )
520
+ ] })
521
+ ] });
522
+ }
523
+
524
+ // src/components/AvatarView.tsx
525
+ import { jsx as jsx5, jsxs as jsxs3 } from "react/jsx-runtime";
526
+ var sizeClasses = {
527
+ small: "w-16 h-16",
528
+ medium: "w-32 h-32",
529
+ large: "w-64 h-64",
530
+ fullscreen: "w-full h-full",
531
+ hero: "w-[500px] h-[500px]"
532
+ };
533
+ function AvatarView({ size = "medium", showStatus = true, className = "" }) {
534
+ const { state, attachVideoElement } = useAvatar();
535
+ const videoRef = useRef2(null);
536
+ useEffect4(() => {
537
+ if (videoRef.current) {
538
+ attachVideoElement(videoRef.current);
539
+ }
540
+ }, [attachVideoElement]);
541
+ const isConnected = state === "connected" || state === "speaking" || state === "listening" || state === "thinking";
542
+ const isFullscreen = size === "fullscreen";
543
+ return /* @__PURE__ */ jsxs3("div", { className: `relative ${className}`, children: [
544
+ /* @__PURE__ */ jsxs3(
545
+ motion3.div,
546
+ {
547
+ className: `relative overflow-hidden ${isFullscreen ? "rounded-2xl" : "rounded-full"} ${sizeClasses[size]}`,
548
+ animate: {
549
+ scale: state === "speaking" ? [1, 1.02, 1] : 1
550
+ },
551
+ transition: {
552
+ duration: state === "speaking" ? 0.5 : 0.3,
553
+ repeat: state === "speaking" ? Infinity : 0
554
+ },
555
+ children: [
556
+ /* @__PURE__ */ jsx5(
557
+ "video",
558
+ {
559
+ ref: videoRef,
560
+ autoPlay: true,
561
+ playsInline: true,
562
+ className: `absolute inset-0 w-full h-full object-cover ${!isConnected ? "hidden" : ""}`
563
+ }
564
+ ),
565
+ !isConnected && /* @__PURE__ */ jsx5("div", { className: "absolute inset-0 flex items-center justify-center bg-gradient-to-br from-slate-800 to-slate-900", children: /* @__PURE__ */ jsx5(AvatarFallback, { state, size }) }),
566
+ state === "connecting" && /* @__PURE__ */ jsx5(
567
+ motion3.div,
568
+ {
569
+ className: "absolute inset-0 rounded-full border-4 border-yellow-500",
570
+ animate: { rotate: 360 },
571
+ transition: { duration: 2, repeat: Infinity, ease: "linear" },
572
+ style: { borderTopColor: "transparent", borderLeftColor: "transparent" }
573
+ }
574
+ ),
575
+ state === "connected" && /* @__PURE__ */ jsx5(
576
+ motion3.div,
577
+ {
578
+ className: "absolute inset-0 rounded-full border-4 border-green-500/50",
579
+ animate: { opacity: [0.3, 0.6, 0.3] },
580
+ transition: { duration: 2, repeat: Infinity }
581
+ }
582
+ ),
583
+ state === "speaking" && /* @__PURE__ */ jsx5(
584
+ motion3.div,
585
+ {
586
+ className: "absolute inset-0 rounded-full border-4 border-blue-400",
587
+ animate: { scale: [1, 1.03, 1], opacity: [0.7, 1, 0.7] },
588
+ transition: { duration: 0.5, repeat: Infinity }
589
+ }
590
+ )
591
+ ]
592
+ }
593
+ ),
594
+ showStatus && /* @__PURE__ */ jsx5(
595
+ motion3.div,
596
+ {
597
+ className: `absolute flex items-center gap-1 px-2 py-1 rounded-full text-xs font-medium shadow-lg ${state === "disconnected" ? "bg-slate-600 text-white" : state === "connecting" ? "bg-yellow-500 text-white" : state === "connected" ? "bg-green-500 text-white" : state === "speaking" ? "bg-blue-500 text-white" : state === "listening" ? "bg-purple-500 text-white" : "bg-slate-600 text-white"} ${size === "small" ? "-bottom-1 -right-1 text-[10px] px-1.5 py-0.5" : size === "medium" ? "bottom-0 right-0" : size === "large" ? "bottom-2 right-2" : "bottom-4 right-4 text-sm px-3 py-1.5"}`,
598
+ initial: { scale: 0 },
599
+ animate: { scale: 1 },
600
+ children: /* @__PURE__ */ jsx5("span", { className: "capitalize", children: state === "disconnected" ? "Offline" : state })
601
+ },
602
+ state
603
+ )
604
+ ] });
605
+ }
606
+
607
+ // src/components/overlays/CardOverlay.tsx
608
+ import { jsx as jsx6, jsxs as jsxs4 } from "react/jsx-runtime";
609
+ function CardOverlay({ data, onDismiss }) {
610
+ const { title, description, items, actions } = data ?? {};
611
+ return /* @__PURE__ */ jsxs4("div", { className: "bg-white rounded-2xl shadow-2xl p-6 min-w-[300px] max-w-md", children: [
612
+ title && /* @__PURE__ */ jsx6("h3", { className: "text-lg font-semibold mb-1", children: title }),
613
+ description && /* @__PURE__ */ jsx6("p", { className: "text-sm text-gray-500 mb-4", children: description }),
614
+ items && items.length > 0 && /* @__PURE__ */ jsx6("div", { className: "space-y-2 mb-4", children: items.map((item, i) => /* @__PURE__ */ jsxs4("div", { className: "flex items-center justify-between py-2 border-b border-gray-100 last:border-0", children: [
615
+ /* @__PURE__ */ jsx6("span", { className: "text-sm font-medium text-gray-700", children: item.label }),
616
+ item.value && /* @__PURE__ */ jsx6("span", { className: "text-sm text-gray-500", children: item.value })
617
+ ] }, i)) }),
618
+ /* @__PURE__ */ jsxs4("div", { className: "flex gap-2 justify-end", children: [
619
+ actions?.map((action, i) => /* @__PURE__ */ jsx6(
620
+ "button",
621
+ {
622
+ onClick: action.onClick,
623
+ className: "px-4 py-2 text-sm font-medium rounded-lg bg-blue-500 text-white hover:bg-blue-600 transition-colors",
624
+ children: action.label
625
+ },
626
+ i
627
+ )),
628
+ /* @__PURE__ */ jsx6(
629
+ "button",
630
+ {
631
+ onClick: onDismiss,
632
+ className: "px-4 py-2 text-sm font-medium rounded-lg bg-gray-100 text-gray-700 hover:bg-gray-200 transition-colors",
633
+ children: "Close"
634
+ }
635
+ )
636
+ ] })
637
+ ] });
638
+ }
639
+
640
+ // src/components/overlays/FormOverlay.tsx
641
+ import { useState as useState3 } from "react";
642
+ import { jsx as jsx7, jsxs as jsxs5 } from "react/jsx-runtime";
643
+ function FormOverlay({ data, onDismiss }) {
644
+ const { title, fields = [], submitLabel = "Submit", onSubmit } = data ?? {};
645
+ const [values, setValues] = useState3({});
646
+ const handleSubmit = (e) => {
647
+ e.preventDefault();
648
+ onSubmit?.(values);
649
+ onDismiss();
650
+ };
651
+ return /* @__PURE__ */ jsxs5("div", { className: "bg-white rounded-2xl shadow-2xl p-6 min-w-[320px] max-w-md", children: [
652
+ title && /* @__PURE__ */ jsx7("h3", { className: "text-lg font-semibold mb-4", children: title }),
653
+ /* @__PURE__ */ jsxs5("form", { onSubmit: handleSubmit, className: "space-y-3", children: [
654
+ fields.map((field) => /* @__PURE__ */ jsxs5("div", { children: [
655
+ /* @__PURE__ */ jsx7("label", { className: "block text-sm font-medium text-gray-700 mb-1", children: field.label }),
656
+ field.type === "textarea" ? /* @__PURE__ */ jsx7(
657
+ "textarea",
658
+ {
659
+ className: "w-full border border-gray-200 rounded-lg px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-blue-500",
660
+ placeholder: field.placeholder,
661
+ required: field.required,
662
+ value: values[field.name] ?? "",
663
+ onChange: (e) => setValues((v) => ({ ...v, [field.name]: e.target.value }))
664
+ }
665
+ ) : field.type === "select" ? /* @__PURE__ */ jsxs5(
666
+ "select",
667
+ {
668
+ className: "w-full border border-gray-200 rounded-lg px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-blue-500",
669
+ required: field.required,
670
+ value: values[field.name] ?? "",
671
+ onChange: (e) => setValues((v) => ({ ...v, [field.name]: e.target.value })),
672
+ children: [
673
+ /* @__PURE__ */ jsx7("option", { value: "", children: field.placeholder ?? "Select..." }),
674
+ field.options?.map((opt) => /* @__PURE__ */ jsx7("option", { value: opt, children: opt }, opt))
675
+ ]
676
+ }
677
+ ) : /* @__PURE__ */ jsx7(
678
+ "input",
679
+ {
680
+ type: field.type ?? "text",
681
+ className: "w-full border border-gray-200 rounded-lg px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-blue-500",
682
+ placeholder: field.placeholder,
683
+ required: field.required,
684
+ value: values[field.name] ?? "",
685
+ onChange: (e) => setValues((v) => ({ ...v, [field.name]: e.target.value }))
686
+ }
687
+ )
688
+ ] }, field.name)),
689
+ /* @__PURE__ */ jsxs5("div", { className: "flex gap-2 justify-end pt-2", children: [
690
+ /* @__PURE__ */ jsx7(
691
+ "button",
692
+ {
693
+ type: "submit",
694
+ className: "px-4 py-2 text-sm font-medium rounded-lg bg-blue-500 text-white hover:bg-blue-600 transition-colors",
695
+ children: submitLabel
696
+ }
697
+ ),
698
+ /* @__PURE__ */ jsx7(
699
+ "button",
700
+ {
701
+ type: "button",
702
+ onClick: onDismiss,
703
+ className: "px-4 py-2 text-sm font-medium rounded-lg bg-gray-100 text-gray-700 hover:bg-gray-200 transition-colors",
704
+ children: "Cancel"
705
+ }
706
+ )
707
+ ] })
708
+ ] })
709
+ ] });
710
+ }
711
+
712
+ // src/components/overlays/TableOverlay.tsx
713
+ import { jsx as jsx8, jsxs as jsxs6 } from "react/jsx-runtime";
714
+ function TableOverlay({ data, onDismiss }) {
715
+ const { title, columns = [], rows = [] } = data ?? {};
716
+ return /* @__PURE__ */ jsxs6("div", { className: "bg-white rounded-2xl shadow-2xl p-6 min-w-[400px] max-w-2xl overflow-x-auto", children: [
717
+ title && /* @__PURE__ */ jsxs6("div", { className: "flex items-center justify-between mb-4", children: [
718
+ /* @__PURE__ */ jsx8("h3", { className: "text-lg font-semibold", children: title }),
719
+ /* @__PURE__ */ jsx8("button", { onClick: onDismiss, className: "text-gray-400 hover:text-gray-600 text-sm", children: "Close" })
720
+ ] }),
721
+ /* @__PURE__ */ jsxs6("table", { className: "w-full", children: [
722
+ columns.length > 0 && /* @__PURE__ */ jsx8("thead", { children: /* @__PURE__ */ jsx8("tr", { className: "border-b", children: columns.map((col, i) => /* @__PURE__ */ jsx8("th", { className: "text-left py-2 px-3 text-sm font-medium text-gray-500", children: col }, i)) }) }),
723
+ /* @__PURE__ */ jsx8("tbody", { children: rows.map((row, i) => /* @__PURE__ */ jsx8("tr", { className: "border-b last:border-0 hover:bg-gray-50", children: row.map((cell, j) => /* @__PURE__ */ jsx8("td", { className: "py-2 px-3 text-sm text-gray-700", children: cell }, j)) }, i)) })
724
+ ] })
725
+ ] });
726
+ }
727
+
728
+ // src/components/overlays/ProgressOverlay.tsx
729
+ import { jsx as jsx9, jsxs as jsxs7 } from "react/jsx-runtime";
730
+ function ProgressOverlay({ data, onDismiss }) {
731
+ const { title, steps = [] } = data ?? {};
732
+ return /* @__PURE__ */ jsxs7("div", { className: "bg-white rounded-2xl shadow-2xl p-6 min-w-[280px] max-w-sm", children: [
733
+ title && /* @__PURE__ */ jsx9("h3", { className: "text-lg font-semibold mb-4", children: title }),
734
+ /* @__PURE__ */ jsx9("div", { className: "space-y-3", children: steps.map((step, i) => /* @__PURE__ */ jsxs7("div", { className: "flex items-center gap-3", children: [
735
+ /* @__PURE__ */ jsx9(
736
+ "div",
737
+ {
738
+ className: `w-8 h-8 rounded-full flex items-center justify-center text-sm font-medium ${step.completed ? "bg-green-500 text-white" : step.active ? "bg-blue-500 text-white" : "bg-gray-100 text-gray-400"}`,
739
+ children: step.completed ? "\u2713" : i + 1
740
+ }
741
+ ),
742
+ /* @__PURE__ */ jsx9("span", { className: `text-sm ${step.completed ? "text-gray-500 line-through" : step.active ? "text-gray-900 font-medium" : "text-gray-400"}`, children: step.label })
743
+ ] }, i)) }),
744
+ /* @__PURE__ */ jsx9(
745
+ "button",
746
+ {
747
+ onClick: onDismiss,
748
+ className: "mt-4 w-full py-2 text-sm font-medium rounded-lg bg-gray-100 text-gray-700 hover:bg-gray-200 transition-colors",
749
+ children: "Close"
750
+ }
751
+ )
752
+ ] });
753
+ }
754
+
755
+ // src/components/overlays/ConfirmOverlay.tsx
756
+ import { jsx as jsx10, jsxs as jsxs8 } from "react/jsx-runtime";
757
+ function ConfirmOverlay({ data, onDismiss }) {
758
+ const { title, message, confirmLabel = "Confirm", cancelLabel = "Cancel", onConfirm, onCancel } = data ?? {};
759
+ return /* @__PURE__ */ jsxs8("div", { className: "bg-white rounded-2xl shadow-2xl p-6 min-w-[300px] max-w-sm", children: [
760
+ title && /* @__PURE__ */ jsx10("h3", { className: "text-lg font-semibold mb-2", children: title }),
761
+ message && /* @__PURE__ */ jsx10("p", { className: "text-sm text-gray-500 mb-6", children: message }),
762
+ /* @__PURE__ */ jsxs8("div", { className: "flex gap-3", children: [
763
+ /* @__PURE__ */ jsx10(
764
+ "button",
765
+ {
766
+ onClick: () => {
767
+ onConfirm?.();
768
+ onDismiss();
769
+ },
770
+ className: "flex-1 py-2 text-sm font-medium rounded-lg bg-blue-500 text-white hover:bg-blue-600 transition-colors",
771
+ children: confirmLabel
772
+ }
773
+ ),
774
+ /* @__PURE__ */ jsx10(
775
+ "button",
776
+ {
777
+ onClick: () => {
778
+ onCancel?.();
779
+ onDismiss();
780
+ },
781
+ className: "flex-1 py-2 text-sm font-medium rounded-lg bg-gray-100 text-gray-700 hover:bg-gray-200 transition-colors",
782
+ children: cancelLabel
783
+ }
784
+ )
785
+ ] })
786
+ ] });
787
+ }
788
+
789
+ // src/components/overlays/ChatOverlay.tsx
790
+ import { useState as useState4 } from "react";
791
+ import { motion as motion4 } from "framer-motion";
792
+ import { jsx as jsx11, jsxs as jsxs9 } from "react/jsx-runtime";
793
+ function ChatOverlay({ data, onDismiss }) {
794
+ const { title = "Chat", messages: initialMessages = [], suggestions = [] } = data ?? {};
795
+ const [messages, setMessages] = useState4(initialMessages);
796
+ const [input, setInput] = useState4("");
797
+ const { speak } = useAvatar();
798
+ const handleSend = async () => {
799
+ if (!input.trim()) return;
800
+ const userMsg = {
801
+ id: Date.now().toString(),
802
+ role: "user",
803
+ content: input,
804
+ timestamp: (/* @__PURE__ */ new Date()).toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" })
805
+ };
806
+ setMessages((prev) => [...prev, userMsg]);
807
+ setInput("");
808
+ await speak(`I understand you want to ${input.toLowerCase()}. Let me help you with that.`);
809
+ const aiMsg = {
810
+ id: (Date.now() + 1).toString(),
811
+ role: "assistant",
812
+ content: `I'll help you ${input.toLowerCase()}. This action has been queued.`,
813
+ timestamp: (/* @__PURE__ */ new Date()).toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" })
814
+ };
815
+ setMessages((prev) => [...prev, aiMsg]);
816
+ };
817
+ return /* @__PURE__ */ jsxs9("div", { className: "bg-white rounded-2xl shadow-2xl overflow-hidden w-96 flex flex-col", style: { maxHeight: "500px" }, children: [
818
+ /* @__PURE__ */ jsxs9("div", { className: "bg-gradient-to-r from-blue-500 to-blue-600 p-4 flex items-center justify-between", children: [
819
+ /* @__PURE__ */ jsx11("h3", { className: "text-white font-semibold", children: title }),
820
+ /* @__PURE__ */ jsx11("button", { onClick: onDismiss, className: "text-white/70 hover:text-white text-sm", children: "Close" })
821
+ ] }),
822
+ /* @__PURE__ */ jsx11("div", { className: "flex-1 overflow-y-auto p-4 space-y-3", children: messages.length === 0 ? /* @__PURE__ */ jsxs9("div", { className: "text-center text-gray-400 py-8", children: [
823
+ /* @__PURE__ */ jsx11("p", { className: "font-medium mb-4", children: "How can I help?" }),
824
+ /* @__PURE__ */ jsx11("div", { className: "space-y-2", children: suggestions.map((s) => /* @__PURE__ */ jsx11(
825
+ "button",
826
+ {
827
+ className: "block w-full text-left text-sm p-2 rounded-lg bg-gray-100 hover:bg-gray-200 transition-colors",
828
+ onClick: () => setInput(s),
829
+ children: s
830
+ },
831
+ s
832
+ )) })
833
+ ] }) : messages.map((msg) => /* @__PURE__ */ jsx11(
834
+ motion4.div,
835
+ {
836
+ className: `flex ${msg.role === "user" ? "justify-end" : "justify-start"}`,
837
+ initial: { opacity: 0, y: 10 },
838
+ animate: { opacity: 1, y: 0 },
839
+ children: /* @__PURE__ */ jsxs9("div", { className: `max-w-[80%] p-3 rounded-2xl ${msg.role === "user" ? "bg-blue-500 text-white rounded-br-none" : "bg-gray-100 text-gray-800 rounded-bl-none"}`, children: [
840
+ /* @__PURE__ */ jsx11("p", { className: "text-sm", children: msg.content }),
841
+ msg.timestamp && /* @__PURE__ */ jsx11("p", { className: `text-xs mt-1 ${msg.role === "user" ? "text-blue-100" : "text-gray-400"}`, children: msg.timestamp })
842
+ ] })
843
+ },
844
+ msg.id
845
+ )) }),
846
+ /* @__PURE__ */ jsx11("div", { className: "p-3 border-t", children: /* @__PURE__ */ jsxs9("div", { className: "flex gap-2", children: [
847
+ /* @__PURE__ */ jsx11(
848
+ "input",
849
+ {
850
+ value: input,
851
+ onChange: (e) => setInput(e.target.value),
852
+ onKeyDown: (e) => e.key === "Enter" && handleSend(),
853
+ placeholder: "Type a message...",
854
+ className: "flex-1 border border-gray-200 rounded-lg px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-blue-500"
855
+ }
856
+ ),
857
+ /* @__PURE__ */ jsx11(
858
+ "button",
859
+ {
860
+ onClick: handleSend,
861
+ disabled: !input.trim(),
862
+ className: "px-4 py-2 text-sm font-medium rounded-lg bg-blue-500 text-white hover:bg-blue-600 disabled:opacity-50 transition-colors",
863
+ children: "Send"
864
+ }
865
+ )
866
+ ] }) })
867
+ ] });
868
+ }
869
+
870
+ // src/components/overlays/InsightOverlay.tsx
871
+ import { motion as motion5 } from "framer-motion";
872
+ import { jsx as jsx12, jsxs as jsxs10 } from "react/jsx-runtime";
873
+ var insightStyles = {
874
+ recommendation: { bg: "bg-green-50", border: "border-green-200", icon: "\u2191", iconColor: "text-green-500" },
875
+ comparison: { bg: "bg-blue-50", border: "border-blue-200", icon: "\u2261", iconColor: "text-blue-500" },
876
+ warning: { bg: "bg-yellow-50", border: "border-yellow-200", icon: "\u26A0", iconColor: "text-yellow-500" }
877
+ };
878
+ function InsightOverlay({ data, onDismiss }) {
879
+ const { title, subtitle, insights = [], actions = [], onAction } = data ?? {};
880
+ return /* @__PURE__ */ jsxs10("div", { className: "bg-white rounded-2xl shadow-2xl p-6 min-w-[320px] max-w-md", children: [
881
+ /* @__PURE__ */ jsxs10("div", { className: "flex items-center justify-between mb-4", children: [
882
+ /* @__PURE__ */ jsxs10("div", { children: [
883
+ title && /* @__PURE__ */ jsx12("h3", { className: "text-lg font-semibold", children: title }),
884
+ subtitle && /* @__PURE__ */ jsx12("p", { className: "text-sm text-gray-500", children: subtitle })
885
+ ] }),
886
+ /* @__PURE__ */ jsx12("button", { onClick: onDismiss, className: "text-gray-400 hover:text-gray-600 text-sm", children: "Close" })
887
+ ] }),
888
+ /* @__PURE__ */ jsx12("div", { className: "space-y-3", children: insights.map((insight, i) => {
889
+ const style = insightStyles[insight.type];
890
+ return /* @__PURE__ */ jsx12(
891
+ motion5.div,
892
+ {
893
+ className: `p-3 rounded-lg border ${style.bg} ${style.border}`,
894
+ initial: { opacity: 0, x: -10 },
895
+ animate: { opacity: 1, x: 0 },
896
+ transition: { delay: i * 0.1 },
897
+ children: /* @__PURE__ */ jsxs10("div", { className: "flex items-start gap-2", children: [
898
+ /* @__PURE__ */ jsx12("span", { className: style.iconColor, children: style.icon }),
899
+ /* @__PURE__ */ jsxs10("div", { className: "flex-1", children: [
900
+ /* @__PURE__ */ jsx12("p", { className: "text-sm", children: insight.text }),
901
+ insight.confidence != null && /* @__PURE__ */ jsxs10("p", { className: "text-xs text-gray-400 mt-1", children: [
902
+ "Confidence: ",
903
+ Math.round(insight.confidence * 100),
904
+ "%"
905
+ ] })
906
+ ] })
907
+ ] })
908
+ },
909
+ i
910
+ );
911
+ }) }),
912
+ actions.length > 0 && /* @__PURE__ */ jsx12("div", { className: "mt-4 pt-3 border-t flex flex-wrap gap-2", children: actions.map((action) => /* @__PURE__ */ jsx12(
913
+ "button",
914
+ {
915
+ onClick: () => onAction?.(action),
916
+ className: "text-xs px-3 py-1.5 rounded-full bg-purple-100 text-purple-700 hover:bg-purple-200 transition-colors",
917
+ children: action
918
+ },
919
+ action
920
+ )) })
921
+ ] });
922
+ }
923
+
924
+ // src/components/overlays/DetailCardOverlay.tsx
925
+ import { jsx as jsx13, jsxs as jsxs11 } from "react/jsx-runtime";
926
+ function DetailCardOverlay({ data, onDismiss }) {
927
+ const { title, body, image, metadata, actions } = data ?? {};
928
+ return /* @__PURE__ */ jsxs11("div", { className: "bg-white rounded-2xl shadow-2xl p-6 min-w-[320px] max-w-lg", children: [
929
+ image && /* @__PURE__ */ jsx13(
930
+ "img",
931
+ {
932
+ src: image,
933
+ alt: title ?? "Detail",
934
+ className: "w-full h-48 object-cover rounded-xl mb-4"
935
+ }
936
+ ),
937
+ title && /* @__PURE__ */ jsx13("h3", { className: "text-lg font-semibold mb-1", children: title }),
938
+ body && /* @__PURE__ */ jsx13("p", { className: "text-sm text-gray-600 mb-4 leading-relaxed", children: body }),
939
+ metadata && Object.keys(metadata).length > 0 && /* @__PURE__ */ jsx13("div", { className: "space-y-1.5 mb-4", children: Object.entries(metadata).map(([key, value]) => /* @__PURE__ */ jsxs11("div", { className: "flex items-center justify-between py-1.5 border-b border-gray-100 last:border-0", children: [
940
+ /* @__PURE__ */ jsx13("span", { className: "text-xs font-medium text-gray-400 uppercase tracking-wider", children: key }),
941
+ /* @__PURE__ */ jsx13("span", { className: "text-sm text-gray-700", children: value })
942
+ ] }, key)) }),
943
+ /* @__PURE__ */ jsxs11("div", { className: "flex gap-2 justify-end", children: [
944
+ actions?.map((action, i) => /* @__PURE__ */ jsx13(
945
+ "button",
946
+ {
947
+ onClick: action.onClick,
948
+ className: "px-4 py-2 text-sm font-medium rounded-lg bg-blue-500 text-white hover:bg-blue-600 transition-colors",
949
+ children: action.label
950
+ },
951
+ i
952
+ )),
953
+ /* @__PURE__ */ jsx13(
954
+ "button",
955
+ {
956
+ onClick: onDismiss,
957
+ className: "px-4 py-2 text-sm font-medium rounded-lg bg-gray-100 text-gray-700 hover:bg-gray-200 transition-colors",
958
+ children: "Close"
959
+ }
960
+ )
961
+ ] })
962
+ ] });
963
+ }
964
+
965
+ // src/components/overlays/ComparisonOverlay.tsx
966
+ import { jsx as jsx14, jsxs as jsxs12 } from "react/jsx-runtime";
967
+ function ComparisonOverlay({ data, onDismiss }) {
968
+ const { title, items } = data ?? {};
969
+ const attributeKeys = items?.length ? [...new Set(items.flatMap((item) => Object.keys(item.attributes)))] : [];
970
+ return /* @__PURE__ */ jsxs12("div", { className: "bg-white rounded-2xl shadow-2xl p-6 min-w-[400px] max-w-2xl", children: [
971
+ title && /* @__PURE__ */ jsx14("h3", { className: "text-lg font-semibold mb-4", children: title }),
972
+ items && items.length > 0 && /* @__PURE__ */ jsx14("div", { className: "overflow-x-auto mb-4", children: /* @__PURE__ */ jsxs12("table", { className: "w-full text-sm", children: [
973
+ /* @__PURE__ */ jsx14("thead", { children: /* @__PURE__ */ jsxs12("tr", { className: "border-b border-gray-200", children: [
974
+ /* @__PURE__ */ jsx14("th", { className: "text-left py-2 pr-4 text-gray-400 font-medium text-xs uppercase tracking-wider", children: "Feature" }),
975
+ items.map((item) => /* @__PURE__ */ jsx14("th", { className: "text-center py-2 px-3 font-semibold text-gray-800", children: item.name }, item.name))
976
+ ] }) }),
977
+ /* @__PURE__ */ jsx14("tbody", { children: attributeKeys.map((key, i) => /* @__PURE__ */ jsxs12("tr", { className: i % 2 === 0 ? "bg-gray-50" : "", children: [
978
+ /* @__PURE__ */ jsx14("td", { className: "py-2 pr-4 text-gray-600 font-medium", children: key }),
979
+ items.map((item) => /* @__PURE__ */ jsx14("td", { className: "text-center py-2 px-3 text-gray-700", children: String(item.attributes[key] ?? "\u2014") }, item.name))
980
+ ] }, key)) })
981
+ ] }) }),
982
+ /* @__PURE__ */ jsx14("div", { className: "flex justify-end", children: /* @__PURE__ */ jsx14(
983
+ "button",
984
+ {
985
+ onClick: onDismiss,
986
+ className: "px-4 py-2 text-sm font-medium rounded-lg bg-gray-100 text-gray-700 hover:bg-gray-200 transition-colors",
987
+ children: "Close"
988
+ }
989
+ ) })
990
+ ] });
991
+ }
992
+
993
+ // src/components/overlays/CalendarOverlay.tsx
994
+ import { useState as useState5, useMemo as useMemo3 } from "react";
995
+ import { Fragment, jsx as jsx15, jsxs as jsxs13 } from "react/jsx-runtime";
996
+ function CalendarOverlay({ data, onDismiss }) {
997
+ const { title, available_slots, onSelect } = data ?? {};
998
+ const [selectedDate, setSelectedDate] = useState5(null);
999
+ const [selectedSlot, setSelectedSlot] = useState5(null);
1000
+ const groupedSlots = useMemo3(() => {
1001
+ if (!available_slots) return /* @__PURE__ */ new Map();
1002
+ const groups = /* @__PURE__ */ new Map();
1003
+ for (const slot of available_slots) {
1004
+ const existing = groups.get(slot.date) ?? [];
1005
+ existing.push(slot);
1006
+ groups.set(slot.date, existing);
1007
+ }
1008
+ return groups;
1009
+ }, [available_slots]);
1010
+ const dates = [...groupedSlots.keys()];
1011
+ const activeDate = selectedDate ?? dates[0] ?? null;
1012
+ const slotsForDate = activeDate ? groupedSlots.get(activeDate) ?? [] : [];
1013
+ const handleConfirm = () => {
1014
+ if (selectedSlot) {
1015
+ onSelect?.(selectedSlot);
1016
+ onDismiss();
1017
+ }
1018
+ };
1019
+ return /* @__PURE__ */ jsxs13("div", { className: "bg-white rounded-2xl shadow-2xl p-6 min-w-[280px] max-w-sm", children: [
1020
+ title && /* @__PURE__ */ jsx15("h3", { className: "text-lg font-semibold mb-4", children: title }),
1021
+ dates.length > 0 && /* @__PURE__ */ jsxs13(Fragment, { children: [
1022
+ /* @__PURE__ */ jsx15("div", { className: "flex gap-2 overflow-x-auto pb-2 mb-3", children: dates.map((date) => /* @__PURE__ */ jsx15(
1023
+ "button",
1024
+ {
1025
+ onClick: () => {
1026
+ setSelectedDate(date);
1027
+ setSelectedSlot(null);
1028
+ },
1029
+ className: `px-3 py-1.5 text-xs font-medium rounded-full whitespace-nowrap transition-colors ${activeDate === date ? "bg-blue-500 text-white" : "bg-gray-100 text-gray-600 hover:bg-gray-200"}`,
1030
+ children: date
1031
+ },
1032
+ date
1033
+ )) }),
1034
+ /* @__PURE__ */ jsx15("div", { className: "flex flex-wrap gap-2 mb-4", children: slotsForDate.map((slot, i) => {
1035
+ const isAvailable = slot.available !== false;
1036
+ const isSelected = selectedSlot === slot;
1037
+ return /* @__PURE__ */ jsx15(
1038
+ "button",
1039
+ {
1040
+ disabled: !isAvailable,
1041
+ onClick: () => setSelectedSlot(slot),
1042
+ className: `px-3 py-1.5 text-sm rounded-lg transition-colors ${!isAvailable ? "bg-gray-50 text-gray-300 cursor-not-allowed" : isSelected ? "bg-blue-500 text-white ring-2 ring-blue-300" : "bg-gray-100 text-gray-700 hover:bg-gray-200"}`,
1043
+ children: slot.time
1044
+ },
1045
+ i
1046
+ );
1047
+ }) })
1048
+ ] }),
1049
+ /* @__PURE__ */ jsxs13("div", { className: "flex gap-2 justify-end", children: [
1050
+ selectedSlot && /* @__PURE__ */ jsx15(
1051
+ "button",
1052
+ {
1053
+ onClick: handleConfirm,
1054
+ className: "px-4 py-2 text-sm font-medium rounded-lg bg-blue-500 text-white hover:bg-blue-600 transition-colors",
1055
+ children: "Confirm"
1056
+ }
1057
+ ),
1058
+ /* @__PURE__ */ jsx15(
1059
+ "button",
1060
+ {
1061
+ onClick: onDismiss,
1062
+ className: "px-4 py-2 text-sm font-medium rounded-lg bg-gray-100 text-gray-700 hover:bg-gray-200 transition-colors",
1063
+ children: "Close"
1064
+ }
1065
+ )
1066
+ ] })
1067
+ ] });
1068
+ }
1069
+
1070
+ // src/components/overlays/MediaOverlay.tsx
1071
+ import { jsx as jsx16, jsxs as jsxs14 } from "react/jsx-runtime";
1072
+ function MediaOverlay({ data, onDismiss }) {
1073
+ const { title, url, type = "image", alt, autoplay } = data ?? {};
1074
+ return /* @__PURE__ */ jsxs14("div", { className: "bg-white rounded-2xl shadow-2xl p-6 min-w-[320px] max-w-xl", children: [
1075
+ title && /* @__PURE__ */ jsx16("h3", { className: "text-lg font-semibold mb-4", children: title }),
1076
+ url && /* @__PURE__ */ jsxs14("div", { className: "mb-4 rounded-xl overflow-hidden bg-gray-100", children: [
1077
+ type === "video" && /* @__PURE__ */ jsx16(
1078
+ "video",
1079
+ {
1080
+ src: url,
1081
+ controls: true,
1082
+ autoPlay: autoplay,
1083
+ className: "w-full max-h-80 object-contain"
1084
+ }
1085
+ ),
1086
+ type === "audio" && /* @__PURE__ */ jsx16("div", { className: "p-6 flex items-center justify-center", children: /* @__PURE__ */ jsx16("audio", { src: url, controls: true, autoPlay: autoplay, className: "w-full" }) }),
1087
+ type === "image" && /* @__PURE__ */ jsx16(
1088
+ "img",
1089
+ {
1090
+ src: url,
1091
+ alt: alt ?? title ?? "Media",
1092
+ className: "w-full max-h-80 object-contain"
1093
+ }
1094
+ )
1095
+ ] }),
1096
+ /* @__PURE__ */ jsx16("div", { className: "flex justify-end", children: /* @__PURE__ */ jsx16(
1097
+ "button",
1098
+ {
1099
+ onClick: onDismiss,
1100
+ className: "px-4 py-2 text-sm font-medium rounded-lg bg-gray-100 text-gray-700 hover:bg-gray-200 transition-colors",
1101
+ children: "Close"
1102
+ }
1103
+ ) })
1104
+ ] });
1105
+ }
1106
+
1107
+ // src/components/overlays/MapOverlay.tsx
1108
+ import { jsx as jsx17, jsxs as jsxs15 } from "react/jsx-runtime";
1109
+ function MapOverlay({ data, onDismiss }) {
1110
+ const { title, locations } = data ?? {};
1111
+ const getMapsUrl = (loc) => `https://www.google.com/maps/search/?api=1&query=${loc.lat},${loc.lng}`;
1112
+ return /* @__PURE__ */ jsxs15("div", { className: "bg-white rounded-2xl shadow-2xl p-6 min-w-[280px] max-w-md", children: [
1113
+ title && /* @__PURE__ */ jsx17("h3", { className: "text-lg font-semibold mb-4", children: title }),
1114
+ locations && locations.length > 0 && /* @__PURE__ */ jsx17("div", { className: "space-y-3 mb-4", children: locations.map((loc, i) => /* @__PURE__ */ jsxs15("div", { className: "flex items-start gap-3 p-3 rounded-xl bg-gray-50", children: [
1115
+ /* @__PURE__ */ jsx17("div", { className: "w-8 h-8 rounded-full bg-red-100 flex items-center justify-center flex-shrink-0", children: /* @__PURE__ */ jsx17("svg", { className: "w-4 h-4 text-red-500", fill: "currentColor", viewBox: "0 0 20 20", children: /* @__PURE__ */ jsx17("path", { fillRule: "evenodd", d: "M5.05 4.05a7 7 0 119.9 9.9L10 18.9l-4.95-4.95a7 7 0 010-9.9zM10 11a2 2 0 100-4 2 2 0 000 4z", clipRule: "evenodd" }) }) }),
1116
+ /* @__PURE__ */ jsxs15("div", { className: "flex-1 min-w-0", children: [
1117
+ loc.label && /* @__PURE__ */ jsx17("p", { className: "text-sm font-medium text-gray-800", children: loc.label }),
1118
+ /* @__PURE__ */ jsxs15("p", { className: "text-xs text-gray-400", children: [
1119
+ loc.lat.toFixed(4),
1120
+ ", ",
1121
+ loc.lng.toFixed(4)
1122
+ ] }),
1123
+ /* @__PURE__ */ jsx17(
1124
+ "a",
1125
+ {
1126
+ href: getMapsUrl(loc),
1127
+ target: "_blank",
1128
+ rel: "noopener noreferrer",
1129
+ className: "text-xs text-blue-500 hover:text-blue-600 font-medium",
1130
+ children: "Open in Maps \u2192"
1131
+ }
1132
+ )
1133
+ ] })
1134
+ ] }, i)) }),
1135
+ /* @__PURE__ */ jsx17("div", { className: "flex justify-end", children: /* @__PURE__ */ jsx17(
1136
+ "button",
1137
+ {
1138
+ onClick: onDismiss,
1139
+ className: "px-4 py-2 text-sm font-medium rounded-lg bg-gray-100 text-gray-700 hover:bg-gray-200 transition-colors",
1140
+ children: "Close"
1141
+ }
1142
+ ) })
1143
+ ] });
1144
+ }
1145
+
1146
+ // src/components/overlays/RichTextOverlay.tsx
1147
+ import { useMemo as useMemo4 } from "react";
1148
+ import { jsx as jsx18, jsxs as jsxs16 } from "react/jsx-runtime";
1149
+ function parseMarkdown(md) {
1150
+ let html = md.replace(/```(\w*)\n([\s\S]*?)```/g, '<pre class="bg-gray-900 text-gray-100 rounded-lg p-3 text-xs overflow-x-auto my-2"><code>$2</code></pre>').replace(/`([^`]+)`/g, '<code class="bg-gray-100 text-gray-800 px-1 py-0.5 rounded text-xs">$1</code>').replace(/^### (.+)$/gm, '<h4 class="text-base font-semibold mt-3 mb-1">$1</h4>').replace(/^## (.+)$/gm, '<h3 class="text-lg font-semibold mt-4 mb-1">$1</h3>').replace(/^# (.+)$/gm, '<h2 class="text-xl font-bold mt-4 mb-2">$1</h2>').replace(/\*\*(.+?)\*\*/g, "<strong>$1</strong>").replace(/\*(.+?)\*/g, "<em>$1</em>").replace(/\[([^\]]+)\]\(([^)]+)\)/g, '<a href="$2" target="_blank" rel="noopener noreferrer" class="text-blue-500 hover:text-blue-600 underline">$1</a>').replace(/^- (.+)$/gm, '<li class="ml-4 list-disc text-sm">$1</li>').replace(/^(?!<[hlupao])(.*\S.*)$/gm, '<p class="text-sm text-gray-600 my-1">$1</p>');
1151
+ return html;
1152
+ }
1153
+ function RichTextOverlay({ data, onDismiss }) {
1154
+ const { title, content } = data ?? {};
1155
+ const html = useMemo4(() => content ? parseMarkdown(content) : "", [content]);
1156
+ return /* @__PURE__ */ jsxs16("div", { className: "bg-white rounded-2xl shadow-2xl p-6 min-w-[320px] max-w-lg", children: [
1157
+ title && /* @__PURE__ */ jsx18("h3", { className: "text-lg font-semibold mb-4", children: title }),
1158
+ html && /* @__PURE__ */ jsx18(
1159
+ "div",
1160
+ {
1161
+ className: "max-h-96 overflow-y-auto mb-4",
1162
+ dangerouslySetInnerHTML: { __html: html }
1163
+ }
1164
+ ),
1165
+ /* @__PURE__ */ jsx18("div", { className: "flex justify-end", children: /* @__PURE__ */ jsx18(
1166
+ "button",
1167
+ {
1168
+ onClick: onDismiss,
1169
+ className: "px-4 py-2 text-sm font-medium rounded-lg bg-gray-100 text-gray-700 hover:bg-gray-200 transition-colors",
1170
+ children: "Close"
1171
+ }
1172
+ ) })
1173
+ ] });
1174
+ }
1175
+
1176
+ // src/components/overlays/ChartOverlay.tsx
1177
+ import { jsx as jsx19, jsxs as jsxs17 } from "react/jsx-runtime";
1178
+ var DEFAULT_COLORS = ["#3b82f6", "#8b5cf6", "#ec4899", "#f59e0b", "#10b981", "#06b6d4", "#ef4444", "#84cc16"];
1179
+ function BarChart({ series }) {
1180
+ const max = Math.max(...series.map((s) => s.value), 1);
1181
+ return /* @__PURE__ */ jsx19("div", { className: "space-y-2", children: series.map((item, i) => /* @__PURE__ */ jsxs17("div", { className: "flex items-center gap-3", children: [
1182
+ /* @__PURE__ */ jsx19("span", { className: "text-xs text-gray-500 w-20 truncate text-right", children: item.label }),
1183
+ /* @__PURE__ */ jsx19("div", { className: "flex-1 h-6 bg-gray-100 rounded-full overflow-hidden", children: /* @__PURE__ */ jsx19(
1184
+ "div",
1185
+ {
1186
+ className: "h-full rounded-full transition-all duration-500",
1187
+ style: {
1188
+ width: `${item.value / max * 100}%`,
1189
+ backgroundColor: item.color ?? DEFAULT_COLORS[i % DEFAULT_COLORS.length]
1190
+ }
1191
+ }
1192
+ ) }),
1193
+ /* @__PURE__ */ jsx19("span", { className: "text-xs font-medium text-gray-700 w-12", children: item.value })
1194
+ ] }, i)) });
1195
+ }
1196
+ function PieChart({ series }) {
1197
+ const total = series.reduce((sum, s) => sum + s.value, 0) || 1;
1198
+ const radius = 60;
1199
+ const circumference = 2 * Math.PI * radius;
1200
+ let offset = 0;
1201
+ return /* @__PURE__ */ jsxs17("div", { className: "flex items-center gap-6", children: [
1202
+ /* @__PURE__ */ jsx19("svg", { width: "160", height: "160", viewBox: "0 0 160 160", className: "flex-shrink-0", children: series.map((item, i) => {
1203
+ const dash = item.value / total * circumference;
1204
+ const currentOffset = offset;
1205
+ offset += dash;
1206
+ return /* @__PURE__ */ jsx19(
1207
+ "circle",
1208
+ {
1209
+ cx: "80",
1210
+ cy: "80",
1211
+ r: radius,
1212
+ fill: "none",
1213
+ stroke: item.color ?? DEFAULT_COLORS[i % DEFAULT_COLORS.length],
1214
+ strokeWidth: "24",
1215
+ strokeDasharray: `${dash} ${circumference - dash}`,
1216
+ strokeDashoffset: -currentOffset,
1217
+ transform: "rotate(-90 80 80)"
1218
+ },
1219
+ i
1220
+ );
1221
+ }) }),
1222
+ /* @__PURE__ */ jsx19("div", { className: "space-y-1.5", children: series.map((item, i) => /* @__PURE__ */ jsxs17("div", { className: "flex items-center gap-2", children: [
1223
+ /* @__PURE__ */ jsx19(
1224
+ "div",
1225
+ {
1226
+ className: "w-3 h-3 rounded-sm flex-shrink-0",
1227
+ style: { backgroundColor: item.color ?? DEFAULT_COLORS[i % DEFAULT_COLORS.length] }
1228
+ }
1229
+ ),
1230
+ /* @__PURE__ */ jsx19("span", { className: "text-xs text-gray-600", children: item.label }),
1231
+ /* @__PURE__ */ jsxs17("span", { className: "text-xs font-medium text-gray-800", children: [
1232
+ Math.round(item.value / total * 100),
1233
+ "%"
1234
+ ] })
1235
+ ] }, i)) })
1236
+ ] });
1237
+ }
1238
+ function LineChart({ series }) {
1239
+ const max = Math.max(...series.map((s) => s.value), 1);
1240
+ const width = 300;
1241
+ const height = 120;
1242
+ const padding = 8;
1243
+ const points = series.map((item, i) => ({
1244
+ x: padding + i / Math.max(series.length - 1, 1) * (width - padding * 2),
1245
+ y: height - padding - item.value / max * (height - padding * 2)
1246
+ }));
1247
+ const pathD = points.map((p, i) => `${i === 0 ? "M" : "L"} ${p.x} ${p.y}`).join(" ");
1248
+ return /* @__PURE__ */ jsxs17("div", { children: [
1249
+ /* @__PURE__ */ jsxs17("svg", { width, height, viewBox: `0 0 ${width} ${height}`, className: "w-full", children: [
1250
+ /* @__PURE__ */ jsx19("path", { d: pathD, fill: "none", stroke: "#3b82f6", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round" }),
1251
+ points.map((p, i) => /* @__PURE__ */ jsx19("circle", { cx: p.x, cy: p.y, r: "3", fill: "#3b82f6" }, i))
1252
+ ] }),
1253
+ /* @__PURE__ */ jsx19("div", { className: "flex justify-between mt-1", children: series.map((item, i) => /* @__PURE__ */ jsx19("span", { className: "text-[10px] text-gray-400", children: item.label }, i)) })
1254
+ ] });
1255
+ }
1256
+ function ChartOverlay({ data, onDismiss }) {
1257
+ const { title, type = "bar", series } = data ?? {};
1258
+ return /* @__PURE__ */ jsxs17("div", { className: "bg-white rounded-2xl shadow-2xl p-6 min-w-[280px] max-w-md", children: [
1259
+ title && /* @__PURE__ */ jsx19("h3", { className: "text-lg font-semibold mb-4", children: title }),
1260
+ series && series.length > 0 && /* @__PURE__ */ jsxs17("div", { className: "mb-4", children: [
1261
+ type === "bar" && /* @__PURE__ */ jsx19(BarChart, { series }),
1262
+ type === "pie" && /* @__PURE__ */ jsx19(PieChart, { series }),
1263
+ type === "line" && /* @__PURE__ */ jsx19(LineChart, { series })
1264
+ ] }),
1265
+ /* @__PURE__ */ jsx19("div", { className: "flex justify-end", children: /* @__PURE__ */ jsx19(
1266
+ "button",
1267
+ {
1268
+ onClick: onDismiss,
1269
+ className: "px-4 py-2 text-sm font-medium rounded-lg bg-gray-100 text-gray-700 hover:bg-gray-200 transition-colors",
1270
+ children: "Close"
1271
+ }
1272
+ ) })
1273
+ ] });
1274
+ }
1275
+
1276
+ // src/components/overlays/FileUploadOverlay.tsx
1277
+ import { useRef as useRef3, useState as useState6, useCallback as useCallback6 } from "react";
1278
+ import { jsx as jsx20, jsxs as jsxs18 } from "react/jsx-runtime";
1279
+ var MIME_MAP = {
1280
+ pdf: "application/pdf",
1281
+ docx: "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
1282
+ doc: "application/msword",
1283
+ txt: "text/plain",
1284
+ csv: "text/csv",
1285
+ png: "image/png",
1286
+ jpg: "image/jpeg",
1287
+ jpeg: "image/jpeg",
1288
+ gif: "image/gif",
1289
+ svg: "image/svg+xml"
1290
+ };
1291
+ function formatSize(bytes) {
1292
+ if (bytes < 1024) return `${bytes} B`;
1293
+ if (bytes < 1048576) return `${(bytes / 1024).toFixed(1)} KB`;
1294
+ return `${(bytes / 1048576).toFixed(1)} MB`;
1295
+ }
1296
+ function FileUploadOverlay({ data, onDismiss }) {
1297
+ const { title, accept, max_size, multiple, onUpload } = data ?? {};
1298
+ const inputRef = useRef3(null);
1299
+ const [files, setFiles] = useState6([]);
1300
+ const [dragging, setDragging] = useState6(false);
1301
+ const acceptMime = accept?.map((ext) => MIME_MAP[ext.toLowerCase()] ?? `.${ext}`).join(",");
1302
+ const addFiles = useCallback6((newFiles) => {
1303
+ if (!newFiles) return;
1304
+ const arr = Array.from(newFiles).filter((f) => {
1305
+ if (max_size && f.size > max_size) return false;
1306
+ return true;
1307
+ });
1308
+ setFiles((prev) => multiple ? [...prev, ...arr] : arr.slice(0, 1));
1309
+ }, [max_size, multiple]);
1310
+ const removeFile = (index) => {
1311
+ setFiles((prev) => prev.filter((_, i) => i !== index));
1312
+ };
1313
+ const handleSubmit = () => {
1314
+ if (files.length > 0) {
1315
+ onUpload?.(files);
1316
+ onDismiss();
1317
+ }
1318
+ };
1319
+ return /* @__PURE__ */ jsxs18("div", { className: "bg-white rounded-2xl shadow-2xl p-6 min-w-[280px] max-w-sm", children: [
1320
+ title && /* @__PURE__ */ jsx20("h3", { className: "text-lg font-semibold mb-4", children: title }),
1321
+ /* @__PURE__ */ jsxs18(
1322
+ "div",
1323
+ {
1324
+ className: `border-2 border-dashed rounded-xl p-6 text-center cursor-pointer transition-colors mb-3 ${dragging ? "border-blue-400 bg-blue-50" : "border-gray-200 hover:border-gray-300"}`,
1325
+ onClick: () => inputRef.current?.click(),
1326
+ onDragOver: (e) => {
1327
+ e.preventDefault();
1328
+ setDragging(true);
1329
+ },
1330
+ onDragLeave: () => setDragging(false),
1331
+ onDrop: (e) => {
1332
+ e.preventDefault();
1333
+ setDragging(false);
1334
+ addFiles(e.dataTransfer.files);
1335
+ },
1336
+ children: [
1337
+ /* @__PURE__ */ jsx20("svg", { className: "w-8 h-8 text-gray-300 mx-auto mb-2", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ jsx20("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 1.5, d: "M7 16a4 4 0 01-.88-7.903A5 5 0 1115.9 6L16 6a5 5 0 011 9.9M15 13l-3-3m0 0l-3 3m3-3v12" }) }),
1338
+ /* @__PURE__ */ jsx20("p", { className: "text-sm text-gray-500", children: "Drop files here or click to browse" }),
1339
+ accept && /* @__PURE__ */ jsx20("p", { className: "text-xs text-gray-400 mt-1", children: accept.map((e) => `.${e}`).join(", ") }),
1340
+ max_size && /* @__PURE__ */ jsxs18("p", { className: "text-xs text-gray-400", children: [
1341
+ "Max ",
1342
+ formatSize(max_size)
1343
+ ] }),
1344
+ /* @__PURE__ */ jsx20(
1345
+ "input",
1346
+ {
1347
+ ref: inputRef,
1348
+ type: "file",
1349
+ className: "hidden",
1350
+ accept: acceptMime,
1351
+ multiple,
1352
+ onChange: (e) => addFiles(e.target.files)
1353
+ }
1354
+ )
1355
+ ]
1356
+ }
1357
+ ),
1358
+ files.length > 0 && /* @__PURE__ */ jsx20("div", { className: "space-y-2 mb-4", children: files.map((file, i) => /* @__PURE__ */ jsxs18("div", { className: "flex items-center justify-between py-1.5 px-3 bg-gray-50 rounded-lg", children: [
1359
+ /* @__PURE__ */ jsxs18("div", { className: "min-w-0", children: [
1360
+ /* @__PURE__ */ jsx20("p", { className: "text-sm text-gray-700 truncate", children: file.name }),
1361
+ /* @__PURE__ */ jsx20("p", { className: "text-xs text-gray-400", children: formatSize(file.size) })
1362
+ ] }),
1363
+ /* @__PURE__ */ jsx20("button", { onClick: () => removeFile(i), className: "text-gray-400 hover:text-red-500 text-sm ml-2", children: "\xD7" })
1364
+ ] }, i)) }),
1365
+ /* @__PURE__ */ jsxs18("div", { className: "flex gap-2 justify-end", children: [
1366
+ files.length > 0 && /* @__PURE__ */ jsx20(
1367
+ "button",
1368
+ {
1369
+ onClick: handleSubmit,
1370
+ className: "px-4 py-2 text-sm font-medium rounded-lg bg-blue-500 text-white hover:bg-blue-600 transition-colors",
1371
+ children: "Upload"
1372
+ }
1373
+ ),
1374
+ /* @__PURE__ */ jsx20(
1375
+ "button",
1376
+ {
1377
+ onClick: onDismiss,
1378
+ className: "px-4 py-2 text-sm font-medium rounded-lg bg-gray-100 text-gray-700 hover:bg-gray-200 transition-colors",
1379
+ children: "Close"
1380
+ }
1381
+ )
1382
+ ] })
1383
+ ] });
1384
+ }
1385
+
1386
+ // src/components/AvatarFirst.tsx
1387
+ import { jsx as jsx21, jsxs as jsxs19 } from "react/jsx-runtime";
1388
+ var defaultOverlays = {
1389
+ card: CardOverlay,
1390
+ form: FormOverlay,
1391
+ table: TableOverlay,
1392
+ progress: ProgressOverlay,
1393
+ confirm: ConfirmOverlay,
1394
+ chat: ChatOverlay,
1395
+ insight: InsightOverlay
1396
+ };
1397
+ function AvatarFirstInner({
1398
+ client,
1399
+ onOverlay,
1400
+ onAction
1401
+ }) {
1402
+ const { state: avatarState, speak } = useAvatar();
1403
+ const { show } = useOverlay();
1404
+ const [sessionId, setSessionId] = useState7(null);
1405
+ const [greeting, setGreeting] = useState7(null);
1406
+ const [hasGreeted, setHasGreeted] = useState7(false);
1407
+ const [isProcessing, setIsProcessing] = useState7(false);
1408
+ useEffect5(() => {
1409
+ if (avatarState === "connected" && !sessionId) {
1410
+ client.createSession().then((session) => {
1411
+ setSessionId(session.sessionId);
1412
+ setGreeting(session.greeting);
1413
+ }).catch((err) => {
1414
+ console.error("[AvatarFirst] Session init failed:", err);
1415
+ });
1416
+ }
1417
+ }, [avatarState, sessionId, client]);
1418
+ useEffect5(() => {
1419
+ if (sessionId && avatarState === "connected" && greeting && !hasGreeted) {
1420
+ setHasGreeted(true);
1421
+ speak(greeting);
1422
+ }
1423
+ }, [sessionId, avatarState, greeting, hasGreeted, speak]);
1424
+ const handleResponse = useCallback7((data) => {
1425
+ if (data.speech) {
1426
+ speak(data.speech);
1427
+ }
1428
+ if (data.overlay) {
1429
+ const overlayId = `overlay-${Date.now()}`;
1430
+ show({
1431
+ id: overlayId,
1432
+ component: data.overlay.type,
1433
+ data: data.overlay.data,
1434
+ position: "right-center",
1435
+ animation: "slide-left",
1436
+ dismissOnClickOutside: true
1437
+ });
1438
+ onOverlay?.(data.overlay);
1439
+ }
1440
+ if (data.actions) {
1441
+ for (const action of data.actions) {
1442
+ onAction?.(action);
1443
+ }
1444
+ }
1445
+ }, [speak, show, onOverlay, onAction]);
1446
+ const handleSend = useCallback7(async (text) => {
1447
+ if (!sessionId || isProcessing) return;
1448
+ setIsProcessing(true);
1449
+ try {
1450
+ const response = await client.sendMessage(sessionId, text);
1451
+ handleResponse(response);
1452
+ } catch (err) {
1453
+ console.error("[AvatarFirst] Message failed:", err);
1454
+ } finally {
1455
+ setIsProcessing(false);
1456
+ }
1457
+ }, [client, sessionId, isProcessing, handleResponse]);
1458
+ const isConnected = avatarState === "connected" || avatarState === "speaking" || avatarState === "listening" || avatarState === "thinking";
1459
+ return /* @__PURE__ */ jsxs19("div", { className: "relative w-full h-full", children: [
1460
+ /* @__PURE__ */ jsx21(AvatarView, { size: "fullscreen", showStatus: false, className: "w-full h-full" }),
1461
+ isConnected && sessionId && /* @__PURE__ */ jsx21("div", { className: "absolute bottom-4 left-1/2 -translate-x-1/2 z-20 w-full max-w-md px-4", children: /* @__PURE__ */ jsx21(
1462
+ "form",
1463
+ {
1464
+ onSubmit: (e) => {
1465
+ e.preventDefault();
1466
+ const input = e.currentTarget.elements.namedItem("message");
1467
+ if (input.value.trim()) {
1468
+ handleSend(input.value.trim());
1469
+ input.value = "";
1470
+ }
1471
+ },
1472
+ children: /* @__PURE__ */ jsx21(
1473
+ "input",
1474
+ {
1475
+ name: "message",
1476
+ type: "text",
1477
+ placeholder: "Type a message...",
1478
+ disabled: isProcessing,
1479
+ className: "w-full px-4 py-3 bg-white/10 backdrop-blur-md border border-white/20 rounded-full text-white placeholder-white/50 outline-none focus:border-white/40"
1480
+ }
1481
+ )
1482
+ }
1483
+ ) }),
1484
+ !isConnected && /* @__PURE__ */ jsx21("div", { className: "absolute inset-0 flex items-center justify-center bg-slate-950/80 z-30", children: /* @__PURE__ */ jsxs19("div", { className: "text-center", children: [
1485
+ /* @__PURE__ */ jsx21("div", { className: "w-12 h-12 border-2 border-purple-500 border-t-transparent rounded-full animate-spin mx-auto mb-4" }),
1486
+ /* @__PURE__ */ jsx21("p", { className: "text-white/60 text-sm", children: "Connecting..." })
1487
+ ] }) })
1488
+ ] });
1489
+ }
1490
+ function AvatarFirst({
1491
+ apiKey,
1492
+ baseUrl,
1493
+ onOverlay,
1494
+ onAction,
1495
+ onStateChange,
1496
+ className,
1497
+ overlayComponents
1498
+ }) {
1499
+ const [agent, setAgent] = useState7(null);
1500
+ const [error, setError] = useState7(false);
1501
+ const client = useMemo5(
1502
+ () => new AvatarFirstClient2({ apiKey, baseUrl }),
1503
+ [apiKey, baseUrl]
1504
+ );
1505
+ const mergedOverlays = useMemo5(
1506
+ () => ({ ...defaultOverlays, ...overlayComponents }),
1507
+ [overlayComponents]
1508
+ );
1509
+ const onStateChangeRef = useRef4(onStateChange);
1510
+ onStateChangeRef.current = onStateChange;
1511
+ useEffect5(() => {
1512
+ let cancelled = false;
1513
+ async function init() {
1514
+ try {
1515
+ onStateChangeRef.current?.("connecting");
1516
+ const session = await client.createSession();
1517
+ if (cancelled) return;
1518
+ const engine = new HeyGenAdapter();
1519
+ const avatarAgent = createAvatarAgent({
1520
+ engine,
1521
+ engineConfig: {
1522
+ serverUrl: baseUrl || "https://avatarfirst.dev",
1523
+ sessionToken: session.avatar?.token,
1524
+ language: "en",
1525
+ voiceChat: false
1526
+ },
1527
+ actions: []
1528
+ });
1529
+ setAgent(avatarAgent);
1530
+ onStateChangeRef.current?.("connected");
1531
+ } catch (err) {
1532
+ if (!cancelled) {
1533
+ console.error("[AvatarFirst] Init failed:", err);
1534
+ setError(true);
1535
+ onStateChangeRef.current?.("error");
1536
+ }
1537
+ }
1538
+ }
1539
+ init();
1540
+ return () => {
1541
+ cancelled = true;
1542
+ };
1543
+ }, [client, baseUrl]);
1544
+ useEffect5(() => {
1545
+ return () => {
1546
+ agent?.destroy();
1547
+ };
1548
+ }, [agent]);
1549
+ if (error || !agent) {
1550
+ if (error) {
1551
+ return /* @__PURE__ */ jsx21("div", { className: `flex items-center justify-center bg-slate-950 ${className || ""}`, children: /* @__PURE__ */ jsxs19("div", { className: "text-center", children: [
1552
+ /* @__PURE__ */ jsx21("p", { className: "text-red-400 mb-2 text-sm", children: "Failed to connect" }),
1553
+ /* @__PURE__ */ jsx21(
1554
+ "button",
1555
+ {
1556
+ onClick: () => window.location.reload(),
1557
+ className: "text-white/60 text-xs underline hover:text-white/80",
1558
+ children: "Retry"
1559
+ }
1560
+ )
1561
+ ] }) });
1562
+ }
1563
+ return /* @__PURE__ */ jsx21("div", { className: `flex items-center justify-center bg-slate-950 ${className || ""}`, children: /* @__PURE__ */ jsx21("div", { className: "w-12 h-12 border-2 border-purple-500 border-t-transparent rounded-full animate-spin" }) });
1564
+ }
1565
+ return /* @__PURE__ */ jsx21("div", { className, children: /* @__PURE__ */ jsxs19(AvatarProvider, { agent, autoStart: true, children: [
1566
+ /* @__PURE__ */ jsx21(OverlayContainer, { components: mergedOverlays }),
1567
+ /* @__PURE__ */ jsx21(AvatarFirstInner, { client, onOverlay, onAction })
1568
+ ] }) });
1569
+ }
1570
+
1571
+ // src/animations/variants.ts
1572
+ var pageTransition = {
1573
+ initial: { opacity: 0, x: -20 },
1574
+ animate: { opacity: 1, x: 0, transition: { duration: 0.3 } },
1575
+ exit: { opacity: 0, x: 20, transition: { duration: 0.2 } }
1576
+ };
1577
+ var fadeIn = {
1578
+ initial: { opacity: 0 },
1579
+ animate: { opacity: 1, transition: { duration: 0.5 } },
1580
+ exit: { opacity: 0 }
1581
+ };
1582
+ var fadeInUp = {
1583
+ initial: { opacity: 0, y: 20 },
1584
+ animate: { opacity: 1, y: 0, transition: { duration: 0.4 } }
1585
+ };
1586
+ var cardHover = {
1587
+ initial: { scale: 1, y: 0 },
1588
+ hover: {
1589
+ scale: 1.03,
1590
+ y: -8,
1591
+ transition: { type: "spring", stiffness: 300, damping: 20 }
1592
+ },
1593
+ tap: { scale: 0.98 }
1594
+ };
1595
+ var avatarPulse = {
1596
+ idle: {
1597
+ scale: [1, 1.02, 1],
1598
+ transition: { repeat: Infinity, duration: 3, ease: "easeInOut" }
1599
+ },
1600
+ speaking: {
1601
+ scale: [1, 1.03, 1],
1602
+ transition: { repeat: Infinity, duration: 0.5, ease: "easeInOut" }
1603
+ },
1604
+ listening: {
1605
+ scale: 1,
1606
+ transition: { duration: 0.2 }
1607
+ },
1608
+ thinking: {
1609
+ scale: [1, 1.01, 1],
1610
+ transition: { repeat: Infinity, duration: 1, ease: "easeInOut" }
1611
+ }
1612
+ };
1613
+ var staggerContainer = {
1614
+ initial: {},
1615
+ animate: {
1616
+ transition: { staggerChildren: 0.1 }
1617
+ }
1618
+ };
1619
+ var slideIn = {
1620
+ initial: { x: "100%", opacity: 0 },
1621
+ animate: { x: 0, opacity: 1, transition: { type: "spring", stiffness: 300, damping: 30 } },
1622
+ exit: { x: "100%", opacity: 0 }
1623
+ };
1624
+ var scaleIn = {
1625
+ initial: { scale: 0.8, opacity: 0 },
1626
+ animate: { scale: 1, opacity: 1, transition: { type: "spring", stiffness: 300, damping: 25 } },
1627
+ exit: { scale: 0.8, opacity: 0 }
1628
+ };
1629
+
1630
+ // src/utils/cn.ts
1631
+ import { clsx } from "clsx";
1632
+ import { twMerge } from "tailwind-merge";
1633
+ function cn(...inputs) {
1634
+ return twMerge(clsx(inputs));
1635
+ }
1636
+ export {
1637
+ AvatarAgent,
1638
+ AvatarContext,
1639
+ AvatarFallback,
1640
+ AvatarFirst,
1641
+ AvatarProvider,
1642
+ AvatarView,
1643
+ CalendarOverlay,
1644
+ CardOverlay,
1645
+ ChartOverlay,
1646
+ ChatOverlay,
1647
+ ComparisonOverlay,
1648
+ ConfirmOverlay,
1649
+ DetailCardOverlay,
1650
+ FileUploadOverlay,
1651
+ FormOverlay,
1652
+ InsightOverlay,
1653
+ MapOverlay,
1654
+ MediaOverlay,
1655
+ OverlayContainer,
1656
+ ProgressOverlay,
1657
+ RichTextOverlay,
1658
+ TableOverlay,
1659
+ avatarPulse,
1660
+ cardHover,
1661
+ cn,
1662
+ fadeIn,
1663
+ fadeInUp,
1664
+ getOverlayVariants,
1665
+ pageTransition,
1666
+ scaleIn,
1667
+ slideIn,
1668
+ staggerContainer,
1669
+ useAction,
1670
+ useAvatar,
1671
+ useAvatarEvent,
1672
+ useAvatarFirst,
1673
+ useOverlay
1674
+ };