@neurodyn/react-ai-try-on 0.0.5 → 0.1.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.
Files changed (3) hide show
  1. package/dist/index.d.ts +280 -10
  2. package/dist/index.js +709 -142
  3. package/package.json +2 -1
package/dist/index.js CHANGED
@@ -2045,6 +2045,10 @@ var __webpack_modules__ = {
2045
2045
  padding-bottom: 5px;
2046
2046
  }
2047
2047
 
2048
+ .pl-2 {
2049
+ padding-left: calc(var(--spacing) * 2);
2050
+ }
2051
+
2048
2052
  .pl-3 {
2049
2053
  padding-left: calc(var(--spacing) * 3);
2050
2054
  }
@@ -2707,6 +2711,10 @@ var __webpack_modules__ = {
2707
2711
  -webkit-mask-size: 18% 18%;
2708
2712
  }
2709
2713
 
2714
+ .paused {
2715
+ animation-play-state: paused;
2716
+ }
2717
+
2710
2718
  .running {
2711
2719
  animation-play-state: running;
2712
2720
  }
@@ -4479,6 +4487,10 @@ var __webpack_modules__ = {
4479
4487
  color: var(--v-text-dark);
4480
4488
  }
4481
4489
 
4490
+ .data-\\[variant\\=destructive\\]\\:\\[\\&_svg\\:not\\(\\[class\\*\\=text-\\]\\)\\]\\:text-inherit[data-variant="destructive"] svg:not([class*="text-"]) {
4491
+ color: inherit;
4492
+ }
4493
+
4482
4494
  .\\[\\&_tr\\]\\:border-b tr {
4483
4495
  border-bottom-style: var(--tw-border-style);
4484
4496
  border-bottom-width: 1px;
@@ -9462,6 +9474,15 @@ function ThemeProvider({ children, defaultTheme = 'system', storageKey = 'v-them
9462
9474
  });
9463
9475
  }
9464
9476
  var fontsinline = __webpack_require__("../ui/src/styles/fonts.css?inline");
9477
+ const externalTryOnClient = {
9478
+ requestUploadUrls: async ()=>{
9479
+ throw new Error('External generation handles its own uploads');
9480
+ },
9481
+ generateTryOn: async ()=>{
9482
+ throw new Error('External generation handler is required');
9483
+ },
9484
+ sendWidgetEvent: ()=>{}
9485
+ };
9465
9486
  const DEFAULT_API_TIMEOUT = 30000;
9466
9487
  function createDefaultClient(defaultBaseURL) {
9467
9488
  return axios.create({
@@ -9859,6 +9880,17 @@ async function tryOnControllerCreateUploadUrls(params, config = {}) {
9859
9880
  });
9860
9881
  return res.data;
9861
9882
  }
9883
+ async function tryOnControllerGenerateShopifyTryOn(data, config = {}) {
9884
+ const { client: request = node_client_node_client, ...requestConfig } = config;
9885
+ const requestData = data;
9886
+ const res = await request({
9887
+ method: "POST",
9888
+ url: "/public/try-on/shopify/generate",
9889
+ data: requestData,
9890
+ ...requestConfig
9891
+ });
9892
+ return res.data;
9893
+ }
9862
9894
  async function tryOnControllerGenerateTryOn(data, config = {}) {
9863
9895
  const { client: request = node_client_node_client, ...requestConfig } = config;
9864
9896
  const requestData = data;
@@ -10146,6 +10178,16 @@ function createTryOnClient({ apiKey } = {}) {
10146
10178
  }) : createInternalTryOnClient();
10147
10179
  }
