@neurodyn/react-ai-try-on 0.0.4 → 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 +737 -160
  3. package/package.json +2 -1
package/dist/index.js CHANGED
@@ -935,6 +935,10 @@ var __webpack_modules__ = {
935
935
  height: auto;
936
936
  }
937
937
 
938
+ .h-dvh {
939
+ height: 100dvh;
940
+ }
941
+
938
942
  .h-full {
939
943
  height: 100%;
940
944
  }
@@ -943,10 +947,6 @@ var __webpack_modules__ = {
943
947
  height: 1px;
944
948
  }
945
949
 
946
- .h-svh {
947
- height: 100svh;
948
- }
949
-
950
950
  .max-h-\\(--radix-select-content-available-height\\) {
951
951
  max-height: var(--radix-select-content-available-height);
952
952
  }
@@ -955,12 +955,12 @@ var __webpack_modules__ = {
955
955
  max-height: calc(var(--spacing) * 179);
956
956
  }
957
957
 
958
- .max-h-none {
959
- max-height: none;
958
+ .max-h-dvh {
959
+ max-height: 100dvh;
960
960
  }
961
961
 
962
- .max-h-svh {
963
- max-height: 100svh;
962
+ .max-h-none {
963
+ max-height: none;
964
964
  }
965
965
 
966
966
  .min-h-0 {
@@ -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: 'Хотите увидеть это на себе?',
@@ -10389,18 +10441,29 @@ function createAiTryOnI18n(language) {
10389
10441
  return instance;
10390
10442
  }
10391
10443
  const PROPERTY_BLOCK_RE = /@property\s+(--[^{\s]+)\s*\{([^}]*)\}/g;
10392
- const INITIAL_VALUE_RE = /initial-value:([^;\n]+);/i;
10444
+ const INITIAL_VALUE_RE = /initial-value:([^;}]+)/i;
10445
+ const NON_INHERITING_RE = /inherits:\s*false(?:\s*;|\s*$)/i;
10393
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}';
10394
10448
  function createShadowRootCssText(cssText) {
10395
10449
  const cssForHost = cssText.replace(ROOT_TO_HOST_SELECTOR_RE, ':host');
10396
10450
  const propertyBlocks = Array.from(cssText.matchAll(PROPERTY_BLOCK_RE));
10397
10451
  const propertyDefaults = propertyBlocks.map(([, name, body])=>{
10398
- const match = body.match(INITIAL_VALUE_RE);
10399
- if (!match) return null;
10400
- const initialValue = match[1].trim();
10401
- return ` ${name}: ${initialValue};`;
10452
+ if (!NON_INHERITING_RE.test(body)) return null;
10453
+ const initialValue = body.match(INITIAL_VALUE_RE)?.[1].trim() ?? 'initial';
10454
+ return ` ${name}: ${initialValue};`;
10402
10455
  }).filter((value)=>null !== value).join('\n');
10403
- return propertyDefaults ? `:host {\n${propertyDefaults}\n}\n\n${cssForHost}` : cssForHost;
10456
+ if (!propertyDefaults) return cssForHost;
10457
+ return `${cssForHost}\n\n@layer base {
10458
+ :host,
10459
+ *,
10460
+ ::before,
10461
+ ::after,
10462
+ ::backdrop,
10463
+ ::file-selector-button {
10464
+ ${propertyDefaults}
10465
+ }
10466
+ }`;
10404
10467
  }
10405
10468
  var styleinline = __webpack_require__("./src/style.css?inline");
10406
10469
  const shadowRootCssText = createShadowRootCssText(styleinline.A);
@@ -18183,7 +18246,7 @@ function ResultImagePanel({ className, onOpenComparison, productLabel, productUr
18183
18246
  });
18184
18247
  }
18185
18248
  const COMPARISON_DIALOG_BACKGROUND_CLASS_NAME = 'bg-[#212124E5]';
