@merit-systems/echo-react-sdk 1.0.16 → 1.0.18

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
@@ -27130,13 +27130,16 @@ var EchoClient = class {
27130
27130
  this.models = new ModelsResource(this.http);
27131
27131
  }
27132
27132
  };
27133
- function createEchoOpenAI({ appId, baseRouterUrl = ROUTER_BASE_URL }, getTokenFn) {
27133
+ function createEchoOpenAI({ appId, baseRouterUrl = ROUTER_BASE_URL }, getTokenFn, onInsufficientFunds) {
27134
27134
  const getProvider = async () => {
27135
27135
  const token = await getTokenFn(appId);
27136
27136
  return createOpenAI({
27137
27137
  baseURL: baseRouterUrl,
27138
- apiKey: token ?? ""
27138
+ apiKey: token ?? "",
27139
27139
  // null will fail auth
27140
+ ...onInsufficientFunds && {
27141
+ fetch: fetchWith402Interceptor(fetch, onInsufficientFunds)
27142
+ }
27140
27143
  });
27141
27144
  };
27142
27145
  const openaiFunction = async (modelId) => {
@@ -27192,13 +27195,21 @@ function createEchoOpenAI({ appId, baseRouterUrl = ROUTER_BASE_URL }, getTokenFn
27192
27195
  });
27193
27196
  return implementation;
27194
27197
  }
27195
- function createEchoAnthropic({ appId, baseRouterUrl = ROUTER_BASE_URL }, getTokenFn) {
27198
+ function createEchoAnthropic({ appId, baseRouterUrl = ROUTER_BASE_URL }, getTokenFn, onInsufficientFunds) {
27196
27199
  const getProvider = async () => {
27197
27200
  const token = await getTokenFn(appId);
27198
27201
  return createAnthropic({
27199
27202
  baseURL: baseRouterUrl,
27200
- apiKey: token ?? ""
27203
+ apiKey: token ?? "",
27201
27204
  // null will fail auth
27205
+ headers: {
27206
+ // this is safe in Echo because the token is an echo oauth jwt
27207
+ // not a long lived anthropic api key
27208
+ "anthropic-dangerous-direct-browser-access": "true"
27209
+ },
27210
+ ...onInsufficientFunds && {
27211
+ fetch: fetchWith402Interceptor(fetch, onInsufficientFunds)
27212
+ }
27202
27213
  });
27203
27214
  };
27204
27215
  const anthropicFunction = async (modelId) => {
@@ -27235,13 +27246,16 @@ function createEchoAnthropic({ appId, baseRouterUrl = ROUTER_BASE_URL }, getToke
27235
27246
  );
27236
27247
  return implementation;
27237
27248
  }
27238
- function createEchoGoogle({ appId, baseRouterUrl = ROUTER_BASE_URL }, getTokenFn) {
27249
+ function createEchoGoogle({ appId, baseRouterUrl = ROUTER_BASE_URL }, getTokenFn, onInsufficientFunds) {
27239
27250
  const getProvider = async () => {
27240
27251
  const token = await getTokenFn(appId);
27241
27252
  return createGoogleGenerativeAI({
27242
27253
  baseURL: baseRouterUrl,
27243
- apiKey: token ?? ""
27254
+ apiKey: token ?? "",
27244
27255
  // null will fail auth
27256
+ ...onInsufficientFunds && {
27257
+ fetch: fetchWith402Interceptor(fetch, onInsufficientFunds)
27258
+ }
27245
27259
  });
27246
27260
  };
27247
27261
  const googleFunction = async (modelId) => {
@@ -27291,6 +27305,15 @@ function createEchoGoogle({ appId, baseRouterUrl = ROUTER_BASE_URL }, getTokenFn
27291
27305
  });
27292
27306
  return implementation;
27293
27307
  }
27308
+ function fetchWith402Interceptor(originalFetch, onInsufficientFunds) {
27309
+ return async (input, init) => {
27310
+ const response = await originalFetch(input, init);
27311
+ if (response.status === 402) {
27312
+ onInsufficientFunds();
27313
+ }
27314
+ return response;
27315
+ };
27316
+ }
27294
27317
  function useEchoBalance(echoClient, appId) {
27295
27318
  const [balance, setBalance] = useState(null);
27296
27319
  const [freeTierBalance, setFreeTierBalance] = useState(
@@ -27422,6 +27445,47 @@ function useEchoPayments(echoClient) {
27422
27445
  isLoading
27423
27446
  };
27424
27447
  }
27448
+ function useEchoUser(echoClient) {
27449
+ const [user, setUser] = useState(null);
27450
+ const [error2, setError] = useState(null);
27451
+ const [isLoading, setIsLoading] = useState(false);
27452
+ const refreshUser = useCallback(async () => {
27453
+ if (!echoClient) {
27454
+ throw new Error("Not authenticated");
27455
+ }
27456
+ setIsLoading(true);
27457
+ try {
27458
+ const userResponse = await echoClient.users.getUserInfo();
27459
+ setUser(userResponse);
27460
+ setError(null);
27461
+ } catch (err) {
27462
+ const echoError = parseEchoError(
27463
+ err instanceof Error ? err : new Error(String(err)),
27464
+ "refreshing user"
27465
+ );
27466
+ setError(echoError.message);
27467
+ throw echoError;
27468
+ } finally {
27469
+ setIsLoading(false);
27470
+ }
27471
+ }, [echoClient]);
27472
+ useEffect(() => {
27473
+ if (echoClient) {
27474
+ refreshUser().catch((err) => {
27475
+ console.error("Error loading initial user:", err);
27476
+ });
27477
+ } else {
27478
+ setUser(null);
27479
+ setError(null);
27480
+ }
27481
+ }, [echoClient, refreshUser]);
27482
+ return {
27483
+ user,
27484
+ error: error2,
27485
+ isLoading,
27486
+ refreshUser
27487
+ };
27488
+ }
27425
27489
  const EchoContext = createContext(null);
27426
27490
  const EchoRefreshContext = createContext(
27427
27491
  null
@@ -27430,10 +27494,10 @@ function EchoProviderInternal({ config: config2, children }) {
27430
27494
  var _a10, _b, _c;
27431
27495
  const auth = useAuth();
27432
27496
  const user = auth.user;
27433
- const echoUser = user ? parseEchoUser(user) : null;
27434
27497
  const apiUrl = config2.baseEchoUrl || "https://echo.merit.systems";
27435
27498
  const token = ((_a10 = auth.user) == null ? void 0 : _a10.access_token) || null;
27436
27499
  const echoClient = useEchoClient({ apiUrl });
27500
+ const [isInsufficientFunds, setIsInsufficientFunds] = useState(false);
27437
27501
  const {
27438
27502
  balance,
27439
27503
  freeTierBalance,
@@ -27441,6 +27505,11 @@ function EchoProviderInternal({ config: config2, children }) {
27441
27505
  error: balanceError,
27442
27506
  isLoading: balanceLoading
27443
27507
  } = useEchoBalance(echoClient, config2.appId);
27508
+ const {
27509
+ user: echoUser,
27510
+ error: userError,
27511
+ isLoading: userLoading
27512
+ } = useEchoUser(echoClient);
27444
27513
  const {
27445
27514
  createPaymentLink,
27446
27515
  error: paymentError,
@@ -27457,13 +27526,13 @@ function EchoProviderInternal({ config: config2, children }) {
27457
27526
  var _a15;
27458
27527
  return ((_a15 = auth.user) == null ? void 0 : _a15.access_token) || null;
27459
27528
  }, [(_b = auth.user) == null ? void 0 : _b.access_token]);
27460
- const combinedError = ((_c = auth.error) == null ? void 0 : _c.message) || balanceError || paymentError || null;
27529
+ const combinedError = ((_c = auth.error) == null ? void 0 : _c.message) || balanceError || paymentError || userError || null;
27461
27530
  const isInitialAuthLoading = auth.isLoading && !auth.isAuthenticated;
27462
27531
  const isTokenRefreshing = auth.isLoading && auth.isAuthenticated;
27463
- const combinedLoading = isInitialAuthLoading || balanceLoading || paymentLoading;
27532
+ const combinedLoading = isInitialAuthLoading || balanceLoading || paymentLoading || userLoading;
27464
27533
  const contextValue = useMemo(
27465
27534
  () => ({
27466
- user: echoUser || null,
27535
+ user: echoUser,
27467
27536
  rawUser: user,
27468
27537
  balance,
27469
27538
  freeTierBalance,
@@ -27478,7 +27547,9 @@ function EchoProviderInternal({ config: config2, children }) {
27478
27547
  createPaymentLink,
27479
27548
  getToken,
27480
27549
  clearAuth,
27481
- config: config2
27550
+ config: config2,
27551
+ isInsufficientFunds,
27552
+ setIsInsufficientFunds
27482
27553
  }),
27483
27554
  [
27484
27555
  echoUser,
@@ -27495,7 +27566,8 @@ function EchoProviderInternal({ config: config2, children }) {
27495
27566
  refreshBalance,
27496
27567
  createPaymentLink,
27497
27568
  getToken,
27498
- config2
27569
+ config2,
27570
+ isInsufficientFunds
27499
27571
  ]
27500
27572
  );
27501
27573
  const refreshContextValue = useMemo(
@@ -27553,14 +27625,6 @@ function EchoProvider({ config: config2, children }) {
27553
27625
  };
27554
27626
  return /* @__PURE__ */ jsx(AuthProvider, { ...oidcConfig, children: /* @__PURE__ */ jsx(EchoProviderInternal, { config: config2, children }) });
27555
27627
  }
27556
- function parseEchoUser(user) {
27557
- return {
27558
- id: user.profile.sub || "",
27559
- email: user.profile.email || "",
27560
- name: user.profile.name || user.profile.preferred_username || "",
27561
- picture: user.profile.picture || ""
27562
- };
27563
- }
27564
27628
  function useEcho() {
27565
27629
  const context = useContext(EchoContext);
27566
27630
  if (!context) {
@@ -28597,7 +28661,7 @@ function sanitizeUrl(url2) {
28597
28661
  }
28598
28662
  }
28599
28663
  async function openPaymentFlow(paymentUrl, options = {}) {
28600
- const { onComplete, onCancel, onError } = options;
28664
+ const { onComplete, onError } = options;
28601
28665
  const safeUrl = sanitizeUrl(paymentUrl);
28602
28666
  if (!safeUrl) {
28603
28667
  const error2 = new Error("Invalid payment URL provided");
@@ -28605,62 +28669,7 @@ async function openPaymentFlow(paymentUrl, options = {}) {
28605
28669
  throw error2;
28606
28670
  }
28607
28671
  try {
28608
- const popup = window.open(
28609
- safeUrl,
28610
- "echo-payment",
28611
- "width=600,height=700,scrollbars=yes,resizable=yes"
28612
- );
28613
- if (popup && !popup.closed) {
28614
- return new Promise((resolve2) => {
28615
- const checkComplete = () => {
28616
- try {
28617
- if (popup.closed) {
28618
- onComplete == null ? void 0 : onComplete();
28619
- resolve2();
28620
- return;
28621
- }
28622
- setTimeout(checkComplete, 1e3);
28623
- } catch {
28624
- onComplete == null ? void 0 : onComplete();
28625
- resolve2();
28626
- }
28627
- };
28628
- checkComplete();
28629
- setTimeout(() => {
28630
- popup.close();
28631
- onCancel == null ? void 0 : onCancel();
28632
- resolve2();
28633
- }, 6e5);
28634
- });
28635
- } else {
28636
- const confirmed = confirm(
28637
- "Payment will open in a new tab. Click OK to continue, or Cancel to abort."
28638
- );
28639
- if (confirmed) {
28640
- sessionStorage.setItem("echo_payment_flow", "true");
28641
- window.location.href = safeUrl;
28642
- return new Promise((resolve2) => {
28643
- const handleFocus = () => {
28644
- if (sessionStorage.getItem("echo_payment_flow") === "completed") {
28645
- sessionStorage.removeItem("echo_payment_flow");
28646
- onComplete == null ? void 0 : onComplete();
28647
- window.removeEventListener("focus", handleFocus);
28648
- resolve2();
28649
- }
28650
- };
28651
- window.addEventListener("focus", handleFocus);
28652
- setTimeout(() => {
28653
- window.removeEventListener("focus", handleFocus);
28654
- sessionStorage.removeItem("echo_payment_flow");
28655
- onCancel == null ? void 0 : onCancel();
28656
- resolve2();
28657
- }, 18e5);
28658
- });
28659
- } else {
28660
- onCancel == null ? void 0 : onCancel();
28661
- return Promise.resolve();
28662
- }
28663
- }
28672
+ window.location.href = safeUrl;
28664
28673
  } catch (err) {
28665
28674
  const error2 = err instanceof Error ? err : new Error("Payment flow failed");
28666
28675
  onError == null ? void 0 : onError(error2);
@@ -28896,6 +28905,482 @@ function EchoSignIn({
28896
28905
  }
28897
28906
  ) });
28898
28907
  }
28908
+ function EchoSignOut({
28909
+ onSuccess,
28910
+ onError,
28911
+ className = "",
28912
+ children
28913
+ }) {
28914
+ const { signOut, isLoading, user, error: error2 } = useEcho();
28915
+ const [isHovered, setIsHovered] = useState(false);
28916
+ const [isSigningOut, setIsSigningOut] = useState(false);
28917
+ React2.useEffect(() => {
28918
+ if (!user && onSuccess && isSigningOut) {
28919
+ onSuccess();
28920
+ setIsSigningOut(false);
28921
+ }
28922
+ }, [user, onSuccess, isSigningOut]);
28923
+ React2.useEffect(() => {
28924
+ if (error2 && onError) {
28925
+ onError(new Error(error2));
28926
+ }
28927
+ }, [error2, onError]);
28928
+ const handleSignOut = async () => {
28929
+ try {
28930
+ setIsSigningOut(true);
28931
+ await signOut();
28932
+ } catch (err) {
28933
+ setIsSigningOut(false);
28934
+ if (onError) {
28935
+ onError(err instanceof Error ? err : new Error("Sign out failed"));
28936
+ }
28937
+ }
28938
+ };
28939
+ if (!user) {
28940
+ return /* @__PURE__ */ jsx("div", { className: `echo-signout-success ${className}`, children: /* @__PURE__ */ jsxs(
28941
+ "div",
28942
+ {
28943
+ style: {
28944
+ display: "flex",
28945
+ alignItems: "center",
28946
+ gap: "8px",
28947
+ padding: "12px 16px",
28948
+ backgroundColor: "#f8fafc",
28949
+ border: "1px solid #e2e8f0",
28950
+ borderRadius: "8px",
28951
+ color: "#334155",
28952
+ fontSize: "14px",
28953
+ fontFamily: "HelveticaNowDisplay, sans-serif",
28954
+ fontWeight: "800",
28955
+ width: "fit-content"
28956
+ },
28957
+ children: [
28958
+ /* @__PURE__ */ jsx(Logo, { width: 16, height: 16, variant: "light" }),
28959
+ /* @__PURE__ */ jsx("span", { children: "Signed out" })
28960
+ ]
28961
+ }
28962
+ ) });
28963
+ }
28964
+ return /* @__PURE__ */ jsx("div", { className: `echo-signout ${className}`, children: children ? /* @__PURE__ */ jsx("div", { onClick: handleSignOut, style: { cursor: "pointer" }, children }) : /* @__PURE__ */ jsxs(
28965
+ "button",
28966
+ {
28967
+ onClick: handleSignOut,
28968
+ disabled: isLoading || isSigningOut,
28969
+ className: "echo-signout-button",
28970
+ style: {
28971
+ display: "flex",
28972
+ alignItems: "center",
28973
+ gap: "8px",
28974
+ padding: "12px 20px",
28975
+ backgroundColor: isLoading || isSigningOut ? "#f3f4f6" : isHovered ? "#fef2f2" : "#ffffff",
28976
+ color: isLoading || isSigningOut ? "#9ca3af" : isHovered ? "#dc2626" : "#374151",
28977
+ border: "1px solid #e2e8f0",
28978
+ borderRadius: "8px",
28979
+ cursor: isLoading || isSigningOut ? "not-allowed" : "pointer",
28980
+ fontSize: "14px",
28981
+ fontWeight: "800",
28982
+ fontFamily: "HelveticaNowDisplay, sans-serif",
28983
+ transition: "all 0.2s ease",
28984
+ boxShadow: "0 1px 2px 0 rgba(0, 0, 0, 0.05)",
28985
+ backdropFilter: "blur(8px)"
28986
+ },
28987
+ onMouseEnter: () => setIsHovered(true),
28988
+ onMouseLeave: () => setIsHovered(false),
28989
+ children: [
28990
+ /* @__PURE__ */ jsx(Logo, { width: 16, height: 16, variant: "light" }),
28991
+ /* @__PURE__ */ jsx("span", { children: isLoading || isSigningOut ? "Signing out..." : `Sign out` })
28992
+ ]
28993
+ }
28994
+ ) });
28995
+ }
28996
+ const CustomAmountInput$1 = ({
28997
+ onAddCredits,
28998
+ onCancel,
28999
+ isProcessing
29000
+ }) => {
29001
+ const [amount, setAmount] = useState("");
29002
+ const inputRef = useRef(null);
29003
+ useEffect(() => {
29004
+ if (inputRef.current) {
29005
+ inputRef.current.focus();
29006
+ }
29007
+ }, []);
29008
+ const handleAddCredits = () => {
29009
+ const purchaseAmount = Number(amount);
29010
+ if (purchaseAmount > 0) {
29011
+ onAddCredits(purchaseAmount);
29012
+ }
29013
+ };
29014
+ const isValidAmount = amount !== "" && Number(amount) > 0;
29015
+ return /* @__PURE__ */ jsxs("div", { style: { display: "flex", flexDirection: "column", gap: "8px" }, children: [
29016
+ /* @__PURE__ */ jsxs("div", { style: { display: "flex", alignItems: "center", gap: "8px" }, children: [
29017
+ /* @__PURE__ */ jsx(
29018
+ "span",
29019
+ {
29020
+ style: {
29021
+ fontSize: "14px",
29022
+ color: "#6b7280",
29023
+ fontFamily: "HelveticaNowDisplay, sans-serif"
29024
+ },
29025
+ children: "$"
29026
+ }
29027
+ ),
29028
+ /* @__PURE__ */ jsx(
29029
+ "input",
29030
+ {
29031
+ ref: inputRef,
29032
+ type: "number",
29033
+ value: amount,
29034
+ onChange: (e) => setAmount(e.target.value),
29035
+ placeholder: "0.00",
29036
+ min: "1",
29037
+ step: "0.01",
29038
+ style: {
29039
+ flex: 1,
29040
+ padding: "8px 12px",
29041
+ border: "1px solid #d1d5db",
29042
+ borderRadius: "6px",
29043
+ fontSize: "14px",
29044
+ fontFamily: "HelveticaNowDisplay, sans-serif",
29045
+ outline: "none"
29046
+ },
29047
+ onKeyDown: (e) => {
29048
+ if (e.key === "Enter" && isValidAmount && !isProcessing) {
29049
+ handleAddCredits();
29050
+ } else if (e.key === "Escape") {
29051
+ onCancel();
29052
+ }
29053
+ }
29054
+ }
29055
+ )
29056
+ ] }),
29057
+ /* @__PURE__ */ jsxs("div", { style: { display: "flex", gap: "8px" }, children: [
29058
+ /* @__PURE__ */ jsx(
29059
+ "button",
29060
+ {
29061
+ onClick: onCancel,
29062
+ style: {
29063
+ flex: 1,
29064
+ padding: "8px 16px",
29065
+ backgroundColor: "transparent",
29066
+ color: "#6b7280",
29067
+ border: "1px solid #d1d5db",
29068
+ borderRadius: "6px",
29069
+ fontSize: "14px",
29070
+ cursor: "pointer",
29071
+ fontFamily: "HelveticaNowDisplay, sans-serif"
29072
+ },
29073
+ children: "Cancel"
29074
+ }
29075
+ ),
29076
+ /* @__PURE__ */ jsx(
29077
+ "button",
29078
+ {
29079
+ onClick: handleAddCredits,
29080
+ disabled: !isValidAmount || isProcessing,
29081
+ style: {
29082
+ flex: 1,
29083
+ padding: "8px 16px",
29084
+ backgroundColor: isValidAmount && !isProcessing ? "#dc2626" : "#9ca3af",
29085
+ color: "white",
29086
+ border: "none",
29087
+ borderRadius: "6px",
29088
+ fontSize: "14px",
29089
+ fontWeight: "600",
29090
+ cursor: isValidAmount && !isProcessing ? "pointer" : "not-allowed",
29091
+ fontFamily: "HelveticaNowDisplay, sans-serif"
29092
+ },
29093
+ children: isProcessing ? "Processing..." : "Add Credits"
29094
+ }
29095
+ )
29096
+ ] })
29097
+ ] });
29098
+ };
29099
+ function InsufficientFundsModal({
29100
+ isOpen,
29101
+ onClose,
29102
+ onPurchaseComplete,
29103
+ onError
29104
+ }) {
29105
+ const { createPaymentLink, user, refreshBalance } = useEcho();
29106
+ const [isProcessing, setIsProcessing] = useState(false);
29107
+ const [purchaseError, setPurchaseError] = useState(null);
29108
+ const [showCustomAmount, setShowCustomAmount] = useState(false);
29109
+ const handlePurchase = async (amount) => {
29110
+ if (!user) {
29111
+ const error2 = new Error("Please sign in to purchase tokens");
29112
+ setPurchaseError(error2.message);
29113
+ onError == null ? void 0 : onError(error2);
29114
+ return;
29115
+ }
29116
+ try {
29117
+ setIsProcessing(true);
29118
+ setPurchaseError(null);
29119
+ const paymentUrl = await createPaymentLink(amount);
29120
+ await openPaymentFlow(paymentUrl, {
29121
+ onComplete: async () => {
29122
+ try {
29123
+ await refreshBalance();
29124
+ onPurchaseComplete == null ? void 0 : onPurchaseComplete();
29125
+ onClose();
29126
+ } catch (refreshError) {
29127
+ console.error("Failed to refresh balance:", refreshError);
29128
+ }
29129
+ },
29130
+ onError: (error2) => {
29131
+ setPurchaseError(error2.message);
29132
+ onError == null ? void 0 : onError(error2);
29133
+ }
29134
+ });
29135
+ } catch (error2) {
29136
+ const errorMessage = error2 instanceof Error ? error2.message : "Purchase failed";
29137
+ setPurchaseError(errorMessage);
29138
+ onError == null ? void 0 : onError(error2 instanceof Error ? error2 : new Error(errorMessage));
29139
+ } finally {
29140
+ setIsProcessing(false);
29141
+ }
29142
+ };
29143
+ if (!isOpen) return null;
29144
+ return /* @__PURE__ */ jsx(
29145
+ "div",
29146
+ {
29147
+ style: {
29148
+ position: "fixed",
29149
+ top: 0,
29150
+ left: 0,
29151
+ right: 0,
29152
+ bottom: 0,
29153
+ backgroundColor: "rgba(0, 0, 0, 0.5)",
29154
+ display: "flex",
29155
+ alignItems: "center",
29156
+ justifyContent: "center",
29157
+ zIndex: 1e3,
29158
+ padding: "16px",
29159
+ margin: 0,
29160
+ boxSizing: "border-box",
29161
+ // CSS reset to prevent inheritance issues
29162
+ border: "none",
29163
+ outline: "none",
29164
+ textDecoration: "none",
29165
+ listStyle: "none"
29166
+ },
29167
+ onClick: (e) => {
29168
+ if (e.target === e.currentTarget) {
29169
+ onClose();
29170
+ }
29171
+ },
29172
+ children: /* @__PURE__ */ jsxs(
29173
+ "div",
29174
+ {
29175
+ style: {
29176
+ backgroundColor: "#ffffff",
29177
+ borderRadius: "12px",
29178
+ border: "1px solid #e5e7eb",
29179
+ padding: "0",
29180
+ margin: "0",
29181
+ width: "100%",
29182
+ maxWidth: "450px",
29183
+ maxHeight: "90vh",
29184
+ overflow: "hidden",
29185
+ boxShadow: "0 10px 25px -5px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05)",
29186
+ fontFamily: "HelveticaNowDisplay, sans-serif",
29187
+ display: "flex",
29188
+ flexDirection: "column",
29189
+ minHeight: "320px",
29190
+ boxSizing: "border-box"
29191
+ },
29192
+ children: [
29193
+ /* @__PURE__ */ jsx("div", { style: { padding: "16px 16px 0 16px", marginBottom: "12px" }, children: /* @__PURE__ */ jsxs(
29194
+ "div",
29195
+ {
29196
+ style: {
29197
+ display: "flex",
29198
+ alignItems: "center",
29199
+ gap: "8px"
29200
+ },
29201
+ children: [
29202
+ /* @__PURE__ */ jsx(Logo, { width: 20, height: 20, variant: "light" }),
29203
+ /* @__PURE__ */ jsx(
29204
+ "h2",
29205
+ {
29206
+ style: {
29207
+ fontSize: "18px",
29208
+ fontWeight: "500",
29209
+ color: "#111827",
29210
+ margin: 0,
29211
+ fontFamily: "HelveticaNowDisplay, sans-serif"
29212
+ },
29213
+ children: "Insufficient Balance"
29214
+ }
29215
+ )
29216
+ ]
29217
+ }
29218
+ ) }),
29219
+ /* @__PURE__ */ jsx("div", { style: { padding: "0 16px 16px 16px" }, children: /* @__PURE__ */ jsx(
29220
+ "p",
29221
+ {
29222
+ style: {
29223
+ fontSize: "14px",
29224
+ color: "#6b7280",
29225
+ margin: "0 0 12px 0",
29226
+ fontFamily: "HelveticaNowDisplay, sans-serif"
29227
+ },
29228
+ children: "You don't have enough credits to complete this request. Please add credits to your account."
29229
+ }
29230
+ ) }),
29231
+ /* @__PURE__ */ jsx(
29232
+ "div",
29233
+ {
29234
+ style: {
29235
+ padding: "0 16px 16px 16px",
29236
+ borderTop: "1px solid #f3f4f6",
29237
+ paddingTop: "16px"
29238
+ },
29239
+ children: !showCustomAmount ? /* @__PURE__ */ jsxs(
29240
+ "div",
29241
+ {
29242
+ style: { display: "flex", flexDirection: "column", gap: "8px" },
29243
+ children: [
29244
+ /* @__PURE__ */ jsx(
29245
+ "button",
29246
+ {
29247
+ onClick: () => handlePurchase(10),
29248
+ disabled: isProcessing,
29249
+ style: {
29250
+ width: "100%",
29251
+ padding: "12px 16px",
29252
+ backgroundColor: isProcessing ? "#9ca3af" : "#dc2626",
29253
+ color: "white",
29254
+ border: "none",
29255
+ borderRadius: "6px",
29256
+ fontSize: "14px",
29257
+ fontWeight: "600",
29258
+ cursor: isProcessing ? "not-allowed" : "pointer",
29259
+ transition: "background-color 0.2s ease",
29260
+ fontFamily: "HelveticaNowDisplay, sans-serif"
29261
+ },
29262
+ onMouseEnter: (e) => {
29263
+ if (!isProcessing) {
29264
+ e.currentTarget.style.backgroundColor = "#b91c1c";
29265
+ }
29266
+ },
29267
+ onMouseLeave: (e) => {
29268
+ if (!isProcessing) {
29269
+ e.currentTarget.style.backgroundColor = "#dc2626";
29270
+ }
29271
+ },
29272
+ children: isProcessing ? "Processing..." : "Add $10.00"
29273
+ }
29274
+ ),
29275
+ /* @__PURE__ */ jsx(
29276
+ "button",
29277
+ {
29278
+ onClick: () => setShowCustomAmount(true),
29279
+ disabled: isProcessing,
29280
+ style: {
29281
+ width: "100%",
29282
+ padding: "8px 16px",
29283
+ backgroundColor: "transparent",
29284
+ color: "#dc2626",
29285
+ border: "1px solid #dc2626",
29286
+ borderRadius: "6px",
29287
+ fontSize: "14px",
29288
+ cursor: isProcessing ? "not-allowed" : "pointer",
29289
+ fontFamily: "HelveticaNowDisplay, sans-serif",
29290
+ transition: "all 0.2s ease"
29291
+ },
29292
+ onMouseEnter: (e) => {
29293
+ if (!isProcessing) {
29294
+ e.currentTarget.style.backgroundColor = "#fef2f2";
29295
+ e.currentTarget.style.borderColor = "#b91c1c";
29296
+ }
29297
+ },
29298
+ onMouseLeave: (e) => {
29299
+ if (!isProcessing) {
29300
+ e.currentTarget.style.backgroundColor = "transparent";
29301
+ e.currentTarget.style.borderColor = "#dc2626";
29302
+ }
29303
+ },
29304
+ children: "Custom Amount"
29305
+ }
29306
+ )
29307
+ ]
29308
+ }
29309
+ ) : /* @__PURE__ */ jsx(
29310
+ CustomAmountInput$1,
29311
+ {
29312
+ onAddCredits: handlePurchase,
29313
+ onCancel: () => setShowCustomAmount(false),
29314
+ isProcessing
29315
+ }
29316
+ )
29317
+ }
29318
+ ),
29319
+ purchaseError && /* @__PURE__ */ jsx("div", { style: { padding: "0 16px 16px 16px" }, children: /* @__PURE__ */ jsx(
29320
+ "div",
29321
+ {
29322
+ style: {
29323
+ padding: "8px 12px",
29324
+ backgroundColor: "#fef2f2",
29325
+ borderRadius: "6px",
29326
+ border: "1px solid #fecaca"
29327
+ },
29328
+ children: /* @__PURE__ */ jsx(
29329
+ "p",
29330
+ {
29331
+ style: {
29332
+ color: "#dc2626",
29333
+ margin: 0,
29334
+ fontSize: "12px",
29335
+ fontFamily: "HelveticaNowDisplay, sans-serif"
29336
+ },
29337
+ children: purchaseError
29338
+ }
29339
+ )
29340
+ }
29341
+ ) }),
29342
+ /* @__PURE__ */ jsx(
29343
+ "div",
29344
+ {
29345
+ style: {
29346
+ display: "flex",
29347
+ justifyContent: "flex-end",
29348
+ alignItems: "center",
29349
+ padding: "12px 16px",
29350
+ borderTop: "1px solid #f3f4f6",
29351
+ gap: "8px"
29352
+ },
29353
+ children: /* @__PURE__ */ jsx(
29354
+ "button",
29355
+ {
29356
+ onClick: onClose,
29357
+ style: {
29358
+ padding: "8px 16px",
29359
+ backgroundColor: "#f3f4f6",
29360
+ color: "#374151",
29361
+ border: "none",
29362
+ borderRadius: "6px",
29363
+ fontSize: "14px",
29364
+ cursor: "pointer",
29365
+ fontFamily: "HelveticaNowDisplay, sans-serif"
29366
+ },
29367
+ onMouseEnter: (e) => {
29368
+ e.currentTarget.style.backgroundColor = "#e5e7eb";
29369
+ },
29370
+ onMouseLeave: (e) => {
29371
+ e.currentTarget.style.backgroundColor = "#f3f4f6";
29372
+ },
29373
+ children: "Close"
29374
+ }
29375
+ )
29376
+ }
29377
+ )
29378
+ ]
29379
+ }
29380
+ )
29381
+ }
29382
+ );
29383
+ }
28899
29384
  const CustomAmountInput = ({
28900
29385
  onAddCredits,
28901
29386
  onCancel,
@@ -29020,13 +29505,22 @@ const CustomAmountInput = ({
29020
29505
  }
29021
29506
  );
29022
29507
  };
29023
- function EchoTokenPurchase({
29508
+ function EchoTokens({
29024
29509
  onPurchaseComplete,
29025
29510
  onError,
29026
29511
  className = "",
29027
- children
29512
+ children,
29513
+ showAvatar = false
29028
29514
  }) {
29029
- const { createPaymentLink, user, balance, freeTierBalance, refreshBalance } = useEcho();
29515
+ const {
29516
+ createPaymentLink,
29517
+ user,
29518
+ balance,
29519
+ freeTierBalance,
29520
+ refreshBalance,
29521
+ isInsufficientFunds,
29522
+ setIsInsufficientFunds
29523
+ } = useEcho();
29030
29524
  const [isProcessing, setIsProcessing] = useState(false);
29031
29525
  const [purchaseError, setPurchaseError] = useState(null);
29032
29526
  const [isModalOpen, setIsModalOpen] = useState(false);
@@ -29093,7 +29587,7 @@ function EchoTokenPurchase({
29093
29587
  setPurchaseError(null);
29094
29588
  };
29095
29589
  if (!user) {
29096
- return /* @__PURE__ */ jsx("div", { className: `echo-token-purchase ${className}`, children: /* @__PURE__ */ jsx(
29590
+ return /* @__PURE__ */ jsx("div", { className: `echo-token-purchase ${className}`, children: children ? /* @__PURE__ */ jsxs(
29097
29591
  "div",
29098
29592
  {
29099
29593
  className: "echo-token-purchase-unauthorized",
@@ -29102,11 +29596,15 @@ function EchoTokenPurchase({
29102
29596
  backgroundColor: "#fef3c7",
29103
29597
  color: "#92400e",
29104
29598
  borderRadius: "8px",
29105
- fontSize: "14px"
29599
+ fontSize: "14px",
29600
+ textAlign: "center"
29106
29601
  },
29107
- children: "Please sign in to purchase tokens"
29602
+ children: [
29603
+ /* @__PURE__ */ jsx("p", { style: { margin: "0 0 12px 0" }, children: "Please sign in to purchase tokens" }),
29604
+ /* @__PURE__ */ jsx(EchoSignIn, {})
29605
+ ]
29108
29606
  }
29109
- ) });
29607
+ ) : /* @__PURE__ */ jsx(EchoSignIn, {}) });
29110
29608
  }
29111
29609
  const CompactButton = () => {
29112
29610
  const [isHovered, setIsHovered] = useState(false);
@@ -29136,7 +29634,23 @@ function EchoTokenPurchase({
29136
29634
  onMouseEnter: () => setIsHovered(true),
29137
29635
  onMouseLeave: () => setIsHovered(false),
29138
29636
  children: [
29139
- /* @__PURE__ */ jsx(Logo, { width: 16, height: 16, variant: "light" }),
29637
+ showAvatar && (user == null ? void 0 : user.picture) ? /* @__PURE__ */ jsx(
29638
+ "img",
29639
+ {
29640
+ src: user.picture,
29641
+ alt: user.name || user.email || "User avatar",
29642
+ style: {
29643
+ width: "24px",
29644
+ height: "24px",
29645
+ borderRadius: "50%",
29646
+ objectFit: "cover",
29647
+ flexShrink: 0
29648
+ },
29649
+ onError: (e) => {
29650
+ e.currentTarget.style.display = "none";
29651
+ }
29652
+ }
29653
+ ) : /* @__PURE__ */ jsx(Logo, { width: 20, height: 20, variant: "light" }),
29140
29654
  /* @__PURE__ */ jsx("span", { children: formatCurrency(calculateAvailableSpend()) }),
29141
29655
  /* @__PURE__ */ jsxs(
29142
29656
  "svg",
@@ -29204,17 +29718,30 @@ function EchoTokenPurchase({
29204
29718
  minHeight: "320px"
29205
29719
  },
29206
29720
  children: [
29207
- /* @__PURE__ */ jsx("div", { style: { padding: "16px 16px 0 16px", marginBottom: "12px" }, children: /* @__PURE__ */ jsx(
29208
- "h2",
29721
+ /* @__PURE__ */ jsx("div", { style: { padding: "16px 16px 0 16px", marginBottom: "12px" }, children: /* @__PURE__ */ jsxs(
29722
+ "div",
29209
29723
  {
29210
29724
  style: {
29211
- fontSize: "18px",
29212
- fontWeight: "500",
29213
- color: "#111827",
29214
- margin: 0,
29215
- fontFamily: "HelveticaNowDisplay, sans-serif"
29725
+ display: "flex",
29726
+ alignItems: "center",
29727
+ gap: "8px"
29216
29728
  },
29217
- children: "Credits"
29729
+ children: [
29730
+ /* @__PURE__ */ jsx(Logo, { width: 20, height: 20, variant: "light" }),
29731
+ /* @__PURE__ */ jsx(
29732
+ "h2",
29733
+ {
29734
+ style: {
29735
+ fontSize: "18px",
29736
+ fontWeight: "500",
29737
+ color: "#111827",
29738
+ margin: 0,
29739
+ fontFamily: "HelveticaNowDisplay, sans-serif"
29740
+ },
29741
+ children: "Credits"
29742
+ }
29743
+ )
29744
+ ]
29218
29745
  }
29219
29746
  ) }),
29220
29747
  /* @__PURE__ */ jsxs("div", { style: { padding: "0 16px", marginBottom: "16px" }, children: [
@@ -29351,10 +29878,10 @@ function EchoTokenPurchase({
29351
29878
  /* @__PURE__ */ jsxs("span", { children: [
29352
29879
  formatCurrency(balance.balance),
29353
29880
  " Paid Credits",
29354
- balance.totalSpent && /* @__PURE__ */ jsxs("span", { style: { color: "#9ca3af", marginLeft: "8px" }, children: [
29881
+ balance.totalPaid > 0 && /* @__PURE__ */ jsxs("span", { style: { color: "#9ca3af", marginLeft: "8px" }, children: [
29355
29882
  "(",
29356
29883
  formatCurrency(balance.totalSpent),
29357
- " /",
29884
+ " of",
29358
29885
  " ",
29359
29886
  formatCurrency(balance.totalPaid),
29360
29887
  " spent)"
@@ -29502,38 +30029,78 @@ function EchoTokenPurchase({
29502
30029
  children: purchaseError
29503
30030
  }
29504
30031
  ),
29505
- /* @__PURE__ */ jsx(
30032
+ /* @__PURE__ */ jsxs(
29506
30033
  "div",
29507
30034
  {
29508
30035
  style: {
29509
30036
  padding: "12px 16px",
29510
30037
  display: "flex",
29511
- justifyContent: "flex-end",
30038
+ justifyContent: "space-between",
30039
+ alignItems: "center",
29512
30040
  borderTop: "1px solid #f3f4f6"
29513
30041
  },
29514
- children: /* @__PURE__ */ jsx(
29515
- "button",
29516
- {
29517
- onClick: closeModal,
29518
- style: {
29519
- padding: "8px 16px",
29520
- backgroundColor: "#f3f4f6",
29521
- color: "#374151",
29522
- border: "none",
29523
- borderRadius: "6px",
29524
- fontSize: "14px",
29525
- cursor: "pointer",
29526
- fontFamily: "HelveticaNowDisplay, sans-serif"
29527
- },
29528
- onMouseEnter: (e) => {
29529
- e.currentTarget.style.backgroundColor = "#e5e7eb";
29530
- },
29531
- onMouseLeave: (e) => {
29532
- e.currentTarget.style.backgroundColor = "#f3f4f6";
29533
- },
29534
- children: "Close"
29535
- }
29536
- )
30042
+ children: [
30043
+ /* @__PURE__ */ jsx(
30044
+ EchoSignOut,
30045
+ {
30046
+ onSuccess: () => {
30047
+ console.log("Signed out from credits modal");
30048
+ closeModal();
30049
+ },
30050
+ onError: (error2) => {
30051
+ console.error("Sign out error from credits modal:", error2);
30052
+ },
30053
+ children: /* @__PURE__ */ jsx(
30054
+ "button",
30055
+ {
30056
+ style: {
30057
+ padding: "8px 16px",
30058
+ backgroundColor: "transparent",
30059
+ color: "#dc2626",
30060
+ border: "1px solid #dc2626",
30061
+ borderRadius: "6px",
30062
+ fontSize: "14px",
30063
+ cursor: "pointer",
30064
+ fontFamily: "HelveticaNowDisplay, sans-serif",
30065
+ transition: "all 0.2s ease"
30066
+ },
30067
+ onMouseEnter: (e) => {
30068
+ e.currentTarget.style.backgroundColor = "#fef2f2";
30069
+ e.currentTarget.style.borderColor = "#b91c1c";
30070
+ },
30071
+ onMouseLeave: (e) => {
30072
+ e.currentTarget.style.backgroundColor = "transparent";
30073
+ e.currentTarget.style.borderColor = "#dc2626";
30074
+ },
30075
+ children: "Sign Out"
30076
+ }
30077
+ )
30078
+ }
30079
+ ),
30080
+ /* @__PURE__ */ jsx(
30081
+ "button",
30082
+ {
30083
+ onClick: closeModal,
30084
+ style: {
30085
+ padding: "8px 16px",
30086
+ backgroundColor: "#f3f4f6",
30087
+ color: "#374151",
30088
+ border: "none",
30089
+ borderRadius: "6px",
30090
+ fontSize: "14px",
30091
+ cursor: "pointer",
30092
+ fontFamily: "HelveticaNowDisplay, sans-serif"
30093
+ },
30094
+ onMouseEnter: (e) => {
30095
+ e.currentTarget.style.backgroundColor = "#e5e7eb";
30096
+ },
30097
+ onMouseLeave: (e) => {
30098
+ e.currentTarget.style.backgroundColor = "#f3f4f6";
30099
+ },
30100
+ children: "Close"
30101
+ }
30102
+ )
30103
+ ]
29537
30104
  }
29538
30105
  )
29539
30106
  ]
@@ -29553,7 +30120,19 @@ function EchoTokenPurchase({
29553
30120
  children
29554
30121
  }
29555
30122
  ) : /* @__PURE__ */ jsx(CompactButton, {}),
29556
- isModalOpen && /* @__PURE__ */ jsx(Modal, {})
30123
+ isModalOpen && /* @__PURE__ */ jsx(Modal, {}),
30124
+ /* @__PURE__ */ jsx(
30125
+ InsufficientFundsModal,
30126
+ {
30127
+ isOpen: isInsufficientFunds,
30128
+ onClose: () => setIsInsufficientFunds(false),
30129
+ onPurchaseComplete: () => {
30130
+ onPurchaseComplete == null ? void 0 : onPurchaseComplete(balance);
30131
+ setIsInsufficientFunds(false);
30132
+ },
30133
+ onError
30134
+ }
30135
+ )
29557
30136
  ] });
29558
30137
  }
29559
30138
  function useEchoOpenAI(options = {}) {
@@ -29628,7 +30207,8 @@ function useEchoOpenAI(options = {}) {
29628
30207
  };
29629
30208
  }
29630
30209
  const useEchoModelProviders = () => {
29631
- const { token, config: config2 } = useEcho();
30210
+ const { token, config: config2, setIsInsufficientFunds } = useEcho();
30211
+ const onInsufficientFunds = () => setIsInsufficientFunds(true);
29632
30212
  return useMemo(() => {
29633
30213
  const baseConfig = {
29634
30214
  appId: config2.appId,
@@ -29636,11 +30216,11 @@ const useEchoModelProviders = () => {
29636
30216
  };
29637
30217
  const getToken = async () => token;
29638
30218
  return {
29639
- openai: createEchoOpenAI(baseConfig, getToken),
29640
- anthropic: createEchoAnthropic(baseConfig, getToken),
29641
- google: createEchoGoogle(baseConfig, getToken)
30219
+ openai: createEchoOpenAI(baseConfig, getToken, onInsufficientFunds),
30220
+ anthropic: createEchoAnthropic(baseConfig, getToken, onInsufficientFunds),
30221
+ google: createEchoGoogle(baseConfig, getToken, onInsufficientFunds)
29642
30222
  };
29643
- }, [token, config2.appId, config2.baseRouterUrl]);
30223
+ }, [token, config2.appId, config2.baseRouterUrl, setIsInsufficientFunds]);
29644
30224
  };
29645
30225
  const EchoChatConfigContext = createContext(null);
29646
30226
  function EchoChatProvider({
@@ -33351,7 +33931,9 @@ export {
33351
33931
  EchoChatProvider,
33352
33932
  EchoProvider,
33353
33933
  EchoSignIn,
33354
- EchoTokenPurchase,
33934
+ EchoSignOut,
33935
+ EchoTokens,
33936
+ InsufficientFundsModal,
33355
33937
  Logo,
33356
33938
  useChat,
33357
33939
  useEcho,