@intentai/react 2.1.1 → 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.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");
@@ -116,15 +121,33 @@ var useAudioLevel = (isRecording) => {
116
121
  const [audioLevel, setAudioLevel] = (0, import_react.useState)(0);
117
122
  const analyserRef = (0, import_react.useRef)(null);
118
123
  const animationRef = (0, import_react.useRef)();
124
+ const streamRef = (0, import_react.useRef)(null);
125
+ const audioContextRef = (0, import_react.useRef)(null);
119
126
  (0, import_react.useEffect)(() => {
120
127
  if (!isRecording) {
121
128
  setAudioLevel(0);
129
+ if (animationRef.current) {
130
+ cancelAnimationFrame(animationRef.current);
131
+ animationRef.current = void 0;
132
+ }
133
+ if (streamRef.current) {
134
+ streamRef.current.getTracks().forEach((track) => track.stop());
135
+ streamRef.current = null;
136
+ }
137
+ if (audioContextRef.current && audioContextRef.current.state !== "closed") {
138
+ audioContextRef.current.close().catch(() => {
139
+ });
140
+ audioContextRef.current = null;
141
+ }
142
+ analyserRef.current = null;
122
143
  return;
123
144
  }
124
145
  const initAudio = async () => {
125
146
  try {
126
147
  const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
148
+ streamRef.current = stream;
127
149
  const audioContext = new AudioContext();
150
+ audioContextRef.current = audioContext;
128
151
  const source = audioContext.createMediaStreamSource(stream);
129
152
  const analyser = audioContext.createAnalyser();
130
153
  analyser.fftSize = 256;
@@ -148,6 +171,13 @@ var useAudioLevel = (isRecording) => {
148
171
  if (animationRef.current) {
149
172
  cancelAnimationFrame(animationRef.current);
150
173
  }
174
+ if (streamRef.current) {
175
+ streamRef.current.getTracks().forEach((track) => track.stop());
176
+ }
177
+ if (audioContextRef.current && audioContextRef.current.state !== "closed") {
178
+ audioContextRef.current.close().catch(() => {
179
+ });
180
+ }
151
181
  };
152
182
  }, [isRecording]);
153
183
  return audioLevel;
@@ -367,12 +397,12 @@ var AmbientOrb = ({ audioLevel, isRecording }) => {
367
397
  ]
368
398
  }
369
399
  ) }),
370
- particles.map((particle) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
400
+ particles.filter((p) => p.size != null).map((particle) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
371
401
  import_framer_motion.motion.circle,
372
402
  {
373
403
  cx: 70,
374
404
  cy: 70,
375
- r: particle.size,
405
+ r: particle.size || 3,
376
406
  fill: particle.color,
377
407
  initial: { opacity: 0.8, scale: 1 },
378
408
  animate: {
@@ -1619,6 +1649,359 @@ function adjustColor(color, amount) {
1619
1649
  return `#${(r << 16 | g << 8 | b).toString(16).padStart(6, "0")}`;
1620
1650
  }
1621
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
+
1622
2005
  // src/index.tsx
1623
2006
  function IntentAIWidget({
1624
2007
  apiKey,
@@ -1637,9 +2020,9 @@ function IntentAIWidget({
1637
2020
  onFeedbackSubmitted: _onFeedbackSubmitted,
1638
2021
  onError
1639
2022
  }) {
1640
- const widgetRef = (0, import_react2.useRef)(null);
1641
- const scriptLoadedRef = (0, import_react2.useRef)(false);
1642
- 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)(() => {
1643
2026
  if (!window.FeedbackIQ || widgetRef.current) return;
1644
2027
  try {
1645
2028
  widgetRef.current = new window.FeedbackIQ({
@@ -1662,7 +2045,7 @@ function IntentAIWidget({
1662
2045
  void _onClose;
1663
2046
  void _onFeedbackSubmitted;
1664
2047
  }, [apiKey, apiUrl, widgetUrl, position, theme, primaryColor, allowVoice, allowText, allowScreenshot, customMetadata, user, onError]);
1665
- (0, import_react2.useEffect)(() => {
2048
+ (0, import_react4.useEffect)(() => {
1666
2049
  if (!widgetUrl) {
1667
2050
  onError?.(new Error("widgetUrl is required for IntentAIWidget"));
1668
2051
  return;
@@ -1693,39 +2076,39 @@ function IntentAIWidget({
1693
2076
  }
1694
2077
  };
1695
2078
  }, [initWidget, onError, widgetUrl]);
1696
- (0, import_react2.useEffect)(() => {
2079
+ (0, import_react4.useEffect)(() => {
1697
2080
  if (widgetRef.current && user) {
1698
2081
  widgetRef.current.identify(user);
1699
2082
  }
1700
2083
  }, [user]);
1701
- (0, import_react2.useEffect)(() => {
2084
+ (0, import_react4.useEffect)(() => {
1702
2085
  if (widgetRef.current && customMetadata) {
1703
2086
  widgetRef.current.setMetadata(customMetadata);
1704
2087
  }
1705
2088
  }, [customMetadata]);
1706
2089
  return null;
1707
2090
  }
1708
- function useIntentAI() {
1709
- const open = (0, import_react2.useCallback)(() => {
2091
+ function useWidgetControls() {
2092
+ const open = (0, import_react4.useCallback)(() => {
1710
2093
  const widget = document.getElementById("feedbackiq-widget");
1711
2094
  if (widget) {
1712
2095
  const trigger = widget.shadowRoot?.querySelector(".fiq-trigger");
1713
2096
  trigger?.click();
1714
2097
  }
1715
2098
  }, []);
1716
- const close = (0, import_react2.useCallback)(() => {
2099
+ const close = (0, import_react4.useCallback)(() => {
1717
2100
  const widget = document.getElementById("feedbackiq-widget");
1718
2101
  if (widget) {
1719
2102
  const closeBtn = widget.shadowRoot?.querySelector(".fiq-close");
1720
2103
  closeBtn?.click();
1721
2104
  }
1722
2105
  }, []);
1723
- const identify = (0, import_react2.useCallback)((user) => {
2106
+ const identify = (0, import_react4.useCallback)((user) => {
1724
2107
  if (window.IntentAI?.widget) {
1725
2108
  window.IntentAI.widget.identify(user);
1726
2109
  }
1727
2110
  }, []);
1728
- const setMetadata = (0, import_react2.useCallback)((metadata) => {
2111
+ const setMetadata = (0, import_react4.useCallback)((metadata) => {
1729
2112
  if (window.IntentAI?.widget) {
1730
2113
  window.IntentAI.widget.setMetadata(metadata);
1731
2114
  }
@@ -1735,7 +2118,12 @@ function useIntentAI() {
1735
2118
  var index_default = IntentAIWidget;
1736
2119
  // Annotate the CommonJS export names for ESM import in node:
1737
2120
  0 && (module.exports = {
2121
+ FeedbackButton,
2122
+ IntentAIProvider,
1738
2123
  IntentAIWidget,
1739
2124
  PremiumVoiceWidget,
1740
- useIntentAI
2125
+ useIdentify,
2126
+ useIntentAI,
2127
+ useSetMetadata,
2128
+ useWidgetControls
1741
2129
  });