18186
- 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 }) {
18187
18250
  const { t } = useTranslation();
18188
18251
  const productLabel = productName ?? t('resultProductLink');
18189
18252
  const normalizedProductPrice = normalizeProductPrice(productPrice);
@@ -18197,7 +18260,7 @@ function ComparisonDialog({ onAddToCart, onClose, onDeleteResult, onSaveResult,
18197
18260
  open: true,
18198
18261
  onOpenChange: (open)=>!open && onClose(),
18199
18262
  children: /*#__PURE__*/ jsxs(DialogContent, {
18200
- className: `dark fixed inset-0 left-0 top-0 flex h-svh max-h-none w-svw max-w-none translate-x-0 translate-y-0 flex-col rounded-none ${COMPARISON_DIALOG_BACKGROUND_CLASS_NAME} p-0 text-text-dark duration-200 sm:max-w-none sm:p-0`,
18263
+ className: `dark fixed inset-0 left-0 top-0 flex h-dvh max-h-none w-svw max-w-none translate-x-0 translate-y-0 flex-col rounded-none ${COMPARISON_DIALOG_BACKGROUND_CLASS_NAME} p-0 text-text-dark duration-200 sm:max-w-none sm:p-0`,
18201
18264
  portalContainer: portalContainer,
18202
18265
  showCloseButton: false,
18203
18266
  children: [
@@ -18297,59 +18360,67 @@ function ComparisonDialog({ onAddToCart, onClose, onDeleteResult, onSaveResult,
18297
18360
  ]
18298
18361
  })
18299
18362
  }),
18300
- /*#__PURE__*/ jsx_runtime_jsx("div", {
18301
- className: "flex h-23 shrink-0 items-center justify-center px-8 pb-6 pt-3",
18302
- children: /*#__PURE__*/ jsxs("div", {
18303
- className: "flex w-full max-w-104 items-start gap-2",
18304
- children: [
18305
- /*#__PURE__*/ jsxs(button_Button, {
18306
- className: "min-w-0 flex-1",
18307
- size: "lg",
18308
- type: "button",
18309
- variant: "contrast",
18310
- onClick: onAddToCart,
18311
- children: [
18312
- /*#__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, {
18313
18418
  "data-icon": "inline-start"
18314
- }),
18315
- /*#__PURE__*/ jsx_runtime_jsx("span", {
18316
- className: "truncate",
18317
- children: addToCartLabel
18318
18419
  })
18319
- ]
18320
- }),
18321
- /*#__PURE__*/ jsx_runtime_jsx(button_Button, {
18322
- "aria-label": t('share'),
18323
- size: "icon-lg",
18324
- type: "button",
18325
- variant: "outline",
18326
- onClick: onShareResult,
18327
- children: /*#__PURE__*/ jsx_runtime_jsx(Share, {
18328
- "data-icon": "inline-start"
18329
- })
18330
- }),
18331
- /*#__PURE__*/ jsx_runtime_jsx(button_Button, {
18332
- "aria-label": t('download'),
18333
- size: "icon-lg",
18334
- type: "button",
18335
- variant: "outline",
18336
- onClick: onSaveResult,
18337
- children: /*#__PURE__*/ jsx_runtime_jsx(Download, {
18338
- "data-icon": "inline-start"
18339
- })
18340
- }),
18341
- /*#__PURE__*/ jsx_runtime_jsx(button_Button, {
18342
- "aria-label": t('reset'),
18343
- size: "icon-lg",
18344
- type: "button",
18345
- variant: "outline",
18346
- onClick: handleDelete,
18347
- children: /*#__PURE__*/ jsx_runtime_jsx(EditorTrash, {
18348
- "data-icon": "inline-start"
18349
18420
  })
18350
- })
18351
- ]
18352
- })
18421
+ ]
18422
+ })
18423
+ ]
18353
18424
  })
18354
18425
  ]
18355
18426
  })
@@ -18392,7 +18463,7 @@ function Badge({ className, variant, size, asChild = false, ...props }) {
18392
18463
  }
18393
18464
  function TryOnShell({ className, ...props }) {
18394
18465
  return /*#__PURE__*/ jsx_runtime_jsx("div", {
18395
- className: utils_cn('flex max-h-svh w-svw flex-col overflow-hidden rounded-t-3xl rounded-b-none bg-background-containers text-foreground shadow-2xl sm:max-h-[min(calc(100svh-var(--spacing)*8),--spacing(176))] sm:w-120 sm:rounded-3xl', className),
18466
+ className: utils_cn('flex max-h-dvh w-svw flex-col overflow-hidden rounded-t-3xl rounded-b-none bg-background-containers text-foreground shadow-2xl sm:max-h-[min(calc(100svh-var(--spacing)*8),--spacing(176))] sm:w-120 sm:rounded-3xl', className),
18396
18467
  ...props
18397
18468
  });
18398
18469
  }
@@ -18871,10 +18942,10 @@ function getAiTryOnGuidance(productType) {
18871
18942
  }
