@intentai/react 2.1.2 → 2.3.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.mjs CHANGED
@@ -1,1746 +1,2 @@
1
- "use client";
2
-
3
- // src/index.tsx
4
- import { useEffect as useEffect2, useRef as useRef2, useCallback as useCallback2 } from "react";
5
-
6
- // src/components/PremiumVoiceWidget.tsx
7
- import {
8
- useState,
9
- useEffect,
10
- useRef,
11
- useCallback,
12
- useMemo,
13
- createContext,
14
- useContext
15
- } from "react";
16
- import { motion, AnimatePresence } from "framer-motion";
17
- import { Fragment, jsx, jsxs } from "react/jsx-runtime";
18
- var WidgetContext = createContext(null);
19
- var useWidget = () => {
20
- const context = useContext(WidgetContext);
21
- if (!context) throw new Error("useWidget must be used within WidgetProvider");
22
- return context;
23
- };
24
- var useReducedMotion = () => {
25
- const [reducedMotion, setReducedMotion] = useState(false);
26
- useEffect(() => {
27
- const mediaQuery = window.matchMedia("(prefers-reduced-motion: reduce)");
28
- setReducedMotion(mediaQuery.matches);
29
- const handler = (e) => setReducedMotion(e.matches);
30
- mediaQuery.addEventListener("change", handler);
31
- return () => mediaQuery.removeEventListener("change", handler);
32
- }, []);
33
- return reducedMotion;
34
- };
35
- var useScrollAwareness = () => {
36
- const [scrollState, setScrollState] = useState("idle");
37
- const scrollTimeout = useRef(null);
38
- useEffect(() => {
39
- const handleScroll = () => {
40
- setScrollState("scrolling");
41
- if (scrollTimeout.current) {
42
- clearTimeout(scrollTimeout.current);
43
- }
44
- scrollTimeout.current = setTimeout(() => {
45
- setScrollState("stopped");
46
- setTimeout(() => setScrollState("idle"), 300);
47
- }, 150);
48
- };
49
- window.addEventListener("scroll", handleScroll, { passive: true });
50
- return () => window.removeEventListener("scroll", handleScroll);
51
- }, []);
52
- return scrollState;
53
- };
54
- var useFrustrationDetection = () => {
55
- const [frustrationLevel, setFrustrationLevel] = useState(0);
56
- const clickTimestamps = useRef([]);
57
- const signals = useRef([]);
58
- useEffect(() => {
59
- const handleClick = () => {
60
- const now = Date.now();
61
- clickTimestamps.current.push(now);
62
- clickTimestamps.current = clickTimestamps.current.filter((t) => now - t < 500);
63
- if (clickTimestamps.current.length >= 4) {
64
- signals.current.push({ weight: 3, timestamp: now });
65
- clickTimestamps.current = [];
66
- }
67
- };
68
- const handleFormError = () => {
69
- signals.current.push({ weight: 2, timestamp: Date.now() });
70
- };
71
- document.addEventListener("click", handleClick);
72
- document.addEventListener("invalid", handleFormError, true);
73
- const decayInterval = setInterval(() => {
74
- const now = Date.now();
75
- signals.current = signals.current.filter((s) => now - s.timestamp < 3e4);
76
- const totalWeight = signals.current.reduce((sum, s) => sum + s.weight, 0);
77
- setFrustrationLevel(Math.min(10, totalWeight));
78
- }, 1e3);
79
- return () => {
80
- document.removeEventListener("click", handleClick);
81
- document.removeEventListener("invalid", handleFormError, true);
82
- clearInterval(decayInterval);
83
- };
84
- }, []);
85
- return frustrationLevel;
86
- };
87
- var useTimeOnPage = () => {
88
- const [timeOnPage, setTimeOnPage] = useState(0);
89
- useEffect(() => {
90
- const interval = setInterval(() => {
91
- setTimeOnPage((prev) => prev + 1);
92
- }, 1e3);
93
- return () => clearInterval(interval);
94
- }, []);
95
- return timeOnPage;
96
- };
97
- var useAudioLevel = (isRecording) => {
98
- const [audioLevel, setAudioLevel] = useState(0);
99
- const analyserRef = useRef(null);
100
- const animationRef = useRef();
101
- const streamRef = useRef(null);
102
- const audioContextRef = useRef(null);
103
- useEffect(() => {
104
- if (!isRecording) {
105
- setAudioLevel(0);
106
- if (animationRef.current) {
107
- cancelAnimationFrame(animationRef.current);
108
- animationRef.current = void 0;
109
- }
110
- if (streamRef.current) {
111
- streamRef.current.getTracks().forEach((track) => track.stop());
112
- streamRef.current = null;
113
- }
114
- if (audioContextRef.current) {
115
- audioContextRef.current.close();
116
- audioContextRef.current = null;
117
- }
118
- analyserRef.current = null;
119
- return;
120
- }
121
- const initAudio = async () => {
122
- try {
123
- const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
124
- streamRef.current = stream;
125
- const audioContext = new AudioContext();
126
- audioContextRef.current = audioContext;
127
- const source = audioContext.createMediaStreamSource(stream);
128
- const analyser = audioContext.createAnalyser();
129
- analyser.fftSize = 256;
130
- source.connect(analyser);
131
- analyserRef.current = analyser;
132
- const dataArray = new Uint8Array(analyser.frequencyBinCount);
133
- const updateLevel = () => {
134
- if (!analyserRef.current) return;
135
- analyserRef.current.getByteFrequencyData(dataArray);
136
- const average = dataArray.reduce((a, b) => a + b) / dataArray.length;
137
- setAudioLevel(average / 255);
138
- animationRef.current = requestAnimationFrame(updateLevel);
139
- };
140
- updateLevel();
141
- } catch (error) {
142
- console.error("Failed to access microphone:", error);
143
- }
144
- };
145
- initAudio();
146
- return () => {
147
- if (animationRef.current) {
148
- cancelAnimationFrame(animationRef.current);
149
- }
150
- if (streamRef.current) {
151
- streamRef.current.getTracks().forEach((track) => track.stop());
152
- }
153
- if (audioContextRef.current) {
154
- audioContextRef.current.close();
155
- }
156
- };
157
- }, [isRecording]);
158
- return audioLevel;
159
- };
160
- var springPresets = {
161
- snappy: { type: "spring", stiffness: 500, damping: 30 },
162
- bouncy: { type: "spring", stiffness: 400, damping: 15 },
163
- smooth: { type: "spring", stiffness: 300, damping: 30 },
164
- gentle: { type: "spring", stiffness: 200, damping: 25 }
165
- };
166
- var triggerVariants = {
167
- idle: {
168
- scale: 1,
169
- opacity: 1
170
- },
171
- hover: {
172
- scale: 1.08,
173
- transition: springPresets.bouncy
174
- },
175
- tap: {
176
- scale: 0.95,
177
- transition: springPresets.snappy
178
- },
179
- scrolling: {
180
- scale: 0.85,
181
- opacity: 0.3,
182
- transition: { duration: 0.2 }
183
- },
184
- hidden: {
185
- scale: 0,
186
- opacity: 0,
187
- transition: springPresets.smooth
188
- }
189
- };
190
- var containerVariants = {
191
- trigger: {
192
- width: 56,
193
- height: 56,
194
- borderRadius: 28,
195
- transition: springPresets.smooth
196
- },
197
- expanded: {
198
- width: 360,
199
- height: "auto",
200
- borderRadius: 20,
201
- transition: {
202
- ...springPresets.smooth,
203
- staggerChildren: 0.05,
204
- delayChildren: 0.15
205
- }
206
- }
207
- };
208
- var contentVariants = {
209
- hidden: {
210
- opacity: 0,
211
- y: 20,
212
- scale: 0.95
213
- },
214
- visible: {
215
- opacity: 1,
216
- y: 0,
217
- scale: 1,
218
- transition: springPresets.smooth
219
- },
220
- exit: {
221
- opacity: 0,
222
- y: -10,
223
- scale: 0.95,
224
- transition: { duration: 0.2 }
225
- }
226
- };
227
- var BreathingRing = ({ isActive }) => {
228
- const { reducedMotion } = useWidget();
229
- if (reducedMotion) return null;
230
- return /* @__PURE__ */ jsxs(Fragment, { children: [
231
- /* @__PURE__ */ jsx(
232
- motion.div,
233
- {
234
- animate: {
235
- scale: isActive ? [1, 1.3, 1] : 1,
236
- opacity: isActive ? [0.2, 0.4, 0.2] : 0.2
237
- },
238
- transition: {
239
- duration: 2.5,
240
- repeat: Infinity,
241
- ease: "easeInOut"
242
- },
243
- style: {
244
- position: "absolute",
245
- inset: -16,
246
- borderRadius: "50%",
247
- background: "radial-gradient(circle, var(--widget-primary) 0%, transparent 70%)",
248
- pointerEvents: "none",
249
- filter: "blur(8px)"
250
- }
251
- }
252
- ),
253
- /* @__PURE__ */ jsx(
254
- motion.div,
255
- {
256
- className: "fiq-breathing-ring",
257
- animate: {
258
- scale: isActive ? [1, 1.08, 1] : 1,
259
- opacity: isActive ? [0.6, 1, 0.6] : 0.6
260
- },
261
- transition: {
262
- duration: 2,
263
- repeat: Infinity,
264
- ease: "easeInOut"
265
- },
266
- style: {
267
- position: "absolute",
268
- inset: -4,
269
- borderRadius: "50%",
270
- border: "2px solid var(--widget-primary)",
271
- pointerEvents: "none",
272
- opacity: 0.5
273
- }
274
- }
275
- )
276
- ] });
277
- };
278
- var AmbientOrb = ({ audioLevel, isRecording }) => {
279
- const { reducedMotion } = useWidget();
280
- const [particles, setParticles] = useState([]);
281
- useEffect(() => {
282
- if (!isRecording || audioLevel < 0.3 || reducedMotion) return;
283
- const particleCount = Math.floor(audioLevel * 4);
284
- const newParticles = Array.from({ length: particleCount }, () => ({
285
- id: Math.random().toString(36).slice(2, 11),
286
- x: 70,
287
- y: 70,
288
- angle: Math.random() * Math.PI * 2,
289
- speed: 1 + Math.random() * 3,
290
- size: 2 + Math.random() * 3,
291
- color: Math.random() > 0.5 ? "var(--widget-primary-light)" : "var(--widget-accent)"
292
- }));
293
- setParticles((prev) => [...prev, ...newParticles].slice(-40));
294
- }, [audioLevel, isRecording, reducedMotion]);
295
- useEffect(() => {
296
- if (particles.length === 0) return;
297
- const timeout = setTimeout(() => {
298
- setParticles((prev) => prev.slice(1));
299
- }, 100);
300
- return () => clearTimeout(timeout);
301
- }, [particles]);
302
- const orbScale = 1 + audioLevel * 0.2;
303
- return /* @__PURE__ */ jsx("div", { className: "fiq-ambient-orb", style: { position: "relative", width: 140, height: 140, margin: "0 auto" }, children: /* @__PURE__ */ jsxs("svg", { width: 140, height: 140, viewBox: "0 0 140 140", children: [
304
- /* @__PURE__ */ jsxs("defs", { children: [
305
- /* @__PURE__ */ jsxs("radialGradient", { id: "orbGradient", cx: "30%", cy: "30%", children: [
306
- /* @__PURE__ */ jsx("stop", { offset: "0%", stopColor: "var(--widget-accent-light)" }),
307
- /* @__PURE__ */ jsx("stop", { offset: "40%", stopColor: "var(--widget-primary)" }),
308
- /* @__PURE__ */ jsx("stop", { offset: "100%", stopColor: "var(--widget-primary-hover)" })
309
- ] }),
310
- /* @__PURE__ */ jsxs("filter", { id: "orbGlow", x: "-50%", y: "-50%", width: "200%", height: "200%", children: [
311
- /* @__PURE__ */ jsx("feGaussianBlur", { stdDeviation: "8", result: "blur" }),
312
- /* @__PURE__ */ jsx("feComposite", { in: "SourceGraphic", in2: "blur", operator: "over" })
313
- ] })
314
- ] }),
315
- /* @__PURE__ */ jsx(
316
- motion.circle,
317
- {
318
- cx: 70,
319
- cy: 70,
320
- r: 50,
321
- fill: "none",
322
- stroke: "var(--widget-primary-glow)",
323
- strokeWidth: 2,
324
- animate: {
325
- r: isRecording ? [50, 58, 50] : 50,
326
- opacity: isRecording ? [0.3, 0.6, 0.3] : 0.3
327
- },
328
- transition: {
329
- duration: 2,
330
- repeat: Infinity,
331
- ease: "easeInOut"
332
- }
333
- }
334
- ),
335
- /* @__PURE__ */ jsx(
336
- motion.circle,
337
- {
338
- cx: 70,
339
- cy: 70,
340
- r: 40,
341
- fill: "url(#orbGradient)",
342
- filter: "url(#orbGlow)",
343
- animate: { scale: reducedMotion ? 1 : orbScale },
344
- transition: springPresets.snappy,
345
- style: { transformOrigin: "center" }
346
- }
347
- ),
348
- /* @__PURE__ */ jsx("g", { transform: "translate(70, 70)", children: /* @__PURE__ */ jsxs(
349
- motion.g,
350
- {
351
- animate: {
352
- opacity: isRecording ? [1, 0.7, 1] : 1
353
- },
354
- transition: {
355
- duration: 1,
356
- repeat: isRecording ? Infinity : 0
357
- },
358
- children: [
359
- /* @__PURE__ */ jsx("rect", { x: "-8", y: "-20", width: "16", height: "26", rx: "8", fill: "white" }),
360
- /* @__PURE__ */ jsx(
361
- "path",
362
- {
363
- d: "M-14 0 v6 a14 14 0 0 0 28 0 v-6",
364
- fill: "none",
365
- stroke: "white",
366
- strokeWidth: "3",
367
- strokeLinecap: "round"
368
- }
369
- ),
370
- /* @__PURE__ */ jsx("line", { x1: "0", y1: "20", x2: "0", y2: "28", stroke: "white", strokeWidth: "3", strokeLinecap: "round" }),
371
- /* @__PURE__ */ jsx("line", { x1: "-8", y1: "28", x2: "8", y2: "28", stroke: "white", strokeWidth: "3", strokeLinecap: "round" })
372
- ]
373
- }
374
- ) }),
375
- particles.map((particle) => /* @__PURE__ */ jsx(
376
- motion.circle,
377
- {
378
- cx: 70,
379
- cy: 70,
380
- r: particle.size,
381
- fill: particle.color,
382
- initial: { opacity: 0.8, scale: 1 },
383
- animate: {
384
- cx: 70 + Math.cos(particle.angle) * 50,
385
- cy: 70 + Math.sin(particle.angle) * 50,
386
- opacity: 0,
387
- scale: 0.5
388
- },
389
- transition: { duration: 1.5, ease: "easeOut" }
390
- },
391
- particle.id
392
- ))
393
- ] }) });
394
- };
395
- var RecordingTimer = ({
396
- seconds,
397
- maxDuration
398
- }) => {
399
- const minutes = Math.floor(seconds / 60);
400
- const secs = seconds % 60;
401
- const progress = seconds / maxDuration;
402
- const isNearLimit = seconds >= maxDuration - 30;
403
- return /* @__PURE__ */ jsxs("div", { className: "fiq-timer", style: { textAlign: "center" }, children: [
404
- /* @__PURE__ */ jsxs(
405
- motion.div,
406
- {
407
- style: {
408
- fontSize: 36,
409
- fontWeight: 700,
410
- fontFamily: "ui-monospace, monospace",
411
- color: isNearLimit ? "var(--widget-recording)" : "var(--widget-text-primary)"
412
- },
413
- animate: isNearLimit ? { scale: [1, 1.05, 1] } : {},
414
- transition: { duration: 0.5, repeat: isNearLimit ? Infinity : 0 },
415
- children: [
416
- String(minutes).padStart(2, "0"),
417
- ":",
418
- String(secs).padStart(2, "0")
419
- ]
420
- }
421
- ),
422
- /* @__PURE__ */ jsx(
423
- "div",
424
- {
425
- style: {
426
- width: 160,
427
- height: 4,
428
- background: "var(--widget-glass-border)",
429
- borderRadius: 2,
430
- margin: "12px auto",
431
- overflow: "hidden"
432
- },
433
- children: /* @__PURE__ */ jsx(
434
- motion.div,
435
- {
436
- style: {
437
- height: "100%",
438
- background: isNearLimit ? "var(--widget-recording)" : "var(--widget-primary)",
439
- borderRadius: 2
440
- },
441
- initial: { width: 0 },
442
- animate: { width: `${progress * 100}%` },
443
- transition: { duration: 0.5 }
444
- }
445
- )
446
- }
447
- )
448
- ] });
449
- };
450
- var TranscriptionDisplay = ({ text, isLive }) => {
451
- const { reducedMotion } = useWidget();
452
- const [displayedText, setDisplayedText] = useState("");
453
- const [cursorVisible, setCursorVisible] = useState(true);
454
- useEffect(() => {
455
- if (reducedMotion) {
456
- setDisplayedText(text);
457
- return;
458
- }
459
- if (text.length > displayedText.length) {
460
- const timeout = setTimeout(() => {
461
- setDisplayedText(text.slice(0, displayedText.length + 1));
462
- }, 30 + Math.random() * 40);
463
- return () => clearTimeout(timeout);
464
- }
465
- }, [text, displayedText, reducedMotion]);
466
- useEffect(() => {
467
- if (!isLive) return;
468
- const interval = setInterval(() => setCursorVisible((v) => !v), 530);
469
- return () => clearInterval(interval);
470
- }, [isLive]);
471
- return /* @__PURE__ */ jsxs(
472
- "div",
473
- {
474
- className: "fiq-transcription",
475
- style: {
476
- padding: 16,
477
- background: "var(--widget-glass-bg)",
478
- borderRadius: 12,
479
- border: "1px solid var(--widget-glass-border)",
480
- minHeight: 80,
481
- maxHeight: 120,
482
- overflow: "auto"
483
- },
484
- children: [
485
- /* @__PURE__ */ jsxs("p", { style: { color: "var(--widget-text-primary)", fontSize: 14, lineHeight: 1.6, margin: 0 }, children: [
486
- displayedText || /* @__PURE__ */ jsx("span", { style: { color: "var(--widget-text-muted)", fontStyle: "italic" }, children: "Start speaking..." }),
487
- isLive && /* @__PURE__ */ jsx(
488
- motion.span,
489
- {
490
- animate: { opacity: cursorVisible ? 1 : 0 },
491
- style: {
492
- display: "inline-block",
493
- width: 2,
494
- height: "1.2em",
495
- background: "var(--widget-primary)",
496
- marginLeft: 2,
497
- verticalAlign: "text-bottom"
498
- }
499
- }
500
- )
501
- ] }),
502
- isLive && /* @__PURE__ */ jsxs(
503
- motion.div,
504
- {
505
- initial: { opacity: 0, y: 5 },
506
- animate: { opacity: 1, y: 0 },
507
- style: {
508
- display: "flex",
509
- alignItems: "center",
510
- gap: 8,
511
- marginTop: 12,
512
- color: "var(--widget-text-secondary)",
513
- fontSize: 12
514
- },
515
- children: [
516
- /* @__PURE__ */ jsx(ListeningDots, {}),
517
- /* @__PURE__ */ jsx("span", { children: "Listening..." })
518
- ]
519
- }
520
- )
521
- ]
522
- }
523
- );
524
- };
525
- var ListeningDots = () => /* @__PURE__ */ jsx("div", { style: { display: "flex", gap: 4 }, children: [0, 1, 2].map((i) => /* @__PURE__ */ jsx(
526
- motion.span,
527
- {
528
- animate: {
529
- scale: [1, 1.4, 1],
530
- opacity: [0.5, 1, 0.5]
531
- },
532
- transition: {
533
- duration: 1,
534
- repeat: Infinity,
535
- delay: i * 0.15
536
- },
537
- style: {
538
- width: 6,
539
- height: 6,
540
- borderRadius: "50%",
541
- background: "var(--widget-primary)"
542
- }
543
- },
544
- i
545
- )) });
546
- var CategorySelector = ({ categories, selected, onSelect }) => {
547
- return /* @__PURE__ */ jsxs("div", { className: "fiq-category-selector", children: [
548
- /* @__PURE__ */ jsx(
549
- "label",
550
- {
551
- style: {
552
- display: "block",
553
- fontSize: 12,
554
- fontWeight: 600,
555
- color: "var(--widget-text-muted)",
556
- marginBottom: 12,
557
- textTransform: "uppercase",
558
- letterSpacing: "0.05em"
559
- },
560
- children: "Category"
561
- }
562
- ),
563
- /* @__PURE__ */ jsx(
564
- "div",
565
- {
566
- style: {
567
- display: "flex",
568
- flexWrap: "wrap",
569
- gap: 8
570
- },
571
- children: categories.map((category) => /* @__PURE__ */ jsxs(
572
- motion.button,
573
- {
574
- whileHover: { scale: 1.04, y: -1 },
575
- whileTap: { scale: 0.96 },
576
- onClick: () => onSelect(category.id),
577
- style: {
578
- padding: "10px 18px",
579
- background: selected === category.id ? "linear-gradient(145deg, var(--widget-primary) 0%, var(--widget-accent) 100%)" : "var(--widget-glass-bg)",
580
- border: `1.5px solid ${selected === category.id ? "transparent" : "var(--widget-glass-border)"}`,
581
- borderRadius: 24,
582
- cursor: "pointer",
583
- transition: "all 0.2s ease",
584
- boxShadow: selected === category.id ? "0 4px 12px -2px var(--widget-primary-glow)" : "0 2px 4px rgba(0,0,0,0.1)",
585
- position: "relative",
586
- overflow: "hidden"
587
- },
588
- children: [
589
- selected === category.id && /* @__PURE__ */ jsx(
590
- "div",
591
- {
592
- style: {
593
- position: "absolute",
594
- inset: 0,
595
- background: "linear-gradient(180deg, rgba(255,255,255,0.2) 0%, rgba(255,255,255,0) 50%)",
596
- borderRadius: "inherit",
597
- pointerEvents: "none"
598
- }
599
- }
600
- ),
601
- /* @__PURE__ */ jsx(
602
- "span",
603
- {
604
- style: {
605
- fontSize: 13,
606
- fontWeight: 600,
607
- color: selected === category.id ? "white" : "var(--widget-text-primary)",
608
- position: "relative",
609
- zIndex: 1
610
- },
611
- children: category.label
612
- }
613
- )
614
- ]
615
- },
616
- category.id
617
- ))
618
- }
619
- )
620
- ] });
621
- };
622
- var SuccessCelebration = ({ onComplete }) => {
623
- const { reducedMotion } = useWidget();
624
- const confettiParticles = useMemo(
625
- () => reducedMotion ? [] : Array.from({ length: 30 }, (_, i) => ({
626
- id: i,
627
- angle: i / 30 * Math.PI * 2,
628
- distance: 100 + Math.random() * 80,
629
- size: 4 + Math.random() * 6,
630
- color: ["#10b981", "#22d3d3", "#fbbf24", "#f472b6"][Math.floor(Math.random() * 4)],
631
- rotation: Math.random() * 360
632
- })),
633
- [reducedMotion]
634
- );
635
- useEffect(() => {
636
- const timer = setTimeout(onComplete, 3e3);
637
- return () => clearTimeout(timer);
638
- }, [onComplete]);
639
- return /* @__PURE__ */ jsxs(
640
- motion.div,
641
- {
642
- initial: { opacity: 0, scale: 0.8 },
643
- animate: { opacity: 1, scale: 1 },
644
- exit: { opacity: 0, scale: 0.8 },
645
- style: {
646
- display: "flex",
647
- flexDirection: "column",
648
- alignItems: "center",
649
- justifyContent: "center",
650
- padding: 40,
651
- textAlign: "center",
652
- position: "relative",
653
- overflow: "hidden"
654
- },
655
- children: [
656
- confettiParticles.map((particle) => /* @__PURE__ */ jsx(
657
- motion.div,
658
- {
659
- initial: { x: 0, y: 0, scale: 0, rotate: 0 },
660
- animate: {
661
- x: Math.cos(particle.angle) * particle.distance,
662
- y: Math.sin(particle.angle) * particle.distance + 50,
663
- scale: [0, 1, 1, 0],
664
- rotate: particle.rotation
665
- },
666
- transition: { duration: 1.2, ease: [0.22, 1, 0.36, 1] },
667
- style: {
668
- position: "absolute",
669
- top: "50%",
670
- left: "50%",
671
- width: particle.size,
672
- height: particle.size,
673
- background: particle.color,
674
- borderRadius: Math.random() > 0.5 ? "50%" : 2
675
- }
676
- },
677
- particle.id
678
- )),
679
- /* @__PURE__ */ jsx(motion.div, { style: { marginBottom: 20 }, children: /* @__PURE__ */ jsxs("svg", { width: 80, height: 80, viewBox: "0 0 80 80", children: [
680
- /* @__PURE__ */ jsx(
681
- motion.circle,
682
- {
683
- cx: 40,
684
- cy: 40,
685
- r: 36,
686
- fill: "var(--widget-success)",
687
- initial: { scale: 0 },
688
- animate: { scale: 1 },
689
- transition: springPresets.bouncy
690
- }
691
- ),
692
- /* @__PURE__ */ jsx(
693
- motion.path,
694
- {
695
- d: "M24 40 L35 51 L56 28",
696
- fill: "none",
697
- stroke: "white",
698
- strokeWidth: 5,
699
- strokeLinecap: "round",
700
- strokeLinejoin: "round",
701
- initial: { pathLength: 0 },
702
- animate: { pathLength: 1 },
703
- transition: { delay: 0.3, duration: 0.5, ease: "easeOut" }
704
- }
705
- )
706
- ] }) }),
707
- /* @__PURE__ */ jsx(
708
- motion.h3,
709
- {
710
- initial: { opacity: 0, y: 20 },
711
- animate: { opacity: 1, y: 0 },
712
- transition: { delay: 0.5 },
713
- style: {
714
- fontSize: 24,
715
- fontWeight: 700,
716
- color: "var(--widget-text-primary)",
717
- margin: 0,
718
- marginBottom: 8
719
- },
720
- children: "Thank you!"
721
- }
722
- ),
723
- /* @__PURE__ */ jsx(
724
- motion.p,
725
- {
726
- initial: { opacity: 0, y: 20 },
727
- animate: { opacity: 1, y: 0 },
728
- transition: { delay: 0.6 },
729
- style: {
730
- fontSize: 14,
731
- color: "var(--widget-text-secondary)",
732
- margin: 0
733
- },
734
- children: "Your feedback helps us improve"
735
- }
736
- ),
737
- /* @__PURE__ */ jsx(
738
- motion.div,
739
- {
740
- style: {
741
- position: "absolute",
742
- bottom: 20,
743
- left: "50%",
744
- transform: "translateX(-50%)",
745
- width: 100,
746
- height: 3,
747
- background: "var(--widget-glass-border)",
748
- borderRadius: 2,
749
- overflow: "hidden"
750
- },
751
- children: /* @__PURE__ */ jsx(
752
- motion.div,
753
- {
754
- initial: { width: "100%" },
755
- animate: { width: 0 },
756
- transition: { duration: 3, ease: "linear" },
757
- style: {
758
- height: "100%",
759
- background: "var(--widget-primary)"
760
- }
761
- }
762
- )
763
- }
764
- )
765
- ]
766
- }
767
- );
768
- };
769
- var MagneticButton = ({ children, onClick, onPressStart, onPressEnd, variant = "primary", disabled, loading }) => {
770
- const buttonRef = useRef(null);
771
- const [magnetOffset, setMagnetOffset] = useState({ x: 0, y: 0 });
772
- const handleMouseMove = (e) => {
773
- if (!buttonRef.current || disabled) return;
774
- const rect = buttonRef.current.getBoundingClientRect();
775
- const centerX = rect.left + rect.width / 2;
776
- const centerY = rect.top + rect.height / 2;
777
- setMagnetOffset({
778
- x: (e.clientX - centerX) * 0.08,
779
- y: (e.clientY - centerY) * 0.08
780
- });
781
- };
782
- const handleMouseLeave = () => {
783
- setMagnetOffset({ x: 0, y: 0 });
784
- };
785
- const baseStyles = {
786
- width: "100%",
787
- padding: "16px 28px",
788
- fontSize: 15,
789
- fontWeight: 600,
790
- borderRadius: 14,
791
- border: "none",
792
- cursor: disabled ? "not-allowed" : "pointer",
793
- transition: "all 0.2s ease",
794
- display: "flex",
795
- alignItems: "center",
796
- justifyContent: "center",
797
- gap: 10,
798
- letterSpacing: "0.01em",
799
- position: "relative",
800
- overflow: "hidden"
801
- };
802
- const variantStyles = {
803
- primary: {
804
- background: "linear-gradient(145deg, var(--widget-primary) 0%, var(--widget-accent) 100%)",
805
- color: "white",
806
- boxShadow: `
807
- 0 4px 16px -2px var(--widget-primary-glow),
808
- 0 2px 8px -2px rgba(0, 0, 0, 0.3),
809
- inset 0 1px 1px rgba(255, 255, 255, 0.2)
810
- `
811
- },
812
- secondary: {
813
- background: "var(--widget-glass-bg)",
814
- color: "var(--widget-text-primary)",
815
- border: "1px solid var(--widget-glass-border)",
816
- boxShadow: "0 2px 8px rgba(0, 0, 0, 0.15)"
817
- },
818
- ghost: {
819
- background: "transparent",
820
- color: "var(--widget-text-secondary)"
821
- },
822
- recording: {
823
- background: "linear-gradient(145deg, #ef4444 0%, #dc2626 100%)",
824
- color: "white",
825
- boxShadow: `
826
- 0 4px 20px -2px var(--widget-recording-glow),
827
- 0 0 40px -5px var(--widget-recording-glow),
828
- inset 0 1px 1px rgba(255, 255, 255, 0.2)
829
- `
830
- }
831
- };
832
- const handlePressStart = () => {
833
- if (disabled || loading) return;
834
- onPressStart?.();
835
- };
836
- const handlePressEnd = () => {
837
- if (disabled || loading) return;
838
- onPressEnd?.();
839
- };
840
- return /* @__PURE__ */ jsxs(
841
- motion.button,
842
- {
843
- ref: buttonRef,
844
- onClick,
845
- disabled: disabled || loading,
846
- onMouseMove: handleMouseMove,
847
- onMouseLeave: () => {
848
- handleMouseLeave();
849
- if (onPressEnd) handlePressEnd();
850
- },
851
- onMouseDown: onPressStart ? handlePressStart : void 0,
852
- onMouseUp: onPressEnd ? handlePressEnd : void 0,
853
- onTouchStart: onPressStart ? handlePressStart : void 0,
854
- onTouchEnd: onPressEnd ? handlePressEnd : void 0,
855
- animate: {
856
- x: magnetOffset.x,
857
- y: magnetOffset.y,
858
- opacity: disabled ? 0.5 : 1
859
- },
860
- whileHover: { scale: disabled ? 1 : 1.02, y: -1 },
861
- whileTap: { scale: disabled ? 1 : 0.97 },
862
- transition: springPresets.snappy,
863
- style: { ...baseStyles, ...variantStyles[variant] },
864
- children: [
865
- /* @__PURE__ */ jsx(
866
- "div",
867
- {
868
- style: {
869
- position: "absolute",
870
- inset: 0,
871
- background: "linear-gradient(180deg, rgba(255,255,255,0.15) 0%, rgba(255,255,255,0) 50%)",
872
- borderRadius: "inherit",
873
- pointerEvents: "none"
874
- }
875
- }
876
- ),
877
- loading ? /* @__PURE__ */ jsx(
878
- motion.div,
879
- {
880
- animate: { rotate: 360 },
881
- transition: { duration: 1, repeat: Infinity, ease: "linear" },
882
- style: {
883
- width: 20,
884
- height: 20,
885
- border: "2.5px solid rgba(255,255,255,0.3)",
886
- borderTopColor: "white",
887
- borderRadius: "50%"
888
- }
889
- }
890
- ) : /* @__PURE__ */ jsx("span", { style: { position: "relative", zIndex: 1, display: "flex", alignItems: "center", gap: 10 }, children })
891
- ]
892
- }
893
- );
894
- };
895
- var DEFAULT_CATEGORIES = [
896
- { id: "bug", label: "Bug", icon: "" },
897
- { id: "feature", label: "Feature", icon: "" },
898
- { id: "improvement", label: "Improve", icon: "" },
899
- { id: "praise", label: "Praise", icon: "" },
900
- { id: "other", label: "Other", icon: "" }
901
- ];
902
- var PremiumVoiceWidget = ({
903
- position = "bottom-right",
904
- primaryColor = "#10b981",
905
- accentColor = "#22d3d3",
906
- theme = "dark",
907
- categories = DEFAULT_CATEGORIES,
908
- allowVoice = true,
909
- allowText = true,
910
- maxRecordingDuration = 180,
911
- onSubmit,
912
- onOpen,
913
- onClose
914
- }) => {
915
- const [state, setState] = useState("idle");
916
- const [isExpanded, setIsExpanded] = useState(false);
917
- const [activeTab, setActiveTab] = useState(allowVoice ? "voice" : "text");
918
- const [selectedCategory, setSelectedCategory] = useState(null);
919
- const [isRecording, setIsRecording] = useState(false);
920
- const [recordingSeconds, setRecordingSeconds] = useState(0);
921
- const [transcription, setTranscription] = useState("");
922
- const [textContent, setTextContent] = useState("");
923
- const [isSubmitting, setIsSubmitting] = useState(false);
924
- const reducedMotion = useReducedMotion();
925
- const scrollState = useScrollAwareness();
926
- const frustrationLevel = useFrustrationDetection();
927
- const timeOnPage = useTimeOnPage();
928
- const audioLevel = useAudioLevel(isRecording);
929
- const triggerRef = useRef(null);
930
- const recordingInterval = useRef(null);
931
- const positionStyles = {
932
- "bottom-right": { bottom: 24, right: 24 },
933
- "bottom-left": { bottom: 24, left: 24 },
934
- "top-right": { top: 24, right: 24 },
935
- "top-left": { top: 24, left: 24 }
936
- };
937
- const cssVariables = {
938
- "--widget-primary": primaryColor,
939
- "--widget-primary-hover": adjustColor(primaryColor, -20),
940
- "--widget-primary-light": adjustColor(primaryColor, 30),
941
- "--widget-primary-glow": `${primaryColor}66`,
942
- "--widget-accent": accentColor,
943
- "--widget-accent-light": adjustColor(accentColor, 30),
944
- "--widget-accent-glow": `${accentColor}4d`,
945
- "--widget-glass-bg": theme === "dark" ? "rgba(24, 24, 27, 0.85)" : "rgba(255, 255, 255, 0.85)",
946
- "--widget-glass-border": theme === "dark" ? "rgba(63, 63, 70, 0.5)" : "rgba(0, 0, 0, 0.1)",
947
- "--widget-glass-highlight": theme === "dark" ? "rgba(255, 255, 255, 0.05)" : "rgba(0, 0, 0, 0.02)",
948
- "--widget-text-primary": theme === "dark" ? "#fafafa" : "#18181b",
949
- "--widget-text-secondary": theme === "dark" ? "#a1a1aa" : "#71717a",
950
- "--widget-text-muted": theme === "dark" ? "#71717a" : "#a1a1aa",
951
- "--widget-recording": "#ef4444",
952
- "--widget-recording-glow": "rgba(239, 68, 68, 0.4)",
953
- "--widget-success": "#22c55e",
954
- "--widget-success-glow": "rgba(34, 197, 94, 0.4)",
955
- "--widget-frustrated": "#f97316"
956
- };
957
- const handleOpen = useCallback(() => {
958
- setIsExpanded(true);
959
- setState("open");
960
- onOpen?.();
961
- }, [onOpen]);
962
- const handleClose = useCallback(() => {
963
- setIsExpanded(false);
964
- setState("idle");
965
- setIsRecording(false);
966
- setRecordingSeconds(0);
967
- setTranscription("");
968
- onClose?.();
969
- }, [onClose]);
970
- const handleStartRecording = useCallback(() => {
971
- setIsRecording(true);
972
- setState("recording");
973
- setRecordingSeconds(0);
974
- const samplePhrases = [
975
- "I really like the new dashboard design, ",
976
- "but I think the navigation could be improved. ",
977
- "Sometimes I have trouble finding the settings page."
978
- ];
979
- let phraseIndex = 0;
980
- recordingInterval.current = setInterval(() => {
981
- setRecordingSeconds((prev) => {
982
- if (prev >= maxRecordingDuration) {
983
- handleStopRecording();
984
- return prev;
985
- }
986
- return prev + 1;
987
- });
988
- if (phraseIndex < samplePhrases.length && Math.random() > 0.7) {
989
- setTranscription((prev) => prev + samplePhrases[phraseIndex]);
990
- phraseIndex++;
991
- }
992
- }, 1e3);
993
- }, [maxRecordingDuration]);
994
- const handleStopRecording = useCallback(() => {
995
- setIsRecording(false);
996
- setState("open");
997
- if (recordingInterval.current) {
998
- clearInterval(recordingInterval.current);
999
- }
1000
- }, []);
1001
- const handleSubmit = useCallback(async () => {
1002
- if (!selectedCategory) return;
1003
- setIsSubmitting(true);
1004
- setState("processing");
1005
- try {
1006
- await onSubmit?.({
1007
- type: activeTab,
1008
- content: activeTab === "voice" ? transcription : textContent,
1009
- category: selectedCategory,
1010
- transcription: activeTab === "voice" ? transcription : void 0
1011
- });
1012
- setState("success");
1013
- } catch (error) {
1014
- setState("error");
1015
- } finally {
1016
- setIsSubmitting(false);
1017
- }
1018
- }, [activeTab, selectedCategory, transcription, textContent, onSubmit]);
1019
- const handleSuccessComplete = useCallback(() => {
1020
- handleClose();
1021
- setSelectedCategory(null);
1022
- setTextContent("");
1023
- setTranscription("");
1024
- }, [handleClose]);
1025
- const contextValue = {
1026
- state,
1027
- setState,
1028
- audioLevel,
1029
- isRecording,
1030
- transcription,
1031
- selectedCategory,
1032
- setSelectedCategory,
1033
- frustrationLevel,
1034
- reducedMotion
1035
- };
1036
- const triggerState = useMemo(() => {
1037
- if (isExpanded) return "hidden";
1038
- if (scrollState === "scrolling") return "scrolling";
1039
- if (state === "hover") return "hover";
1040
- return "idle";
1041
- }, [isExpanded, scrollState, state]);
1042
- const shouldShowAttentionPulse = timeOnPage === 60 && !isExpanded && frustrationLevel < 3;
1043
- const [showPrompt, setShowPrompt] = useState(false);
1044
- const promptMessages = [
1045
- "Help us improve!",
1046
- "Got feedback?",
1047
- "Share your thoughts",
1048
- "We'd love to hear from you"
1049
- ];
1050
- const [promptMessage, setPromptMessage] = useState(promptMessages[0]);
1051
- useEffect(() => {
1052
- if (isExpanded) return;
1053
- const showTimes = [30, 90, 180];
1054
- if (showTimes.includes(timeOnPage)) {
1055
- setPromptMessage(promptMessages[Math.floor(Math.random() * promptMessages.length)]);
1056
- setShowPrompt(true);
1057
- const timer = setTimeout(() => setShowPrompt(false), 4e3);
1058
- return () => clearTimeout(timer);
1059
- }
1060
- }, [timeOnPage, isExpanded]);
1061
- return /* @__PURE__ */ jsx(WidgetContext.Provider, { value: contextValue, children: /* @__PURE__ */ jsxs(
1062
- "div",
1063
- {
1064
- className: "fiq-premium-widget",
1065
- style: {
1066
- ...cssVariables,
1067
- position: "fixed",
1068
- ...positionStyles[position],
1069
- zIndex: 2147483640,
1070
- fontFamily: "system-ui, -apple-system, sans-serif"
1071
- },
1072
- children: [
1073
- /* @__PURE__ */ jsxs(AnimatePresence, { mode: "wait", children: [
1074
- !isExpanded && /* @__PURE__ */ jsxs(
1075
- motion.button,
1076
- {
1077
- ref: triggerRef,
1078
- variants: triggerVariants,
1079
- initial: "idle",
1080
- animate: triggerState,
1081
- exit: "hidden",
1082
- whileHover: "hover",
1083
- whileTap: "tap",
1084
- onClick: handleOpen,
1085
- onMouseEnter: () => setState("hover"),
1086
- onMouseLeave: () => setState("idle"),
1087
- "aria-label": "Open feedback widget",
1088
- "aria-expanded": isExpanded,
1089
- style: {
1090
- width: 60,
1091
- height: 60,
1092
- borderRadius: "50%",
1093
- border: "none",
1094
- cursor: "pointer",
1095
- position: "relative",
1096
- background: `linear-gradient(145deg, var(--widget-primary) 0%, var(--widget-accent) 100%)`,
1097
- boxShadow: `
1098
- 0 8px 32px -4px var(--widget-primary-glow),
1099
- 0 4px 16px -2px rgba(0, 0, 0, 0.4),
1100
- inset 0 1px 1px rgba(255, 255, 255, 0.3),
1101
- inset 0 -1px 1px rgba(0, 0, 0, 0.2)
1102
- `
1103
- },
1104
- children: [
1105
- /* @__PURE__ */ jsx(BreathingRing, { isActive: !reducedMotion && triggerState === "idle" }),
1106
- /* @__PURE__ */ jsx(
1107
- "div",
1108
- {
1109
- style: {
1110
- position: "absolute",
1111
- inset: 0,
1112
- borderRadius: "50%",
1113
- background: "linear-gradient(180deg, rgba(255,255,255,0.25) 0%, rgba(255,255,255,0) 50%, rgba(0,0,0,0.1) 100%)",
1114
- pointerEvents: "none"
1115
- }
1116
- }
1117
- ),
1118
- /* @__PURE__ */ jsx(
1119
- "div",
1120
- {
1121
- style: {
1122
- position: "absolute",
1123
- top: "50%",
1124
- left: "50%",
1125
- transform: "translate(-50%, -50%)",
1126
- zIndex: 1,
1127
- display: "flex",
1128
- alignItems: "center",
1129
- justifyContent: "center"
1130
- },
1131
- children: /* @__PURE__ */ jsxs(
1132
- "svg",
1133
- {
1134
- width: 26,
1135
- height: 26,
1136
- viewBox: "0 0 24 24",
1137
- fill: "none",
1138
- style: { filter: "drop-shadow(0 1px 2px rgba(0,0,0,0.2))" },
1139
- children: [
1140
- /* @__PURE__ */ jsx(
1141
- "path",
1142
- {
1143
- d: "M20 2H4c-1.1 0-2 .9-2 2v12c0 1.1.9 2 2 2h4l4 4 4-4h4c1.1 0 2-.9 2-2V4c0-1.1-.9-2-2-2z",
1144
- fill: "white",
1145
- opacity: "0.15"
1146
- }
1147
- ),
1148
- /* @__PURE__ */ jsx(
1149
- "path",
1150
- {
1151
- d: "M12 3a2.5 2.5 0 0 0-2.5 2.5v5a2.5 2.5 0 0 0 5 0v-5A2.5 2.5 0 0 0 12 3z",
1152
- fill: "white"
1153
- }
1154
- ),
1155
- /* @__PURE__ */ jsx(
1156
- "path",
1157
- {
1158
- d: "M16.5 10.5v1a4.5 4.5 0 0 1-9 0v-1",
1159
- stroke: "white",
1160
- strokeWidth: "2",
1161
- strokeLinecap: "round",
1162
- fill: "none"
1163
- }
1164
- ),
1165
- /* @__PURE__ */ jsx(
1166
- "path",
1167
- {
1168
- d: "M12 15.5v2.5M9.5 18h5",
1169
- stroke: "white",
1170
- strokeWidth: "2",
1171
- strokeLinecap: "round"
1172
- }
1173
- )
1174
- ]
1175
- }
1176
- )
1177
- }
1178
- ),
1179
- shouldShowAttentionPulse && /* @__PURE__ */ jsx(
1180
- motion.div,
1181
- {
1182
- initial: { scale: 1, opacity: 1 },
1183
- animate: { scale: 1.6, opacity: 0 },
1184
- transition: { duration: 1.2, repeat: 3 },
1185
- style: {
1186
- position: "absolute",
1187
- inset: -2,
1188
- borderRadius: "50%",
1189
- border: "2px solid var(--widget-primary)",
1190
- pointerEvents: "none"
1191
- }
1192
- }
1193
- ),
1194
- frustrationLevel >= 5 && /* @__PURE__ */ jsx(
1195
- motion.div,
1196
- {
1197
- initial: { scale: 0 },
1198
- animate: { scale: 1 },
1199
- style: {
1200
- position: "absolute",
1201
- top: -2,
1202
- right: -2,
1203
- width: 18,
1204
- height: 18,
1205
- borderRadius: "50%",
1206
- background: "linear-gradient(135deg, #ff6b6b 0%, #ee5a5a 100%)",
1207
- border: "2px solid white",
1208
- display: "flex",
1209
- alignItems: "center",
1210
- justifyContent: "center",
1211
- fontSize: 11,
1212
- fontWeight: 700,
1213
- color: "white",
1214
- boxShadow: "0 2px 8px rgba(239, 68, 68, 0.5)"
1215
- },
1216
- children: "!"
1217
- }
1218
- )
1219
- ]
1220
- },
1221
- "trigger"
1222
- ),
1223
- /* @__PURE__ */ jsx(AnimatePresence, { children: showPrompt && !isExpanded && /* @__PURE__ */ jsxs(
1224
- motion.div,
1225
- {
1226
- initial: { opacity: 0, x: 10, scale: 0.9 },
1227
- animate: { opacity: 1, x: 0, scale: 1 },
1228
- exit: { opacity: 0, x: 10, scale: 0.9 },
1229
- transition: springPresets.smooth,
1230
- onClick: handleOpen,
1231
- style: {
1232
- position: "absolute",
1233
- right: 70,
1234
- top: "50%",
1235
- transform: "translateY(-50%)",
1236
- background: "var(--widget-glass-bg)",
1237
- backdropFilter: "blur(12px)",
1238
- WebkitBackdropFilter: "blur(12px)",
1239
- border: "1px solid var(--widget-glass-border)",
1240
- borderRadius: 12,
1241
- padding: "10px 16px",
1242
- boxShadow: "0 4px 20px rgba(0,0,0,0.3)",
1243
- cursor: "pointer",
1244
- whiteSpace: "nowrap"
1245
- },
1246
- children: [
1247
- /* @__PURE__ */ jsx("span", { style: {
1248
- fontSize: 13,
1249
- fontWeight: 500,
1250
- color: "var(--widget-text-primary)"
1251
- }, children: promptMessage }),
1252
- /* @__PURE__ */ jsx(
1253
- "div",
1254
- {
1255
- style: {
1256
- position: "absolute",
1257
- right: -6,
1258
- top: "50%",
1259
- transform: "translateY(-50%) rotate(45deg)",
1260
- width: 12,
1261
- height: 12,
1262
- background: "var(--widget-glass-bg)",
1263
- borderRight: "1px solid var(--widget-glass-border)",
1264
- borderTop: "1px solid var(--widget-glass-border)"
1265
- }
1266
- }
1267
- )
1268
- ]
1269
- }
1270
- ) })
1271
- ] }),
1272
- /* @__PURE__ */ jsx(AnimatePresence, { mode: "wait", children: isExpanded && /* @__PURE__ */ jsx(
1273
- motion.div,
1274
- {
1275
- variants: containerVariants,
1276
- initial: "trigger",
1277
- animate: "expanded",
1278
- exit: "trigger",
1279
- style: {
1280
- background: theme === "dark" ? "linear-gradient(180deg, rgba(28, 28, 32, 0.95) 0%, rgba(20, 20, 24, 0.98) 100%)" : "linear-gradient(180deg, rgba(255, 255, 255, 0.98) 0%, rgba(248, 248, 250, 0.98) 100%)",
1281
- backdropFilter: "blur(24px) saturate(200%)",
1282
- WebkitBackdropFilter: "blur(24px) saturate(200%)",
1283
- border: "1px solid var(--widget-glass-border)",
1284
- boxShadow: `
1285
- 0 24px 80px -12px rgba(0, 0, 0, 0.6),
1286
- 0 0 1px 0 rgba(255, 255, 255, 0.1) inset,
1287
- 0 1px 0 0 rgba(255, 255, 255, 0.05) inset
1288
- `,
1289
- overflow: "hidden",
1290
- display: "flex",
1291
- flexDirection: "column"
1292
- },
1293
- role: "dialog",
1294
- "aria-modal": "true",
1295
- "aria-labelledby": "widget-title",
1296
- children: /* @__PURE__ */ jsx(AnimatePresence, { mode: "wait", children: state === "success" ? /* @__PURE__ */ jsx(SuccessCelebration, { onComplete: handleSuccessComplete }, "success") : /* @__PURE__ */ jsxs(
1297
- motion.div,
1298
- {
1299
- variants: contentVariants,
1300
- initial: "hidden",
1301
- animate: "visible",
1302
- exit: "exit",
1303
- style: { display: "flex", flexDirection: "column", height: "100%" },
1304
- children: [
1305
- /* @__PURE__ */ jsxs(
1306
- "div",
1307
- {
1308
- style: {
1309
- display: "flex",
1310
- justifyContent: "space-between",
1311
- alignItems: "center",
1312
- padding: "20px 24px",
1313
- borderBottom: "1px solid var(--widget-glass-border)",
1314
- background: theme === "dark" ? "linear-gradient(180deg, rgba(255,255,255,0.02) 0%, transparent 100%)" : "linear-gradient(180deg, rgba(0,0,0,0.01) 0%, transparent 100%)"
1315
- },
1316
- children: [
1317
- /* @__PURE__ */ jsxs("div", { style: { display: "flex", alignItems: "center", gap: 12 }, children: [
1318
- /* @__PURE__ */ jsx(
1319
- "div",
1320
- {
1321
- style: {
1322
- width: 36,
1323
- height: 36,
1324
- borderRadius: 10,
1325
- background: "linear-gradient(145deg, var(--widget-primary) 0%, var(--widget-accent) 100%)",
1326
- display: "flex",
1327
- alignItems: "center",
1328
- justifyContent: "center",
1329
- boxShadow: "0 2px 8px -2px var(--widget-primary-glow)"
1330
- },
1331
- children: /* @__PURE__ */ jsxs("svg", { width: 18, height: 18, viewBox: "0 0 24 24", fill: "white", children: [
1332
- /* @__PURE__ */ jsx("path", { d: "M12 3a2.5 2.5 0 0 0-2.5 2.5v5a2.5 2.5 0 0 0 5 0v-5A2.5 2.5 0 0 0 12 3z" }),
1333
- /* @__PURE__ */ jsx("path", { d: "M16.5 10.5v1a4.5 4.5 0 0 1-9 0v-1", stroke: "white", strokeWidth: "2", strokeLinecap: "round", fill: "none" }),
1334
- /* @__PURE__ */ jsx("path", { d: "M12 15.5v2.5M9.5 18h5", stroke: "white", strokeWidth: "2", strokeLinecap: "round" })
1335
- ] })
1336
- }
1337
- ),
1338
- /* @__PURE__ */ jsx(
1339
- "h2",
1340
- {
1341
- id: "widget-title",
1342
- style: {
1343
- fontSize: 17,
1344
- fontWeight: 700,
1345
- color: "var(--widget-text-primary)",
1346
- margin: 0,
1347
- letterSpacing: "-0.01em"
1348
- },
1349
- children: "Share Feedback"
1350
- }
1351
- )
1352
- ] }),
1353
- /* @__PURE__ */ jsx(
1354
- motion.button,
1355
- {
1356
- whileHover: { scale: 1.1, backgroundColor: "var(--widget-glass-border)" },
1357
- whileTap: { scale: 0.9 },
1358
- onClick: handleClose,
1359
- "aria-label": "Close widget",
1360
- style: {
1361
- width: 32,
1362
- height: 32,
1363
- borderRadius: 8,
1364
- border: "none",
1365
- background: "transparent",
1366
- color: "var(--widget-text-secondary)",
1367
- cursor: "pointer",
1368
- display: "flex",
1369
- alignItems: "center",
1370
- justifyContent: "center",
1371
- transition: "background 0.15s ease"
1372
- },
1373
- children: /* @__PURE__ */ jsx(
1374
- "svg",
1375
- {
1376
- width: 18,
1377
- height: 18,
1378
- viewBox: "0 0 24 24",
1379
- fill: "none",
1380
- stroke: "currentColor",
1381
- strokeWidth: 2.5,
1382
- strokeLinecap: "round",
1383
- children: /* @__PURE__ */ jsx("path", { d: "M18 6L6 18M6 6l12 12" })
1384
- }
1385
- )
1386
- }
1387
- )
1388
- ]
1389
- }
1390
- ),
1391
- allowVoice && allowText && /* @__PURE__ */ jsx(
1392
- "div",
1393
- {
1394
- style: {
1395
- display: "flex",
1396
- padding: "8px 12px",
1397
- gap: 4,
1398
- background: "var(--widget-glass-bg)",
1399
- margin: "0 16px",
1400
- marginTop: 16,
1401
- borderRadius: 12,
1402
- border: "1px solid var(--widget-glass-border)"
1403
- },
1404
- children: ["voice", "text"].map((tab) => /* @__PURE__ */ jsxs(
1405
- motion.button,
1406
- {
1407
- onClick: () => setActiveTab(tab),
1408
- style: {
1409
- flex: 1,
1410
- padding: "10px 0",
1411
- border: "none",
1412
- background: activeTab === tab ? "linear-gradient(145deg, var(--widget-primary) 0%, var(--widget-accent) 100%)" : "transparent",
1413
- fontSize: 13,
1414
- fontWeight: 600,
1415
- color: activeTab === tab ? "white" : "var(--widget-text-secondary)",
1416
- cursor: "pointer",
1417
- position: "relative",
1418
- borderRadius: 8,
1419
- transition: "all 0.2s ease",
1420
- display: "flex",
1421
- alignItems: "center",
1422
- justifyContent: "center",
1423
- gap: 6,
1424
- boxShadow: activeTab === tab ? "0 2px 8px -2px var(--widget-primary-glow)" : "none"
1425
- },
1426
- whileHover: { scale: activeTab === tab ? 1 : 1.02 },
1427
- whileTap: { scale: 0.98 },
1428
- children: [
1429
- tab === "voice" ? /* @__PURE__ */ jsxs("svg", { width: 14, height: 14, viewBox: "0 0 24 24", fill: "currentColor", children: [
1430
- /* @__PURE__ */ jsx("path", { d: "M12 3a2.5 2.5 0 0 0-2.5 2.5v5a2.5 2.5 0 0 0 5 0v-5A2.5 2.5 0 0 0 12 3z" }),
1431
- /* @__PURE__ */ jsx("path", { d: "M16.5 10.5v1a4.5 4.5 0 0 1-9 0v-1", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", fill: "none" })
1432
- ] }) : /* @__PURE__ */ jsx("svg", { width: 14, height: 14, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", children: /* @__PURE__ */ jsx("path", { d: "M4 6h16M4 12h16M4 18h10" }) }),
1433
- tab === "voice" ? "Voice" : "Text"
1434
- ]
1435
- },
1436
- tab
1437
- ))
1438
- }
1439
- ),
1440
- /* @__PURE__ */ jsxs("div", { style: { padding: "20px 24px" }, children: [
1441
- /* @__PURE__ */ jsx("div", { style: { marginBottom: 24 }, children: /* @__PURE__ */ jsx(
1442
- CategorySelector,
1443
- {
1444
- categories,
1445
- selected: selectedCategory,
1446
- onSelect: setSelectedCategory
1447
- }
1448
- ) }),
1449
- activeTab === "voice" && /* @__PURE__ */ jsxs("div", { style: { textAlign: "center" }, children: [
1450
- /* @__PURE__ */ jsxs(
1451
- "div",
1452
- {
1453
- style: {
1454
- background: "var(--widget-glass-bg)",
1455
- borderRadius: 16,
1456
- padding: "24px 16px",
1457
- border: "1px solid var(--widget-glass-border)",
1458
- marginBottom: 16
1459
- },
1460
- children: [
1461
- /* @__PURE__ */ jsx(AmbientOrb, { audioLevel, isRecording }),
1462
- isRecording && /* @__PURE__ */ jsx(
1463
- RecordingTimer,
1464
- {
1465
- seconds: recordingSeconds,
1466
- maxDuration: maxRecordingDuration
1467
- }
1468
- ),
1469
- !isRecording && recordingSeconds === 0 && !transcription && /* @__PURE__ */ jsx(
1470
- "p",
1471
- {
1472
- style: {
1473
- color: "var(--widget-text-muted)",
1474
- fontSize: 13,
1475
- marginTop: 12,
1476
- marginBottom: 0
1477
- },
1478
- children: "Click Record to start"
1479
- }
1480
- )
1481
- ]
1482
- }
1483
- ),
1484
- (isRecording || transcription) && /* @__PURE__ */ jsx("div", { style: { marginBottom: 16 }, children: /* @__PURE__ */ jsx(
1485
- TranscriptionDisplay,
1486
- {
1487
- text: transcription,
1488
- isLive: isRecording
1489
- }
1490
- ) }),
1491
- /* @__PURE__ */ jsx(
1492
- MagneticButton,
1493
- {
1494
- onClick: isRecording ? handleStopRecording : handleStartRecording,
1495
- variant: isRecording ? "recording" : "primary",
1496
- children: isRecording ? /* @__PURE__ */ jsxs(Fragment, { children: [
1497
- /* @__PURE__ */ jsx(
1498
- motion.div,
1499
- {
1500
- animate: { scale: [1, 1.2, 1] },
1501
- transition: { duration: 1, repeat: Infinity },
1502
- style: {
1503
- width: 10,
1504
- height: 10,
1505
- borderRadius: 2,
1506
- background: "white"
1507
- }
1508
- }
1509
- ),
1510
- "Stop Recording"
1511
- ] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
1512
- /* @__PURE__ */ jsx("svg", { width: 16, height: 16, viewBox: "0 0 24 24", fill: "currentColor", children: /* @__PURE__ */ jsx("path", { d: "M12 3a2.5 2.5 0 0 0-2.5 2.5v5a2.5 2.5 0 0 0 5 0v-5A2.5 2.5 0 0 0 12 3z" }) }),
1513
- transcription ? "Record Again" : "Record"
1514
- ] })
1515
- }
1516
- )
1517
- ] }),
1518
- activeTab === "text" && /* @__PURE__ */ jsx("div", { children: /* @__PURE__ */ jsxs(
1519
- "div",
1520
- {
1521
- style: {
1522
- position: "relative",
1523
- background: "var(--widget-glass-bg)",
1524
- border: "1px solid var(--widget-glass-border)",
1525
- borderRadius: 14,
1526
- overflow: "hidden"
1527
- },
1528
- children: [
1529
- /* @__PURE__ */ jsx(
1530
- "textarea",
1531
- {
1532
- value: textContent,
1533
- onChange: (e) => setTextContent(e.target.value),
1534
- placeholder: "Share your thoughts, ideas, or feedback...",
1535
- style: {
1536
- width: "100%",
1537
- minHeight: 140,
1538
- padding: 16,
1539
- fontSize: 14,
1540
- lineHeight: 1.7,
1541
- color: "var(--widget-text-primary)",
1542
- background: "transparent",
1543
- border: "none",
1544
- resize: "none",
1545
- fontFamily: "inherit",
1546
- outline: "none"
1547
- }
1548
- }
1549
- ),
1550
- /* @__PURE__ */ jsx(
1551
- "div",
1552
- {
1553
- style: {
1554
- display: "flex",
1555
- justifyContent: "flex-end",
1556
- padding: "8px 12px",
1557
- borderTop: "1px solid var(--widget-glass-border)",
1558
- background: theme === "dark" ? "rgba(0,0,0,0.2)" : "rgba(0,0,0,0.02)"
1559
- },
1560
- children: /* @__PURE__ */ jsxs(
1561
- "span",
1562
- {
1563
- style: {
1564
- fontSize: 11,
1565
- fontWeight: 500,
1566
- color: textContent.length > 4500 ? "var(--widget-recording)" : "var(--widget-text-muted)"
1567
- },
1568
- children: [
1569
- textContent.length.toLocaleString(),
1570
- " / 5,000"
1571
- ]
1572
- }
1573
- )
1574
- }
1575
- )
1576
- ]
1577
- }
1578
- ) })
1579
- ] }),
1580
- /* @__PURE__ */ jsx(
1581
- "div",
1582
- {
1583
- style: {
1584
- padding: "16px 24px 20px",
1585
- borderTop: "1px solid var(--widget-glass-border)",
1586
- background: theme === "dark" ? "linear-gradient(180deg, transparent 0%, rgba(0,0,0,0.2) 100%)" : "linear-gradient(180deg, transparent 0%, rgba(0,0,0,0.02) 100%)"
1587
- },
1588
- children: /* @__PURE__ */ jsxs(
1589
- MagneticButton,
1590
- {
1591
- onClick: handleSubmit,
1592
- disabled: !selectedCategory || activeTab === "voice" && !transcription || activeTab === "text" && !textContent.trim(),
1593
- loading: isSubmitting,
1594
- children: [
1595
- /* @__PURE__ */ jsx("svg", { width: 16, height: 16, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2.5", strokeLinecap: "round", children: /* @__PURE__ */ jsx("path", { d: "M22 2L11 13M22 2l-7 20-4-9-9-4 20-7z" }) }),
1596
- "Submit Feedback"
1597
- ]
1598
- }
1599
- )
1600
- }
1601
- )
1602
- ]
1603
- },
1604
- "content"
1605
- ) })
1606
- },
1607
- "modal"
1608
- ) }),
1609
- /* @__PURE__ */ jsxs("div", { "aria-live": "polite", className: "sr-only", style: { position: "absolute", width: 1, height: 1, overflow: "hidden" }, children: [
1610
- isRecording && "Recording started. Speak now.",
1611
- !isRecording && transcription && `Transcription complete.`,
1612
- state === "success" && "Feedback submitted successfully."
1613
- ] })
1614
- ]
1615
- }
1616
- ) });
1617
- };
1618
- function adjustColor(color, amount) {
1619
- const hex = color.replace("#", "");
1620
- const num = parseInt(hex, 16);
1621
- const r = Math.min(255, Math.max(0, (num >> 16) + amount));
1622
- const g = Math.min(255, Math.max(0, (num >> 8 & 255) + amount));
1623
- const b = Math.min(255, Math.max(0, (num & 255) + amount));
1624
- return `#${(r << 16 | g << 8 | b).toString(16).padStart(6, "0")}`;
1625
- }
1626
-
1627
- // src/index.tsx
1628
- function IntentAIWidget({
1629
- apiKey,
1630
- apiUrl,
1631
- widgetUrl,
1632
- position = "bottom-right",
1633
- theme = "light",
1634
- primaryColor,
1635
- allowVoice = true,
1636
- allowText = true,
1637
- allowScreenshot = true,
1638
- customMetadata,
1639
- user,
1640
- onOpen: _onOpen,
1641
- onClose: _onClose,
1642
- onFeedbackSubmitted: _onFeedbackSubmitted,
1643
- onError
1644
- }) {
1645
- const widgetRef = useRef2(null);
1646
- const scriptLoadedRef = useRef2(false);
1647
- const initWidget = useCallback2(() => {
1648
- if (!window.FeedbackIQ || widgetRef.current) return;
1649
- try {
1650
- widgetRef.current = new window.FeedbackIQ({
1651
- apiKey,
1652
- apiUrl,
1653
- widgetUrl,
1654
- position,
1655
- theme,
1656
- primaryColor,
1657
- allowVoice,
1658
- allowText,
1659
- allowScreenshot,
1660
- customMetadata,
1661
- user
1662
- });
1663
- } catch (error) {
1664
- onError?.(error instanceof Error ? error : new Error("Failed to initialize widget"));
1665
- }
1666
- void _onOpen;
1667
- void _onClose;
1668
- void _onFeedbackSubmitted;
1669
- }, [apiKey, apiUrl, widgetUrl, position, theme, primaryColor, allowVoice, allowText, allowScreenshot, customMetadata, user, onError]);
1670
- useEffect2(() => {
1671
- if (!widgetUrl) {
1672
- onError?.(new Error("widgetUrl is required for IntentAIWidget"));
1673
- return;
1674
- }
1675
- if (window.FeedbackIQ) {
1676
- initWidget();
1677
- return;
1678
- }
1679
- if (scriptLoadedRef.current) return;
1680
- const existingScript = document.querySelector(`script[src="${widgetUrl}"]`);
1681
- if (existingScript) {
1682
- existingScript.addEventListener("load", initWidget);
1683
- return;
1684
- }
1685
- scriptLoadedRef.current = true;
1686
- const script = document.createElement("script");
1687
- script.src = widgetUrl;
1688
- script.async = true;
1689
- script.onload = initWidget;
1690
- script.onerror = () => {
1691
- onError?.(new Error("Failed to load Intent AI widget script"));
1692
- };
1693
- document.head.appendChild(script);
1694
- return () => {
1695
- if (widgetRef.current) {
1696
- widgetRef.current.destroy();
1697
- widgetRef.current = null;
1698
- }
1699
- };
1700
- }, [initWidget, onError, widgetUrl]);
1701
- useEffect2(() => {
1702
- if (widgetRef.current && user) {
1703
- widgetRef.current.identify(user);
1704
- }
1705
- }, [user]);
1706
- useEffect2(() => {
1707
- if (widgetRef.current && customMetadata) {
1708
- widgetRef.current.setMetadata(customMetadata);
1709
- }
1710
- }, [customMetadata]);
1711
- return null;
1712
- }
1713
- function useIntentAI() {
1714
- const open = useCallback2(() => {
1715
- const widget = document.getElementById("feedbackiq-widget");
1716
- if (widget) {
1717
- const trigger = widget.shadowRoot?.querySelector(".fiq-trigger");
1718
- trigger?.click();
1719
- }
1720
- }, []);
1721
- const close = useCallback2(() => {
1722
- const widget = document.getElementById("feedbackiq-widget");
1723
- if (widget) {
1724
- const closeBtn = widget.shadowRoot?.querySelector(".fiq-close");
1725
- closeBtn?.click();
1726
- }
1727
- }, []);
1728
- const identify = useCallback2((user) => {
1729
- if (window.IntentAI?.widget) {
1730
- window.IntentAI.widget.identify(user);
1731
- }
1732
- }, []);
1733
- const setMetadata = useCallback2((metadata) => {
1734
- if (window.IntentAI?.widget) {
1735
- window.IntentAI.widget.setMetadata(metadata);
1736
- }
1737
- }, []);
1738
- return { open, close, identify, setMetadata };
1739
- }
1740
- var index_default = IntentAIWidget;
1741
- export {
1742
- IntentAIWidget,
1743
- PremiumVoiceWidget,
1744
- index_default as default,
1745
- useIntentAI
1746
- };
1
+ import $,{createContext,forwardRef,useContext,useState,useRef,useMemo,useEffect,useCallback}from'react';import {jsx,Fragment}from'react/jsx-runtime';var Y="https://intetnt-ai-v1-web-app-production.up.railway.app/widget/v1.js",k=null,E=false;function H(e){return E&&window.IntentAI?Promise.resolve():k||(k=new Promise((t,r)=>{if(document.querySelector(`script[src^="${Y}"]`)){window.IntentAI?(E=true,t()):window.addEventListener("intentai:ready",()=>{E=true,t();},{once:true});return}let n=document.createElement("script");n.src=Y,n.async=true,n.id="intentai-widget-script",n.setAttribute("data-api-key",e.apiKey),n.onload=()=>{window.addEventListener("intentai:ready",()=>{E=true,t();},{once:true}),setTimeout(()=>{!E&&window.IntentAI&&(E=true,t());},3e3);},n.onerror=()=>{k=null,r(new Error("Failed to load Intent AI widget script"));},document.head.appendChild(n);}),k)}var L={};var G=createContext(null),v=typeof window!="undefined";function j(e){if(e)return e;if(typeof process!="undefined"&&process.env){let t=process.env.NEXT_PUBLIC_INTENT_AI_KEY||process.env.REACT_APP_INTENT_AI_KEY||process.env.VITE_INTENT_AI_KEY;if(t)return t}try{if(typeof L!="undefined"&&L.env)return L.env.VITE_INTENT_AI_KEY}catch(t){}}function ee({children:e,apiKey:t,user:r,position:n="bottom-right",theme:p="auto",primaryColor:s,autoShow:a=true,metadata:I,onReady:f,onOpen:x,onClose:m,onSubmit:O,onError:C}){let[b,z]=useState(false),[N,F]=useState(null),R=useRef(r),l=useRef({onReady:f,onOpen:x,onClose:m,onSubmit:O,onError:C});l.current={onReady:f,onOpen:x,onClose:m,onSubmit:O,onError:C};let w=useMemo(()=>j(t),[t]);useEffect(()=>{w&&!w.startsWith("pk_")&&console.error('[IntentAI] Invalid API key format. Public keys should start with "pk_". Never use secret keys (sk_*) in client-side code.');},[w]),useEffect(()=>{if(!v)return;if(!w){console.warn("[IntentAI] No API key provided. Set the apiKey prop or NEXT_PUBLIC_INTENT_AI_KEY environment variable.");return}window.IntentAIConfig={apiKey:w,position:n,theme:p,primaryColor:s,autoShow:a,metadata:I};let d=()=>{var i,o,c;z(true),(o=(i=l.current).onReady)==null||o.call(i),R.current&&((c=window.IntentAI)==null||c.identify(R.current));},y=()=>{var i,o;(o=(i=l.current).onOpen)==null||o.call(i);},A=()=>{var i,o;(o=(i=l.current).onClose)==null||o.call(i);},W=i=>{var o,c;(c=(o=l.current).onSubmit)==null||c.call(o,i.detail);},V=i=>{var o,c;F(i.detail),(c=(o=l.current).onError)==null||c.call(o,i.detail);};return window.addEventListener("intentai:ready",d),window.addEventListener("intentai:open",y),window.addEventListener("intentai:close",A),window.addEventListener("intentai:submit",W),window.addEventListener("intentai:error",V),H({apiKey:w}).catch(i=>{var o,c;F(i),(c=(o=l.current).onError)==null||c.call(o,i);}),()=>{window.removeEventListener("intentai:ready",d),window.removeEventListener("intentai:open",y),window.removeEventListener("intentai:close",A),window.removeEventListener("intentai:submit",W),window.removeEventListener("intentai:error",V);}},[w,n,p,s,a,I]),useEffect(()=>{R.current=r,v&&b&&r&&window.IntentAI&&window.IntentAI.identify(r);},[r,b]);let M=useCallback(d=>{v&&(window.IntentAI?window.IntentAI.open(d):console.warn("[IntentAI] Widget not ready. Call will be ignored."));},[]),U=useCallback(()=>{var d;v&&((d=window.IntentAI)==null||d.close());},[]),B=useCallback(d=>{v&&(window.IntentAI?window.IntentAI.identify(d):R.current=d);},[]),K=useCallback((d,y)=>{var A;v&&((A=window.IntentAI)==null||A.track(d,y));},[]),S=useCallback(d=>{var y;v&&((y=window.IntentAI)==null||y.setMetadata(d));},[]),J=useMemo(()=>({isReady:b,error:N,open:M,close:U,identify:B,track:K,setMetadata:S}),[b,N,M,U,B,K,S]);return jsx(G.Provider,{value:J,children:e})}function u(){let e=useContext(G);if(!e)throw new Error('useIntentAI must be used within an IntentAIProvider. Wrap your app with <IntentAIProvider apiKey="pk_...">.');return e}function te(e){let{identify:t,isReady:r}=u();useEffect(()=>{r&&(e!=null&&e.id)&&t(e);},[r,e==null?void 0:e.id,e==null?void 0:e.email,e==null?void 0:e.name,t,e]);}var oe=forwardRef(function({openOptions:t,children:r="Feedback",onClick:n,disabled:p,...s},a){let{open:I,isReady:f}=u();return jsx("button",{ref:a,type:"button",onClick:m=>{n==null||n(m),m.defaultPrevented||I(t);},disabled:p||!f,"aria-label":"Open feedback",...s,children:r})});function ie({children:e,openOptions:t}){let{open:r,isReady:n,error:p}=u(),s=a=>{r(a||t);};return typeof e=="function"?e({open:s,isReady:n,error:p}):$.cloneElement(e,{onClick:a=>{var I,f;(f=(I=e.props).onClick)==null||f.call(I,a),a.defaultPrevented||s();},disabled:e.props.disabled||!n})}function de({user:e,children:t}){let{identify:r,isReady:n}=u();return useEffect(()=>{n&&(e!=null&&e.id)&&r(e);},[n,e,r]),jsx(Fragment,{children:t})}function ae({metadata:e,children:t}){let{setMetadata:r,isReady:n}=u();return useEffect(()=>{n&&e&&r(e);},[n,e,r]),jsx(Fragment,{children:t})}function se({event:e,properties:t,trigger:r="mount",children:n}){let{track:p,isReady:s}=u(),a=$.useRef(false);return useEffect(()=>{r==="mount"&&s&&!a.current&&(p(e,t),a.current=true);},[s,e,t,p,r]),r==="render"&&s&&p(e,t),jsx(Fragment,{children:n})}
2
+ export{oe as FeedbackButton,ie as FeedbackTrigger,de as IdentifyUser,ee as IntentAIProvider,ae as SetMetadata,se as TrackEvent,te as useIdentify,u as useIntentAI};