@intentai/react 2.2.0 → 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,2106 +1,2 @@
1
- "use client";
2
-
3
- // src/index.tsx
4
- import { useEffect as useEffect3, useRef as useRef4, useCallback as useCallback4 } 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 && audioContextRef.current.state !== "closed") {
115
- audioContextRef.current.close().catch(() => {
116
- });
117
- audioContextRef.current = null;
118
- }
119
- analyserRef.current = null;
120
- return;
121
- }
122
- const initAudio = async () => {
123
- try {
124
- const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
125
- streamRef.current = stream;
126
- const audioContext = new AudioContext();
127
- audioContextRef.current = audioContext;
128
- const source = audioContext.createMediaStreamSource(stream);
129
- const analyser = audioContext.createAnalyser();
130
- analyser.fftSize = 256;
131
- source.connect(analyser);
132
- analyserRef.current = analyser;
133
- const dataArray = new Uint8Array(analyser.frequencyBinCount);
134
- const updateLevel = () => {
135
- if (!analyserRef.current) return;
136
- analyserRef.current.getByteFrequencyData(dataArray);
137
- const average = dataArray.reduce((a, b) => a + b) / dataArray.length;
138
- setAudioLevel(average / 255);
139
- animationRef.current = requestAnimationFrame(updateLevel);
140
- };
141
- updateLevel();
142
- } catch (error) {
143
- console.error("Failed to access microphone:", error);
144
- }
145
- };
146
- initAudio();
147
- return () => {
148
- if (animationRef.current) {
149
- cancelAnimationFrame(animationRef.current);
150
- }
151
- if (streamRef.current) {
152
- streamRef.current.getTracks().forEach((track) => track.stop());
153
- }
154
- if (audioContextRef.current && audioContextRef.current.state !== "closed") {
155
- audioContextRef.current.close().catch(() => {
156
- });
157
- }
158
- };
159
- }, [isRecording]);
160
- return audioLevel;
161
- };
162
- var springPresets = {
163
- snappy: { type: "spring", stiffness: 500, damping: 30 },
164
- bouncy: { type: "spring", stiffness: 400, damping: 15 },
165
- smooth: { type: "spring", stiffness: 300, damping: 30 },
166
- gentle: { type: "spring", stiffness: 200, damping: 25 }
167
- };
168
- var triggerVariants = {
169
- idle: {
170
- scale: 1,
171
- opacity: 1
172
- },
173
- hover: {
174
- scale: 1.08,
175
- transition: springPresets.bouncy
176
- },
177
- tap: {
178
- scale: 0.95,
179
- transition: springPresets.snappy
180
- },
181
- scrolling: {
182
- scale: 0.85,
183
- opacity: 0.3,
184
- transition: { duration: 0.2 }
185
- },
186
- hidden: {
187
- scale: 0,
188
- opacity: 0,
189
- transition: springPresets.smooth
190
- }
191
- };
192
- var containerVariants = {
193
- trigger: {
194
- width: 56,
195
- height: 56,
196
- borderRadius: 28,
197
- transition: springPresets.smooth
198
- },
199
- expanded: {
200
- width: 360,
201
- height: "auto",
202
- borderRadius: 20,
203
- transition: {
204
- ...springPresets.smooth,
205
- staggerChildren: 0.05,
206
- delayChildren: 0.15
207
- }
208
- }
209
- };
210
- var contentVariants = {
211
- hidden: {
212
- opacity: 0,
213
- y: 20,
214
- scale: 0.95
215
- },
216
- visible: {
217
- opacity: 1,
218
- y: 0,
219
- scale: 1,
220
- transition: springPresets.smooth
221
- },
222
- exit: {
223
- opacity: 0,
224
- y: -10,
225
- scale: 0.95,
226
- transition: { duration: 0.2 }
227
- }
228
- };
229
- var BreathingRing = ({ isActive }) => {
230
- const { reducedMotion } = useWidget();
231
- if (reducedMotion) return null;
232
- return /* @__PURE__ */ jsxs(Fragment, { children: [
233
- /* @__PURE__ */ jsx(
234
- motion.div,
235
- {
236
- animate: {
237
- scale: isActive ? [1, 1.3, 1] : 1,
238
- opacity: isActive ? [0.2, 0.4, 0.2] : 0.2
239
- },
240
- transition: {
241
- duration: 2.5,
242
- repeat: Infinity,
243
- ease: "easeInOut"
244
- },
245
- style: {
246
- position: "absolute",
247
- inset: -16,
248
- borderRadius: "50%",
249
- background: "radial-gradient(circle, var(--widget-primary) 0%, transparent 70%)",
250
- pointerEvents: "none",
251
- filter: "blur(8px)"
252
- }
253
- }
254
- ),
255
- /* @__PURE__ */ jsx(
256
- motion.div,
257
- {
258
- className: "fiq-breathing-ring",
259
- animate: {
260
- scale: isActive ? [1, 1.08, 1] : 1,
261
- opacity: isActive ? [0.6, 1, 0.6] : 0.6
262
- },
263
- transition: {
264
- duration: 2,
265
- repeat: Infinity,
266
- ease: "easeInOut"
267
- },
268
- style: {
269
- position: "absolute",
270
- inset: -4,
271
- borderRadius: "50%",
272
- border: "2px solid var(--widget-primary)",
273
- pointerEvents: "none",
274
- opacity: 0.5
275
- }
276
- }
277
- )
278
- ] });
279
- };
280
- var AmbientOrb = ({ audioLevel, isRecording }) => {
281
- const { reducedMotion } = useWidget();
282
- const [particles, setParticles] = useState([]);
283
- useEffect(() => {
284
- if (!isRecording || audioLevel < 0.3 || reducedMotion) return;
285
- const particleCount = Math.floor(audioLevel * 4);
286
- const newParticles = Array.from({ length: particleCount }, () => ({
287
- id: Math.random().toString(36).slice(2, 11),
288
- x: 70,
289
- y: 70,
290
- angle: Math.random() * Math.PI * 2,
291
- speed: 1 + Math.random() * 3,
292
- size: 2 + Math.random() * 3,
293
- color: Math.random() > 0.5 ? "var(--widget-primary-light)" : "var(--widget-accent)"
294
- }));
295
- setParticles((prev) => [...prev, ...newParticles].slice(-40));
296
- }, [audioLevel, isRecording, reducedMotion]);
297
- useEffect(() => {
298
- if (particles.length === 0) return;
299
- const timeout = setTimeout(() => {
300
- setParticles((prev) => prev.slice(1));
301
- }, 100);
302
- return () => clearTimeout(timeout);
303
- }, [particles]);
304
- const orbScale = 1 + audioLevel * 0.2;
305
- 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: [
306
- /* @__PURE__ */ jsxs("defs", { children: [
307
- /* @__PURE__ */ jsxs("radialGradient", { id: "orbGradient", cx: "30%", cy: "30%", children: [
308
- /* @__PURE__ */ jsx("stop", { offset: "0%", stopColor: "var(--widget-accent-light)" }),
309
- /* @__PURE__ */ jsx("stop", { offset: "40%", stopColor: "var(--widget-primary)" }),
310
- /* @__PURE__ */ jsx("stop", { offset: "100%", stopColor: "var(--widget-primary-hover)" })
311
- ] }),
312
- /* @__PURE__ */ jsxs("filter", { id: "orbGlow", x: "-50%", y: "-50%", width: "200%", height: "200%", children: [
313
- /* @__PURE__ */ jsx("feGaussianBlur", { stdDeviation: "8", result: "blur" }),
314
- /* @__PURE__ */ jsx("feComposite", { in: "SourceGraphic", in2: "blur", operator: "over" })
315
- ] })
316
- ] }),
317
- /* @__PURE__ */ jsx(
318
- motion.circle,
319
- {
320
- cx: 70,
321
- cy: 70,
322
- r: 50,
323
- fill: "none",
324
- stroke: "var(--widget-primary-glow)",
325
- strokeWidth: 2,
326
- animate: {
327
- r: isRecording ? [50, 58, 50] : 50,
328
- opacity: isRecording ? [0.3, 0.6, 0.3] : 0.3
329
- },
330
- transition: {
331
- duration: 2,
332
- repeat: Infinity,
333
- ease: "easeInOut"
334
- }
335
- }
336
- ),
337
- /* @__PURE__ */ jsx(
338
- motion.circle,
339
- {
340
- cx: 70,
341
- cy: 70,
342
- r: 40,
343
- fill: "url(#orbGradient)",
344
- filter: "url(#orbGlow)",
345
- animate: { scale: reducedMotion ? 1 : orbScale },
346
- transition: springPresets.snappy,
347
- style: { transformOrigin: "center" }
348
- }
349
- ),
350
- /* @__PURE__ */ jsx("g", { transform: "translate(70, 70)", children: /* @__PURE__ */ jsxs(
351
- motion.g,
352
- {
353
- animate: {
354
- opacity: isRecording ? [1, 0.7, 1] : 1
355
- },
356
- transition: {
357
- duration: 1,
358
- repeat: isRecording ? Infinity : 0
359
- },
360
- children: [
361
- /* @__PURE__ */ jsx("rect", { x: "-8", y: "-20", width: "16", height: "26", rx: "8", fill: "white" }),
362
- /* @__PURE__ */ jsx(
363
- "path",
364
- {
365
- d: "M-14 0 v6 a14 14 0 0 0 28 0 v-6",
366
- fill: "none",
367
- stroke: "white",
368
- strokeWidth: "3",
369
- strokeLinecap: "round"
370
- }
371
- ),
372
- /* @__PURE__ */ jsx("line", { x1: "0", y1: "20", x2: "0", y2: "28", stroke: "white", strokeWidth: "3", strokeLinecap: "round" }),
373
- /* @__PURE__ */ jsx("line", { x1: "-8", y1: "28", x2: "8", y2: "28", stroke: "white", strokeWidth: "3", strokeLinecap: "round" })
374
- ]
375
- }
376
- ) }),
377
- particles.filter((p) => p.size != null).map((particle) => /* @__PURE__ */ jsx(
378
- motion.circle,
379
- {
380
- cx: 70,
381
- cy: 70,
382
- r: particle.size || 3,
383
- fill: particle.color,
384
- initial: { opacity: 0.8, scale: 1 },
385
- animate: {
386
- cx: 70 + Math.cos(particle.angle) * 50,
387
- cy: 70 + Math.sin(particle.angle) * 50,
388
- opacity: 0,
389
- scale: 0.5
390
- },
391
- transition: { duration: 1.5, ease: "easeOut" }
392
- },
393
- particle.id
394
- ))
395
- ] }) });
396
- };
397
- var RecordingTimer = ({
398
- seconds,
399
- maxDuration
400
- }) => {
401
- const minutes = Math.floor(seconds / 60);
402
- const secs = seconds % 60;
403
- const progress = seconds / maxDuration;
404
- const isNearLimit = seconds >= maxDuration - 30;
405
- return /* @__PURE__ */ jsxs("div", { className: "fiq-timer", style: { textAlign: "center" }, children: [
406
- /* @__PURE__ */ jsxs(
407
- motion.div,
408
- {
409
- style: {
410
- fontSize: 36,
411
- fontWeight: 700,
412
- fontFamily: "ui-monospace, monospace",
413
- color: isNearLimit ? "var(--widget-recording)" : "var(--widget-text-primary)"
414
- },
415
- animate: isNearLimit ? { scale: [1, 1.05, 1] } : {},
416
- transition: { duration: 0.5, repeat: isNearLimit ? Infinity : 0 },
417
- children: [
418
- String(minutes).padStart(2, "0"),
419
- ":",
420
- String(secs).padStart(2, "0")
421
- ]
422
- }
423
- ),
424
- /* @__PURE__ */ jsx(
425
- "div",
426
- {
427
- style: {
428
- width: 160,
429
- height: 4,
430
- background: "var(--widget-glass-border)",
431
- borderRadius: 2,
432
- margin: "12px auto",
433
- overflow: "hidden"
434
- },
435
- children: /* @__PURE__ */ jsx(
436
- motion.div,
437
- {
438
- style: {
439
- height: "100%",
440
- background: isNearLimit ? "var(--widget-recording)" : "var(--widget-primary)",
441
- borderRadius: 2
442
- },
443
- initial: { width: 0 },
444
- animate: { width: `${progress * 100}%` },
445
- transition: { duration: 0.5 }
446
- }
447
- )
448
- }
449
- )
450
- ] });
451
- };
452
- var TranscriptionDisplay = ({ text, isLive }) => {
453
- const { reducedMotion } = useWidget();
454
- const [displayedText, setDisplayedText] = useState("");
455
- const [cursorVisible, setCursorVisible] = useState(true);
456
- useEffect(() => {
457
- if (reducedMotion) {
458
- setDisplayedText(text);
459
- return;
460
- }
461
- if (text.length > displayedText.length) {
462
- const timeout = setTimeout(() => {
463
- setDisplayedText(text.slice(0, displayedText.length + 1));
464
- }, 30 + Math.random() * 40);
465
- return () => clearTimeout(timeout);
466
- }
467
- }, [text, displayedText, reducedMotion]);
468
- useEffect(() => {
469
- if (!isLive) return;
470
- const interval = setInterval(() => setCursorVisible((v) => !v), 530);
471
- return () => clearInterval(interval);
472
- }, [isLive]);
473
- return /* @__PURE__ */ jsxs(
474
- "div",
475
- {
476
- className: "fiq-transcription",
477
- style: {
478
- padding: 16,
479
- background: "var(--widget-glass-bg)",
480
- borderRadius: 12,
481
- border: "1px solid var(--widget-glass-border)",
482
- minHeight: 80,
483
- maxHeight: 120,
484
- overflow: "auto"
485
- },
486
- children: [
487
- /* @__PURE__ */ jsxs("p", { style: { color: "var(--widget-text-primary)", fontSize: 14, lineHeight: 1.6, margin: 0 }, children: [
488
- displayedText || /* @__PURE__ */ jsx("span", { style: { color: "var(--widget-text-muted)", fontStyle: "italic" }, children: "Start speaking..." }),
489
- isLive && /* @__PURE__ */ jsx(
490
- motion.span,
491
- {
492
- animate: { opacity: cursorVisible ? 1 : 0 },
493
- style: {
494
- display: "inline-block",
495
- width: 2,
496
- height: "1.2em",
497
- background: "var(--widget-primary)",
498
- marginLeft: 2,
499
- verticalAlign: "text-bottom"
500
- }
501
- }
502
- )
503
- ] }),
504
- isLive && /* @__PURE__ */ jsxs(
505
- motion.div,
506
- {
507
- initial: { opacity: 0, y: 5 },
508
- animate: { opacity: 1, y: 0 },
509
- style: {
510
- display: "flex",
511
- alignItems: "center",
512
- gap: 8,
513
- marginTop: 12,
514
- color: "var(--widget-text-secondary)",
515
- fontSize: 12
516
- },
517
- children: [
518
- /* @__PURE__ */ jsx(ListeningDots, {}),
519
- /* @__PURE__ */ jsx("span", { children: "Listening..." })
520
- ]
521
- }
522
- )
523
- ]
524
- }
525
- );
526
- };
527
- var ListeningDots = () => /* @__PURE__ */ jsx("div", { style: { display: "flex", gap: 4 }, children: [0, 1, 2].map((i) => /* @__PURE__ */ jsx(
528
- motion.span,
529
- {
530
- animate: {
531
- scale: [1, 1.4, 1],
532
- opacity: [0.5, 1, 0.5]
533
- },
534
- transition: {
535
- duration: 1,
536
- repeat: Infinity,
537
- delay: i * 0.15
538
- },
539
- style: {
540
- width: 6,
541
- height: 6,
542
- borderRadius: "50%",
543
- background: "var(--widget-primary)"
544
- }
545
- },
546
- i
547
- )) });
548
- var CategorySelector = ({ categories, selected, onSelect }) => {
549
- return /* @__PURE__ */ jsxs("div", { className: "fiq-category-selector", children: [
550
- /* @__PURE__ */ jsx(
551
- "label",
552
- {
553
- style: {
554
- display: "block",
555
- fontSize: 12,
556
- fontWeight: 600,
557
- color: "var(--widget-text-muted)",
558
- marginBottom: 12,
559
- textTransform: "uppercase",
560
- letterSpacing: "0.05em"
561
- },
562
- children: "Category"
563
- }
564
- ),
565
- /* @__PURE__ */ jsx(
566
- "div",
567
- {
568
- style: {
569
- display: "flex",
570
- flexWrap: "wrap",
571
- gap: 8
572
- },
573
- children: categories.map((category) => /* @__PURE__ */ jsxs(
574
- motion.button,
575
- {
576
- whileHover: { scale: 1.04, y: -1 },
577
- whileTap: { scale: 0.96 },
578
- onClick: () => onSelect(category.id),
579
- style: {
580
- padding: "10px 18px",
581
- background: selected === category.id ? "linear-gradient(145deg, var(--widget-primary) 0%, var(--widget-accent) 100%)" : "var(--widget-glass-bg)",
582
- border: `1.5px solid ${selected === category.id ? "transparent" : "var(--widget-glass-border)"}`,
583
- borderRadius: 24,
584
- cursor: "pointer",
585
- transition: "all 0.2s ease",
586
- boxShadow: selected === category.id ? "0 4px 12px -2px var(--widget-primary-glow)" : "0 2px 4px rgba(0,0,0,0.1)",
587
- position: "relative",
588
- overflow: "hidden"
589
- },
590
- children: [
591
- selected === category.id && /* @__PURE__ */ jsx(
592
- "div",
593
- {
594
- style: {
595
- position: "absolute",
596
- inset: 0,
597
- background: "linear-gradient(180deg, rgba(255,255,255,0.2) 0%, rgba(255,255,255,0) 50%)",
598
- borderRadius: "inherit",
599
- pointerEvents: "none"
600
- }
601
- }
602
- ),
603
- /* @__PURE__ */ jsx(
604
- "span",
605
- {
606
- style: {
607
- fontSize: 13,
608
- fontWeight: 600,
609
- color: selected === category.id ? "white" : "var(--widget-text-primary)",
610
- position: "relative",
611
- zIndex: 1
612
- },
613
- children: category.label
614
- }
615
- )
616
- ]
617
- },
618
- category.id
619
- ))
620
- }
621
- )
622
- ] });
623
- };
624
- var SuccessCelebration = ({ onComplete }) => {
625
- const { reducedMotion } = useWidget();
626
- const confettiParticles = useMemo(
627
- () => reducedMotion ? [] : Array.from({ length: 30 }, (_, i) => ({
628
- id: i,
629
- angle: i / 30 * Math.PI * 2,
630
- distance: 100 + Math.random() * 80,
631
- size: 4 + Math.random() * 6,
632
- color: ["#10b981", "#22d3d3", "#fbbf24", "#f472b6"][Math.floor(Math.random() * 4)],
633
- rotation: Math.random() * 360
634
- })),
635
- [reducedMotion]
636
- );
637
- useEffect(() => {
638
- const timer = setTimeout(onComplete, 3e3);
639
- return () => clearTimeout(timer);
640
- }, [onComplete]);
641
- return /* @__PURE__ */ jsxs(
642
- motion.div,
643
- {
644
- initial: { opacity: 0, scale: 0.8 },
645
- animate: { opacity: 1, scale: 1 },
646
- exit: { opacity: 0, scale: 0.8 },
647
- style: {
648
- display: "flex",
649
- flexDirection: "column",
650
- alignItems: "center",
651
- justifyContent: "center",
652
- padding: 40,
653
- textAlign: "center",
654
- position: "relative",
655
- overflow: "hidden"
656
- },
657
- children: [
658
- confettiParticles.map((particle) => /* @__PURE__ */ jsx(
659
- motion.div,
660
- {
661
- initial: { x: 0, y: 0, scale: 0, rotate: 0 },
662
- animate: {
663
- x: Math.cos(particle.angle) * particle.distance,
664
- y: Math.sin(particle.angle) * particle.distance + 50,
665
- scale: [0, 1, 1, 0],
666
- rotate: particle.rotation
667
- },
668
- transition: { duration: 1.2, ease: [0.22, 1, 0.36, 1] },
669
- style: {
670
- position: "absolute",
671
- top: "50%",
672
- left: "50%",
673
- width: particle.size,
674
- height: particle.size,
675
- background: particle.color,
676
- borderRadius: Math.random() > 0.5 ? "50%" : 2
677
- }
678
- },
679
- particle.id
680
- )),
681
- /* @__PURE__ */ jsx(motion.div, { style: { marginBottom: 20 }, children: /* @__PURE__ */ jsxs("svg", { width: 80, height: 80, viewBox: "0 0 80 80", children: [
682
- /* @__PURE__ */ jsx(
683
- motion.circle,
684
- {
685
- cx: 40,
686
- cy: 40,
687
- r: 36,
688
- fill: "var(--widget-success)",
689
- initial: { scale: 0 },
690
- animate: { scale: 1 },
691
- transition: springPresets.bouncy
692
- }
693
- ),
694
- /* @__PURE__ */ jsx(
695
- motion.path,
696
- {
697
- d: "M24 40 L35 51 L56 28",
698
- fill: "none",
699
- stroke: "white",
700
- strokeWidth: 5,
701
- strokeLinecap: "round",
702
- strokeLinejoin: "round",
703
- initial: { pathLength: 0 },
704
- animate: { pathLength: 1 },
705
- transition: { delay: 0.3, duration: 0.5, ease: "easeOut" }
706
- }
707
- )
708
- ] }) }),
709
- /* @__PURE__ */ jsx(
710
- motion.h3,
711
- {
712
- initial: { opacity: 0, y: 20 },
713
- animate: { opacity: 1, y: 0 },
714
- transition: { delay: 0.5 },
715
- style: {
716
- fontSize: 24,
717
- fontWeight: 700,
718
- color: "var(--widget-text-primary)",
719
- margin: 0,
720
- marginBottom: 8
721
- },
722
- children: "Thank you!"
723
- }
724
- ),
725
- /* @__PURE__ */ jsx(
726
- motion.p,
727
- {
728
- initial: { opacity: 0, y: 20 },
729
- animate: { opacity: 1, y: 0 },
730
- transition: { delay: 0.6 },
731
- style: {
732
- fontSize: 14,
733
- color: "var(--widget-text-secondary)",
734
- margin: 0
735
- },
736
- children: "Your feedback helps us improve"
737
- }
738
- ),
739
- /* @__PURE__ */ jsx(
740
- motion.div,
741
- {
742
- style: {
743
- position: "absolute",
744
- bottom: 20,
745
- left: "50%",
746
- transform: "translateX(-50%)",
747
- width: 100,
748
- height: 3,
749
- background: "var(--widget-glass-border)",
750
- borderRadius: 2,
751
- overflow: "hidden"
752
- },
753
- children: /* @__PURE__ */ jsx(
754
- motion.div,
755
- {
756
- initial: { width: "100%" },
757
- animate: { width: 0 },
758
- transition: { duration: 3, ease: "linear" },
759
- style: {
760
- height: "100%",
761
- background: "var(--widget-primary)"
762
- }
763
- }
764
- )
765
- }
766
- )
767
- ]
768
- }
769
- );
770
- };
771
- var MagneticButton = ({ children, onClick, onPressStart, onPressEnd, variant = "primary", disabled, loading }) => {
772
- const buttonRef = useRef(null);
773
- const [magnetOffset, setMagnetOffset] = useState({ x: 0, y: 0 });
774
- const handleMouseMove = (e) => {
775
- if (!buttonRef.current || disabled) return;
776
- const rect = buttonRef.current.getBoundingClientRect();
777
- const centerX = rect.left + rect.width / 2;
778
- const centerY = rect.top + rect.height / 2;
779
- setMagnetOffset({
780
- x: (e.clientX - centerX) * 0.08,
781
- y: (e.clientY - centerY) * 0.08
782
- });
783
- };
784
- const handleMouseLeave = () => {
785
- setMagnetOffset({ x: 0, y: 0 });
786
- };
787
- const baseStyles = {
788
- width: "100%",
789
- padding: "16px 28px",
790
- fontSize: 15,
791
- fontWeight: 600,
792
- borderRadius: 14,
793
- border: "none",
794
- cursor: disabled ? "not-allowed" : "pointer",
795
- transition: "all 0.2s ease",
796
- display: "flex",
797
- alignItems: "center",
798
- justifyContent: "center",
799
- gap: 10,
800
- letterSpacing: "0.01em",
801
- position: "relative",
802
- overflow: "hidden"
803
- };
804
- const variantStyles = {
805
- primary: {
806
- background: "linear-gradient(145deg, var(--widget-primary) 0%, var(--widget-accent) 100%)",
807
- color: "white",
808
- boxShadow: `
809
- 0 4px 16px -2px var(--widget-primary-glow),
810
- 0 2px 8px -2px rgba(0, 0, 0, 0.3),
811
- inset 0 1px 1px rgba(255, 255, 255, 0.2)
812
- `
813
- },
814
- secondary: {
815
- background: "var(--widget-glass-bg)",
816
- color: "var(--widget-text-primary)",
817
- border: "1px solid var(--widget-glass-border)",
818
- boxShadow: "0 2px 8px rgba(0, 0, 0, 0.15)"
819
- },
820
- ghost: {
821
- background: "transparent",
822
- color: "var(--widget-text-secondary)"
823
- },
824
- recording: {
825
- background: "linear-gradient(145deg, #ef4444 0%, #dc2626 100%)",
826
- color: "white",
827
- boxShadow: `
828
- 0 4px 20px -2px var(--widget-recording-glow),
829
- 0 0 40px -5px var(--widget-recording-glow),
830
- inset 0 1px 1px rgba(255, 255, 255, 0.2)
831
- `
832
- }
833
- };
834
- const handlePressStart = () => {
835
- if (disabled || loading) return;
836
- onPressStart?.();
837
- };
838
- const handlePressEnd = () => {
839
- if (disabled || loading) return;
840
- onPressEnd?.();
841
- };
842
- return /* @__PURE__ */ jsxs(
843
- motion.button,
844
- {
845
- ref: buttonRef,
846
- onClick,
847
- disabled: disabled || loading,
848
- onMouseMove: handleMouseMove,
849
- onMouseLeave: () => {
850
- handleMouseLeave();
851
- if (onPressEnd) handlePressEnd();
852
- },
853
- onMouseDown: onPressStart ? handlePressStart : void 0,
854
- onMouseUp: onPressEnd ? handlePressEnd : void 0,
855
- onTouchStart: onPressStart ? handlePressStart : void 0,
856
- onTouchEnd: onPressEnd ? handlePressEnd : void 0,
857
- animate: {
858
- x: magnetOffset.x,
859
- y: magnetOffset.y,
860
- opacity: disabled ? 0.5 : 1
861
- },
862
- whileHover: { scale: disabled ? 1 : 1.02, y: -1 },
863
- whileTap: { scale: disabled ? 1 : 0.97 },
864
- transition: springPresets.snappy,
865
- style: { ...baseStyles, ...variantStyles[variant] },
866
- children: [
867
- /* @__PURE__ */ jsx(
868
- "div",
869
- {
870
- style: {
871
- position: "absolute",
872
- inset: 0,
873
- background: "linear-gradient(180deg, rgba(255,255,255,0.15) 0%, rgba(255,255,255,0) 50%)",
874
- borderRadius: "inherit",
875
- pointerEvents: "none"
876
- }
877
- }
878
- ),
879
- loading ? /* @__PURE__ */ jsx(
880
- motion.div,
881
- {
882
- animate: { rotate: 360 },
883
- transition: { duration: 1, repeat: Infinity, ease: "linear" },
884
- style: {
885
- width: 20,
886
- height: 20,
887
- border: "2.5px solid rgba(255,255,255,0.3)",
888
- borderTopColor: "white",
889
- borderRadius: "50%"
890
- }
891
- }
892
- ) : /* @__PURE__ */ jsx("span", { style: { position: "relative", zIndex: 1, display: "flex", alignItems: "center", gap: 10 }, children })
893
- ]
894
- }
895
- );
896
- };
897
- var DEFAULT_CATEGORIES = [
898
- { id: "bug", label: "Bug", icon: "" },
899
- { id: "feature", label: "Feature", icon: "" },
900
- { id: "improvement", label: "Improve", icon: "" },
901
- { id: "praise", label: "Praise", icon: "" },
902
- { id: "other", label: "Other", icon: "" }
903
- ];
904
- var PremiumVoiceWidget = ({
905
- position = "bottom-right",
906
- primaryColor = "#10b981",
907
- accentColor = "#22d3d3",
908
- theme = "dark",
909
- categories = DEFAULT_CATEGORIES,
910
- allowVoice = true,
911
- allowText = true,
912
- maxRecordingDuration = 180,
913
- onSubmit,
914
- onOpen,
915
- onClose
916
- }) => {
917
- const [state, setState] = useState("idle");
918
- const [isExpanded, setIsExpanded] = useState(false);
919
- const [activeTab, setActiveTab] = useState(allowVoice ? "voice" : "text");
920
- const [selectedCategory, setSelectedCategory] = useState(null);
921
- const [isRecording, setIsRecording] = useState(false);
922
- const [recordingSeconds, setRecordingSeconds] = useState(0);
923
- const [transcription, setTranscription] = useState("");
924
- const [textContent, setTextContent] = useState("");
925
- const [isSubmitting, setIsSubmitting] = useState(false);
926
- const reducedMotion = useReducedMotion();
927
- const scrollState = useScrollAwareness();
928
- const frustrationLevel = useFrustrationDetection();
929
- const timeOnPage = useTimeOnPage();
930
- const audioLevel = useAudioLevel(isRecording);
931
- const triggerRef = useRef(null);
932
- const recordingInterval = useRef(null);
933
- const positionStyles = {
934
- "bottom-right": { bottom: 24, right: 24 },
935
- "bottom-left": { bottom: 24, left: 24 },
936
- "top-right": { top: 24, right: 24 },
937
- "top-left": { top: 24, left: 24 }
938
- };
939
- const cssVariables = {
940
- "--widget-primary": primaryColor,
941
- "--widget-primary-hover": adjustColor(primaryColor, -20),
942
- "--widget-primary-light": adjustColor(primaryColor, 30),
943
- "--widget-primary-glow": `${primaryColor}66`,
944
- "--widget-accent": accentColor,
945
- "--widget-accent-light": adjustColor(accentColor, 30),
946
- "--widget-accent-glow": `${accentColor}4d`,
947
- "--widget-glass-bg": theme === "dark" ? "rgba(24, 24, 27, 0.85)" : "rgba(255, 255, 255, 0.85)",
948
- "--widget-glass-border": theme === "dark" ? "rgba(63, 63, 70, 0.5)" : "rgba(0, 0, 0, 0.1)",
949
- "--widget-glass-highlight": theme === "dark" ? "rgba(255, 255, 255, 0.05)" : "rgba(0, 0, 0, 0.02)",
950
- "--widget-text-primary": theme === "dark" ? "#fafafa" : "#18181b",
951
- "--widget-text-secondary": theme === "dark" ? "#a1a1aa" : "#71717a",
952
- "--widget-text-muted": theme === "dark" ? "#71717a" : "#a1a1aa",
953
- "--widget-recording": "#ef4444",
954
- "--widget-recording-glow": "rgba(239, 68, 68, 0.4)",
955
- "--widget-success": "#22c55e",
956
- "--widget-success-glow": "rgba(34, 197, 94, 0.4)",
957
- "--widget-frustrated": "#f97316"
958
- };
959
- const handleOpen = useCallback(() => {
960
- setIsExpanded(true);
961
- setState("open");
962
- onOpen?.();
963
- }, [onOpen]);
964
- const handleClose = useCallback(() => {
965
- setIsExpanded(false);
966
- setState("idle");
967
- setIsRecording(false);
968
- setRecordingSeconds(0);
969
- setTranscription("");
970
- onClose?.();
971
- }, [onClose]);
972
- const handleStartRecording = useCallback(() => {
973
- setIsRecording(true);
974
- setState("recording");
975
- setRecordingSeconds(0);
976
- const samplePhrases = [
977
- "I really like the new dashboard design, ",
978
- "but I think the navigation could be improved. ",
979
- "Sometimes I have trouble finding the settings page."
980
- ];
981
- let phraseIndex = 0;
982
- recordingInterval.current = setInterval(() => {
983
- setRecordingSeconds((prev) => {
984
- if (prev >= maxRecordingDuration) {
985
- handleStopRecording();
986
- return prev;
987
- }
988
- return prev + 1;
989
- });
990
- if (phraseIndex < samplePhrases.length && Math.random() > 0.7) {
991
- setTranscription((prev) => prev + samplePhrases[phraseIndex]);
992
- phraseIndex++;
993
- }
994
- }, 1e3);
995
- }, [maxRecordingDuration]);
996
- const handleStopRecording = useCallback(() => {
997
- setIsRecording(false);
998
- setState("open");
999
- if (recordingInterval.current) {
1000
- clearInterval(recordingInterval.current);
1001
- }
1002
- }, []);
1003
- const handleSubmit = useCallback(async () => {
1004
- if (!selectedCategory) return;
1005
- setIsSubmitting(true);
1006
- setState("processing");
1007
- try {
1008
- await onSubmit?.({
1009
- type: activeTab,
1010
- content: activeTab === "voice" ? transcription : textContent,
1011
- category: selectedCategory,
1012
- transcription: activeTab === "voice" ? transcription : void 0
1013
- });
1014
- setState("success");
1015
- } catch (error) {
1016
- setState("error");
1017
- } finally {
1018
- setIsSubmitting(false);
1019
- }
1020
- }, [activeTab, selectedCategory, transcription, textContent, onSubmit]);
1021
- const handleSuccessComplete = useCallback(() => {
1022
- handleClose();
1023
- setSelectedCategory(null);
1024
- setTextContent("");
1025
- setTranscription("");
1026
- }, [handleClose]);
1027
- const contextValue = {
1028
- state,
1029
- setState,
1030
- audioLevel,
1031
- isRecording,
1032
- transcription,
1033
- selectedCategory,
1034
- setSelectedCategory,
1035
- frustrationLevel,
1036
- reducedMotion
1037
- };
1038
- const triggerState = useMemo(() => {
1039
- if (isExpanded) return "hidden";
1040
- if (scrollState === "scrolling") return "scrolling";
1041
- if (state === "hover") return "hover";
1042
- return "idle";
1043
- }, [isExpanded, scrollState, state]);
1044
- const shouldShowAttentionPulse = timeOnPage === 60 && !isExpanded && frustrationLevel < 3;
1045
- const [showPrompt, setShowPrompt] = useState(false);
1046
- const promptMessages = [
1047
- "Help us improve!",
1048
- "Got feedback?",
1049
- "Share your thoughts",
1050
- "We'd love to hear from you"
1051
- ];
1052
- const [promptMessage, setPromptMessage] = useState(promptMessages[0]);
1053
- useEffect(() => {
1054
- if (isExpanded) return;
1055
- const showTimes = [30, 90, 180];
1056
- if (showTimes.includes(timeOnPage)) {
1057
- setPromptMessage(promptMessages[Math.floor(Math.random() * promptMessages.length)]);
1058
- setShowPrompt(true);
1059
- const timer = setTimeout(() => setShowPrompt(false), 4e3);
1060
- return () => clearTimeout(timer);
1061
- }
1062
- }, [timeOnPage, isExpanded]);
1063
- return /* @__PURE__ */ jsx(WidgetContext.Provider, { value: contextValue, children: /* @__PURE__ */ jsxs(
1064
- "div",
1065
- {
1066
- className: "fiq-premium-widget",
1067
- style: {
1068
- ...cssVariables,
1069
- position: "fixed",
1070
- ...positionStyles[position],
1071
- zIndex: 2147483640,
1072
- fontFamily: "system-ui, -apple-system, sans-serif"
1073
- },
1074
- children: [
1075
- /* @__PURE__ */ jsxs(AnimatePresence, { mode: "wait", children: [
1076
- !isExpanded && /* @__PURE__ */ jsxs(
1077
- motion.button,
1078
- {
1079
- ref: triggerRef,
1080
- variants: triggerVariants,
1081
- initial: "idle",
1082
- animate: triggerState,
1083
- exit: "hidden",
1084
- whileHover: "hover",
1085
- whileTap: "tap",
1086
- onClick: handleOpen,
1087
- onMouseEnter: () => setState("hover"),
1088
- onMouseLeave: () => setState("idle"),
1089
- "aria-label": "Open feedback widget",
1090
- "aria-expanded": isExpanded,
1091
- style: {
1092
- width: 60,
1093
- height: 60,
1094
- borderRadius: "50%",
1095
- border: "none",
1096
- cursor: "pointer",
1097
- position: "relative",
1098
- background: `linear-gradient(145deg, var(--widget-primary) 0%, var(--widget-accent) 100%)`,
1099
- boxShadow: `
1100
- 0 8px 32px -4px var(--widget-primary-glow),
1101
- 0 4px 16px -2px rgba(0, 0, 0, 0.4),
1102
- inset 0 1px 1px rgba(255, 255, 255, 0.3),
1103
- inset 0 -1px 1px rgba(0, 0, 0, 0.2)
1104
- `
1105
- },
1106
- children: [
1107
- /* @__PURE__ */ jsx(BreathingRing, { isActive: !reducedMotion && triggerState === "idle" }),
1108
- /* @__PURE__ */ jsx(
1109
- "div",
1110
- {
1111
- style: {
1112
- position: "absolute",
1113
- inset: 0,
1114
- borderRadius: "50%",
1115
- background: "linear-gradient(180deg, rgba(255,255,255,0.25) 0%, rgba(255,255,255,0) 50%, rgba(0,0,0,0.1) 100%)",
1116
- pointerEvents: "none"
1117
- }
1118
- }
1119
- ),
1120
- /* @__PURE__ */ jsx(
1121
- "div",
1122
- {
1123
- style: {
1124
- position: "absolute",
1125
- top: "50%",
1126
- left: "50%",
1127
- transform: "translate(-50%, -50%)",
1128
- zIndex: 1,
1129
- display: "flex",
1130
- alignItems: "center",
1131
- justifyContent: "center"
1132
- },
1133
- children: /* @__PURE__ */ jsxs(
1134
- "svg",
1135
- {
1136
- width: 26,
1137
- height: 26,
1138
- viewBox: "0 0 24 24",
1139
- fill: "none",
1140
- style: { filter: "drop-shadow(0 1px 2px rgba(0,0,0,0.2))" },
1141
- children: [
1142
- /* @__PURE__ */ jsx(
1143
- "path",
1144
- {
1145
- 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",
1146
- fill: "white",
1147
- opacity: "0.15"
1148
- }
1149
- ),
1150
- /* @__PURE__ */ jsx(
1151
- "path",
1152
- {
1153
- 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",
1154
- fill: "white"
1155
- }
1156
- ),
1157
- /* @__PURE__ */ jsx(
1158
- "path",
1159
- {
1160
- d: "M16.5 10.5v1a4.5 4.5 0 0 1-9 0v-1",
1161
- stroke: "white",
1162
- strokeWidth: "2",
1163
- strokeLinecap: "round",
1164
- fill: "none"
1165
- }
1166
- ),
1167
- /* @__PURE__ */ jsx(
1168
- "path",
1169
- {
1170
- d: "M12 15.5v2.5M9.5 18h5",
1171
- stroke: "white",
1172
- strokeWidth: "2",
1173
- strokeLinecap: "round"
1174
- }
1175
- )
1176
- ]
1177
- }
1178
- )
1179
- }
1180
- ),
1181
- shouldShowAttentionPulse && /* @__PURE__ */ jsx(
1182
- motion.div,
1183
- {
1184
- initial: { scale: 1, opacity: 1 },
1185
- animate: { scale: 1.6, opacity: 0 },
1186
- transition: { duration: 1.2, repeat: 3 },
1187
- style: {
1188
- position: "absolute",
1189
- inset: -2,
1190
- borderRadius: "50%",
1191
- border: "2px solid var(--widget-primary)",
1192
- pointerEvents: "none"
1193
- }
1194
- }
1195
- ),
1196
- frustrationLevel >= 5 && /* @__PURE__ */ jsx(
1197
- motion.div,
1198
- {
1199
- initial: { scale: 0 },
1200
- animate: { scale: 1 },
1201
- style: {
1202
- position: "absolute",
1203
- top: -2,
1204
- right: -2,
1205
- width: 18,
1206
- height: 18,
1207
- borderRadius: "50%",
1208
- background: "linear-gradient(135deg, #ff6b6b 0%, #ee5a5a 100%)",
1209
- border: "2px solid white",
1210
- display: "flex",
1211
- alignItems: "center",
1212
- justifyContent: "center",
1213
- fontSize: 11,
1214
- fontWeight: 700,
1215
- color: "white",
1216
- boxShadow: "0 2px 8px rgba(239, 68, 68, 0.5)"
1217
- },
1218
- children: "!"
1219
- }
1220
- )
1221
- ]
1222
- },
1223
- "trigger"
1224
- ),
1225
- /* @__PURE__ */ jsx(AnimatePresence, { children: showPrompt && !isExpanded && /* @__PURE__ */ jsxs(
1226
- motion.div,
1227
- {
1228
- initial: { opacity: 0, x: 10, scale: 0.9 },
1229
- animate: { opacity: 1, x: 0, scale: 1 },
1230
- exit: { opacity: 0, x: 10, scale: 0.9 },
1231
- transition: springPresets.smooth,
1232
- onClick: handleOpen,
1233
- style: {
1234
- position: "absolute",
1235
- right: 70,
1236
- top: "50%",
1237
- transform: "translateY(-50%)",
1238
- background: "var(--widget-glass-bg)",
1239
- backdropFilter: "blur(12px)",
1240
- WebkitBackdropFilter: "blur(12px)",
1241
- border: "1px solid var(--widget-glass-border)",
1242
- borderRadius: 12,
1243
- padding: "10px 16px",
1244
- boxShadow: "0 4px 20px rgba(0,0,0,0.3)",
1245
- cursor: "pointer",
1246
- whiteSpace: "nowrap"
1247
- },
1248
- children: [
1249
- /* @__PURE__ */ jsx("span", { style: {
1250
- fontSize: 13,
1251
- fontWeight: 500,
1252
- color: "var(--widget-text-primary)"
1253
- }, children: promptMessage }),
1254
- /* @__PURE__ */ jsx(
1255
- "div",
1256
- {
1257
- style: {
1258
- position: "absolute",
1259
- right: -6,
1260
- top: "50%",
1261
- transform: "translateY(-50%) rotate(45deg)",
1262
- width: 12,
1263
- height: 12,
1264
- background: "var(--widget-glass-bg)",
1265
- borderRight: "1px solid var(--widget-glass-border)",
1266
- borderTop: "1px solid var(--widget-glass-border)"
1267
- }
1268
- }
1269
- )
1270
- ]
1271
- }
1272
- ) })
1273
- ] }),
1274
- /* @__PURE__ */ jsx(AnimatePresence, { mode: "wait", children: isExpanded && /* @__PURE__ */ jsx(
1275
- motion.div,
1276
- {
1277
- variants: containerVariants,
1278
- initial: "trigger",
1279
- animate: "expanded",
1280
- exit: "trigger",
1281
- style: {
1282
- 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%)",
1283
- backdropFilter: "blur(24px) saturate(200%)",
1284
- WebkitBackdropFilter: "blur(24px) saturate(200%)",
1285
- border: "1px solid var(--widget-glass-border)",
1286
- boxShadow: `
1287
- 0 24px 80px -12px rgba(0, 0, 0, 0.6),
1288
- 0 0 1px 0 rgba(255, 255, 255, 0.1) inset,
1289
- 0 1px 0 0 rgba(255, 255, 255, 0.05) inset
1290
- `,
1291
- overflow: "hidden",
1292
- display: "flex",
1293
- flexDirection: "column"
1294
- },
1295
- role: "dialog",
1296
- "aria-modal": "true",
1297
- "aria-labelledby": "widget-title",
1298
- children: /* @__PURE__ */ jsx(AnimatePresence, { mode: "wait", children: state === "success" ? /* @__PURE__ */ jsx(SuccessCelebration, { onComplete: handleSuccessComplete }, "success") : /* @__PURE__ */ jsxs(
1299
- motion.div,
1300
- {
1301
- variants: contentVariants,
1302
- initial: "hidden",
1303
- animate: "visible",
1304
- exit: "exit",
1305
- style: { display: "flex", flexDirection: "column", height: "100%" },
1306
- children: [
1307
- /* @__PURE__ */ jsxs(
1308
- "div",
1309
- {
1310
- style: {
1311
- display: "flex",
1312
- justifyContent: "space-between",
1313
- alignItems: "center",
1314
- padding: "20px 24px",
1315
- borderBottom: "1px solid var(--widget-glass-border)",
1316
- 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%)"
1317
- },
1318
- children: [
1319
- /* @__PURE__ */ jsxs("div", { style: { display: "flex", alignItems: "center", gap: 12 }, children: [
1320
- /* @__PURE__ */ jsx(
1321
- "div",
1322
- {
1323
- style: {
1324
- width: 36,
1325
- height: 36,
1326
- borderRadius: 10,
1327
- background: "linear-gradient(145deg, var(--widget-primary) 0%, var(--widget-accent) 100%)",
1328
- display: "flex",
1329
- alignItems: "center",
1330
- justifyContent: "center",
1331
- boxShadow: "0 2px 8px -2px var(--widget-primary-glow)"
1332
- },
1333
- children: /* @__PURE__ */ jsxs("svg", { width: 18, height: 18, viewBox: "0 0 24 24", fill: "white", children: [
1334
- /* @__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" }),
1335
- /* @__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" }),
1336
- /* @__PURE__ */ jsx("path", { d: "M12 15.5v2.5M9.5 18h5", stroke: "white", strokeWidth: "2", strokeLinecap: "round" })
1337
- ] })
1338
- }
1339
- ),
1340
- /* @__PURE__ */ jsx(
1341
- "h2",
1342
- {
1343
- id: "widget-title",
1344
- style: {
1345
- fontSize: 17,
1346
- fontWeight: 700,
1347
- color: "var(--widget-text-primary)",
1348
- margin: 0,
1349
- letterSpacing: "-0.01em"
1350
- },
1351
- children: "Share Feedback"
1352
- }
1353
- )
1354
- ] }),
1355
- /* @__PURE__ */ jsx(
1356
- motion.button,
1357
- {
1358
- whileHover: { scale: 1.1, backgroundColor: "var(--widget-glass-border)" },
1359
- whileTap: { scale: 0.9 },
1360
- onClick: handleClose,
1361
- "aria-label": "Close widget",
1362
- style: {
1363
- width: 32,
1364
- height: 32,
1365
- borderRadius: 8,
1366
- border: "none",
1367
- background: "transparent",
1368
- color: "var(--widget-text-secondary)",
1369
- cursor: "pointer",
1370
- display: "flex",
1371
- alignItems: "center",
1372
- justifyContent: "center",
1373
- transition: "background 0.15s ease"
1374
- },
1375
- children: /* @__PURE__ */ jsx(
1376
- "svg",
1377
- {
1378
- width: 18,
1379
- height: 18,
1380
- viewBox: "0 0 24 24",
1381
- fill: "none",
1382
- stroke: "currentColor",
1383
- strokeWidth: 2.5,
1384
- strokeLinecap: "round",
1385
- children: /* @__PURE__ */ jsx("path", { d: "M18 6L6 18M6 6l12 12" })
1386
- }
1387
- )
1388
- }
1389
- )
1390
- ]
1391
- }
1392
- ),
1393
- allowVoice && allowText && /* @__PURE__ */ jsx(
1394
- "div",
1395
- {
1396
- style: {
1397
- display: "flex",
1398
- padding: "8px 12px",
1399
- gap: 4,
1400
- background: "var(--widget-glass-bg)",
1401
- margin: "0 16px",
1402
- marginTop: 16,
1403
- borderRadius: 12,
1404
- border: "1px solid var(--widget-glass-border)"
1405
- },
1406
- children: ["voice", "text"].map((tab) => /* @__PURE__ */ jsxs(
1407
- motion.button,
1408
- {
1409
- onClick: () => setActiveTab(tab),
1410
- style: {
1411
- flex: 1,
1412
- padding: "10px 0",
1413
- border: "none",
1414
- background: activeTab === tab ? "linear-gradient(145deg, var(--widget-primary) 0%, var(--widget-accent) 100%)" : "transparent",
1415
- fontSize: 13,
1416
- fontWeight: 600,
1417
- color: activeTab === tab ? "white" : "var(--widget-text-secondary)",
1418
- cursor: "pointer",
1419
- position: "relative",
1420
- borderRadius: 8,
1421
- transition: "all 0.2s ease",
1422
- display: "flex",
1423
- alignItems: "center",
1424
- justifyContent: "center",
1425
- gap: 6,
1426
- boxShadow: activeTab === tab ? "0 2px 8px -2px var(--widget-primary-glow)" : "none"
1427
- },
1428
- whileHover: { scale: activeTab === tab ? 1 : 1.02 },
1429
- whileTap: { scale: 0.98 },
1430
- children: [
1431
- tab === "voice" ? /* @__PURE__ */ jsxs("svg", { width: 14, height: 14, viewBox: "0 0 24 24", fill: "currentColor", children: [
1432
- /* @__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" }),
1433
- /* @__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" })
1434
- ] }) : /* @__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" }) }),
1435
- tab === "voice" ? "Voice" : "Text"
1436
- ]
1437
- },
1438
- tab
1439
- ))
1440
- }
1441
- ),
1442
- /* @__PURE__ */ jsxs("div", { style: { padding: "20px 24px" }, children: [
1443
- /* @__PURE__ */ jsx("div", { style: { marginBottom: 24 }, children: /* @__PURE__ */ jsx(
1444
- CategorySelector,
1445
- {
1446
- categories,
1447
- selected: selectedCategory,
1448
- onSelect: setSelectedCategory
1449
- }
1450
- ) }),
1451
- activeTab === "voice" && /* @__PURE__ */ jsxs("div", { style: { textAlign: "center" }, children: [
1452
- /* @__PURE__ */ jsxs(
1453
- "div",
1454
- {
1455
- style: {
1456
- background: "var(--widget-glass-bg)",
1457
- borderRadius: 16,
1458
- padding: "24px 16px",
1459
- border: "1px solid var(--widget-glass-border)",
1460
- marginBottom: 16
1461
- },
1462
- children: [
1463
- /* @__PURE__ */ jsx(AmbientOrb, { audioLevel, isRecording }),
1464
- isRecording && /* @__PURE__ */ jsx(
1465
- RecordingTimer,
1466
- {
1467
- seconds: recordingSeconds,
1468
- maxDuration: maxRecordingDuration
1469
- }
1470
- ),
1471
- !isRecording && recordingSeconds === 0 && !transcription && /* @__PURE__ */ jsx(
1472
- "p",
1473
- {
1474
- style: {
1475
- color: "var(--widget-text-muted)",
1476
- fontSize: 13,
1477
- marginTop: 12,
1478
- marginBottom: 0
1479
- },
1480
- children: "Click Record to start"
1481
- }
1482
- )
1483
- ]
1484
- }
1485
- ),
1486
- (isRecording || transcription) && /* @__PURE__ */ jsx("div", { style: { marginBottom: 16 }, children: /* @__PURE__ */ jsx(
1487
- TranscriptionDisplay,
1488
- {
1489
- text: transcription,
1490
- isLive: isRecording
1491
- }
1492
- ) }),
1493
- /* @__PURE__ */ jsx(
1494
- MagneticButton,
1495
- {
1496
- onClick: isRecording ? handleStopRecording : handleStartRecording,
1497
- variant: isRecording ? "recording" : "primary",
1498
- children: isRecording ? /* @__PURE__ */ jsxs(Fragment, { children: [
1499
- /* @__PURE__ */ jsx(
1500
- motion.div,
1501
- {
1502
- animate: { scale: [1, 1.2, 1] },
1503
- transition: { duration: 1, repeat: Infinity },
1504
- style: {
1505
- width: 10,
1506
- height: 10,
1507
- borderRadius: 2,
1508
- background: "white"
1509
- }
1510
- }
1511
- ),
1512
- "Stop Recording"
1513
- ] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
1514
- /* @__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" }) }),
1515
- transcription ? "Record Again" : "Record"
1516
- ] })
1517
- }
1518
- )
1519
- ] }),
1520
- activeTab === "text" && /* @__PURE__ */ jsx("div", { children: /* @__PURE__ */ jsxs(
1521
- "div",
1522
- {
1523
- style: {
1524
- position: "relative",
1525
- background: "var(--widget-glass-bg)",
1526
- border: "1px solid var(--widget-glass-border)",
1527
- borderRadius: 14,
1528
- overflow: "hidden"
1529
- },
1530
- children: [
1531
- /* @__PURE__ */ jsx(
1532
- "textarea",
1533
- {
1534
- value: textContent,
1535
- onChange: (e) => setTextContent(e.target.value),
1536
- placeholder: "Share your thoughts, ideas, or feedback...",
1537
- style: {
1538
- width: "100%",
1539
- minHeight: 140,
1540
- padding: 16,
1541
- fontSize: 14,
1542
- lineHeight: 1.7,
1543
- color: "var(--widget-text-primary)",
1544
- background: "transparent",
1545
- border: "none",
1546
- resize: "none",
1547
- fontFamily: "inherit",
1548
- outline: "none"
1549
- }
1550
- }
1551
- ),
1552
- /* @__PURE__ */ jsx(
1553
- "div",
1554
- {
1555
- style: {
1556
- display: "flex",
1557
- justifyContent: "flex-end",
1558
- padding: "8px 12px",
1559
- borderTop: "1px solid var(--widget-glass-border)",
1560
- background: theme === "dark" ? "rgba(0,0,0,0.2)" : "rgba(0,0,0,0.02)"
1561
- },
1562
- children: /* @__PURE__ */ jsxs(
1563
- "span",
1564
- {
1565
- style: {
1566
- fontSize: 11,
1567
- fontWeight: 500,
1568
- color: textContent.length > 4500 ? "var(--widget-recording)" : "var(--widget-text-muted)"
1569
- },
1570
- children: [
1571
- textContent.length.toLocaleString(),
1572
- " / 5,000"
1573
- ]
1574
- }
1575
- )
1576
- }
1577
- )
1578
- ]
1579
- }
1580
- ) })
1581
- ] }),
1582
- /* @__PURE__ */ jsx(
1583
- "div",
1584
- {
1585
- style: {
1586
- padding: "16px 24px 20px",
1587
- borderTop: "1px solid var(--widget-glass-border)",
1588
- 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%)"
1589
- },
1590
- children: /* @__PURE__ */ jsxs(
1591
- MagneticButton,
1592
- {
1593
- onClick: handleSubmit,
1594
- disabled: !selectedCategory || activeTab === "voice" && !transcription || activeTab === "text" && !textContent.trim(),
1595
- loading: isSubmitting,
1596
- children: [
1597
- /* @__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" }) }),
1598
- "Submit Feedback"
1599
- ]
1600
- }
1601
- )
1602
- }
1603
- )
1604
- ]
1605
- },
1606
- "content"
1607
- ) })
1608
- },
1609
- "modal"
1610
- ) }),
1611
- /* @__PURE__ */ jsxs("div", { "aria-live": "polite", className: "sr-only", style: { position: "absolute", width: 1, height: 1, overflow: "hidden" }, children: [
1612
- isRecording && "Recording started. Speak now.",
1613
- !isRecording && transcription && `Transcription complete.`,
1614
- state === "success" && "Feedback submitted successfully."
1615
- ] })
1616
- ]
1617
- }
1618
- ) });
1619
- };
1620
- function adjustColor(color, amount) {
1621
- const hex = color.replace("#", "");
1622
- const num = parseInt(hex, 16);
1623
- const r = Math.min(255, Math.max(0, (num >> 16) + amount));
1624
- const g = Math.min(255, Math.max(0, (num >> 8 & 255) + amount));
1625
- const b = Math.min(255, Math.max(0, (num & 255) + amount));
1626
- return `#${(r << 16 | g << 8 | b).toString(16).padStart(6, "0")}`;
1627
- }
1628
-
1629
- // src/components/IntentAIProvider.tsx
1630
- import { createContext as createContext2, useContext as useContext2, useCallback as useCallback2, useMemo as useMemo2, useRef as useRef2, useEffect as useEffect2 } from "react";
1631
- import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
1632
- var IntentAIContext = createContext2(null);
1633
- function useIntentAI() {
1634
- const context = useContext2(IntentAIContext);
1635
- if (!context) {
1636
- throw new Error("useIntentAI must be used within an IntentAIProvider");
1637
- }
1638
- return context;
1639
- }
1640
- function generateSessionId() {
1641
- return `session_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
1642
- }
1643
- function getDeviceInfo() {
1644
- const ua = navigator.userAgent;
1645
- let browser = "Unknown";
1646
- let browserVersion = "Unknown";
1647
- if (ua.includes("Firefox/")) {
1648
- browser = "Firefox";
1649
- const match = ua.match(/Firefox\/(\d+(\.\d+)?)/);
1650
- if (match) browserVersion = match[1];
1651
- } else if (ua.includes("Edg/")) {
1652
- browser = "Edge";
1653
- const match = ua.match(/Edg\/(\d+(\.\d+)?)/);
1654
- if (match) browserVersion = match[1];
1655
- } else if (ua.includes("Chrome/")) {
1656
- browser = "Chrome";
1657
- const match = ua.match(/Chrome\/(\d+(\.\d+)?)/);
1658
- if (match) browserVersion = match[1];
1659
- } else if (ua.includes("Safari/") && !ua.includes("Chrome")) {
1660
- browser = "Safari";
1661
- const match = ua.match(/Version\/(\d+(\.\d+)?)/);
1662
- if (match) browserVersion = match[1];
1663
- }
1664
- let os = "Unknown";
1665
- if (ua.includes("Windows NT 10")) os = "Windows 10";
1666
- else if (ua.includes("Windows")) os = "Windows";
1667
- else if (ua.includes("Mac OS X")) os = "macOS";
1668
- else if (ua.includes("Android")) os = "Android";
1669
- else if (ua.includes("iPhone") || ua.includes("iPad")) os = "iOS";
1670
- else if (ua.includes("Linux")) os = "Linux";
1671
- let device = "Desktop";
1672
- if (/iPhone|iPad|iPod/.test(ua)) device = "iOS Device";
1673
- else if (/Android.*Mobile/.test(ua)) device = "Android Phone";
1674
- else if (/Android/.test(ua)) device = "Android Tablet";
1675
- return { browser, browserVersion, os, device };
1676
- }
1677
- var DEFAULT_API_URL = "https://api-production-5b88.up.railway.app";
1678
- function IntentAIProvider({
1679
- apiKey,
1680
- apiUrl = DEFAULT_API_URL,
1681
- user: initialUser,
1682
- metadata: initialMetadata,
1683
- widgetConfig,
1684
- children,
1685
- disableWidget = false,
1686
- onFeedbackSubmitted,
1687
- onError
1688
- }) {
1689
- const userRef = useRef2(initialUser);
1690
- const metadataRef = useRef2(initialMetadata || {});
1691
- const sessionIdRef = useRef2(generateSessionId());
1692
- useEffect2(() => {
1693
- if (initialUser) {
1694
- userRef.current = initialUser;
1695
- }
1696
- }, [initialUser]);
1697
- useEffect2(() => {
1698
- if (initialMetadata) {
1699
- metadataRef.current = { ...metadataRef.current, ...initialMetadata };
1700
- }
1701
- }, [initialMetadata]);
1702
- const identify = useCallback2((user) => {
1703
- userRef.current = { ...userRef.current, ...user };
1704
- }, []);
1705
- const setMetadata = useCallback2((metadata) => {
1706
- metadataRef.current = { ...metadataRef.current, ...metadata };
1707
- }, []);
1708
- const clearUser = useCallback2(() => {
1709
- userRef.current = void 0;
1710
- }, []);
1711
- const handleSubmit = useCallback2(async (feedback) => {
1712
- const deviceInfo = getDeviceInfo();
1713
- const user = userRef.current;
1714
- const customMetadata = metadataRef.current;
1715
- const categoryMap = {
1716
- bug: "BUG",
1717
- feature: "FEATURE",
1718
- improvement: "IMPROVEMENT",
1719
- praise: "PRAISE",
1720
- other: "OTHER"
1721
- };
1722
- const payload = {
1723
- sessionId: sessionIdRef.current,
1724
- type: feedback.type === "voice" ? "VOICE" : "TEXT",
1725
- category: categoryMap[feedback.category.toLowerCase()] || feedback.category.toUpperCase(),
1726
- rawText: feedback.type === "text" ? feedback.content : void 0,
1727
- transcription: feedback.type === "voice" ? feedback.transcription || feedback.content : void 0,
1728
- pageUrl: typeof window !== "undefined" ? window.location.href : "",
1729
- pageTitle: typeof document !== "undefined" ? document.title : "",
1730
- userId: user?.id,
1731
- userEmail: user?.email,
1732
- userName: user?.name,
1733
- customData: {
1734
- ...customMetadata,
1735
- ...feedback.metadata
1736
- },
1737
- browser: deviceInfo.browser,
1738
- browserVersion: deviceInfo.browserVersion,
1739
- os: deviceInfo.os,
1740
- device: deviceInfo.device,
1741
- screenWidth: typeof window !== "undefined" ? window.screen.width : void 0,
1742
- screenHeight: typeof window !== "undefined" ? window.screen.height : void 0
1743
- };
1744
- try {
1745
- const response = await fetch(`${apiUrl}/feedback`, {
1746
- method: "POST",
1747
- headers: {
1748
- "Content-Type": "application/json",
1749
- "X-API-Key": apiKey
1750
- },
1751
- body: JSON.stringify(payload)
1752
- });
1753
- if (!response.ok) {
1754
- const errorData = await response.json().catch(() => ({}));
1755
- throw new Error(errorData.error || `Failed to submit feedback: ${response.status}`);
1756
- }
1757
- onFeedbackSubmitted?.();
1758
- } catch (error) {
1759
- const err = error instanceof Error ? error : new Error("Failed to submit feedback");
1760
- onError?.(err);
1761
- throw err;
1762
- }
1763
- }, [apiKey, apiUrl, onFeedbackSubmitted, onError]);
1764
- const contextValue = useMemo2(() => ({
1765
- identify,
1766
- setMetadata,
1767
- clearUser,
1768
- isConfigured: Boolean(apiKey)
1769
- }), [identify, setMetadata, clearUser, apiKey]);
1770
- return /* @__PURE__ */ jsxs2(IntentAIContext.Provider, { value: contextValue, children: [
1771
- children,
1772
- !disableWidget && apiKey && /* @__PURE__ */ jsx2(
1773
- PremiumVoiceWidget,
1774
- {
1775
- ...widgetConfig,
1776
- onSubmit: handleSubmit
1777
- }
1778
- )
1779
- ] });
1780
- }
1781
- function useIdentify() {
1782
- const { identify } = useIntentAI();
1783
- return identify;
1784
- }
1785
- function useSetMetadata() {
1786
- const { setMetadata } = useIntentAI();
1787
- return setMetadata;
1788
- }
1789
-
1790
- // src/components/FeedbackButton.tsx
1791
- import { useCallback as useCallback3, useState as useState2, useRef as useRef3 } from "react";
1792
- import { motion as motion2 } from "framer-motion";
1793
- import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
1794
- var FeedbackIcon = ({ size }) => /* @__PURE__ */ jsxs3(
1795
- "svg",
1796
- {
1797
- width: size,
1798
- height: size,
1799
- viewBox: "0 0 24 24",
1800
- fill: "none",
1801
- stroke: "currentColor",
1802
- strokeWidth: "2",
1803
- strokeLinecap: "round",
1804
- strokeLinejoin: "round",
1805
- children: [
1806
- /* @__PURE__ */ jsx3("path", { d: "M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z" }),
1807
- /* @__PURE__ */ jsx3("circle", { cx: "9", cy: "10", r: "1", fill: "currentColor" }),
1808
- /* @__PURE__ */ jsx3("circle", { cx: "15", cy: "10", r: "1", fill: "currentColor" })
1809
- ]
1810
- }
1811
- );
1812
- function FeedbackButton({
1813
- variant = "primary",
1814
- size = "md",
1815
- showIcon = true,
1816
- icon,
1817
- label = "Feedback",
1818
- className,
1819
- style,
1820
- primaryColor = "#10b981",
1821
- onClick,
1822
- disabled = false
1823
- }) {
1824
- const [isHovered, setIsHovered] = useState2(false);
1825
- const [isPressed, setIsPressed] = useState2(false);
1826
- const buttonRef = useRef3(null);
1827
- const sizes = {
1828
- sm: {
1829
- padding: "8px 14px",
1830
- fontSize: 13,
1831
- iconSize: 14,
1832
- gap: 6,
1833
- borderRadius: 8
1834
- },
1835
- md: {
1836
- padding: "10px 18px",
1837
- fontSize: 14,
1838
- iconSize: 16,
1839
- gap: 8,
1840
- borderRadius: 10
1841
- },
1842
- lg: {
1843
- padding: "14px 24px",
1844
- fontSize: 15,
1845
- iconSize: 18,
1846
- gap: 10,
1847
- borderRadius: 12
1848
- }
1849
- };
1850
- const sizeConfig = sizes[size];
1851
- const variants = {
1852
- primary: {
1853
- background: `linear-gradient(145deg, ${primaryColor} 0%, ${adjustColor2(primaryColor, -20)} 100%)`,
1854
- color: "white",
1855
- border: "none",
1856
- boxShadow: `0 4px 16px -4px ${primaryColor}66, 0 2px 4px rgba(0, 0, 0, 0.1)`
1857
- },
1858
- secondary: {
1859
- background: "white",
1860
- color: "#374151",
1861
- border: "1px solid #e5e7eb",
1862
- boxShadow: "0 2px 4px rgba(0, 0, 0, 0.05)"
1863
- },
1864
- ghost: {
1865
- background: "transparent",
1866
- color: "#6b7280",
1867
- border: "1px solid transparent",
1868
- boxShadow: "none"
1869
- },
1870
- minimal: {
1871
- background: "transparent",
1872
- color: primaryColor,
1873
- border: "none",
1874
- boxShadow: "none"
1875
- }
1876
- };
1877
- const variantStyle = variants[variant];
1878
- const handleClick = useCallback3(() => {
1879
- if (disabled) return;
1880
- if (onClick) {
1881
- onClick();
1882
- return;
1883
- }
1884
- const widget = document.querySelector(".fiq-premium-widget");
1885
- if (widget) {
1886
- const trigger = widget.querySelector('button[aria-label="Open feedback widget"]');
1887
- if (trigger) {
1888
- trigger.click();
1889
- return;
1890
- }
1891
- }
1892
- const scriptWidget = document.getElementById("feedbackiq-widget");
1893
- if (scriptWidget?.shadowRoot) {
1894
- const trigger = scriptWidget.shadowRoot.querySelector(".fiq-trigger");
1895
- if (trigger) {
1896
- trigger.click();
1897
- }
1898
- }
1899
- }, [onClick, disabled]);
1900
- return /* @__PURE__ */ jsxs3(
1901
- motion2.button,
1902
- {
1903
- ref: buttonRef,
1904
- onClick: handleClick,
1905
- onMouseEnter: () => setIsHovered(true),
1906
- onMouseLeave: () => {
1907
- setIsHovered(false);
1908
- setIsPressed(false);
1909
- },
1910
- onMouseDown: () => setIsPressed(true),
1911
- onMouseUp: () => setIsPressed(false),
1912
- disabled,
1913
- animate: {
1914
- scale: isPressed ? 0.97 : isHovered ? 1.02 : 1,
1915
- y: isHovered ? -1 : 0
1916
- },
1917
- transition: {
1918
- type: "spring",
1919
- stiffness: 400,
1920
- damping: 25
1921
- },
1922
- className,
1923
- style: {
1924
- display: "inline-flex",
1925
- alignItems: "center",
1926
- justifyContent: "center",
1927
- gap: sizeConfig.gap,
1928
- padding: sizeConfig.padding,
1929
- fontSize: sizeConfig.fontSize,
1930
- fontWeight: 600,
1931
- fontFamily: "system-ui, -apple-system, sans-serif",
1932
- borderRadius: sizeConfig.borderRadius,
1933
- cursor: disabled ? "not-allowed" : "pointer",
1934
- opacity: disabled ? 0.5 : 1,
1935
- transition: "all 0.2s ease",
1936
- position: "relative",
1937
- overflow: "hidden",
1938
- ...variantStyle,
1939
- ...style
1940
- },
1941
- children: [
1942
- variant === "primary" && /* @__PURE__ */ jsx3(
1943
- motion2.div,
1944
- {
1945
- initial: { opacity: 0 },
1946
- animate: { opacity: isHovered ? 0.1 : 0 },
1947
- style: {
1948
- position: "absolute",
1949
- inset: 0,
1950
- background: "white",
1951
- pointerEvents: "none"
1952
- }
1953
- }
1954
- ),
1955
- variant === "primary" && /* @__PURE__ */ jsx3(
1956
- "div",
1957
- {
1958
- style: {
1959
- position: "absolute",
1960
- inset: 0,
1961
- background: "linear-gradient(180deg, rgba(255,255,255,0.2) 0%, rgba(255,255,255,0) 50%)",
1962
- borderRadius: "inherit",
1963
- pointerEvents: "none"
1964
- }
1965
- }
1966
- ),
1967
- showIcon && /* @__PURE__ */ jsx3("span", { style: { display: "flex", alignItems: "center", position: "relative", zIndex: 1 }, children: icon || /* @__PURE__ */ jsx3(FeedbackIcon, { size: sizeConfig.iconSize }) }),
1968
- label && /* @__PURE__ */ jsx3("span", { style: { position: "relative", zIndex: 1 }, children: label })
1969
- ]
1970
- }
1971
- );
1972
- }
1973
- function adjustColor2(color, amount) {
1974
- const hex = color.replace("#", "");
1975
- const num = parseInt(hex, 16);
1976
- const r = Math.min(255, Math.max(0, (num >> 16) + amount));
1977
- const g = Math.min(255, Math.max(0, (num >> 8 & 255) + amount));
1978
- const b = Math.min(255, Math.max(0, (num & 255) + amount));
1979
- return `#${(r << 16 | g << 8 | b).toString(16).padStart(6, "0")}`;
1980
- }
1981
-
1982
- // src/index.tsx
1983
- function IntentAIWidget({
1984
- apiKey,
1985
- apiUrl,
1986
- widgetUrl,
1987
- position = "bottom-right",
1988
- theme = "light",
1989
- primaryColor,
1990
- allowVoice = true,
1991
- allowText = true,
1992
- allowScreenshot = true,
1993
- customMetadata,
1994
- user,
1995
- onOpen: _onOpen,
1996
- onClose: _onClose,
1997
- onFeedbackSubmitted: _onFeedbackSubmitted,
1998
- onError
1999
- }) {
2000
- const widgetRef = useRef4(null);
2001
- const scriptLoadedRef = useRef4(false);
2002
- const initWidget = useCallback4(() => {
2003
- if (!window.FeedbackIQ || widgetRef.current) return;
2004
- try {
2005
- widgetRef.current = new window.FeedbackIQ({
2006
- apiKey,
2007
- apiUrl,
2008
- widgetUrl,
2009
- position,
2010
- theme,
2011
- primaryColor,
2012
- allowVoice,
2013
- allowText,
2014
- allowScreenshot,
2015
- customMetadata,
2016
- user
2017
- });
2018
- } catch (error) {
2019
- onError?.(error instanceof Error ? error : new Error("Failed to initialize widget"));
2020
- }
2021
- void _onOpen;
2022
- void _onClose;
2023
- void _onFeedbackSubmitted;
2024
- }, [apiKey, apiUrl, widgetUrl, position, theme, primaryColor, allowVoice, allowText, allowScreenshot, customMetadata, user, onError]);
2025
- useEffect3(() => {
2026
- if (!widgetUrl) {
2027
- onError?.(new Error("widgetUrl is required for IntentAIWidget"));
2028
- return;
2029
- }
2030
- if (window.FeedbackIQ) {
2031
- initWidget();
2032
- return;
2033
- }
2034
- if (scriptLoadedRef.current) return;
2035
- const existingScript = document.querySelector(`script[src="${widgetUrl}"]`);
2036
- if (existingScript) {
2037
- existingScript.addEventListener("load", initWidget);
2038
- return;
2039
- }
2040
- scriptLoadedRef.current = true;
2041
- const script = document.createElement("script");
2042
- script.src = widgetUrl;
2043
- script.async = true;
2044
- script.onload = initWidget;
2045
- script.onerror = () => {
2046
- onError?.(new Error("Failed to load Intent AI widget script"));
2047
- };
2048
- document.head.appendChild(script);
2049
- return () => {
2050
- if (widgetRef.current) {
2051
- widgetRef.current.destroy();
2052
- widgetRef.current = null;
2053
- }
2054
- };
2055
- }, [initWidget, onError, widgetUrl]);
2056
- useEffect3(() => {
2057
- if (widgetRef.current && user) {
2058
- widgetRef.current.identify(user);
2059
- }
2060
- }, [user]);
2061
- useEffect3(() => {
2062
- if (widgetRef.current && customMetadata) {
2063
- widgetRef.current.setMetadata(customMetadata);
2064
- }
2065
- }, [customMetadata]);
2066
- return null;
2067
- }
2068
- function useWidgetControls() {
2069
- const open = useCallback4(() => {
2070
- const widget = document.getElementById("feedbackiq-widget");
2071
- if (widget) {
2072
- const trigger = widget.shadowRoot?.querySelector(".fiq-trigger");
2073
- trigger?.click();
2074
- }
2075
- }, []);
2076
- const close = useCallback4(() => {
2077
- const widget = document.getElementById("feedbackiq-widget");
2078
- if (widget) {
2079
- const closeBtn = widget.shadowRoot?.querySelector(".fiq-close");
2080
- closeBtn?.click();
2081
- }
2082
- }, []);
2083
- const identify = useCallback4((user) => {
2084
- if (window.IntentAI?.widget) {
2085
- window.IntentAI.widget.identify(user);
2086
- }
2087
- }, []);
2088
- const setMetadata = useCallback4((metadata) => {
2089
- if (window.IntentAI?.widget) {
2090
- window.IntentAI.widget.setMetadata(metadata);
2091
- }
2092
- }, []);
2093
- return { open, close, identify, setMetadata };
2094
- }
2095
- var index_default = IntentAIWidget;
2096
- export {
2097
- FeedbackButton,
2098
- IntentAIProvider,
2099
- IntentAIWidget,
2100
- PremiumVoiceWidget,
2101
- index_default as default,
2102
- useIdentify,
2103
- useIntentAI,
2104
- useSetMetadata,
2105
- useWidgetControls
2106
- };
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};