18872
18943
  function StartScreen({ view }) {
18873
18944
  const { t } = useTranslation();
18874
- const guidance = getAiTryOnGuidance(view.productType);
18945
+ const guidance = view.productType ? getAiTryOnGuidance(view.productType) : null;
18875
18946
  return /*#__PURE__*/ jsxs(ScreenLayout, {
18876
18947
  title: t('startTitle'),
18877
- subtitle: t(guidance.titleKey),
18948
+ subtitle: t(guidance?.titleKey ?? 'externalPhotoHint'),
18878
18949
  children: [
18879
18950
  /*#__PURE__*/ jsxs(TryOnImagePanel, {
18880
18951
  className: "mt-3",
@@ -18925,7 +18996,8 @@ function StartScreen({ view }) {
18925
18996
  }
18926
18997
  function IntroScreen({ view }) {
18927
18998
  const { t } = useTranslation();
18928
- const guidance = getAiTryOnGuidance(view.productType);
18999
+ const guidance = view.productType ? getAiTryOnGuidance(view.productType) : null;
19000
+ if (!guidance) return null;
18929
19001
  return /*#__PURE__*/ jsxs(ScreenLayout, {
18930
19002
  title: t(guidance.titleKey),
18931
19003
  subtitle: t(guidance.subtitleKey),
@@ -19043,6 +19115,79 @@ function ConfirmScreen({ view }) {
19043
19115
  ]
19044
19116
  });
19045
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
+ }
19046
19191
  function ProcessingScreen({ view }) {
19047
19192
  const { t } = useTranslation();
19048
19193
  return /*#__PURE__*/ jsxs(ScreenLayout, {
@@ -19091,14 +19236,25 @@ function ProcessingScreen({ view }) {
19091
19236
  ]
19092
19237
  });
19093
19238
  }
19094
- function ErrorScreen({ view }) {
19239
+ function ErrorScreen({ view, onClose }) {
19095
19240
  const { t } = useTranslation();
19096
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;
19097
19253
  return /*#__PURE__*/ jsxs(ScreenLayout, {
19098
19254
  title: t(isFileTooLarge ? 'errorFileTooLarge' : 'errorTitle'),
19099
- subtitle: isFileTooLarge ? void 0 : t('errorSubtitle'),
19255
+ subtitle: isFileTooLarge ? void 0 : t(externalKey ?? 'errorSubtitle'),
19100
19256
  children: [
19101
- !isFileTooLarge && /*#__PURE__*/ jsxs(TryOnImagePanel, {
19257
+ !isFileTooLarge && !code && /*#__PURE__*/ jsxs(TryOnImagePanel, {
19102
19258
  className: "mt-3",
19103
19259
  children: [
19104
19260
  /*#__PURE__*/ jsx_runtime_jsx(BlurredPhotoBackdrop, {
@@ -19146,7 +19302,7 @@ function ErrorScreen({ view }) {
19146
19302
  /*#__PURE__*/ jsxs(ActionStack, {
19147
19303
  className: "mt-4",
19148
19304
  children: [
19149
- /*#__PURE__*/ jsxs(button_Button, {
19305
+ canRetry && /*#__PURE__*/ jsxs(button_Button, {
19150
19306
  className: "w-full",
19151
19307
  size: "lg",
19152
19308
  type: "button",
@@ -19159,7 +19315,7 @@ function ErrorScreen({ view }) {
19159
19315
  t('errorRetry')
19160
19316
  ]
19161
19317
  }),
19162
- /*#__PURE__*/ jsxs(button_Button, {
19318
+ canChangePhoto && /*#__PURE__*/ jsxs(button_Button, {
19163
19319
  className: "w-full",
19164
19320
  size: "lg",
19165
19321
  type: "button",
@@ -19172,6 +19328,14 @@ function ErrorScreen({ view }) {
19172
19328
  t('errorDifferentPhoto')
19173
19329
  ]
19174
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
+ }),
19175
19339
  /*#__PURE__*/ jsx_runtime_jsx(TermsCopy, {
19176
19340
  className: "text-text-sub"
19177
19341
  })
@@ -19180,8 +19344,9 @@ function ErrorScreen({ view }) {
19180
19344
  ]
19181
19345
  });
19182
19346
  }
19183
- function ResultScreen({ view }) {
19347
+ function ResultScreen({ view, showAddToCart = true, onAddToCart, addToCartPending = false, addToCartError }) {
19184
19348
  const { t } = useTranslation();
19349
+ const addToCart = onAddToCart ?? view.addToCart;
19185
19350
  const normalizedProductPrice = try_on_screens_normalizeProductPrice(view.productPrice);
19186
19351
  const addToCartLabel = normalizedProductPrice ? t('resultTryAnotherWithPrice', {
19187
19352
  price: normalizedProductPrice
@@ -19192,7 +19357,8 @@ function ResultScreen({ view }) {
19192
19357
  className: "flex flex-col gap-4 px-6 pb-2 pt-4 sm:px-8",
19193
19358
  children: view.resultHistory.map((historyItem, index)=>/*#__PURE__*/ jsx_runtime_jsx(ResultFitCard, {
19194
19359
  compact: index > 0,
19195
- onAddToCart: view.addToCart,
19360
+ onAddToCart: showAddToCart ? addToCart : void 0,
19361
+ addToCartPending: addToCartPending,
19196
19362
  productName: historyItem.productName ?? view.productName,
19197
19363
  productPrice: historyItem.productPrice,
19198
19364
  productUrl: historyItem.productUrl ?? view.productUrl,
@@ -19220,12 +19386,13 @@ function ResultScreen({ view }) {
19220
19386
  /*#__PURE__*/ jsxs(ActionStack, {
19221
19387
  className: "mt-4",
19222
19388
  children: [
19223
- /*#__PURE__*/ jsxs(button_Button, {
19389
+ showAddToCart && /*#__PURE__*/ jsxs(button_Button, {
19224
19390
  className: "w-full",
19391
+ disabled: addToCartPending,
19225
19392
  size: "lg",
19226
19393
  type: "button",
19227
19394
  variant: "contrast",
19228
- onClick: view.addToCart,
19395
+ onClick: addToCart,
19229
19396
  children: [
19230
19397
  /*#__PURE__*/ jsx_runtime_jsx(Cart, {
19231
19398
  "data-icon": "inline-start"
@@ -19233,6 +19400,11 @@ function ResultScreen({ view }) {
19233
19400
  addToCartLabel
19234
19401
  ]
19235
19402
  }),
19403
+ addToCartError && /*#__PURE__*/ jsx_runtime_jsx("p", {
19404
+ className: "text-center text-xs text-text-sub",
19405
+ role: "alert",
19406
+ children: addToCartError
19407
+ }),
19236
19408
  /*#__PURE__*/ jsxs("div", {
19237
19409
  className: "grid grid-cols-3 gap-2",
19238
19410
  children: [
@@ -19276,7 +19448,7 @@ function ResultScreen({ view }) {
19276
19448
  ]
19277
19449
  });
19278
19450
  }
19279
- 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 }) {
19280
19452
  const { t } = useTranslation();
19281
19453
  const productLabel = productName ?? t('resultProductLink');
19282
19454
  const normalizedProductPrice = try_on_screens_normalizeProductPrice(productPrice);
@@ -19296,8 +19468,9 @@ function ResultFitCard({ compact = false, onAddToCart, onDeleteResult, onOpenCom
19296
19468
  /*#__PURE__*/ jsxs("div", {
19297
19469
  className: "flex gap-2",
19298
19470
  children: [
19299
- /*#__PURE__*/ jsxs(button_Button, {
19471
+ onAddToCart && /*#__PURE__*/ jsxs(button_Button, {
19300
19472
  className: "min-w-0 flex-1",
19473
+ disabled: addToCartPending,
19301
19474
  size: "default",
19302
19475
  type: "button",
19303
19476
  variant: "contrast",
@@ -19419,6 +19592,55 @@ function try_on_screens_normalizeProductPrice(value) {
19419
19592
  const normalized = value?.trim();
19420
19593
  return normalized || void 0;
19421
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
+ }
19422
19644
  const REQUIRED_UPLOAD_COUNT = 2;
19423
19645
  function createNanoBananaAiTryOnProcessor({ createObjectUrl = defaultCreateObjectUrl, fetch: fetchImpl, productImageSrc, productType, tryOnClient, apiKey }) {
19424
19646
  assertProductType(productType);
@@ -19437,8 +19659,12 @@ function createNanoBananaAiTryOnProcessor({ createObjectUrl = defaultCreateObjec
19437
19659
  const [personUpload, productUpload] = resolveUploads(await client.requestUploadUrls(REQUIRED_UPLOAD_COUNT), REQUIRED_UPLOAD_COUNT);
19438
19660
  context?.reportProgress(44);
19439
19661
  await Promise.all([
19440
- uploadImageToPresignedUrl(personUpload.uploadUrl, personImageBlob, fetchImpl),
19441
- uploadImageToPresignedUrl(productUpload.uploadUrl, productImageBlob, fetchImpl)
19662
+ uploadImageToPresignedUrl(personUpload.uploadUrl, personImageBlob, {
19663
+ fetch: fetchImpl
19664
+ }),
19665
+ uploadImageToPresignedUrl(productUpload.uploadUrl, productImageBlob, {
19666
+ fetch: fetchImpl
19667
+ })
19442
19668
  ]);
19443
19669
  context?.reportProgress(62);
19444
19670
  const stopGenerationProgress = startGenerationProgress(context?.reportProgress);
@@ -19457,8 +19683,8 @@ function createNanoBananaAiTryOnProcessor({ createObjectUrl = defaultCreateObjec
19457
19683
  stopGenerationProgress();
19458
19684
  }
19459
19685
  context?.reportProgress(96);
19460
- const generatedImageBlob = await resolveGeneratedImageBlob(response);
19461
- 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');
19462
19688
  context?.reportProgress(98);
19463
19689
  const generatedImageSrc = createObjectUrl(generatedImageBlob);
19464
19690
  if (!generatedImageSrc) throw new Error('AI try-on result image URL cannot be created');
@@ -19500,14 +19726,28 @@ function resolveFetch(fetchImpl) {
19500
19726
  if ('function' == typeof fetch) return fetch;
19501
19727
  throw new Error('Fetch API is not available for AI try-on image upload');
19502
19728
  }
19503
- async function uploadImageToPresignedUrl(uploadUrl, imageBlob, fetchImpl) {
19504
- const response = await resolveFetch(fetchImpl)(uploadUrl, {
19505
- body: imageBlob,
19506
- 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
19507
19737
  });
19508
- if (!response.ok) {
19509
- const details = await response.text().catch(()=>'');
19510
- 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);
19511
19751
  }
19512
19752
  }
19513
19753
  async function resolveProductImageBlob(productImageSrc, fetchImpl) {
@@ -19529,24 +19769,6 @@ async function resolveBlobFromUrl(src, fetchImpl, message) {
19529
19769
  }
19530
19770
  return response.blob();
19531
19771
  }
19532
- async function resolveGeneratedImageBlob(response) {
19533
- if (response instanceof Blob) return response.type ? response : new Blob([
19534
- response
19535
- ], {
19536
- type: 'image/jpeg'
19537
- });
19538
- if (response instanceof ArrayBuffer) return new Blob([
19539
- response
19540
- ], {
19541
- type: 'image/jpeg'
19542
- });
19543
- if ('string' == typeof response) return new Blob([
19544
- response
19545
- ], {
19546
- type: 'image/jpeg'
19547
- });
19548
- throw new Error('AI try-on generation did not return an image');
19549
- }
19550
19772
  function startGenerationProgress(reportProgress) {
19551
19773
  if (!reportProgress) return ()=>{};
19552
19774
  reportProgress(70);
@@ -19955,7 +20177,7 @@ class AiTryOnSession {
19955
20177
  };
19956
20178
  }
19957
20179
  choosePhoto() {
19958
- if (this.tipStore.consumeFirstView(this.state.productType)) {
20180
+ if (this.state.productType && this.tipStore.consumeFirstView(this.state.productType)) {
19959
20181
  this.state = {
19960
20182
  ...this.state,
19961
20183
  stage: 'intro'
@@ -19967,14 +20189,14 @@ class AiTryOnSession {
19967
20189
  };
19968
20190
  }
19969
20191
  continueIntro() {
19970
- this.tipStore.markSeen(this.state.productType);
20192
+ if (this.state.productType) this.tipStore.markSeen(this.state.productType);
19971
20193
  return {
19972
20194
  type: 'request-file-input'
19973
20195
  };
19974
20196
  }
19975
20197
  requestClose() {
19976
20198
  if ('intro' === this.state.stage) {
19977
- this.tipStore.markSeen(this.state.productType);
20199
+ if (this.state.productType) this.tipStore.markSeen(this.state.productType);
19978
20200
  this.goBack();
19979
20201
  return null;
19980
20202
  }
@@ -20118,12 +20340,15 @@ class AiTryOnSession {
20118
20340
  };
20119
20341
  return historyItem;
20120
20342
  }
20121
- failGeneration(message) {
20343
+ failGeneration(message, code) {
20122
20344
  this.state = {
20123
20345
  ...this.state,
20124
20346
  error: {
20125
20347
  kind: 'generation',
20126
- message
20348
+ message,
20349
+ ...code ? {
20350
+ code
20351
+ } : {}
20127
20352
  },
20128
20353
  generationProduct: null,
20129
20354
  stage: 'error'
@@ -20294,6 +20519,13 @@ function clampProgress(progress) {
20294
20519
  function createPhotoId(file) {
20295
20520
  return `${file.name}-${file.size}-${file.lastModified}`;
20296
20521
  }
20522
+ class AiTryOnGenerationError extends Error {
20523
+ code;
20524
+ constructor(code){
20525
+ super(code), this.code = code;
20526
+ this.name = 'AiTryOnGenerationError';
20527
+ }
20528
+ }
20297
20529
  function createAiTryOnInteractiveView(session, { addToCart, choosePhoto, deleteResult, generate, runSessionCommand, saveResult, shareResult, sync }) {
20298
20530
  const currentView = session.view;
20299
20531
  switch(currentView.stage){
@@ -20430,6 +20662,13 @@ function collectPhotoObjectUrl(photo) {
20430
20662
  function useAiTryOnSession({ historyStore = indexedDbAiTryOnHistoryStore, objectUrls = browserAiTryOnObjectUrls, onRequestClose, onRequestFileInput, processPhoto, productImageSrc, productName, productPrice, productType, productUrl, tryOnClient, resultActions = browserAiTryOnResultActions, tipStore = browserAiTryOnTipStore }) {
20431
20663
  const [, rerender] = (0, __rspack_external_react.useReducer)((value)=>value + 1, 0);
20432
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
+ }, []);
20433
20672
  const createTrackedObjectUrl = (0, __rspack_external_react.useCallback)((blob)=>{
20434
20673
  const src = objectUrls.createObjectUrl(blob);
20435
20674
  if (src) trackedObjectUrls.current.add(src);
@@ -20455,12 +20694,16 @@ function useAiTryOnSession({ historyStore = indexedDbAiTryOnHistoryStore, object
20455
20694
  productType,
20456
20695
  productUrl
20457
20696
  });
20458
- 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({
20459
20701
  createObjectUrl: createTrackedObjectUrl,
20460
20702
  productImageSrc,
20461
20703
  productType,
20462
20704
  tryOnClient
20463
- }), [
20705
+ });
20706
+ }, [
20464
20707
  createTrackedObjectUrl,
20465
20708
  processPhoto,
20466
20709
  productImageSrc,
@@ -20521,21 +20764,29 @@ function useAiTryOnSession({ historyStore = indexedDbAiTryOnHistoryStore, object
20521
20764
  try {
20522
20765
  const result = await processSelectedPhoto(photo, {
20523
20766
  reportProgress: (progress)=>{
20767
+ if (!mounted.current) return;
20524
20768
  session.reportGenerationProgress(progress);
20525
20769
  sync();
20526
20770
  }
20527
20771
  });
20528
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
+ }
20529
20778
  const historyItem = session.completeGeneration(result);
20530
20779
  trackResultObjectUrls(historyItem, trackedObjectUrls.current);
20531
20780
  persistHistoryItem(historyItem);
20532
20781
  sync();
20533
20782
  } catch (error) {
20534
20783
  await waitForRemainingProcessingDuration(processingStartedAt);
20535
- 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);
20536
20786
  sync();
20537
20787
  }
20538
20788
  }, [
20789
+ objectUrls,
20539
20790
  persistHistoryItem,
20540
20791
  processSelectedPhoto,
20541
20792
  session,
@@ -20643,8 +20894,11 @@ function useAiTryOnSession({ historyStore = indexedDbAiTryOnHistoryStore, object
20643
20894
  function waitForRemainingProcessingDuration(startedAt) {
20644
20895
  return waitForProcessingDuration(getRemainingProcessingDuration(startedAt));
20645
20896
  }
20646
- function AiTryOnRoot({ dialogPortalContainer, language, onClose, processPhoto, productImageSrc, productName, productPrice, productType, productUrl, tryOnClient: providedTryOnClient, apiKey, showDialogCloseButton = false }) {
20647
- 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);
20648
20902
  const { i18n, t } = useTranslation();
20649
20903
  const fileInputRef = (0, __rspack_external_react.useRef)(null);
20650
20904
  const normalizedApiKey = normalizeApiKey(apiKey);
@@ -20672,7 +20926,22 @@ function AiTryOnRoot({ dialogPortalContainer, language, onClose, processPhoto, p
20672
20926
  tryOnClient
20673
20927
  });
20674
20928
  const stage = session.view.stage;
20929
+ const closeDialog = onClose ?? session.requestClose;
20675
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
+ ]);
20676
20945
  function handleFileChange(event) {
20677
20946
  const file = event.currentTarget.files?.[0];
20678
20947
  if (file) session.selectFile(file);
@@ -20697,18 +20966,18 @@ function AiTryOnRoot({ dialogPortalContainer, language, onClose, processPhoto, p
20697
20966
  ]
20698
20967
  }) : null;
20699
20968
  return /*#__PURE__*/ jsxs(TryOnShell, {
20700
- className: isMyFitsActive ? 'h-svh max-h-svh sm:h-230 sm:max-h-[calc(100svh-var(--spacing)*8)]' : void 0,
20969
+ className: isMyFitsActive ? 'h-dvh max-h-dvh sm:h-230 sm:max-h-[calc(100svh-var(--spacing)*8)]' : void 0,
20701
20970
  "data-ai-try-on-stage": stage,
20702
20971
  children: [
20703
20972
  /*#__PURE__*/ jsx_runtime_jsx(TryOnHeader, {
20704
- badge: 'history' === stage ? null : t(getBadgeKey(stage)),
20973
+ badge: 'history' === stage || 'ready' !== activation.status ? null : t(getBadgeKey(stage)),
20705
20974
  badgeTone: getBadgeTone(stage),
20706
20975
  closeLabel: t('close'),
20707
- leftSlot: leftSlot,
20708
- middleSlot: middleSlot,
20976
+ leftSlot: 'ready' === activation.status ? leftSlot : null,
20977
+ middleSlot: 'ready' === activation.status ? middleSlot : null,
20709
20978
  preferMiddleSlotFirst: 'result' === stage,
20710
20979
  showCloseButton: showDialogCloseButton,
20711
- onClose: session.requestClose
20980
+ onClose: 'ready' === activation.status ? session.requestClose : closeDialog
20712
20981
  }),
20713
20982
  /*#__PURE__*/ jsx_runtime_jsx("input", {
20714
20983
  ref: fileInputRef,
@@ -20719,36 +20988,56 @@ function AiTryOnRoot({ dialogPortalContainer, language, onClose, processPhoto, p
20719
20988
  }),
20720
20989
  /*#__PURE__*/ jsxs(TryOnScreenTransition, {
20721
20990
  children: [
20722
- 'start' === session.view.stage ? /*#__PURE__*/ jsx_runtime_jsx(StartScreen, {
20723
- view: session.view
20724
- }) : null,
20725
- 'intro' === session.view.stage ? /*#__PURE__*/ jsx_runtime_jsx(IntroScreen, {
20726
- view: session.view
20727
- }) : null,
20728
- 'history' === session.view.stage ? /*#__PURE__*/ jsx_runtime_jsx(HistoryScreen, {
20729
- view: session.view
20730
- }) : null,
20731
- 'confirm' === session.view.stage ? /*#__PURE__*/ jsx_runtime_jsx(ConfirmScreen, {
20732
- 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
20733
20995
  }) : null,
20734
- 'processing' === session.view.stage ? /*#__PURE__*/ jsx_runtime_jsx(ProcessingScreen, {
20735
- view: session.view
20996
+ 'error' === activation.status ? /*#__PURE__*/ jsx_runtime_jsx(ActivationErrorScreen, {
20997
+ onClose: closeDialog,
20998
+ onRetry: onRetryActivation
20736
20999
  }) : null,
20737
- 'error' === session.view.stage ? /*#__PURE__*/ jsx_runtime_jsx(ErrorScreen, {
20738
- view: session.view
20739
- }) : null,
20740
- 'result' === session.view.stage ? /*#__PURE__*/ jsx_runtime_jsx(ResultScreen, {
20741
- view: session.view
20742
- }) : 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
+ })
20743
21030
  ]
20744
- }, stage),
21031
+ }, 'ready' === activation.status ? stage : `activation-${activation.status}`),
20745
21032
  session.comparison.open && /*#__PURE__*/ jsx_runtime_jsx(ComparisonDialog, {
20746
21033
  productName: session.comparison.productName,
20747
21034
  productPrice: session.comparison.productPrice,
20748
21035
  productUrl: session.comparison.productUrl,
20749
21036
  portalContainer: dialogPortalContainer ?? void 0,
20750
21037
  result: session.comparison.result,
20751
- onAddToCart: session.comparison.addToCart,
21038
+ addToCartPending: cartPending,
21039
+ addToCartError: cartError,
21040
+ onAddToCart: showAddToCart ? onAddToCart ? addToCart : session.comparison.addToCart : void 0,
20752
21041
  onClose: session.comparison.close,
20753
21042
  onDeleteResult: session.comparison.deleteResult,
20754
21043
  onSaveResult: session.comparison.saveResult,
@@ -20788,8 +21077,246 @@ function getBadgeTone(stage) {
20788
21077
  configureNodeApiClient({
20789
21078
  baseURL: "https://api.neurodyn.ai/"
20790
21079
  });
20791
- function AiTryOnDialog({ defaultOpen = false, language, onOpenChange, open, productImageSrc, productName, productPrice, productType, productUrl, apiKey, ...props }) {
20792
- 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);
20793
21320
  const i18n = (0, __rspack_external_react.useMemo)(()=>createAiTryOnI18n(language), [
20794
21321
  language
20795
21322
  ]);
@@ -20799,20 +21326,58 @@ function AiTryOnDialog({ defaultOpen = false, language, onOpenChange, open, prod
20799
21326
  const isControlled = void 0 !== open;
20800
21327
  const currentOpen = isControlled ? open : internalOpen;
20801
21328
  const normalizedApiKey = normalizeApiKey(apiKey);
20802
- const tryOnClient = (0, __rspack_external_react.useMemo)(()=>createTryOnClient({
21329
+ const tryOnClient = (0, __rspack_external_react.useMemo)(()=>externalGeneration ? externalClient ?? externalTryOnClient : createTryOnClient({
20803
21330
  apiKey: normalizedApiKey
20804
21331
  }), [
20805
- 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
20806
21370
  ]);
20807
21371
  (0, __rspack_external_react.useEffect)(()=>{
20808
21372
  if (!currentOpen) {
20809
21373
  hasTrackedOpen.current = false;
20810
21374
  return;
20811
21375
  }
20812
- if (hasTrackedOpen.current) return;
21376
+ if ('ready' !== activation.status || hasTrackedOpen.current) return;
20813
21377
  hasTrackedOpen.current = true;
20814
21378
  tryOnClient.sendWidgetEvent('widget_opened');
20815
21379
  }, [
21380
+ activation.status,
20816
21381
  currentOpen,
20817
21382
  tryOnClient
20818
21383
  ]);
@@ -20831,6 +21396,9 @@ function AiTryOnDialog({ defaultOpen = false, language, onOpenChange, open, prod
20831
21396
  zIndex: 2147483647
20832
21397
  },
20833
21398
  children: [
21399
+ /*#__PURE__*/ jsx_runtime_jsx("style", {
21400
+ children: SHADOW_HOST_VISIBILITY_CSS
21401
+ }),
20834
21402
  /*#__PURE__*/ jsx_runtime_jsx("style", {
20835
21403
  children: shadowRootCssText
20836
21404
  }),
@@ -20853,6 +21421,10 @@ function AiTryOnDialog({ defaultOpen = false, language, onOpenChange, open, prod
20853
21421
  productType: productType,
20854
21422
  productUrl: productUrl,
20855
21423
  tryOnClient: tryOnClient,
21424
+ externalGeneration: externalGeneration,
21425
+ externalAddToCart: externalAddToCart,
21426
+ activation: activation,
21427
+ onRetryActivation: ()=>setActivationAttempt((attempt)=>attempt + 1),
20856
21428
  onRequestClose: ()=>handleOpenChange(false),
20857
21429
  onOpenChange: handleOpenChange,
20858
21430
  ...props
@@ -20864,12 +21436,12 @@ function AiTryOnDialog({ defaultOpen = false, language, onOpenChange, open, prod
20864
21436
  ]
20865
21437
  });
20866
21438
  }
20867
- 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 }) {
20868
21440
  const { t } = useTranslation();
20869
21441
  return /*#__PURE__*/ jsx_runtime_jsx(Dialog, {
20870
21442
  ...props,
20871
21443
  children: /*#__PURE__*/ jsxs(DialogContent, {
20872
- className: "inset-x-0 bottom-0 top-auto block max-h-svh w-svw max-w-none translate-x-0 translate-y-0 gap-0 overflow-hidden rounded-t-3xl rounded-b-none p-0 sm:inset-x-auto sm:right-4 sm:bottom-4 sm:left-auto sm:max-h-230 sm:w-120 sm:max-w-120 sm:rounded-3xl sm:p-0",
21444
+ className: "inset-x-0 bottom-0 top-auto block max-h-dvh w-svw max-w-none translate-x-0 translate-y-0 gap-0 overflow-hidden rounded-t-3xl rounded-b-none p-0 sm:inset-x-auto sm:right-4 sm:bottom-4 sm:left-auto sm:max-h-230 sm:w-120 sm:max-w-120 sm:rounded-3xl sm:p-0",
20873
21445
  contentInsideOverlay: true,
20874
21446
  portalContainer: portalContainer,
20875
21447
  showCloseButton: false,
@@ -20891,12 +21463,17 @@ function AiTryOnDialogContent({ language, onRequestClose, portalContainer, produ
20891
21463
  /*#__PURE__*/ jsx_runtime_jsx(AiTryOnRoot, {
20892
21464
  dialogPortalContainer: portalContainer,
20893
21465
  language: language,
20894
- productImageSrc: productImageSrc,
21466
+ productImageSrc: 'ready' === activation.status && activation.productImageSrc || productImageSrc,
20895
21467
  productName: productName,
20896
21468
  productPrice: productPrice,
20897
- productType: productType,
21469
+ productType: 'ready' === activation.status ? activation.productType ?? productType : productType,
20898
21470
  productUrl: productUrl,
20899
21471
  tryOnClient: tryOnClient,
21472
+ processPhoto: externalGeneration,
21473
+ activation: activation,
21474
+ onRetryActivation: onRetryActivation,
21475
+ onAddToCart: externalAddToCart,
21476
+ showAddToCart: !externalGeneration || Boolean(externalAddToCart),
20900
21477
  showDialogCloseButton: true,
20901
21478
  onClose: onRequestClose
20902
21479
  })
@@ -20904,4 +21481,4 @@ function AiTryOnDialogContent({ language, onRequestClose, portalContainer, produ
20904
21481
  })
20905
21482
  });
20906
21483
  }
20907
- export { AiTryOnDialog, PRODUCT_TYPES, isProductType };
21484
+ export { AiTryOnDialog, AiTryOnGenerationError, PRODUCT_TYPES, SHOPIFY_REQUEST_TIMEOUT_MS, createAppProxyOperations, createShopifyTryOnClient, isProductType, uploadImageToPresignedUrl };