@intentai/react 2.1.2 → 2.2.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.d.mts CHANGED
@@ -31,6 +31,149 @@ interface FeedbackData {
31
31
  }
32
32
  declare const PremiumVoiceWidget: React.FC<WidgetConfig>;
33
33
 
34
+ interface IntentAIProviderConfig {
35
+ /** Your Intent AI API key (required) */
36
+ apiKey: string;
37
+ /** API URL (defaults to production) */
38
+ apiUrl?: string;
39
+ /** User information for identifying feedback */
40
+ user?: IntentAIUser$1;
41
+ /** Additional metadata to attach to all feedback */
42
+ metadata?: Record<string, unknown>;
43
+ }
44
+ interface IntentAIUser$1 {
45
+ id?: string;
46
+ email?: string;
47
+ name?: string;
48
+ [key: string]: unknown;
49
+ }
50
+ interface IntentAIContextValue {
51
+ /** Identify the current user */
52
+ identify: (user: IntentAIUser$1) => void;
53
+ /** Set metadata to attach to feedback */
54
+ setMetadata: (metadata: Record<string, unknown>) => void;
55
+ /** Clear the current user */
56
+ clearUser: () => void;
57
+ /** Whether the provider is configured */
58
+ isConfigured: boolean;
59
+ }
60
+ /**
61
+ * Hook to access Intent AI context for user identification and metadata
62
+ */
63
+ declare function useIntentAI(): IntentAIContextValue;
64
+ interface IntentAIProviderProps extends IntentAIProviderConfig {
65
+ /** Widget configuration options */
66
+ widgetConfig?: Omit<WidgetConfig, 'onSubmit'>;
67
+ /** Children components */
68
+ children?: React.ReactNode;
69
+ /** Disable the widget (only provide context) */
70
+ disableWidget?: boolean;
71
+ /** Callback when feedback is submitted successfully */
72
+ onFeedbackSubmitted?: () => void;
73
+ /** Callback when there's an error */
74
+ onError?: (error: Error) => void;
75
+ }
76
+ /**
77
+ * Intent AI Provider - Wraps your app with feedback collection capabilities
78
+ *
79
+ * @example
80
+ * ```tsx
81
+ * import { IntentAIProvider } from '@intentai/react';
82
+ *
83
+ * function App() {
84
+ * return (
85
+ * <IntentAIProvider
86
+ * apiKey="your-api-key"
87
+ * user={{ id: 'user-123', email: 'user@example.com' }}
88
+ * >
89
+ * <YourApp />
90
+ * </IntentAIProvider>
91
+ * );
92
+ * }
93
+ * ```
94
+ */
95
+ declare function IntentAIProvider({ apiKey, apiUrl, user: initialUser, metadata: initialMetadata, widgetConfig, children, disableWidget, onFeedbackSubmitted, onError, }: IntentAIProviderProps): React.ReactElement;
96
+ /**
97
+ * Hook to identify users - convenience wrapper around useIntentAI
98
+ *
99
+ * @example
100
+ * ```tsx
101
+ * function UserProfile({ user }) {
102
+ * const identify = useIdentify();
103
+ *
104
+ * useEffect(() => {
105
+ * identify({ id: user.id, email: user.email, name: user.name });
106
+ * }, [user]);
107
+ *
108
+ * return <div>...</div>;
109
+ * }
110
+ * ```
111
+ */
112
+ declare function useIdentify(): (user: IntentAIUser$1) => void;
113
+ /**
114
+ * Hook to set metadata - convenience wrapper around useIntentAI
115
+ *
116
+ * @example
117
+ * ```tsx
118
+ * function Dashboard({ plan }) {
119
+ * const setMetadata = useSetMetadata();
120
+ *
121
+ * useEffect(() => {
122
+ * setMetadata({ plan, feature: 'dashboard' });
123
+ * }, [plan]);
124
+ *
125
+ * return <div>...</div>;
126
+ * }
127
+ * ```
128
+ */
129
+ declare function useSetMetadata(): (metadata: Record<string, unknown>) => void;
130
+
131
+ interface FeedbackButtonProps {
132
+ /** Button variant style */
133
+ variant?: 'primary' | 'secondary' | 'ghost' | 'minimal';
134
+ /** Button size */
135
+ size?: 'sm' | 'md' | 'lg';
136
+ /** Show icon */
137
+ showIcon?: boolean;
138
+ /** Custom icon (React element) */
139
+ icon?: React.ReactNode;
140
+ /** Button label */
141
+ label?: string;
142
+ /** Additional class name */
143
+ className?: string;
144
+ /** Custom styles */
145
+ style?: React.CSSProperties;
146
+ /** Primary color */
147
+ primaryColor?: string;
148
+ /** Click handler - if provided, will be called instead of opening widget */
149
+ onClick?: () => void;
150
+ /** Disabled state */
151
+ disabled?: boolean;
152
+ }
153
+ /**
154
+ * Customizable feedback button that can trigger the widget or custom actions
155
+ *
156
+ * @example
157
+ * ```tsx
158
+ * // Basic usage - triggers widget
159
+ * <FeedbackButton />
160
+ *
161
+ * // Custom label and style
162
+ * <FeedbackButton
163
+ * label="Give Feedback"
164
+ * variant="secondary"
165
+ * size="lg"
166
+ * />
167
+ *
168
+ * // With custom click handler
169
+ * <FeedbackButton
170
+ * label="Report Issue"
171
+ * onClick={() => console.log('clicked')}
172
+ * />
173
+ * ```
174
+ */
175
+ declare function FeedbackButton({ variant, size, showIcon, icon, label, className, style, primaryColor, onClick, disabled, }: FeedbackButtonProps): React.ReactElement;
176
+
34
177
  interface IntentAIUser {
35
178
  id?: string;
36
179
  email?: string;
@@ -102,29 +245,30 @@ declare global {
102
245
  */
103
246
  declare function IntentAIWidget({ apiKey, apiUrl, widgetUrl, position, theme, primaryColor, allowVoice, allowText, allowScreenshot, customMetadata, user, onOpen: _onOpen, onClose: _onClose, onFeedbackSubmitted: _onFeedbackSubmitted, onError, }: IntentAIProps): null;
104
247
  /**
105
- * Hook to control the Intent AI widget programmatically
248
+ * Hook to control the script-tag widget programmatically
249
+ * @deprecated Use useIntentAI from IntentAIProvider instead for React apps
106
250
  *
107
251
  * @example
108
252
  * ```tsx
109
- * import { IntentAIWidget, useIntentAI } from '@intent-ai/react';
253
+ * import { IntentAIWidget, useWidgetControls } from '@intentai/react';
110
254
  *
111
255
  * function App() {
112
- * const { open, close, identify } = useIntentAI();
256
+ * const { open, close, identify } = useWidgetControls();
113
257
  *
114
258
  * return (
115
259
  * <>
116
- * <IntentAIWidget apiKey="your-api-key" />
260
+ * <IntentAIWidget apiKey="your-api-key" widgetUrl="..." apiUrl="..." />
117
261
  * <button onClick={open}>Open Feedback</button>
118
262
  * </>
119
263
  * );
120
264
  * }
121
265
  * ```
122
266
  */
123
- declare function useIntentAI(): {
267
+ declare function useWidgetControls(): {
124
268
  open: () => void;
125
269
  close: () => void;
126
270
  identify: (user: IntentAIUser) => void;
127
271
  setMetadata: (metadata: Record<string, any>) => void;
128
272
  };
129
273
 
130
- export { type IntentAIConfig, type IntentAIProps, type IntentAIUser, IntentAIWidget, PremiumVoiceWidget, type WidgetConfig as PremiumWidgetConfig, IntentAIWidget as default, useIntentAI };
274
+ export { FeedbackButton, type FeedbackButtonProps, type IntentAIConfig, type IntentAIContextValue, type IntentAIProps, IntentAIProvider, type IntentAIProviderConfig, type IntentAIProviderProps, type IntentAIUser, IntentAIWidget, PremiumVoiceWidget, type WidgetConfig as PremiumWidgetConfig, IntentAIWidget as default, useIdentify, useIntentAI, useSetMetadata, useWidgetControls };
package/dist/index.d.ts CHANGED
@@ -31,6 +31,149 @@ interface FeedbackData {
31
31
  }
32
32
  declare const PremiumVoiceWidget: React.FC<WidgetConfig>;
33
33
 
34
+ interface IntentAIProviderConfig {
35
+ /** Your Intent AI API key (required) */
36
+ apiKey: string;
37
+ /** API URL (defaults to production) */
38
+ apiUrl?: string;
39
+ /** User information for identifying feedback */
40
+ user?: IntentAIUser$1;
41
+ /** Additional metadata to attach to all feedback */
42
+ metadata?: Record<string, unknown>;
43
+ }
44
+ interface IntentAIUser$1 {
45
+ id?: string;
46
+ email?: string;
47
+ name?: string;
48
+ [key: string]: unknown;
49
+ }
50
+ interface IntentAIContextValue {
51
+ /** Identify the current user */
52
+ identify: (user: IntentAIUser$1) => void;
53
+ /** Set metadata to attach to feedback */
54
+ setMetadata: (metadata: Record<string, unknown>) => void;
55
+ /** Clear the current user */
56
+ clearUser: () => void;
57
+ /** Whether the provider is configured */
58
+ isConfigured: boolean;
59
+ }
60
+ /**
61
+ * Hook to access Intent AI context for user identification and metadata
62
+ */
63
+ declare function useIntentAI(): IntentAIContextValue;
64
+ interface IntentAIProviderProps extends IntentAIProviderConfig {
65
+ /** Widget configuration options */
66
+ widgetConfig?: Omit<WidgetConfig, 'onSubmit'>;
67
+ /** Children components */
68
+ children?: React.ReactNode;
69
+ /** Disable the widget (only provide context) */
70
+ disableWidget?: boolean;
71
+ /** Callback when feedback is submitted successfully */
72
+ onFeedbackSubmitted?: () => void;
73
+ /** Callback when there's an error */
74
+ onError?: (error: Error) => void;
75
+ }
76
+ /**
77
+ * Intent AI Provider - Wraps your app with feedback collection capabilities
78
+ *
79
+ * @example
80
+ * ```tsx
81
+ * import { IntentAIProvider } from '@intentai/react';
82
+ *
83
+ * function App() {
84
+ * return (
85
+ * <IntentAIProvider
86
+ * apiKey="your-api-key"
87
+ * user={{ id: 'user-123', email: 'user@example.com' }}
88
+ * >
89
+ * <YourApp />
90
+ * </IntentAIProvider>
91
+ * );
92
+ * }
93
+ * ```
94
+ */
95
+ declare function IntentAIProvider({ apiKey, apiUrl, user: initialUser, metadata: initialMetadata, widgetConfig, children, disableWidget, onFeedbackSubmitted, onError, }: IntentAIProviderProps): React.ReactElement;
96
+ /**
97
+ * Hook to identify users - convenience wrapper around useIntentAI
98
+ *
99
+ * @example
100
+ * ```tsx
101
+ * function UserProfile({ user }) {
102
+ * const identify = useIdentify();
103
+ *
104
+ * useEffect(() => {
105
+ * identify({ id: user.id, email: user.email, name: user.name });
106
+ * }, [user]);
107
+ *
108
+ * return <div>...</div>;
109
+ * }
110
+ * ```
111
+ */
112
+ declare function useIdentify(): (user: IntentAIUser$1) => void;
113
+ /**
114
+ * Hook to set metadata - convenience wrapper around useIntentAI
115
+ *
116
+ * @example
117
+ * ```tsx
118
+ * function Dashboard({ plan }) {
119
+ * const setMetadata = useSetMetadata();
120
+ *
121
+ * useEffect(() => {
122
+ * setMetadata({ plan, feature: 'dashboard' });
123
+ * }, [plan]);
124
+ *
125
+ * return <div>...</div>;
126
+ * }
127
+ * ```
128
+ */
129
+ declare function useSetMetadata(): (metadata: Record<string, unknown>) => void;
130
+
131
+ interface FeedbackButtonProps {
132
+ /** Button variant style */
133
+ variant?: 'primary' | 'secondary' | 'ghost' | 'minimal';
134
+ /** Button size */
135
+ size?: 'sm' | 'md' | 'lg';
136
+ /** Show icon */
137
+ showIcon?: boolean;
138
+ /** Custom icon (React element) */
139
+ icon?: React.ReactNode;
140
+ /** Button label */
141
+ label?: string;
142
+ /** Additional class name */
143
+ className?: string;
144
+ /** Custom styles */
145
+ style?: React.CSSProperties;
146
+ /** Primary color */
147
+ primaryColor?: string;
148
+ /** Click handler - if provided, will be called instead of opening widget */
149
+ onClick?: () => void;
150
+ /** Disabled state */
151
+ disabled?: boolean;
152
+ }
153
+ /**
154
+ * Customizable feedback button that can trigger the widget or custom actions
155
+ *
156
+ * @example
157
+ * ```tsx
158
+ * // Basic usage - triggers widget
159
+ * <FeedbackButton />
160
+ *
161
+ * // Custom label and style
162
+ * <FeedbackButton
163
+ * label="Give Feedback"
164
+ * variant="secondary"
165
+ * size="lg"
166
+ * />
167
+ *
168
+ * // With custom click handler
169
+ * <FeedbackButton
170
+ * label="Report Issue"
171
+ * onClick={() => console.log('clicked')}
172
+ * />
173
+ * ```
174
+ */
175
+ declare function FeedbackButton({ variant, size, showIcon, icon, label, className, style, primaryColor, onClick, disabled, }: FeedbackButtonProps): React.ReactElement;
176
+
34
177
  interface IntentAIUser {
35
178
  id?: string;
36
179
  email?: string;
@@ -102,29 +245,30 @@ declare global {
102
245
  */
103
246
  declare function IntentAIWidget({ apiKey, apiUrl, widgetUrl, position, theme, primaryColor, allowVoice, allowText, allowScreenshot, customMetadata, user, onOpen: _onOpen, onClose: _onClose, onFeedbackSubmitted: _onFeedbackSubmitted, onError, }: IntentAIProps): null;
104
247
  /**
105
- * Hook to control the Intent AI widget programmatically
248
+ * Hook to control the script-tag widget programmatically
249
+ * @deprecated Use useIntentAI from IntentAIProvider instead for React apps
106
250
  *
107
251
  * @example
108
252
  * ```tsx
109
- * import { IntentAIWidget, useIntentAI } from '@intent-ai/react';
253
+ * import { IntentAIWidget, useWidgetControls } from '@intentai/react';
110
254
  *
111
255
  * function App() {
112
- * const { open, close, identify } = useIntentAI();
256
+ * const { open, close, identify } = useWidgetControls();
113
257
  *
114
258
  * return (
115
259
  * <>
116
- * <IntentAIWidget apiKey="your-api-key" />
260
+ * <IntentAIWidget apiKey="your-api-key" widgetUrl="..." apiUrl="..." />
117
261
  * <button onClick={open}>Open Feedback</button>
118
262
  * </>
119
263
  * );
120
264
  * }
121
265
  * ```
122
266
  */
123
- declare function useIntentAI(): {
267
+ declare function useWidgetControls(): {
124
268
  open: () => void;
125
269
  close: () => void;
126
270
  identify: (user: IntentAIUser) => void;
127
271
  setMetadata: (metadata: Record<string, any>) => void;
128
272
  };
129
273
 
130
- export { type IntentAIConfig, type IntentAIProps, type IntentAIUser, IntentAIWidget, PremiumVoiceWidget, type WidgetConfig as PremiumWidgetConfig, IntentAIWidget as default, useIntentAI };
274
+ export { FeedbackButton, type FeedbackButtonProps, type IntentAIConfig, type IntentAIContextValue, type IntentAIProps, IntentAIProvider, type IntentAIProviderConfig, type IntentAIProviderProps, type IntentAIUser, IntentAIWidget, PremiumVoiceWidget, type WidgetConfig as PremiumWidgetConfig, IntentAIWidget as default, useIdentify, useIntentAI, useSetMetadata, useWidgetControls };
package/dist/index.js CHANGED
@@ -21,13 +21,18 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
21
21
  // src/index.tsx
22
22
  var index_exports = {};
23
23
  __export(index_exports, {
24
+ FeedbackButton: () => FeedbackButton,
25
+ IntentAIProvider: () => IntentAIProvider,
24
26
  IntentAIWidget: () => IntentAIWidget,
25
27
  PremiumVoiceWidget: () => PremiumVoiceWidget,
26
28
  default: () => index_default,
27
- useIntentAI: () => useIntentAI
29
+ useIdentify: () => useIdentify,
30
+ useIntentAI: () => useIntentAI,
31
+ useSetMetadata: () => useSetMetadata,
32
+ useWidgetControls: () => useWidgetControls
28
33
  });
29
34
  module.exports = __toCommonJS(index_exports);
30
- var import_react2 = require("react");
35
+ var import_react4 = require("react");
31
36
 
32
37
  // src/components/PremiumVoiceWidget.tsx
33
38
  var import_react = require("react");
@@ -129,8 +134,9 @@ var useAudioLevel = (isRecording) => {
129
134
  streamRef.current.getTracks().forEach((track) => track.stop());
130
135
  streamRef.current = null;
131
136
  }
132
- if (audioContextRef.current) {
133
- audioContextRef.current.close();
137
+ if (audioContextRef.current && audioContextRef.current.state !== "closed") {
138
+ audioContextRef.current.close().catch(() => {
139
+ });
134
140
  audioContextRef.current = null;
135
141
  }
136
142
  analyserRef.current = null;
@@ -168,8 +174,9 @@ var useAudioLevel = (isRecording) => {
168
174
  if (streamRef.current) {
169
175
  streamRef.current.getTracks().forEach((track) => track.stop());
170
176
  }
171
- if (audioContextRef.current) {
172
- audioContextRef.current.close();
177
+ if (audioContextRef.current && audioContextRef.current.state !== "closed") {
178
+ audioContextRef.current.close().catch(() => {
179
+ });
173
180
  }
174
181
  };
175
182
  }, [isRecording]);
@@ -390,12 +397,12 @@ var AmbientOrb = ({ audioLevel, isRecording }) => {
390
397
  ]
391
398
  }
392
399
  ) }),
393
- particles.map((particle) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
400
+ particles.filter((p) => p.size != null).map((particle) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
394
401
  import_framer_motion.motion.circle,
395
402
  {
396
403
  cx: 70,
397
404
  cy: 70,
398
- r: particle.size,
405
+ r: particle.size || 3,
399
406
  fill: particle.color,
400
407
  initial: { opacity: 0.8, scale: 1 },
401
408
  animate: {
@@ -1642,6 +1649,359 @@ function adjustColor(color, amount) {
1642
1649
  return `#${(r << 16 | g << 8 | b).toString(16).padStart(6, "0")}`;
1643
1650
  }
1644
1651
 
1652
+ // src/components/IntentAIProvider.tsx
1653
+ var import_react2 = require("react");
1654
+ var import_jsx_runtime2 = require("react/jsx-runtime");
1655
+ var IntentAIContext = (0, import_react2.createContext)(null);
1656
+ function useIntentAI() {
1657
+ const context = (0, import_react2.useContext)(IntentAIContext);
1658
+ if (!context) {
1659
+ throw new Error("useIntentAI must be used within an IntentAIProvider");
1660
+ }
1661
+ return context;
1662
+ }
1663
+ function generateSessionId() {
1664
+ return `session_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
1665
+ }
1666
+ function getDeviceInfo() {
1667
+ const ua = navigator.userAgent;
1668
+ let browser = "Unknown";
1669
+ let browserVersion = "Unknown";
1670
+ if (ua.includes("Firefox/")) {
1671
+ browser = "Firefox";
1672
+ const match = ua.match(/Firefox\/(\d+(\.\d+)?)/);
1673
+ if (match) browserVersion = match[1];
1674
+ } else if (ua.includes("Edg/")) {
1675
+ browser = "Edge";
1676
+ const match = ua.match(/Edg\/(\d+(\.\d+)?)/);
1677
+ if (match) browserVersion = match[1];
1678
+ } else if (ua.includes("Chrome/")) {
1679
+ browser = "Chrome";
1680
+ const match = ua.match(/Chrome\/(\d+(\.\d+)?)/);
1681
+ if (match) browserVersion = match[1];
1682
+ } else if (ua.includes("Safari/") && !ua.includes("Chrome")) {
1683
+ browser = "Safari";
1684
+ const match = ua.match(/Version\/(\d+(\.\d+)?)/);
1685
+ if (match) browserVersion = match[1];
1686
+ }
1687
+ let os = "Unknown";
1688
+ if (ua.includes("Windows NT 10")) os = "Windows 10";
1689
+ else if (ua.includes("Windows")) os = "Windows";
1690
+ else if (ua.includes("Mac OS X")) os = "macOS";
1691
+ else if (ua.includes("Android")) os = "Android";
1692
+ else if (ua.includes("iPhone") || ua.includes("iPad")) os = "iOS";
1693
+ else if (ua.includes("Linux")) os = "Linux";
1694
+ let device = "Desktop";
1695
+ if (/iPhone|iPad|iPod/.test(ua)) device = "iOS Device";
1696
+ else if (/Android.*Mobile/.test(ua)) device = "Android Phone";
1697
+ else if (/Android/.test(ua)) device = "Android Tablet";
1698
+ return { browser, browserVersion, os, device };
1699
+ }
1700
+ var DEFAULT_API_URL = "https://api-production-5b88.up.railway.app";
1701
+ function IntentAIProvider({
1702
+ apiKey,
1703
+ apiUrl = DEFAULT_API_URL,
1704
+ user: initialUser,
1705
+ metadata: initialMetadata,
1706
+ widgetConfig,
1707
+ children,
1708
+ disableWidget = false,
1709
+ onFeedbackSubmitted,
1710
+ onError
1711
+ }) {
1712
+ const userRef = (0, import_react2.useRef)(initialUser);
1713
+ const metadataRef = (0, import_react2.useRef)(initialMetadata || {});
1714
+ const sessionIdRef = (0, import_react2.useRef)(generateSessionId());
1715
+ (0, import_react2.useEffect)(() => {
1716
+ if (initialUser) {
1717
+ userRef.current = initialUser;
1718
+ }
1719
+ }, [initialUser]);
1720
+ (0, import_react2.useEffect)(() => {
1721
+ if (initialMetadata) {
1722
+ metadataRef.current = { ...metadataRef.current, ...initialMetadata };
1723
+ }
1724
+ }, [initialMetadata]);
1725
+ const identify = (0, import_react2.useCallback)((user) => {
1726
+ userRef.current = { ...userRef.current, ...user };
1727
+ }, []);
1728
+ const setMetadata = (0, import_react2.useCallback)((metadata) => {
1729
+ metadataRef.current = { ...metadataRef.current, ...metadata };
1730
+ }, []);
1731
+ const clearUser = (0, import_react2.useCallback)(() => {
1732
+ userRef.current = void 0;
1733
+ }, []);
1734
+ const handleSubmit = (0, import_react2.useCallback)(async (feedback) => {
1735
+ const deviceInfo = getDeviceInfo();
1736
+ const user = userRef.current;
1737
+ const customMetadata = metadataRef.current;
1738
+ const categoryMap = {
1739
+ bug: "BUG",
1740
+ feature: "FEATURE",
1741
+ improvement: "IMPROVEMENT",
1742
+ praise: "PRAISE",
1743
+ other: "OTHER"
1744
+ };
1745
+ const payload = {
1746
+ sessionId: sessionIdRef.current,
1747
+ type: feedback.type === "voice" ? "VOICE" : "TEXT",
1748
+ category: categoryMap[feedback.category.toLowerCase()] || feedback.category.toUpperCase(),
1749
+ rawText: feedback.type === "text" ? feedback.content : void 0,
1750
+ transcription: feedback.type === "voice" ? feedback.transcription || feedback.content : void 0,
1751
+ pageUrl: typeof window !== "undefined" ? window.location.href : "",
1752
+ pageTitle: typeof document !== "undefined" ? document.title : "",
1753
+ userId: user?.id,
1754
+ userEmail: user?.email,
1755
+ userName: user?.name,
1756
+ customData: {
1757
+ ...customMetadata,
1758
+ ...feedback.metadata
1759
+ },
1760
+ browser: deviceInfo.browser,
1761
+ browserVersion: deviceInfo.browserVersion,
1762
+ os: deviceInfo.os,
1763
+ device: deviceInfo.device,
1764
+ screenWidth: typeof window !== "undefined" ? window.screen.width : void 0,
1765
+ screenHeight: typeof window !== "undefined" ? window.screen.height : void 0
1766
+ };
1767
+ try {
1768
+ const response = await fetch(`${apiUrl}/feedback`, {
1769
+ method: "POST",
1770
+ headers: {
1771
+ "Content-Type": "application/json",
1772
+ "X-API-Key": apiKey
1773
+ },
1774
+ body: JSON.stringify(payload)
1775
+ });
1776
+ if (!response.ok) {
1777
+ const errorData = await response.json().catch(() => ({}));
1778
+ throw new Error(errorData.error || `Failed to submit feedback: ${response.status}`);
1779
+ }
1780
+ onFeedbackSubmitted?.();
1781
+ } catch (error) {
1782
+ const err = error instanceof Error ? error : new Error("Failed to submit feedback");
1783
+ onError?.(err);
1784
+ throw err;
1785
+ }
1786
+ }, [apiKey, apiUrl, onFeedbackSubmitted, onError]);
1787
+ const contextValue = (0, import_react2.useMemo)(() => ({
1788
+ identify,
1789
+ setMetadata,
1790
+ clearUser,
1791
+ isConfigured: Boolean(apiKey)
1792
+ }), [identify, setMetadata, clearUser, apiKey]);
1793
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(IntentAIContext.Provider, { value: contextValue, children: [
1794
+ children,
1795
+ !disableWidget && apiKey && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1796
+ PremiumVoiceWidget,
1797
+ {
1798
+ ...widgetConfig,
1799
+ onSubmit: handleSubmit
1800
+ }
1801
+ )
1802
+ ] });
1803
+ }
1804
+ function useIdentify() {
1805
+ const { identify } = useIntentAI();
1806
+ return identify;
1807
+ }
1808
+ function useSetMetadata() {
1809
+ const { setMetadata } = useIntentAI();
1810
+ return setMetadata;
1811
+ }
1812
+
1813
+ // src/components/FeedbackButton.tsx
1814
+ var import_react3 = require("react");
1815
+ var import_framer_motion2 = require("framer-motion");
1816
+ var import_jsx_runtime3 = require("react/jsx-runtime");
1817
+ var FeedbackIcon = ({ size }) => /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
1818
+ "svg",
1819
+ {
1820
+ width: size,
1821
+ height: size,
1822
+ viewBox: "0 0 24 24",
1823
+ fill: "none",
1824
+ stroke: "currentColor",
1825
+ strokeWidth: "2",
1826
+ strokeLinecap: "round",
1827
+ strokeLinejoin: "round",
1828
+ children: [
1829
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("path", { d: "M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z" }),
1830
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("circle", { cx: "9", cy: "10", r: "1", fill: "currentColor" }),
1831
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("circle", { cx: "15", cy: "10", r: "1", fill: "currentColor" })
1832
+ ]
1833
+ }
1834
+ );
1835
+ function FeedbackButton({
1836
+ variant = "primary",
1837
+ size = "md",
1838
+ showIcon = true,
1839
+ icon,
1840
+ label = "Feedback",
1841
+ className,
1842
+ style,
1843
+ primaryColor = "#10b981",
1844
+ onClick,
1845
+ disabled = false
1846
+ }) {
1847
+ const [isHovered, setIsHovered] = (0, import_react3.useState)(false);
1848
+ const [isPressed, setIsPressed] = (0, import_react3.useState)(false);
1849
+ const buttonRef = (0, import_react3.useRef)(null);
1850
+ const sizes = {
1851
+ sm: {
1852
+ padding: "8px 14px",
1853
+ fontSize: 13,
1854
+ iconSize: 14,
1855
+ gap: 6,
1856
+ borderRadius: 8
1857
+ },
1858
+ md: {
1859
+ padding: "10px 18px",
1860
+ fontSize: 14,
1861
+ iconSize: 16,
1862
+ gap: 8,
1863
+ borderRadius: 10
1864
+ },
1865
+ lg: {
1866
+ padding: "14px 24px",
1867
+ fontSize: 15,
1868
+ iconSize: 18,
1869
+ gap: 10,
1870
+ borderRadius: 12
1871
+ }
1872
+ };
1873
+ const sizeConfig = sizes[size];
1874
+ const variants = {
1875
+ primary: {
1876
+ background: `linear-gradient(145deg, ${primaryColor} 0%, ${adjustColor2(primaryColor, -20)} 100%)`,
1877
+ color: "white",
1878
+ border: "none",
1879
+ boxShadow: `0 4px 16px -4px ${primaryColor}66, 0 2px 4px rgba(0, 0, 0, 0.1)`
1880
+ },
1881
+ secondary: {
1882
+ background: "white",
1883
+ color: "#374151",
1884
+ border: "1px solid #e5e7eb",
1885
+ boxShadow: "0 2px 4px rgba(0, 0, 0, 0.05)"
1886
+ },
1887
+ ghost: {
1888
+ background: "transparent",
1889
+ color: "#6b7280",
1890
+ border: "1px solid transparent",
1891
+ boxShadow: "none"
1892
+ },
1893
+ minimal: {
1894
+ background: "transparent",
1895
+ color: primaryColor,
1896
+ border: "none",
1897
+ boxShadow: "none"
1898
+ }
1899
+ };
1900
+ const variantStyle = variants[variant];
1901
+ const handleClick = (0, import_react3.useCallback)(() => {
1902
+ if (disabled) return;
1903
+ if (onClick) {
1904
+ onClick();
1905
+ return;
1906
+ }
1907
+ const widget = document.querySelector(".fiq-premium-widget");
1908
+ if (widget) {
1909
+ const trigger = widget.querySelector('button[aria-label="Open feedback widget"]');
1910
+ if (trigger) {
1911
+ trigger.click();
1912
+ return;
1913
+ }
1914
+ }
1915
+ const scriptWidget = document.getElementById("feedbackiq-widget");
1916
+ if (scriptWidget?.shadowRoot) {
1917
+ const trigger = scriptWidget.shadowRoot.querySelector(".fiq-trigger");
1918
+ if (trigger) {
1919
+ trigger.click();
1920
+ }
1921
+ }
1922
+ }, [onClick, disabled]);
1923
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
1924
+ import_framer_motion2.motion.button,
1925
+ {
1926
+ ref: buttonRef,
1927
+ onClick: handleClick,
1928
+ onMouseEnter: () => setIsHovered(true),
1929
+ onMouseLeave: () => {
1930
+ setIsHovered(false);
1931
+ setIsPressed(false);
1932
+ },
1933
+ onMouseDown: () => setIsPressed(true),
1934
+ onMouseUp: () => setIsPressed(false),
1935
+ disabled,
1936
+ animate: {
1937
+ scale: isPressed ? 0.97 : isHovered ? 1.02 : 1,
1938
+ y: isHovered ? -1 : 0
1939
+ },
1940
+ transition: {
1941
+ type: "spring",
1942
+ stiffness: 400,
1943
+ damping: 25
1944
+ },
1945
+ className,
1946
+ style: {
1947
+ display: "inline-flex",
1948
+ alignItems: "center",
1949
+ justifyContent: "center",
1950
+ gap: sizeConfig.gap,
1951
+ padding: sizeConfig.padding,
1952
+ fontSize: sizeConfig.fontSize,
1953
+ fontWeight: 600,
1954
+ fontFamily: "system-ui, -apple-system, sans-serif",
1955
+ borderRadius: sizeConfig.borderRadius,
1956
+ cursor: disabled ? "not-allowed" : "pointer",
1957
+ opacity: disabled ? 0.5 : 1,
1958
+ transition: "all 0.2s ease",
1959
+ position: "relative",
1960
+ overflow: "hidden",
1961
+ ...variantStyle,
1962
+ ...style
1963
+ },
1964
+ children: [
1965
+ variant === "primary" && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1966
+ import_framer_motion2.motion.div,
1967
+ {
1968
+ initial: { opacity: 0 },
1969
+ animate: { opacity: isHovered ? 0.1 : 0 },
1970
+ style: {
1971
+ position: "absolute",
1972
+ inset: 0,
1973
+ background: "white",
1974
+ pointerEvents: "none"
1975
+ }
1976
+ }
1977
+ ),
1978
+ variant === "primary" && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
1979
+ "div",
1980
+ {
1981
+ style: {
1982
+ position: "absolute",
1983
+ inset: 0,
1984
+ background: "linear-gradient(180deg, rgba(255,255,255,0.2) 0%, rgba(255,255,255,0) 50%)",
1985
+ borderRadius: "inherit",
1986
+ pointerEvents: "none"
1987
+ }
1988
+ }
1989
+ ),
1990
+ showIcon && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { style: { display: "flex", alignItems: "center", position: "relative", zIndex: 1 }, children: icon || /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(FeedbackIcon, { size: sizeConfig.iconSize }) }),
1991
+ label && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { style: { position: "relative", zIndex: 1 }, children: label })
1992
+ ]
1993
+ }
1994
+ );
1995
+ }
1996
+ function adjustColor2(color, amount) {
1997
+ const hex = color.replace("#", "");
1998
+ const num = parseInt(hex, 16);
1999
+ const r = Math.min(255, Math.max(0, (num >> 16) + amount));
2000
+ const g = Math.min(255, Math.max(0, (num >> 8 & 255) + amount));
2001
+ const b = Math.min(255, Math.max(0, (num & 255) + amount));
2002
+ return `#${(r << 16 | g << 8 | b).toString(16).padStart(6, "0")}`;
2003
+ }
2004
+
1645
2005
  // src/index.tsx
1646
2006
  function IntentAIWidget({
1647
2007
  apiKey,
@@ -1660,9 +2020,9 @@ function IntentAIWidget({
1660
2020
  onFeedbackSubmitted: _onFeedbackSubmitted,
1661
2021
  onError
1662
2022
  }) {
1663
- const widgetRef = (0, import_react2.useRef)(null);
1664
- const scriptLoadedRef = (0, import_react2.useRef)(false);
1665
- const initWidget = (0, import_react2.useCallback)(() => {
2023
+ const widgetRef = (0, import_react4.useRef)(null);
2024
+ const scriptLoadedRef = (0, import_react4.useRef)(false);
2025
+ const initWidget = (0, import_react4.useCallback)(() => {
1666
2026
  if (!window.FeedbackIQ || widgetRef.current) return;
1667
2027
  try {
1668
2028
  widgetRef.current = new window.FeedbackIQ({
@@ -1685,7 +2045,7 @@ function IntentAIWidget({
1685
2045
  void _onClose;
1686
2046
  void _onFeedbackSubmitted;
1687
2047
  }, [apiKey, apiUrl, widgetUrl, position, theme, primaryColor, allowVoice, allowText, allowScreenshot, customMetadata, user, onError]);
1688
- (0, import_react2.useEffect)(() => {
2048
+ (0, import_react4.useEffect)(() => {
1689
2049
  if (!widgetUrl) {
1690
2050
  onError?.(new Error("widgetUrl is required for IntentAIWidget"));
1691
2051
  return;
@@ -1716,39 +2076,39 @@ function IntentAIWidget({
1716
2076
  }
1717
2077
  };
1718
2078
  }, [initWidget, onError, widgetUrl]);
1719
- (0, import_react2.useEffect)(() => {
2079
+ (0, import_react4.useEffect)(() => {
1720
2080
  if (widgetRef.current && user) {
1721
2081
  widgetRef.current.identify(user);
1722
2082
  }
1723
2083
  }, [user]);
1724
- (0, import_react2.useEffect)(() => {
2084
+ (0, import_react4.useEffect)(() => {
1725
2085
  if (widgetRef.current && customMetadata) {
1726
2086
  widgetRef.current.setMetadata(customMetadata);
1727
2087
  }
1728
2088
  }, [customMetadata]);
1729
2089
  return null;
1730
2090
  }
1731
- function useIntentAI() {
1732
- const open = (0, import_react2.useCallback)(() => {
2091
+ function useWidgetControls() {
2092
+ const open = (0, import_react4.useCallback)(() => {
1733
2093
  const widget = document.getElementById("feedbackiq-widget");
1734
2094
  if (widget) {
1735
2095
  const trigger = widget.shadowRoot?.querySelector(".fiq-trigger");
1736
2096
  trigger?.click();
1737
2097
  }
1738
2098
  }, []);
1739
- const close = (0, import_react2.useCallback)(() => {
2099
+ const close = (0, import_react4.useCallback)(() => {
1740
2100
  const widget = document.getElementById("feedbackiq-widget");
1741
2101
  if (widget) {
1742
2102
  const closeBtn = widget.shadowRoot?.querySelector(".fiq-close");
1743
2103
  closeBtn?.click();
1744
2104
  }
1745
2105
  }, []);
1746
- const identify = (0, import_react2.useCallback)((user) => {
2106
+ const identify = (0, import_react4.useCallback)((user) => {
1747
2107
  if (window.IntentAI?.widget) {
1748
2108
  window.IntentAI.widget.identify(user);
1749
2109
  }
1750
2110
  }, []);
1751
- const setMetadata = (0, import_react2.useCallback)((metadata) => {
2111
+ const setMetadata = (0, import_react4.useCallback)((metadata) => {
1752
2112
  if (window.IntentAI?.widget) {
1753
2113
  window.IntentAI.widget.setMetadata(metadata);
1754
2114
  }
@@ -1758,7 +2118,12 @@ function useIntentAI() {
1758
2118
  var index_default = IntentAIWidget;
1759
2119
  // Annotate the CommonJS export names for ESM import in node:
1760
2120
  0 && (module.exports = {
2121
+ FeedbackButton,
2122
+ IntentAIProvider,
1761
2123
  IntentAIWidget,
1762
2124
  PremiumVoiceWidget,
1763
- useIntentAI
2125
+ useIdentify,
2126
+ useIntentAI,
2127
+ useSetMetadata,
2128
+ useWidgetControls
1764
2129
  });
package/dist/index.mjs CHANGED
@@ -1,7 +1,7 @@
1
1
  "use client";
2
2
 
3
3
  // src/index.tsx
4
- import { useEffect as useEffect2, useRef as useRef2, useCallback as useCallback2 } from "react";
4
+ import { useEffect as useEffect3, useRef as useRef4, useCallback as useCallback4 } from "react";
5
5
 
6
6
  // src/components/PremiumVoiceWidget.tsx
7
7
  import {
@@ -111,8 +111,9 @@ var useAudioLevel = (isRecording) => {
111
111
  streamRef.current.getTracks().forEach((track) => track.stop());
112
112
  streamRef.current = null;
113
113
  }
114
- if (audioContextRef.current) {
115
- audioContextRef.current.close();
114
+ if (audioContextRef.current && audioContextRef.current.state !== "closed") {
115
+ audioContextRef.current.close().catch(() => {
116
+ });
116
117
  audioContextRef.current = null;
117
118
  }
118
119
  analyserRef.current = null;
@@ -150,8 +151,9 @@ var useAudioLevel = (isRecording) => {
150
151
  if (streamRef.current) {
151
152
  streamRef.current.getTracks().forEach((track) => track.stop());
152
153
  }
153
- if (audioContextRef.current) {
154
- audioContextRef.current.close();
154
+ if (audioContextRef.current && audioContextRef.current.state !== "closed") {
155
+ audioContextRef.current.close().catch(() => {
156
+ });
155
157
  }
156
158
  };
157
159
  }, [isRecording]);
@@ -372,12 +374,12 @@ var AmbientOrb = ({ audioLevel, isRecording }) => {
372
374
  ]
373
375
  }
374
376
  ) }),
375
- particles.map((particle) => /* @__PURE__ */ jsx(
377
+ particles.filter((p) => p.size != null).map((particle) => /* @__PURE__ */ jsx(
376
378
  motion.circle,
377
379
  {
378
380
  cx: 70,
379
381
  cy: 70,
380
- r: particle.size,
382
+ r: particle.size || 3,
381
383
  fill: particle.color,
382
384
  initial: { opacity: 0.8, scale: 1 },
383
385
  animate: {
@@ -1624,6 +1626,359 @@ function adjustColor(color, amount) {
1624
1626
  return `#${(r << 16 | g << 8 | b).toString(16).padStart(6, "0")}`;
1625
1627
  }
1626
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
+
1627
1982
  // src/index.tsx
1628
1983
  function IntentAIWidget({
1629
1984
  apiKey,
@@ -1642,9 +1997,9 @@ function IntentAIWidget({
1642
1997
  onFeedbackSubmitted: _onFeedbackSubmitted,
1643
1998
  onError
1644
1999
  }) {
1645
- const widgetRef = useRef2(null);
1646
- const scriptLoadedRef = useRef2(false);
1647
- const initWidget = useCallback2(() => {
2000
+ const widgetRef = useRef4(null);
2001
+ const scriptLoadedRef = useRef4(false);
2002
+ const initWidget = useCallback4(() => {
1648
2003
  if (!window.FeedbackIQ || widgetRef.current) return;
1649
2004
  try {
1650
2005
  widgetRef.current = new window.FeedbackIQ({
@@ -1667,7 +2022,7 @@ function IntentAIWidget({
1667
2022
  void _onClose;
1668
2023
  void _onFeedbackSubmitted;
1669
2024
  }, [apiKey, apiUrl, widgetUrl, position, theme, primaryColor, allowVoice, allowText, allowScreenshot, customMetadata, user, onError]);
1670
- useEffect2(() => {
2025
+ useEffect3(() => {
1671
2026
  if (!widgetUrl) {
1672
2027
  onError?.(new Error("widgetUrl is required for IntentAIWidget"));
1673
2028
  return;
@@ -1698,39 +2053,39 @@ function IntentAIWidget({
1698
2053
  }
1699
2054
  };
1700
2055
  }, [initWidget, onError, widgetUrl]);
1701
- useEffect2(() => {
2056
+ useEffect3(() => {
1702
2057
  if (widgetRef.current && user) {
1703
2058
  widgetRef.current.identify(user);
1704
2059
  }
1705
2060
  }, [user]);
1706
- useEffect2(() => {
2061
+ useEffect3(() => {
1707
2062
  if (widgetRef.current && customMetadata) {
1708
2063
  widgetRef.current.setMetadata(customMetadata);
1709
2064
  }
1710
2065
  }, [customMetadata]);
1711
2066
  return null;
1712
2067
  }
1713
- function useIntentAI() {
1714
- const open = useCallback2(() => {
2068
+ function useWidgetControls() {
2069
+ const open = useCallback4(() => {
1715
2070
  const widget = document.getElementById("feedbackiq-widget");
1716
2071
  if (widget) {
1717
2072
  const trigger = widget.shadowRoot?.querySelector(".fiq-trigger");
1718
2073
  trigger?.click();
1719
2074
  }
1720
2075
  }, []);
1721
- const close = useCallback2(() => {
2076
+ const close = useCallback4(() => {
1722
2077
  const widget = document.getElementById("feedbackiq-widget");
1723
2078
  if (widget) {
1724
2079
  const closeBtn = widget.shadowRoot?.querySelector(".fiq-close");
1725
2080
  closeBtn?.click();
1726
2081
  }
1727
2082
  }, []);
1728
- const identify = useCallback2((user) => {
2083
+ const identify = useCallback4((user) => {
1729
2084
  if (window.IntentAI?.widget) {
1730
2085
  window.IntentAI.widget.identify(user);
1731
2086
  }
1732
2087
  }, []);
1733
- const setMetadata = useCallback2((metadata) => {
2088
+ const setMetadata = useCallback4((metadata) => {
1734
2089
  if (window.IntentAI?.widget) {
1735
2090
  window.IntentAI.widget.setMetadata(metadata);
1736
2091
  }
@@ -1739,8 +2094,13 @@ function useIntentAI() {
1739
2094
  }
1740
2095
  var index_default = IntentAIWidget;
1741
2096
  export {
2097
+ FeedbackButton,
2098
+ IntentAIProvider,
1742
2099
  IntentAIWidget,
1743
2100
  PremiumVoiceWidget,
1744
2101
  index_default as default,
1745
- useIntentAI
2102
+ useIdentify,
2103
+ useIntentAI,
2104
+ useSetMetadata,
2105
+ useWidgetControls
1746
2106
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@intentai/react",
3
- "version": "2.1.2",
3
+ "version": "2.2.0",
4
4
  "description": "React component for Intent AI feedback widget - easily collect user feedback with AI-powered analysis",
5
5
  "publishConfig": {
6
6
  "registry": "https://registry.npmjs.org",