@intentai/react 1.0.2 → 2.0.1

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