10148
10180
  const ns1 = {
10181
+ activationLoading: 'Checking this product…',
10182
+ activationLoadingSubtitle: 'One moment while the store confirms try-on for this item.',
10183
+ activationTemporarilyUnavailable: 'Try-on is paused for this store right now. Please check back later.',
10184
+ activationUnavailableTitle: 'Try-on is unavailable',
10185
+ externalPhotoRequired: 'Please upload your photo again.',
10186
+ externalNoTokens: 'The store has no tokens remaining.',
10187
+ externalProductUnavailable: 'Try-on is unavailable for this product.',
10188
+ externalServiceUnavailable: 'Try-on is unavailable. Please try again later.',
10189
+ externalTimeout: 'The request timed out. Please try again.',
10190
+ externalPhotoHint: 'Upload a clear photo of yourself. The store chooses the item used for your try-on.',
10149
10191
  back: 'Back',
10150
10192
  badge: 'Try it on',
10151
10193
  confirmBadge: 'Almost there',
@@ -10235,7 +10277,7 @@ const ns1 = {
10235
10277
  startUpload: 'Choose your photo',
10236
10278
  termsJoiner: '&',
10237
10279
  termsLink: 'Terms',
10238
- termsPrefix: 'By using this service, your agree to',
10280
+ termsPrefix: 'By using this service, you agree to',
10239
10281
  privacyLink: 'privacy Policy',
10240
10282
  upperBodyIntroBadArmsCrossed: 'Arms crossed hide the fit',
10241
10283
  upperBodyIntroBadObjectBlockingTorso: 'Object blocks the torso',
@@ -10257,6 +10299,16 @@ const ns1 = {
10257
10299
  wristIntroTitle: 'Give us a hand'
10258
10300
  };
10259
10301
  const ru_ns1 = {
10302
+ activationLoading: 'Проверяем товар…',
10303
+ activationLoadingSubtitle: 'Магазин подтверждает примерку для этого товара.',
10304
+ activationTemporarilyUnavailable: 'Примерка в этом магазине сейчас приостановлена. Загляните позже.',
10305
+ activationUnavailableTitle: 'Примерка недоступна',
10306
+ externalPhotoRequired: 'Загрузите фото заново.',
10307
+ externalNoTokens: 'У магазина закончились токены.',
10308
+ externalProductUnavailable: 'Примерка для этого товара недоступна.',
10309
+ externalServiceUnavailable: 'Примерка недоступна. Попробуйте позже.',
10310
+ externalTimeout: 'Время ожидания истекло. Попробуйте снова.',
10311
+ externalPhotoHint: 'Загрузите чёткое фото себя. Изображение товара для примерки определяется настройками магазина.',
10260
10312
  back: 'Назад',
10261
10313
  badge: 'Примерить',
10262
10314
  confirmBadge: 'Почти готово',
@@ -10313,7 +10365,7 @@ const ru_ns1 = {
10313
10365
  headwearIntroTitle: 'Загрузите фото головы и плеч',
10314
10366
  introCta: 'Продолжить',
10315
10367
  introBadge: 'Совет',
10316
- myFits: 'My fits',
10368
+ myFits: 'Мои образы',
10317
10369
  lowerBodyIntroBadCoveredWaist: 'Свободная одежда скрывает линию талии',
10318
10370
  lowerBodyIntroBadSitting: 'В сидячей позе ноги согнуты',
10319
10371
  lowerBodyIntroGoodFullBody: 'Фото в полный рост',
@@ -10338,8 +10390,8 @@ const ru_ns1 = {
10338
10390
  resultReset: 'Сохранить',
10339
10391
  resultShare: 'Поделиться',
10340
10392
  resultTitle: 'Вот как это выглядит на вас',
10341
- resultTryAnother: 'Add To Cart',
10342
- resultTryAnotherWithPrice: 'Add To Cart – {{price}}',
10393
+ resultTryAnother: 'Добавить в корзину',
10394
+ resultTryAnotherWithPrice: 'Добавить в корзину – {{price}}',
10343
10395
  share: 'Поделиться',
10344
10396
  startHistory: 'Смотреть прошлые результаты',
10345
10397
  startTitle: 'Хотите увидеть это на себе?',
@@ -10392,6 +10444,7 @@ const PROPERTY_BLOCK_RE = /@property\s+(--[^{\s]+)\s*\{([^}]*)\}/g;
10392
10444
  const INITIAL_VALUE_RE = /initial-value:([^;}]+)/i;
10393
10445
  const NON_INHERITING_RE = /inherits:\s*false(?:\s*;|\s*$)/i;
10394
10446
  const ROOT_TO_HOST_SELECTOR_RE = /:root\s*,\s*:host|:root/g;
10447
+ const SHADOW_HOST_VISIBILITY_CSS = ':host{display:block!important;visibility:visible!important}';
10395
10448
  function createShadowRootCssText(cssText) {
10396
10449
  const cssForHost = cssText.replace(ROOT_TO_HOST_SELECTOR_RE, ':host');
10397
10450
  const propertyBlocks = Array.from(cssText.matchAll(PROPERTY_BLOCK_RE));
@@ -18193,7 +18246,7 @@ function ResultImagePanel({ className, onOpenComparison, productLabel, productUr
18193
18246
  });
18194
18247
  }
18195
18248
  const COMPARISON_DIALOG_BACKGROUND_CLASS_NAME = 'bg-[#212124E5]';
18196
- function ComparisonDialog({ onAddToCart, onClose, onDeleteResult, onSaveResult, onShareResult, portalContainer, productName, productPrice, productUrl, result }) {
18249
+ function ComparisonDialog({ addToCartError, addToCartPending = false, onAddToCart, onClose, onDeleteResult, onSaveResult, onShareResult, portalContainer, productName, productPrice, productUrl, result }) {
18197
18250
  const { t } = useTranslation();
18198
18251
  const productLabel = productName ?? t('resultProductLink');
18199
18252
  const normalizedProductPrice = normalizeProductPrice(productPrice);
@@ -18307,59 +18360,67 @@ function ComparisonDialog({ onAddToCart, onClose, onDeleteResult, onSaveResult,
18307
18360
  ]
18308
18361
  })
18309
18362
  }),
18310
- /*#__PURE__*/ jsx_runtime_jsx("div", {
18311
- className: "flex h-23 shrink-0 items-center justify-center px-8 pb-6 pt-3",
18312
- children: /*#__PURE__*/ jsxs("div", {
18313
- className: "flex w-full max-w-104 items-start gap-2",
18314
- children: [
18315
- /*#__PURE__*/ jsxs(button_Button, {
18316
- className: "min-w-0 flex-1",
18317
- size: "lg",
18318
- type: "button",
18319
- variant: "contrast",
18320
- onClick: onAddToCart,
18321
- children: [
18322
- /*#__PURE__*/ jsx_runtime_jsx(Cart, {
18363
+ /*#__PURE__*/ jsxs("div", {
18364
+ className: "flex shrink-0 flex-col items-center gap-1 px-8 pb-6 pt-3",
18365
+ children: [
18366
+ addToCartError && /*#__PURE__*/ jsx_runtime_jsx("p", {
18367
+ className: "text-xs text-text-sub",
18368
+ role: "alert",
18369
+ children: addToCartError
18370
+ }),
18371
+ /*#__PURE__*/ jsxs("div", {
18372
+ className: "flex h-23 w-full max-w-104 items-start gap-2",
18373
+ children: [
18374
+ onAddToCart && /*#__PURE__*/ jsxs(button_Button, {
18375
+ className: "min-w-0 flex-1",
18376
+ disabled: addToCartPending,
18377
+ size: "lg",
18378
+ type: "button",
18379
+ variant: "contrast",
18380
+ onClick: onAddToCart,
18381
+ children: [
18382
+ /*#__PURE__*/ jsx_runtime_jsx(Cart, {
18383
+ "data-icon": "inline-start"
18384
+ }),
18385
+ /*#__PURE__*/ jsx_runtime_jsx("span", {
18386
+ className: "truncate",
18387
+ children: addToCartLabel
18388
+ })
18389
+ ]
18390
+ }),
18391
+ /*#__PURE__*/ jsx_runtime_jsx(button_Button, {
18392
+ "aria-label": t('share'),
18393
+ size: "icon-lg",
18394
+ type: "button",
18395
+ variant: "outline",
18396
+ onClick: onShareResult,
18397
+ children: /*#__PURE__*/ jsx_runtime_jsx(Share, {
18398
+ "data-icon": "inline-start"
18399
+ })
18400
+ }),
18401
+ /*#__PURE__*/ jsx_runtime_jsx(button_Button, {
18402
+ "aria-label": t('download'),
18403
+ size: "icon-lg",
18404
+ type: "button",
18405
+ variant: "outline",
18406
+ onClick: onSaveResult,
18407
+ children: /*#__PURE__*/ jsx_runtime_jsx(Download, {
18408
+ "data-icon": "inline-start"
18409
+ })
18410
+ }),
18411
+ /*#__PURE__*/ jsx_runtime_jsx(button_Button, {
18412
+ "aria-label": t('reset'),
18413
+ size: "icon-lg",
18414
+ type: "button",
18415
+ variant: "outline",
18416
+ onClick: handleDelete,
18417
+ children: /*#__PURE__*/ jsx_runtime_jsx(EditorTrash, {
18323
18418
  "data-icon": "inline-start"
18324
- }),
18325
- /*#__PURE__*/ jsx_runtime_jsx("span", {
18326
- className: "truncate",
18327
- children: addToCartLabel
18328
18419
  })
18329
- ]
18330
- }),
18331
- /*#__PURE__*/ jsx_runtime_jsx(button_Button, {
18332
- "aria-label": t('share'),
18333
- size: "icon-lg",
18334
- type: "button",
18335
- variant: "outline",
18336
- onClick: onShareResult,
18337
- children: /*#__PURE__*/ jsx_runtime_jsx(Share, {
18338
- "data-icon": "inline-start"
18339
- })
18340
- }),
18341
- /*#__PURE__*/ jsx_runtime_jsx(button_Button, {
18342
- "aria-label": t('download'),
18343
- size: "icon-lg",
18344
- type: "button",
18345
- variant: "outline",
18346
- onClick: onSaveResult,
18347
- children: /*#__PURE__*/ jsx_runtime_jsx(Download, {
18348
- "data-icon": "inline-start"
18349
- })
18350
- }),
18351
- /*#__PURE__*/ jsx_runtime_jsx(button_Button, {
18352
- "aria-label": t('reset'),
18353
- size: "icon-lg",
18354
- type: "button",
18355
- variant: "outline",
18356
- onClick: handleDelete,
18357
- children: /*#__PURE__*/ jsx_runtime_jsx(EditorTrash, {
18358
- "data-icon": "inline-start"
18359
18420
  })
18360
- })
18361
- ]
18362
- })
18421
+ ]
18422
+ })
18423
+ ]
18363
18424
  })
18364
18425
  ]
18365
18426
  })
@@ -18881,10 +18942,10 @@ function getAiTryOnGuidance(productType) {
18881
18942
  }
18882
18943
  function StartScreen({ view }) {
18883
18944
  const { t } = useTranslation();
18884
- const guidance = getAiTryOnGuidance(view.productType);
18945
+ const guidance = view.productType ? getAiTryOnGuidance(view.productType) : null;
18885
18946
  return /*#__PURE__*/ jsxs(ScreenLayout, {
18886
18947
  title: t('startTitle'),
18887
- subtitle: t(guidance.titleKey),
18948
+ subtitle: t(guidance?.titleKey ?? 'externalPhotoHint'),
18888
18949
  children: [
18889
18950
  /*#__PURE__*/ jsxs(TryOnImagePanel, {
18890
18951
  className: "mt-3",
@@ -18935,7 +18996,8 @@ function StartScreen({ view }) {
18935
18996
  }
18936
18997
  function IntroScreen({ view }) {
18937
18998
  const { t } = useTranslation();
18938
- const guidance = getAiTryOnGuidance(view.productType);
18999
+ const guidance = view.productType ? getAiTryOnGuidance(view.productType) : null;
19000
+ if (!guidance) return null;
18939
19001
  return /*#__PURE__*/ jsxs(ScreenLayout, {
18940
19002
  title: t(guidance.titleKey),
18941
19003
  subtitle: t(guidance.subtitleKey),
@@ -19053,6 +19115,79 @@ function ConfirmScreen({ view }) {
19053
19115
  ]
19054
19116
  });
19055
19117
  }
19118
+ function ActivationLoadingScreen() {
19119
+ const { t } = useTranslation();
19120
+ return /*#__PURE__*/ jsx_runtime_jsx(ScreenLayout, {
19121
+ title: t('activationLoading'),
19122
+ subtitle: t('activationLoadingSubtitle'),
19123
+ children: /*#__PURE__*/ jsx_runtime_jsx(TryOnImagePanel, {
19124
+ className: "mt-3",
19125
+ children: /*#__PURE__*/ jsx_runtime_jsx(LoadingTexture, {
19126
+ animated: true,
19127
+ className: "left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2"
19128
+ })
19129
+ })
19130
+ });
19131
+ }
19132
+ function ActivationUnavailableScreen({ onClose, temporary }) {
19133
+ const { t } = useTranslation();
19134
+ return /*#__PURE__*/ jsx_runtime_jsx(ScreenLayout, {
19135
+ title: t('activationUnavailableTitle'),
19136
+ subtitle: t(temporary ? 'activationTemporarilyUnavailable' : 'externalProductUnavailable'),
19137
+ children: /*#__PURE__*/ jsxs(ActionStack, {
19138
+ className: "mt-4",
19139
+ children: [
19140
+ onClose && /*#__PURE__*/ jsx_runtime_jsx(button_Button, {
19141
+ className: "w-full",
19142
+ size: "lg",
19143
+ type: "button",
19144
+ variant: "contrast",
19145
+ onClick: onClose,
19146
+ children: t('close')
19147
+ }),
19148
+ /*#__PURE__*/ jsx_runtime_jsx(TermsCopy, {
19149
+ className: "text-text-sub"
19150
+ })
19151
+ ]
19152
+ })
19153
+ });
19154
+ }
19155
+ function ActivationErrorScreen({ onClose, onRetry }) {
19156
+ const { t } = useTranslation();
19157
+ return /*#__PURE__*/ jsx_runtime_jsx(ScreenLayout, {
19158
+ title: t('errorTitle'),
19159
+ subtitle: t('externalServiceUnavailable'),
19160
+ children: /*#__PURE__*/ jsxs(ActionStack, {
19161
+ className: "mt-4",
19162
+ children: [
19163
+ onRetry && /*#__PURE__*/ jsxs(button_Button, {
19164
+ className: "w-full",
19165
+ size: "lg",
19166
+ type: "button",
19167
+ variant: "contrast",
19168
+ onClick: onRetry,
19169
+ children: [
19170
+ /*#__PURE__*/ jsx_runtime_jsx(Vizbl, {
19171
+ "data-icon": "inline-start"
19172
+ }),
19173
+ t('errorRetry')
19174
+ ]
19175
+ }),
19176
+ onClose && /*#__PURE__*/ jsx_runtime_jsx(button_Button, {
19177
+ className: "w-full",
19178
+ size: "lg",
19179
+ type: "button",
19180
+ variant: "outline",
19181
+ onClick: onClose,
19182
+ children: t('close')
19183
+ }),
19184
+ /*#__PURE__*/ jsx_runtime_jsx(TermsCopy, {
19185
+ className: "text-text-sub"
19186
+ })
19187
+ ]
19188
+ })
19189
+ });
19190
+ }
19056
19191
  function ProcessingScreen({ view }) {
19057
19192
  const { t } = useTranslation();
19058
19193
  return /*#__PURE__*/ jsxs(ScreenLayout, {
@@ -19101,14 +19236,25 @@ function ProcessingScreen({ view }) {
19101
19236
  ]
19102
19237
  });
19103
19238
  }
19104
- function ErrorScreen({ view }) {
19239
+ function ErrorScreen({ view, onClose }) {
19105
19240
  const { t } = useTranslation();
19106
19241
  const isFileTooLarge = 'upload-validation' === view.error.kind && 'file-too-large' === view.error.code;
19242
+ const externalErrors = {
19243
+ tokens: 'externalNoTokens',
19244
+ product: 'externalProductUnavailable',
19245
+ service: 'externalServiceUnavailable',
19246
+ timeout: 'externalTimeout',
19247
+ 'photo-required': 'externalPhotoRequired'
19248
+ };
19249
+ const code = 'generation' === view.error.kind ? view.error.code : void 0;
19250
+ const externalKey = code ? externalErrors[code] : void 0;
19251
+ const canRetry = !code || 'service' === code || 'timeout' === code;
19252
+ const canChangePhoto = !code || 'photo-required' === code;
19107
19253
  return /*#__PURE__*/ jsxs(ScreenLayout, {
19108
19254
  title: t(isFileTooLarge ? 'errorFileTooLarge' : 'errorTitle'),
19109
- subtitle: isFileTooLarge ? void 0 : t('errorSubtitle'),
19255
+ subtitle: isFileTooLarge ? void 0 : t(externalKey ?? 'errorSubtitle'),
19110
19256
  children: [
19111
- !isFileTooLarge && /*#__PURE__*/ jsxs(TryOnImagePanel, {
19257
+ !isFileTooLarge && !code && /*#__PURE__*/ jsxs(TryOnImagePanel, {
19112
19258
  className: "mt-3",
19113
19259
  children: [
19114
19260
  /*#__PURE__*/ jsx_runtime_jsx(BlurredPhotoBackdrop, {
@@ -19156,7 +19302,7 @@ function ErrorScreen({ view }) {
19156
19302
  /*#__PURE__*/ jsxs(ActionStack, {
19157
19303
  className: "mt-4",
19158
19304
  children: [
19159
- /*#__PURE__*/ jsxs(button_Button, {
19305
+ canRetry && /*#__PURE__*/ jsxs(button_Button, {
19160
19306
  className: "w-full",
19161
19307
  size: "lg",
19162
19308
  type: "button",
@@ -19169,7 +19315,7 @@ function ErrorScreen({ view }) {
19169
19315
  t('errorRetry')
19170
19316
  ]
19171
19317
  }),
19172
- /*#__PURE__*/ jsxs(button_Button, {
19318
+ canChangePhoto && /*#__PURE__*/ jsxs(button_Button, {
19173
19319
  className: "w-full",
19174
19320
  size: "lg",
19175
19321
  type: "button",
@@ -19182,6 +19328,14 @@ function ErrorScreen({ view }) {
19182
19328
  t('errorDifferentPhoto')
19183
19329
  ]
19184
19330
  }),
19331
+ code && onClose && /*#__PURE__*/ jsx_runtime_jsx(button_Button, {
19332
+ className: "w-full",
19333
+ size: "lg",
19334
+ type: "button",
19335
+ variant: "outline",
19336
+ onClick: onClose,
19337
+ children: t('close')
19338
+ }),
19185
19339
  /*#__PURE__*/ jsx_runtime_jsx(TermsCopy, {
19186
19340
  className: "text-text-sub"
19187
19341
  })
@@ -19190,8 +19344,9 @@ function ErrorScreen({ view }) {
19190
19344
  ]
19191
19345
  });
19192
19346
  }
19193
- function ResultScreen({ view }) {
19347
+ function ResultScreen({ view, showAddToCart = true, onAddToCart, addToCartPending = false, addToCartError }) {
19194
19348
  const { t } = useTranslation();
19349
+ const addToCart = onAddToCart ?? view.addToCart;
19195
19350
  const normalizedProductPrice = try_on_screens_normalizeProductPrice(view.productPrice);
19196
19351
  const addToCartLabel = normalizedProductPrice ? t('resultTryAnotherWithPrice', {
19197
19352
  price: normalizedProductPrice
@@ -19202,7 +19357,8 @@ function ResultScreen({ view }) {
19202
19357
  className: "flex flex-col gap-4 px-6 pb-2 pt-4 sm:px-8",
19203
19358
  children: view.resultHistory.map((historyItem, index)=>/*#__PURE__*/ jsx_runtime_jsx(ResultFitCard, {
19204
19359
  compact: index > 0,
19205
- onAddToCart: view.addToCart,
19360
+ onAddToCart: showAddToCart ? addToCart : void 0,
19361
+ addToCartPending: addToCartPending,
19206
19362
  productName: historyItem.productName ?? view.productName,
19207
19363
  productPrice: historyItem.productPrice,
19208
19364
  productUrl: historyItem.productUrl ?? view.productUrl,
@@ -19230,12 +19386,13 @@ function ResultScreen({ view }) {
19230
19386
  /*#__PURE__*/ jsxs(ActionStack, {
19231
19387
  className: "mt-4",
19232
19388
  children: [
19233
- /*#__PURE__*/ jsxs(button_Button, {
19389
+ showAddToCart && /*#__PURE__*/ jsxs(button_Button, {
19234
19390
  className: "w-full",
19391
+ disabled: addToCartPending,
19235
19392
  size: "lg",
19236
19393
  type: "button",
19237
19394
  variant: "contrast",
19238
- onClick: view.addToCart,
19395
+ onClick: addToCart,
19239
19396
  children: [
19240
19397
  /*#__PURE__*/ jsx_runtime_jsx(Cart, {
19241
19398
  "data-icon": "inline-start"
@@ -19243,6 +19400,11 @@ function ResultScreen({ view }) {
19243
19400
  addToCartLabel
19244
19401
  ]
19245
19402
  }),
19403
+ addToCartError && /*#__PURE__*/ jsx_runtime_jsx("p", {
19404
+ className: "text-center text-xs text-text-sub",
19405
+ role: "alert",
19406
+ children: addToCartError
19407
+ }),
19246
19408
  /*#__PURE__*/ jsxs("div", {
19247
19409
  className: "grid grid-cols-3 gap-2",
19248
19410
  children: [
@@ -19286,7 +19448,7 @@ function ResultScreen({ view }) {
19286
19448
  ]
19287
19449
  });
19288
19450
  }
19289
- function ResultFitCard({ compact = false, onAddToCart, onDeleteResult, onOpenComparison, onSaveResult, onShareResult, productName, productPrice, productUrl, result }) {
19451
+ function ResultFitCard({ compact = false, onAddToCart, addToCartPending = false, onDeleteResult, onOpenComparison, onSaveResult, onShareResult, productName, productPrice, productUrl, result }) {
19290
19452
  const { t } = useTranslation();
19291
19453
  const productLabel = productName ?? t('resultProductLink');
19292
19454
  const normalizedProductPrice = try_on_screens_normalizeProductPrice(productPrice);
@@ -19306,8 +19468,9 @@ function ResultFitCard({ compact = false, onAddToCart, onDeleteResult, onOpenCom
19306
19468
  /*#__PURE__*/ jsxs("div", {
19307
19469
  className: "flex gap-2",
19308
19470
  children: [
19309
- /*#__PURE__*/ jsxs(button_Button, {
19471
+ onAddToCart && /*#__PURE__*/ jsxs(button_Button, {
19310
19472
  className: "min-w-0 flex-1",
19473
+ disabled: addToCartPending,
19311
19474
  size: "default",
19312
19475
  type: "button",
19313
19476
  variant: "contrast",
@@ -19429,6 +19592,55 @@ function try_on_screens_normalizeProductPrice(value) {
19429
19592
  const normalized = value?.trim();
19430
19593
  return normalized || void 0;
19431
19594
  }
19595
+ const IMAGE_SNIFF_BYTES = 16;
19596
+ const IMAGE_BRANDS = new Set([
19597
+ 'avif',
19598
+ 'avis',
19599
+ 'heic',
19600
+ 'heix',
19601
+ 'heim',
19602
+ 'heis',
19603
+ 'hevc',
19604
+ 'hevx',
19605
+ 'mif1',
19606
+ 'msf1'
19607
+ ]);
19608
+ async function readGeneratedImage(response) {
19609
+ const blob = toBlob(response);
19610
+ if (!blob || blob.size <= 0) return {
19611
+ blob: null,
19612
+ errorText: null
19613
+ };
19614
+ const header = new Uint8Array(await blob.slice(0, IMAGE_SNIFF_BYTES).arrayBuffer());
19615
+ if (!isImageHeader(header)) return {
19616
+ blob: null,
19617
+ errorText: (await blob.text().catch(()=>'')).slice(0, 2000)
19618
+ };
19619
+ return {
19620
+ blob,
19621
+ errorText: null
19622
+ };
19623
+ }
19624
+ function toBlob(response) {
19625
+ if (response instanceof Blob) return response;
19626
+ if (response instanceof ArrayBuffer) return new Blob([
19627
+ response
19628
+ ]);
19629
+ if (ArrayBuffer.isView(response)) return new Blob([
19630
+ new Uint8Array(response.buffer, response.byteOffset, response.byteLength)
19631
+ ]);
19632
+ return null;
19633
+ }
19634
+ function isImageHeader(header) {
19635
+ const startsWith = (...bytes)=>bytes.every((byte, index)=>header[index] === byte);
19636
+ const at = (offset, ...bytes)=>bytes.every((byte, index)=>header[offset + index] === byte);
19637
+ return startsWith(0xFF, 0xD8, 0xFF) || startsWith(0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A) || startsWith(0x47, 0x49, 0x46, 0x38) || startsWith(0x42, 0x4D) || startsWith(0x52, 0x49, 0x46, 0x46) && at(8, 0x57, 0x45, 0x42, 0x50) || at(4, 0x66, 0x74, 0x79, 0x70) && IMAGE_BRANDS.has(brand(header));
19638
+ }
19639
+ function brand(header) {
19640
+ let value = '';
19641
+ for(let index = 8; index < 12; index++)value += String.fromCharCode(header[index] ?? 0);
19642
+ return value;
19643
+ }
19432
19644
  const REQUIRED_UPLOAD_COUNT = 2;
19433
19645
  function createNanoBananaAiTryOnProcessor({ createObjectUrl = defaultCreateObjectUrl, fetch: fetchImpl, productImageSrc, productType, tryOnClient, apiKey }) {
19434
19646
  assertProductType(productType);
@@ -19447,8 +19659,12 @@ function createNanoBananaAiTryOnProcessor({ createObjectUrl = defaultCreateObjec
19447
19659
  const [personUpload, productUpload] = resolveUploads(await client.requestUploadUrls(REQUIRED_UPLOAD_COUNT), REQUIRED_UPLOAD_COUNT);
19448
19660
  context?.reportProgress(44);
19449
19661
  await Promise.all([
19450
- uploadImageToPresignedUrl(personUpload.uploadUrl, personImageBlob, fetchImpl),
19451
- uploadImageToPresignedUrl(productUpload.uploadUrl, productImageBlob, fetchImpl)
19662
+ uploadImageToPresignedUrl(personUpload.uploadUrl, personImageBlob, {
19663
+ fetch: fetchImpl
19664
+ }),
19665
+ uploadImageToPresignedUrl(productUpload.uploadUrl, productImageBlob, {
19666
+ fetch: fetchImpl
19667
+ })
19452
19668
  ]);
19453
19669
  context?.reportProgress(62);
19454
19670
  const stopGenerationProgress = startGenerationProgress(context?.reportProgress);
@@ -19467,8 +19683,8 @@ function createNanoBananaAiTryOnProcessor({ createObjectUrl = defaultCreateObjec
19467
19683
  stopGenerationProgress();
19468
19684
  }
19469
19685
  context?.reportProgress(96);
19470
- const generatedImageBlob = await resolveGeneratedImageBlob(response);
19471
- if (generatedImageBlob.size <= 0) throw new Error('AI try-on generation returned an empty image');
19686
+ const { blob: generatedImageBlob } = await readGeneratedImage(response);
19687
+ if (!generatedImageBlob) throw new Error('AI try-on generation did not return an image');
19472
19688
  context?.reportProgress(98);
19473
19689
  const generatedImageSrc = createObjectUrl(generatedImageBlob);
19474
19690
  if (!generatedImageSrc) throw new Error('AI try-on result image URL cannot be created');
@@ -19510,14 +19726,28 @@ function resolveFetch(fetchImpl) {
19510
19726
  if ('function' == typeof fetch) return fetch;
19511
19727
  throw new Error('Fetch API is not available for AI try-on image upload');
19512
19728
  }
19513
- async function uploadImageToPresignedUrl(uploadUrl, imageBlob, fetchImpl) {
19514
- const response = await resolveFetch(fetchImpl)(uploadUrl, {
19515
- body: imageBlob,
19516
- method: 'PUT'
19729
+ async function uploadImageToPresignedUrl(uploadUrl, imageBlob, options = {}) {
19730
+ const { fetch: fetchImpl, signal, timeoutMs } = options;
19731
+ const controller = new AbortController();
19732
+ const abort = ()=>controller.abort(signal?.reason);
19733
+ const timer = void 0 === timeoutMs ? void 0 : setTimeout(()=>controller.abort(new DOMException('AI try-on image upload timed out', 'TimeoutError')), timeoutMs);
19734
+ if (signal?.aborted) abort();
19735
+ else signal?.addEventListener('abort', abort, {
19736
+ once: true
19517
19737
  });
19518
- if (!response.ok) {
19519
- const details = await response.text().catch(()=>'');
19520
- throw new Error(`Nano Banana image upload failed with status ${response.status}${details ? `: ${details}` : ''}`);
19738
+ try {
19739
+ const response = await resolveFetch(fetchImpl)(uploadUrl, {
19740
+ body: imageBlob,
19741
+ method: 'PUT',
19742
+ signal: controller.signal
19743
+ });
19744
+ if (!response.ok) {
19745
+ const details = await response.text().catch(()=>'');
19746
+ throw new Error(`Nano Banana image upload failed with status ${response.status}${details ? `: ${details}` : ''}`);
19747
+ }
19748
+ } finally{
19749
+ clearTimeout(timer);
19750
+ signal?.removeEventListener('abort', abort);
19521
19751
  }
19522
19752
  }
19523
19753
  async function resolveProductImageBlob(productImageSrc, fetchImpl) {
@@ -19539,24 +19769,6 @@ async function resolveBlobFromUrl(src, fetchImpl, message) {
19539
19769
  }
19540
19770
  return response.blob();
19541
19771
  }
19542
- async function resolveGeneratedImageBlob(response) {
19543
- if (response instanceof Blob) return response.type ? response : new Blob([
19544
- response
19545
- ], {
19546
- type: 'image/jpeg'
19547
- });
19548
- if (response instanceof ArrayBuffer) return new Blob([
19549
- response
19550
- ], {
19551
- type: 'image/jpeg'
19552
- });
19553
- if ('string' == typeof response) return new Blob([
19554
- response
19555
- ], {
19556
- type: 'image/jpeg'
19557
- });
19558
- throw new Error('AI try-on generation did not return an image');
19559
- }
19560
19772
  function startGenerationProgress(reportProgress) {
19561
19773
  if (!reportProgress) return ()=>{};
19562
19774
  reportProgress(70);
@@ -19965,7 +20177,7 @@ class AiTryOnSession {
19965
20177
  };
19966
20178
  }
19967
20179
  choosePhoto() {
19968
- if (this.tipStore.consumeFirstView(this.state.productType)) {
20180
+ if (this.state.productType && this.tipStore.consumeFirstView(this.state.productType)) {
19969
20181
  this.state = {
19970
20182
  ...this.state,
19971
20183
  stage: 'intro'
@@ -19977,14 +20189,14 @@ class AiTryOnSession {
19977
20189
  };
19978
20190
  }
19979
20191
  continueIntro() {
19980
- this.tipStore.markSeen(this.state.productType);
20192
+ if (this.state.productType) this.tipStore.markSeen(this.state.productType);
19981
20193
  return {
19982
20194
  type: 'request-file-input'
19983
20195
  };
19984
20196
  }
19985
20197
  requestClose() {
19986
20198
  if ('intro' === this.state.stage) {
19987
- this.tipStore.markSeen(this.state.productType);
20199
+ if (this.state.productType) this.tipStore.markSeen(this.state.productType);
19988
20200
  this.goBack();
19989
20201
  return null;
19990
20202
  }
@@ -20128,12 +20340,15 @@ class AiTryOnSession {
20128
20340
  };
20129
20341
  return historyItem;
20130
20342
  }
20131
- failGeneration(message) {
20343
+ failGeneration(message, code) {
20132
20344
  this.state = {
20133
20345
  ...this.state,
20134
20346
  error: {
20135
20347
  kind: 'generation',
20136
- message
20348
+ message,
20349
+ ...code ? {
20350
+ code
20351
+ } : {}
20137
20352
  },
20138
20353
  generationProduct: null,
20139
20354
  stage: 'error'
@@ -20304,6 +20519,13 @@ function clampProgress(progress) {
20304
20519
  function createPhotoId(file) {
20305
20520
  return `${file.name}-${file.size}-${file.lastModified}`;
20306
20521
  }
20522
+ class AiTryOnGenerationError extends Error {
20523
+ code;
20524
+ constructor(code){
20525
+ super(code), this.code = code;
20526
+ this.name = 'AiTryOnGenerationError';
20527
+ }
20528
+ }
20307
20529
  function createAiTryOnInteractiveView(session, { addToCart, choosePhoto, deleteResult, generate, runSessionCommand, saveResult, shareResult, sync }) {
20308
20530
  const currentView = session.view;
20309
20531
  switch(currentView.stage){
@@ -20440,6 +20662,13 @@ function collectPhotoObjectUrl(photo) {
20440
20662
  function useAiTryOnSession({ historyStore = indexedDbAiTryOnHistoryStore, objectUrls = browserAiTryOnObjectUrls, onRequestClose, onRequestFileInput, processPhoto, productImageSrc, productName, productPrice, productType, productUrl, tryOnClient, resultActions = browserAiTryOnResultActions, tipStore = browserAiTryOnTipStore }) {
20441
20663
  const [, rerender] = (0, __rspack_external_react.useReducer)((value)=>value + 1, 0);
20442
20664
  const trackedObjectUrls = (0, __rspack_external_react.useRef)(new Set());
20665
+ const mounted = (0, __rspack_external_react.useRef)(true);
20666
+ (0, __rspack_external_react.useEffect)(()=>{
20667
+ mounted.current = true;
20668
+ return ()=>{
20669
+ mounted.current = false;
20670
+ };
20671
+ }, []);
20443
20672
  const createTrackedObjectUrl = (0, __rspack_external_react.useCallback)((blob)=>{
20444
20673
  const src = objectUrls.createObjectUrl(blob);
20445
20674
  if (src) trackedObjectUrls.current.add(src);
@@ -20465,12 +20694,16 @@ function useAiTryOnSession({ historyStore = indexedDbAiTryOnHistoryStore, object
20465
20694
  productType,
20466
20695
  productUrl
20467
20696
  });
20468
- const processSelectedPhoto = (0, __rspack_external_react.useMemo)(()=>processPhoto ?? createNanoBananaAiTryOnProcessor({
20697
+ const processSelectedPhoto = (0, __rspack_external_react.useMemo)(()=>{
20698
+ if (processPhoto) return processPhoto;
20699
+ assertProductType(productType);
20700
+ return createNanoBananaAiTryOnProcessor({
20469
20701
  createObjectUrl: createTrackedObjectUrl,
20470
20702
  productImageSrc,
20471
20703
  productType,
20472
20704
  tryOnClient
20473
- }), [
20705
+ });
20706
+ }, [
20474
20707
  createTrackedObjectUrl,
20475
20708
  processPhoto,
20476
20709
  productImageSrc,
@@ -20531,21 +20764,29 @@ function useAiTryOnSession({ historyStore = indexedDbAiTryOnHistoryStore, object
20531
20764
  try {
20532
20765
  const result = await processSelectedPhoto(photo, {
20533
20766
  reportProgress: (progress)=>{
20767
+ if (!mounted.current) return;
20534
20768
  session.reportGenerationProgress(progress);
20535
20769
  sync();
20536
20770
  }
20537
20771
  });
20538
20772
  await waitForRemainingProcessingDuration(processingStartedAt);
20773
+ if (!mounted.current) {
20774
+ persistHistoryItem(session.completeGeneration(result));
20775
+ if (result.after.src.startsWith('blob:')) objectUrls.revokeObjectUrl(result.after.src);
20776
+ return;
20777
+ }
20539
20778
  const historyItem = session.completeGeneration(result);
20540
20779
  trackResultObjectUrls(historyItem, trackedObjectUrls.current);
20541
20780
  persistHistoryItem(historyItem);
20542
20781
  sync();
20543
20782
  } catch (error) {
20544
20783
  await waitForRemainingProcessingDuration(processingStartedAt);
20545
- session.failGeneration(error instanceof Error ? error.message : void 0);
20784
+ if (!mounted.current) return;
20785
+ session.failGeneration(error instanceof Error ? error.message : void 0, error instanceof AiTryOnGenerationError ? error.code : void 0);
20546
20786
  sync();
20547
20787
  }
20548
20788
  }, [
20789
+ objectUrls,
20549
20790
  persistHistoryItem,
20550
20791
  processSelectedPhoto,
20551
20792
  session,
@@ -20653,8 +20894,11 @@ function useAiTryOnSession({ historyStore = indexedDbAiTryOnHistoryStore, object
20653
20894
  function waitForRemainingProcessingDuration(startedAt) {
20654
20895
  return waitForProcessingDuration(getRemainingProcessingDuration(startedAt));
20655
20896
  }
20656
- function AiTryOnRoot({ dialogPortalContainer, language, onClose, processPhoto, productImageSrc, productName, productPrice, productType, productUrl, tryOnClient: providedTryOnClient, apiKey, showDialogCloseButton = false }) {
20657
- assertProductType(productType);
20897
+ const READY_ACTIVATION = {
20898
+ status: 'ready'
20899
+ };
20900
+ function AiTryOnRoot({ dialogPortalContainer, language, onClose, onAddToCart, processPhoto, productImageSrc, productName, productPrice, productType, productUrl, tryOnClient: providedTryOnClient, apiKey, showDialogCloseButton = false, showAddToCart = true, activation = READY_ACTIVATION, onRetryActivation }) {
20901
+ if (!processPhoto) assertProductType(productType);
20658
20902
  const { i18n, t } = useTranslation();
20659
20903
  const fileInputRef = (0, __rspack_external_react.useRef)(null);
20660
20904
  const normalizedApiKey = normalizeApiKey(apiKey);
@@ -20682,7 +20926,22 @@ function AiTryOnRoot({ dialogPortalContainer, language, onClose, processPhoto, p
20682
20926
  tryOnClient
20683
20927
  });
20684
20928
  const stage = session.view.stage;
20929
+ const closeDialog = onClose ?? session.requestClose;
20685
20930
  const isMyFitsActive = 'result' === session.view.stage && session.view.myFitsOpen;
20931
+ const [cartPending, setCartPending] = (0, __rspack_external_react.useState)(false);
20932
+ const [cartError, setCartError] = (0, __rspack_external_react.useState)(void 0);
20933
+ const addToCart = (0, __rspack_external_react.useCallback)(()=>{
20934
+ if ('result' !== session.view.stage) return;
20935
+ session.view.addToCart();
20936
+ if (!onAddToCart) return;
20937
+ setCartPending(true);
20938
+ setCartError(void 0);
20939
+ Promise.resolve(onAddToCart()).catch((error)=>setCartError(error instanceof Error && error.message ? error.message : t('externalServiceUnavailable'))).finally(()=>setCartPending(false));
20940
+ }, [
20941
+ onAddToCart,
20942
+ session.view,
20943
+ t
20944
+ ]);
20686
20945
  function handleFileChange(event) {
20687
20946
  const file = event.currentTarget.files?.[0];
20688
20947
  if (file) session.selectFile(file);
@@ -20711,14 +20970,14 @@ function AiTryOnRoot({ dialogPortalContainer, language, onClose, processPhoto, p
20711
20970
  "data-ai-try-on-stage": stage,
20712
20971
  children: [
20713
20972
  /*#__PURE__*/ jsx_runtime_jsx(TryOnHeader, {
20714
- badge: 'history' === stage ? null : t(getBadgeKey(stage)),
20973
+ badge: 'history' === stage || 'ready' !== activation.status ? null : t(getBadgeKey(stage)),
20715
20974
  badgeTone: getBadgeTone(stage),
20716
20975
  closeLabel: t('close'),
20717
- leftSlot: leftSlot,
20718
- middleSlot: middleSlot,
20976
+ leftSlot: 'ready' === activation.status ? leftSlot : null,
20977
+ middleSlot: 'ready' === activation.status ? middleSlot : null,
20719
20978
  preferMiddleSlotFirst: 'result' === stage,
20720
20979
  showCloseButton: showDialogCloseButton,
20721
- onClose: session.requestClose
20980
+ onClose: 'ready' === activation.status ? session.requestClose : closeDialog
20722
20981
  }),
20723
20982
  /*#__PURE__*/ jsx_runtime_jsx("input", {
20724
20983
  ref: fileInputRef,
@@ -20729,36 +20988,56 @@ function AiTryOnRoot({ dialogPortalContainer, language, onClose, processPhoto, p
20729
20988
  }),
20730
20989
  /*#__PURE__*/ jsxs(TryOnScreenTransition, {
20731
20990
  children: [
20732
- 'start' === session.view.stage ? /*#__PURE__*/ jsx_runtime_jsx(StartScreen, {
20733
- view: session.view
20734
- }) : null,
20735
- 'intro' === session.view.stage ? /*#__PURE__*/ jsx_runtime_jsx(IntroScreen, {
20736
- view: session.view
20737
- }) : null,
20738
- 'history' === session.view.stage ? /*#__PURE__*/ jsx_runtime_jsx(HistoryScreen, {
20739
- view: session.view
20991
+ 'loading' === activation.status ? /*#__PURE__*/ jsx_runtime_jsx(ActivationLoadingScreen, {}) : null,
20992
+ 'unavailable' === activation.status ? /*#__PURE__*/ jsx_runtime_jsx(ActivationUnavailableScreen, {
20993
+ temporary: activation.temporary,
20994
+ onClose: closeDialog
20740
20995
  }) : null,
20741
- 'confirm' === session.view.stage ? /*#__PURE__*/ jsx_runtime_jsx(ConfirmScreen, {
20742
- view: session.view
20996
+ 'error' === activation.status ? /*#__PURE__*/ jsx_runtime_jsx(ActivationErrorScreen, {
20997
+ onClose: closeDialog,
20998
+ onRetry: onRetryActivation
20743
20999
  }) : null,
20744
- 'processing' === session.view.stage ? /*#__PURE__*/ jsx_runtime_jsx(ProcessingScreen, {
20745
- view: session.view
20746
- }) : null,
20747
- 'error' === session.view.stage ? /*#__PURE__*/ jsx_runtime_jsx(ErrorScreen, {
20748
- view: session.view
20749
- }) : null,
20750
- 'result' === session.view.stage ? /*#__PURE__*/ jsx_runtime_jsx(ResultScreen, {
20751
- view: session.view
20752
- }) : null
21000
+ 'ready' !== activation.status ? null : /*#__PURE__*/ jsxs(Fragment, {
21001
+ children: [
21002
+ 'start' === session.view.stage ? /*#__PURE__*/ jsx_runtime_jsx(StartScreen, {
21003
+ view: session.view
21004
+ }) : null,
21005
+ 'intro' === session.view.stage ? /*#__PURE__*/ jsx_runtime_jsx(IntroScreen, {
21006
+ view: session.view
21007
+ }) : null,
21008
+ 'history' === session.view.stage ? /*#__PURE__*/ jsx_runtime_jsx(HistoryScreen, {
21009
+ view: session.view
21010
+ }) : null,
21011
+ 'confirm' === session.view.stage ? /*#__PURE__*/ jsx_runtime_jsx(ConfirmScreen, {
21012
+ view: session.view
21013
+ }) : null,
21014
+ 'processing' === session.view.stage ? /*#__PURE__*/ jsx_runtime_jsx(ProcessingScreen, {
21015
+ view: session.view
21016
+ }) : null,
21017
+ 'error' === session.view.stage ? /*#__PURE__*/ jsx_runtime_jsx(ErrorScreen, {
21018
+ view: session.view,
21019
+ onClose: session.requestClose
21020
+ }) : null,
21021
+ 'result' === session.view.stage ? /*#__PURE__*/ jsx_runtime_jsx(ResultScreen, {
21022
+ view: session.view,
21023
+ showAddToCart: showAddToCart,
21024
+ onAddToCart: onAddToCart ? addToCart : void 0,
21025
+ addToCartPending: cartPending,
21026
+ addToCartError: cartError
21027
+ }) : null
21028
+ ]
21029
+ })
20753
21030
  ]
20754
- }, stage),
21031
+ }, 'ready' === activation.status ? stage : `activation-${activation.status}`),
20755
21032
  session.comparison.open && /*#__PURE__*/ jsx_runtime_jsx(ComparisonDialog, {
20756
21033
  productName: session.comparison.productName,
20757
21034
  productPrice: session.comparison.productPrice,
20758
21035
  productUrl: session.comparison.productUrl,
20759
21036
  portalContainer: dialogPortalContainer ?? void 0,
20760
21037
  result: session.comparison.result,
20761
- onAddToCart: session.comparison.addToCart,
21038
+ addToCartPending: cartPending,
21039
+ addToCartError: cartError,
21040
+ onAddToCart: showAddToCart ? onAddToCart ? addToCart : session.comparison.addToCart : void 0,
20762
21041
  onClose: session.comparison.close,
20763
21042
  onDeleteResult: session.comparison.deleteResult,
20764
21043
  onSaveResult: session.comparison.saveResult,
@@ -20798,8 +21077,246 @@ function getBadgeTone(stage) {
20798
21077
  configureNodeApiClient({
20799
21078
  baseURL: "https://api.neurodyn.ai/"
20800
21079
  });
20801
- function AiTryOnDialog({ defaultOpen = false, language, onOpenChange, open, productImageSrc, productName, productPrice, productType, productUrl, apiKey, ...props }) {
20802
- assertProductType(productType);
21080
+ const SHOPIFY_REQUEST_TIMEOUT_MS = 30000;
21081
+ const SHOPIFY_UPLOAD_TIMEOUT_MS = 300000;
21082
+ const SHOPPER_UPLOAD_COUNT = 1;
21083
+ const MAX_ERROR_BODY = 2000;
21084
+ class ShopifyTryOnClient {
21085
+ createObjectUrl;
21086
+ operations;
21087
+ signal;
21088
+ session = null;
21089
+ constructor({ createObjectUrl = shopify_try_on_client_defaultCreateObjectUrl, operations, signal }){
21090
+ this.createObjectUrl = createObjectUrl;
21091
+ this.operations = operations;
21092
+ this.signal = signal;
21093
+ }
21094
+ async activate(target) {
21095
+ const response = await this.run(()=>this.operations.activate(target, this.baseConfig()));
21096
+ const { activation, session } = normalizeShopifyActivation(response);
21097
+ if (session) this.session = createWidgetSessionClient(session, {
21098
+ apiConfig: this.baseConfig(),
21099
+ issueWidgetToken: this.operations.issueWidgetToken
21100
+ });
21101
+ return activation;
21102
+ }
21103
+ createProcessPhoto(target) {
21104
+ return async (photo, context)=>{
21105
+ context?.reportProgress(4);
21106
+ const source = await resolveAiTryOnSourceBlob(photo);
21107
+ if (!source || source.size <= 0) throw new AiTryOnGenerationError('photo-required');
21108
+ context?.reportProgress(18);
21109
+ const [upload] = resolveUploads(await this.run(async ()=>this.operations.requestUploadUrls(SHOPPER_UPLOAD_COUNT, await this.requestConfig())), SHOPPER_UPLOAD_COUNT);
21110
+ context?.reportProgress(38);
21111
+ const uploadOptions = {
21112
+ signal: this.signal,
21113
+ timeoutMs: SHOPIFY_UPLOAD_TIMEOUT_MS
21114
+ };
21115
+ await this.run(()=>this.operations.uploadPhoto ? this.operations.uploadPhoto(upload.uploadUrl, source, uploadOptions) : uploadImageToPresignedUrl(upload.uploadUrl, source, uploadOptions));
21116
+ context?.reportProgress(58);
21117
+ const stopGenerationProgress = startGenerationProgress(context?.reportProgress);
21118
+ let response;
21119
+ try {
21120
+ response = await this.run(async ()=>this.operations.generate({
21121
+ ...target,
21122
+ personImageKey: upload.key
21123
+ }, await this.requestConfig({
21124
+ responseType: 'blob',
21125
+ timeout: 90000
21126
+ })));
21127
+ } finally{
21128
+ stopGenerationProgress();
21129
+ }
21130
+ const { blob, errorText } = await readGeneratedImage(response);
21131
+ if (!blob) throw toGenerationError(errorText);
21132
+ context?.reportProgress(98);
21133
+ const src = this.createObjectUrl(blob);
21134
+ if (!src) throw new AiTryOnGenerationError('service');
21135
+ return {
21136
+ before: photo,
21137
+ after: {
21138
+ blob,
21139
+ id: `${photo.id}-shopify-result`,
21140
+ name: photo.name ? `${photo.name} result` : 'AI try-on result',
21141
+ size: blob.size,
21142
+ src,
21143
+ type: blob.type || 'image/jpeg'
21144
+ }
21145
+ };
21146
+ };
21147
+ }
21148
+ async run(operation) {
21149
+ try {
21150
+ return await operation();
21151
+ } catch (error) {
21152
+ if (error instanceof AiTryOnGenerationError || isCanceled(error, this.signal)) throw error;
21153
+ throw isTimeout(error) ? new AiTryOnGenerationError('timeout') : toGenerationError(await readRejectionBody(error));
21154
+ }
21155
+ }
21156
+ sendWidgetEvent(event) {
21157
+ const send = this.operations.sendWidgetEvent;
21158
+ if (!send) return;
21159
+ this.requestConfig().then((config)=>send({
21160
+ event,
21161
+ timestamp: new Date().toISOString()
21162
+ }, config)).catch(()=>{});
21163
+ }
21164
+ async requestUploadUrls() {
21165
+ throw new Error('Shopify generation handles its own uploads');
21166
+ }
21167
+ async generateTryOn() {
21168
+ throw new Error('Shopify generation runs through its own processor');
21169
+ }
21170
+ baseConfig() {
21171
+ return {
21172
+ signal: this.signal,
21173
+ timeout: SHOPIFY_REQUEST_TIMEOUT_MS
21174
+ };
21175
+ }
21176
+ async requestConfig(config = {}) {
21177
+ const merged = {
21178
+ ...this.baseConfig(),
21179
+ ...config
21180
+ };
21181
+ return this.session ? this.session.getApiConfigForRequest(merged) : merged;
21182
+ }
21183
+ }
21184
+ function createShopifyTryOnClient(options) {
21185
+ return new ShopifyTryOnClient(options);
21186
+ }
21187
+ function normalizeShopifyActivation(response) {
21188
+ const body = shopify_try_on_client_isRecord(response) ? response : {};
21189
+ const zoneType = 'string' == typeof body.zoneType ? body.zoneType : void 0;
21190
+ const photoUrl = shopify_try_on_client_normalizeString(body.photoUrl) || void 0;
21191
+ const session = normalizeWidgetSession(body.widgetSession);
21192
+ const hasSource = void 0 !== photoUrl;
21193
+ const available = true === body.available && null !== session && hasSource;
21194
+ const reason = available ? void 0 : readReason(body.unavailableReason) ?? (true !== body.available || hasSource ? void 0 : 'no_product_photo');
21195
+ return {
21196
+ activation: {
21197
+ available,
21198
+ photoUrl,
21199
+ reason,
21200
+ zoneType: isProductType(zoneType) ? zoneType : void 0
21201
+ },
21202
+ session: available ? session : null
21203
+ };
21204
+ }
21205
+ const UNAVAILABLE_REASONS = new Set([
21206
+ 'not_enabled',
21207
+ 'zone_not_configured',
21208
+ 'no_product_photo',
21209
+ 'tokens_exhausted'
21210
+ ]);
21211
+ function readReason(value) {
21212
+ return 'string' == typeof value && UNAVAILABLE_REASONS.has(value) ? value : void 0;
21213
+ }
21214
+ function toGenerationError(errorText) {
21215
+ if (!errorText) return new AiTryOnGenerationError('service');
21216
+ try {
21217
+ const body = JSON.parse(errorText);
21218
+ const code = shopify_try_on_client_isRecord(body) ? body.code : void 0;
21219
+ if ('tokens' === code || 'product' === code || 'timeout' === code || 'service' === code) return new AiTryOnGenerationError(code);
21220
+ } catch {}
21221
+ return new AiTryOnGenerationError('service');
21222
+ }
21223
+ function normalizeWidgetSession(value) {
21224
+ if (!shopify_try_on_client_isRecord(value)) return null;
21225
+ const token = shopify_try_on_client_normalizeString(value.token);
21226
+ const sid = shopify_try_on_client_normalizeString(value.sid);
21227
+ const expiresIn = 'number' == typeof value.expiresIn ? value.expiresIn : NaN;
21228
+ return token && sid && Number.isFinite(expiresIn) && expiresIn > 0 ? {
21229
+ expiresIn,
21230
+ sid,
21231
+ token
21232
+ } : null;
21233
+ }
21234
+ function isCanceled(error, signal) {
21235
+ return signal?.aborted === true || 'AbortError' === errorName(error) || 'ERR_CANCELED' === shopify_try_on_client_errorCode(error);
21236
+ }
21237
+ function isTimeout(error) {
21238
+ return 'TimeoutError' === errorName(error) || 'ECONNABORTED' === shopify_try_on_client_errorCode(error) || 'ETIMEDOUT' === shopify_try_on_client_errorCode(error);
21239
+ }
21240
+ function errorName(error) {
21241
+ return shopify_try_on_client_isRecord(error) && 'string' == typeof error.name ? error.name : '';
21242
+ }
21243
+ function shopify_try_on_client_errorCode(error) {
21244
+ return shopify_try_on_client_isRecord(error) && 'string' == typeof error.code ? error.code : '';
21245
+ }
21246
+ async function readRejectionBody(error) {
21247
+ const response = shopify_try_on_client_isRecord(error) ? error.response : void 0;
21248
+ const data = shopify_try_on_client_isRecord(response) ? response.data : void 0;
21249
+ if (data instanceof Blob) return (await data.text().catch(()=>'')).slice(0, MAX_ERROR_BODY) || null;
21250
+ if ('string' == typeof data) return data.slice(0, MAX_ERROR_BODY) || null;
21251
+ if (shopify_try_on_client_isRecord(data)) return JSON.stringify(data).slice(0, MAX_ERROR_BODY);
21252
+ return null;
21253
+ }
21254
+ function shopify_try_on_client_isRecord(value) {
21255
+ return null !== value && 'object' == typeof value;
21256
+ }
21257
+ function shopify_try_on_client_normalizeString(value) {
21258
+ return 'string' == typeof value ? value.trim() : '';
21259
+ }
21260
+ function shopify_try_on_client_defaultCreateObjectUrl(blob) {
21261
+ return 'function' == typeof URL.createObjectURL ? URL.createObjectURL(blob) : '';
21262
+ }
21263
+ const SHOPIFY_PRODUCT_TYPE = 'ai-try-on-shopify';
21264
+ function createProxyRequest(baseURL) {
21265
+ const instance = axios.create({
21266
+ baseURL,
21267
+ timeout: SHOPIFY_REQUEST_TIMEOUT_MS
21268
+ });
21269
+ return async function(config) {
21270
+ const response = await instance.request(config);
21271
+ return {
21272
+ data: response.data,
21273
+ headers: response.headers,
21274
+ status: response.status,
21275
+ statusText: response.statusText
21276
+ };
21277
+ };
21278
+ }
21279
+ function createAppProxyOperations(basePath) {
21280
+ const mount = basePath.trim().replace(/\/+$/, '');
21281
+ if (!mount) return;
21282
+ const client = createProxyRequest(mount);
21283
+ return {
21284
+ activate: ({ productId, variantId }, config)=>tryOnControllerActivateTryOn({
21285
+ product_id: productId,
21286
+ variant_id: variantId,
21287
+ productType: SHOPIFY_PRODUCT_TYPE
21288
+ }, {
21289
+ ...config,
21290
+ client
21291
+ }),
21292
+ generate: (data, config)=>tryOnControllerGenerateShopifyTryOn(data, {
21293
+ ...config,
21294
+ client
21295
+ }),
21296
+ issueWidgetToken: (data, config)=>publicApiControllerIssueWidgetToken(data, {
21297
+ ...config,
21298
+ client
21299
+ }),
21300
+ requestUploadUrls: (count, config)=>tryOnControllerCreateUploadUrls({
21301
+ count
21302
+ }, {
21303
+ ...config,
21304
+ client
21305
+ }),
21306
+ sendWidgetEvent: (data, config)=>widgetEventsControllerCreateWidgetEvent(data, {
21307
+ ...config,
21308
+ client
21309
+ })
21310
+ };
21311
+ }
21312
+ const LOADING_ACTIVATION = {
21313
+ status: 'loading'
21314
+ };
21315
+ const src_READY_ACTIVATION = {
21316
+ status: 'ready'
21317
+ };
21318
+ function AiTryOnDialog({ defaultOpen = false, language, onOpenChange, open, productImageSrc, productName, productPrice, productType, productUrl, apiKey, externalGeneration, externalActivation, externalAddToCart, externalClient, ...props }) {
21319
+ if (!externalGeneration) assertProductType(productType);
20803
21320
  const i18n = (0, __rspack_external_react.useMemo)(()=>createAiTryOnI18n(language), [
20804
21321
  language
20805
21322
  ]);
@@ -20809,20 +21326,58 @@ function AiTryOnDialog({ defaultOpen = false, language, onOpenChange, open, prod
20809
21326
  const isControlled = void 0 !== open;
20810
21327
  const currentOpen = isControlled ? open : internalOpen;
20811
21328
  const normalizedApiKey = normalizeApiKey(apiKey);
20812
- const tryOnClient = (0, __rspack_external_react.useMemo)(()=>createTryOnClient({
21329
+ const tryOnClient = (0, __rspack_external_react.useMemo)(()=>externalGeneration ? externalClient ?? externalTryOnClient : createTryOnClient({
20813
21330
  apiKey: normalizedApiKey
20814
21331
  }), [
20815
- normalizedApiKey
21332
+ normalizedApiKey,
21333
+ externalGeneration,
21334
+ externalClient
21335
+ ]);
21336
+ const [externalActivationState, setExternalActivationState] = (0, __rspack_external_react.useState)(LOADING_ACTIVATION);
21337
+ const [activationAttempt, setActivationAttempt] = (0, __rspack_external_react.useState)(0);
21338
+ const activation = externalActivation ? externalActivationState : src_READY_ACTIVATION;
21339
+ const activationRef = (0, __rspack_external_react.useRef)(externalActivation);
21340
+ const hasActivation = Boolean(externalActivation);
21341
+ (0, __rspack_external_react.useEffect)(()=>{
21342
+ activationRef.current = externalActivation;
21343
+ });
21344
+ (0, __rspack_external_react.useEffect)(()=>{
21345
+ const activate = activationRef.current;
21346
+ if (!activate) return;
21347
+ setExternalActivationState(LOADING_ACTIVATION);
21348
+ if (!currentOpen) return;
21349
+ let cancelled = false;
21350
+ activate().then((result)=>{
21351
+ if (!cancelled) setExternalActivationState(result.enabled ? {
21352
+ ...result,
21353
+ status: 'ready'
21354
+ } : {
21355
+ status: 'unavailable',
21356
+ temporary: result.temporarilyUnavailable
21357
+ });
21358
+ }).catch(()=>{
21359
+ if (!cancelled) setExternalActivationState({
21360
+ status: 'error'
21361
+ });
21362
+ });
21363
+ return ()=>{
21364
+ cancelled = true;
21365
+ };
21366
+ }, [
21367
+ activationAttempt,
21368
+ currentOpen,
21369
+ hasActivation
20816
21370
  ]);
20817
21371
  (0, __rspack_external_react.useEffect)(()=>{
20818
21372
  if (!currentOpen) {
20819
21373
  hasTrackedOpen.current = false;
20820
21374
  return;
20821
21375
  }
20822
- if (hasTrackedOpen.current) return;
21376
+ if ('ready' !== activation.status || hasTrackedOpen.current) return;
20823
21377
  hasTrackedOpen.current = true;
20824
21378
  tryOnClient.sendWidgetEvent('widget_opened');
20825
21379
  }, [
21380
+ activation.status,
20826
21381
  currentOpen,
20827
21382
  tryOnClient
20828
21383
  ]);
@@ -20841,6 +21396,9 @@ function AiTryOnDialog({ defaultOpen = false, language, onOpenChange, open, prod
20841
21396
  zIndex: 2147483647
20842
21397
  },
20843
21398
  children: [
21399
+ /*#__PURE__*/ jsx_runtime_jsx("style", {
21400
+ children: SHADOW_HOST_VISIBILITY_CSS
21401
+ }),
20844
21402
  /*#__PURE__*/ jsx_runtime_jsx("style", {
20845
21403
  children: shadowRootCssText
20846
21404
  }),
@@ -20863,6 +21421,10 @@ function AiTryOnDialog({ defaultOpen = false, language, onOpenChange, open, prod
20863
21421
  productType: productType,
20864
21422
  productUrl: productUrl,
20865
21423
  tryOnClient: tryOnClient,
21424
+ externalGeneration: externalGeneration,
21425
+ externalAddToCart: externalAddToCart,
21426
+ activation: activation,
21427
+ onRetryActivation: ()=>setActivationAttempt((attempt)=>attempt + 1),
20866
21428
  onRequestClose: ()=>handleOpenChange(false),
20867
21429
  onOpenChange: handleOpenChange,
20868
21430
  ...props
@@ -20874,7 +21436,7 @@ function AiTryOnDialog({ defaultOpen = false, language, onOpenChange, open, prod
20874
21436
  ]
20875
21437
  });
20876
21438
  }
20877
- function AiTryOnDialogContent({ language, onRequestClose, portalContainer, productImageSrc, productName, productPrice, productType, productUrl, tryOnClient, ...props }) {
21439
+ function AiTryOnDialogContent({ language, onRequestClose, portalContainer, productImageSrc, productName, productPrice, productType, productUrl, tryOnClient, externalGeneration, externalAddToCart, activation, onRetryActivation, ...props }) {
20878
21440
  const { t } = useTranslation();
20879
21441
  return /*#__PURE__*/ jsx_runtime_jsx(Dialog, {
20880
21442
  ...props,
@@ -20901,12 +21463,17 @@ function AiTryOnDialogContent({ language, onRequestClose, portalContainer, produ
20901
21463
  /*#__PURE__*/ jsx_runtime_jsx(AiTryOnRoot, {
20902
21464
  dialogPortalContainer: portalContainer,
20903
21465
  language: language,
20904
- productImageSrc: productImageSrc,
21466
+ productImageSrc: 'ready' === activation.status && activation.productImageSrc || productImageSrc,
20905
21467
  productName: productName,
20906
21468
  productPrice: productPrice,
20907
- productType: productType,
21469
+ productType: 'ready' === activation.status ? activation.productType ?? productType : productType,
20908
21470
  productUrl: productUrl,
20909
21471
  tryOnClient: tryOnClient,
21472
+ processPhoto: externalGeneration,
21473
+ activation: activation,
21474
+ onRetryActivation: onRetryActivation,
21475
+ onAddToCart: externalAddToCart,
21476
+ showAddToCart: !externalGeneration || Boolean(externalAddToCart),
20910
21477
  showDialogCloseButton: true,
20911
21478
  onClose: onRequestClose
20912
21479
  })
@@ -20914,4 +21481,4 @@ function AiTryOnDialogContent({ language, onRequestClose, portalContainer, produ
20914
21481
  })
20915
21482
  });
20916
21483
  }
20917
- export { AiTryOnDialog, PRODUCT_TYPES, isProductType };
21484
+ export { AiTryOnDialog, AiTryOnGenerationError, PRODUCT_TYPES, SHOPIFY_REQUEST_TIMEOUT_MS, createAppProxyOperations, createShopifyTryOnClient, isProductType, uploadImageToPresignedUrl };