@primestyleai/tryon 5.10.170 → 5.10.172
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/react/index.js +4638 -4543
- package/dist/react/index.js.map +1 -1
- package/dist/react/views/PoseOverlay.d.ts +14 -0
- package/dist/storefront/primestyle-tryon.js +241 -104
- package/package.json +4 -1
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { BodyLandmarks } from "../../pose-detect";
|
|
2
|
+
export interface PoseOverlayProps {
|
|
3
|
+
landmarks: BodyLandmarks;
|
|
4
|
+
/** Image width in pixels — used for the SVG viewBox so the overlay
|
|
5
|
+
* scales with the underlying photo via `preserveAspectRatio="xMidYMid meet"`. */
|
|
6
|
+
width: number;
|
|
7
|
+
height: number;
|
|
8
|
+
/** Optional `className`/`style` to position the SVG. Defaults to
|
|
9
|
+
* absolutely-filling the parent container, which is what both
|
|
10
|
+
* consumers want. */
|
|
11
|
+
className?: string;
|
|
12
|
+
style?: React.CSSProperties;
|
|
13
|
+
}
|
|
14
|
+
export declare function PoseOverlay({ landmarks, width, height, className, style }: PoseOverlayProps): import("react/jsx-runtime").JSX.Element;
|
|
@@ -10361,20 +10361,28 @@ function getCachedMediaPipe() {
|
|
|
10361
10361
|
return cachedMP;
|
|
10362
10362
|
}
|
|
10363
10363
|
async function recommendForProduct(input) {
|
|
10364
|
-
const
|
|
10364
|
+
const callId = (() => {
|
|
10365
|
+
if (typeof window === "undefined") return 0;
|
|
10366
|
+
const w2 = window;
|
|
10367
|
+
w2.__psSdkDiag = w2.__psSdkDiag || {};
|
|
10368
|
+
w2.__psSdkDiag.recommendCalls = (w2.__psSdkDiag.recommendCalls || 0) + 1;
|
|
10369
|
+
return w2.__psSdkDiag.recommendCalls;
|
|
10370
|
+
})();
|
|
10371
|
+
const log = (...args) => console.log(`[ps-sdk:recommend#${callId}]`, ...args);
|
|
10372
|
+
const t0 = Date.now();
|
|
10373
|
+
log("ENTER", { productId: input.productId, apiUrl: input.apiUrl, skipCache: true });
|
|
10365
10374
|
const profile = input.profile ?? getActiveProfile();
|
|
10366
10375
|
if (!profile) {
|
|
10367
10376
|
log("no active profile — returning null");
|
|
10368
10377
|
return null;
|
|
10369
10378
|
}
|
|
10370
|
-
log("
|
|
10371
|
-
productId: input.productId,
|
|
10379
|
+
log("profile resolved", {
|
|
10372
10380
|
profileId: profile.id,
|
|
10373
10381
|
profileName: profile.name,
|
|
10374
10382
|
hasMeasurements: !!profile.measurements && Object.keys(profile.measurements || {}).length,
|
|
10375
10383
|
measurementsCount: Object.keys(profile.measurements || {}).length
|
|
10376
10384
|
});
|
|
10377
|
-
log(
|
|
10385
|
+
log(`cache MISS — calling backend (elapsed in pre-flight: ${Date.now() - t0}ms)`);
|
|
10378
10386
|
let apiKey;
|
|
10379
10387
|
try {
|
|
10380
10388
|
apiKey = input.apiKey ?? getApiKey();
|
|
@@ -10389,6 +10397,8 @@ async function recommendForProduct(input) {
|
|
|
10389
10397
|
}
|
|
10390
10398
|
let sizeGuide = null;
|
|
10391
10399
|
if (input.sizeGuideData != null) {
|
|
10400
|
+
const tSg = Date.now();
|
|
10401
|
+
log("→ POST /api/v1/sizing/sizeguide");
|
|
10392
10402
|
try {
|
|
10393
10403
|
const sgRes = await fetch(`${apiUrl}/api/v1/sizing/sizeguide`, {
|
|
10394
10404
|
method: "POST",
|
|
@@ -10400,15 +10410,15 @@ async function recommendForProduct(input) {
|
|
|
10400
10410
|
});
|
|
10401
10411
|
if (sgRes.ok) {
|
|
10402
10412
|
sizeGuide = await sgRes.json();
|
|
10403
|
-
log(
|
|
10413
|
+
log(`← sizeguide OK in ${Date.now() - tSg}ms`, { found: sizeGuide?.found, sectionCount: Object.keys(sizeGuide?.sections || {}).length });
|
|
10404
10414
|
} else {
|
|
10405
|
-
log(
|
|
10415
|
+
log(`← sizeguide FAILED in ${Date.now() - tSg}ms`, sgRes.status, sgRes.statusText);
|
|
10406
10416
|
}
|
|
10407
10417
|
} catch (e) {
|
|
10408
|
-
log(
|
|
10418
|
+
log(`← sizeguide threw in ${Date.now() - tSg}ms`, e);
|
|
10409
10419
|
}
|
|
10410
10420
|
} else {
|
|
10411
|
-
log("no sizeGuideData provided");
|
|
10421
|
+
log("no sizeGuideData provided — skipping /sizing/sizeguide");
|
|
10412
10422
|
}
|
|
10413
10423
|
const measurements = {
|
|
10414
10424
|
gender: profile.gender,
|
|
@@ -10448,7 +10458,8 @@ async function recommendForProduct(input) {
|
|
|
10448
10458
|
if (sizeGuide && sizeGuide.found) {
|
|
10449
10459
|
payload.sizeGuide = sizeGuide;
|
|
10450
10460
|
}
|
|
10451
|
-
log("
|
|
10461
|
+
log("→ POST /api/v1/sizing/recommend", { measurements: Object.keys(measurements), hasSizeGuide: !!payload.sizeGuide });
|
|
10462
|
+
const tRec = Date.now();
|
|
10452
10463
|
let result = null;
|
|
10453
10464
|
try {
|
|
10454
10465
|
const res = await fetch(`${apiUrl}/api/v1/sizing/recommend`, {
|
|
@@ -10457,17 +10468,17 @@ async function recommendForProduct(input) {
|
|
|
10457
10468
|
body: JSON.stringify(payload)
|
|
10458
10469
|
});
|
|
10459
10470
|
if (!res.ok) {
|
|
10460
|
-
log(
|
|
10471
|
+
log(`← recommend FAILED in ${Date.now() - tRec}ms`, res.status, res.statusText);
|
|
10461
10472
|
return null;
|
|
10462
10473
|
}
|
|
10463
10474
|
result = await res.json();
|
|
10464
|
-
log(
|
|
10475
|
+
log(`← recommend OK in ${Date.now() - tRec}ms (total fn ${Date.now() - t0}ms)`, {
|
|
10465
10476
|
recommendedSize: result?.recommendedSize,
|
|
10466
10477
|
sectionKeys: result?.sections ? Object.keys(result.sections) : null,
|
|
10467
10478
|
sections: result?.sections
|
|
10468
10479
|
});
|
|
10469
10480
|
} catch (e) {
|
|
10470
|
-
log(
|
|
10481
|
+
log(`← recommend threw in ${Date.now() - tRec}ms`, e);
|
|
10471
10482
|
return null;
|
|
10472
10483
|
}
|
|
10473
10484
|
if (!result || !result.recommendedSize) {
|
|
@@ -19314,8 +19325,6 @@ function useIsMobile() {
|
|
|
19314
19325
|
return isMobile;
|
|
19315
19326
|
}
|
|
19316
19327
|
const SKELETON_CONNECTIONS$1 = [
|
|
19317
|
-
// Head → torso. Bridges the gap between the nose dot and the shoulder
|
|
19318
|
-
// line — without these the head reads as floating above the body.
|
|
19319
19328
|
["nose", "leftShoulder"],
|
|
19320
19329
|
["nose", "rightShoulder"],
|
|
19321
19330
|
["leftShoulder", "rightShoulder"],
|
|
@@ -19339,13 +19348,18 @@ function isVisible(p2) {
|
|
|
19339
19348
|
if (pt2.x < 1e-3 && pt2.y < 1e-3) return false;
|
|
19340
19349
|
return true;
|
|
19341
19350
|
}
|
|
19342
|
-
function
|
|
19351
|
+
function PoseOverlay({ landmarks, width, height, className, style }) {
|
|
19352
|
+
const longEdge = Math.max(width, height);
|
|
19353
|
+
const stroke = Math.max(2, longEdge / 260);
|
|
19354
|
+
const dotR = Math.max(3, longEdge / 200);
|
|
19343
19355
|
return /* @__PURE__ */ jsxRuntimeExports.jsxs(
|
|
19344
19356
|
"svg",
|
|
19345
19357
|
{
|
|
19346
|
-
className
|
|
19347
|
-
viewBox: `0 0 ${
|
|
19358
|
+
className,
|
|
19359
|
+
viewBox: `0 0 ${width} ${height}`,
|
|
19348
19360
|
preserveAspectRatio: "xMidYMid meet",
|
|
19361
|
+
style: style ?? { position: "absolute", inset: 0, width: "100%", height: "100%", pointerEvents: "none" },
|
|
19362
|
+
"aria-hidden": "true",
|
|
19349
19363
|
children: [
|
|
19350
19364
|
SKELETON_CONNECTIONS$1.map(([a, b], i) => {
|
|
19351
19365
|
const pa2 = landmarks[a];
|
|
@@ -19354,12 +19368,12 @@ function MobileSkeleton({ landmarks, w: w2, h }) {
|
|
|
19354
19368
|
return /* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
19355
19369
|
"line",
|
|
19356
19370
|
{
|
|
19357
|
-
x1: pa2.x *
|
|
19358
|
-
y1: pa2.y *
|
|
19359
|
-
x2: pb2.x *
|
|
19360
|
-
y2: pb2.y *
|
|
19371
|
+
x1: pa2.x * width,
|
|
19372
|
+
y1: pa2.y * height,
|
|
19373
|
+
x2: pb2.x * width,
|
|
19374
|
+
y2: pb2.y * height,
|
|
19361
19375
|
stroke: "rgba(100,210,255,0.9)",
|
|
19362
|
-
strokeWidth:
|
|
19376
|
+
strokeWidth: stroke,
|
|
19363
19377
|
strokeLinecap: "round"
|
|
19364
19378
|
},
|
|
19365
19379
|
`l-${i}`
|
|
@@ -19367,31 +19381,24 @@ function MobileSkeleton({ landmarks, w: w2, h }) {
|
|
|
19367
19381
|
}),
|
|
19368
19382
|
Object.entries(landmarks).filter(([, v2]) => isVisible(v2)).map(([key, v2]) => {
|
|
19369
19383
|
const p2 = v2;
|
|
19370
|
-
return /* @__PURE__ */ jsxRuntimeExports.
|
|
19371
|
-
|
|
19372
|
-
|
|
19373
|
-
|
|
19374
|
-
|
|
19375
|
-
|
|
19376
|
-
|
|
19377
|
-
|
|
19378
|
-
|
|
19379
|
-
|
|
19380
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
19381
|
-
"circle",
|
|
19382
|
-
{
|
|
19383
|
-
cx: p2.x * w2,
|
|
19384
|
-
cy: p2.y * h,
|
|
19385
|
-
r: 9,
|
|
19386
|
-
fill: "rgba(100,210,255,0.95)"
|
|
19387
|
-
}
|
|
19388
|
-
)
|
|
19389
|
-
] }, key);
|
|
19384
|
+
return /* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
19385
|
+
"circle",
|
|
19386
|
+
{
|
|
19387
|
+
cx: p2.x * width,
|
|
19388
|
+
cy: p2.y * height,
|
|
19389
|
+
r: dotR,
|
|
19390
|
+
fill: "rgba(100,210,255,0.95)"
|
|
19391
|
+
},
|
|
19392
|
+
key
|
|
19393
|
+
);
|
|
19390
19394
|
})
|
|
19391
19395
|
]
|
|
19392
19396
|
}
|
|
19393
19397
|
);
|
|
19394
19398
|
}
|
|
19399
|
+
function MobileSkeleton({ landmarks, w: w2, h }) {
|
|
19400
|
+
return /* @__PURE__ */ jsxRuntimeExports.jsx(PoseOverlay, { landmarks, width: w2, height: h, className: "ps-msc-pose-overlay", style: { position: "absolute", inset: 0, width: "100%", height: "100%", pointerEvents: "none" } });
|
|
19401
|
+
}
|
|
19395
19402
|
function MobileScanningView({
|
|
19396
19403
|
previewUrl,
|
|
19397
19404
|
productImage,
|
|
@@ -20255,6 +20262,8 @@ function SectionDetailView({
|
|
|
20255
20262
|
showLines,
|
|
20256
20263
|
onToggleLines,
|
|
20257
20264
|
overlayNode,
|
|
20265
|
+
renderOverlayWithFit,
|
|
20266
|
+
onActiveFitChange,
|
|
20258
20267
|
onImageLoad,
|
|
20259
20268
|
onTryOn,
|
|
20260
20269
|
tryOnProcessing,
|
|
@@ -20525,6 +20534,15 @@ function SectionDetailView({
|
|
|
20525
20534
|
};
|
|
20526
20535
|
});
|
|
20527
20536
|
}, [sectionResult, lengthEntry, userMeasurements, renderRaw, allSizes, displaySize, selectedLength]);
|
|
20537
|
+
reactExports.useEffect(() => {
|
|
20538
|
+
if (!onActiveFitChange) return;
|
|
20539
|
+
onActiveFitChange(fitRows.map((r2) => ({
|
|
20540
|
+
area: r2.area,
|
|
20541
|
+
userNum: r2.userNum ?? 0,
|
|
20542
|
+
chartLabel: r2.chartLabel ?? "",
|
|
20543
|
+
fit: r2.fit ?? "good"
|
|
20544
|
+
})));
|
|
20545
|
+
}, [fitRows, onActiveFitChange]);
|
|
20528
20546
|
const goodCount = fitRows.filter(
|
|
20529
20547
|
(r2) => r2.fit === "good" || r2.fit === "a-bit-tight" || r2.fit === "a-bit-loose"
|
|
20530
20548
|
).length;
|
|
@@ -20669,7 +20687,12 @@ function SectionDetailView({
|
|
|
20669
20687
|
onLoad: onImageLoad
|
|
20670
20688
|
}
|
|
20671
20689
|
),
|
|
20672
|
-
showLines &&
|
|
20690
|
+
showLines && (renderOverlayWithFit ? renderOverlayWithFit(fitRows.map((r2) => ({
|
|
20691
|
+
area: r2.area,
|
|
20692
|
+
userNum: r2.userNum ?? 0,
|
|
20693
|
+
chartLabel: r2.chartLabel ?? "",
|
|
20694
|
+
fit: r2.fit ?? "good"
|
|
20695
|
+
}))) : overlayNode),
|
|
20673
20696
|
tryOnProcessing && tryOnStartedAt != null && /* @__PURE__ */ jsxRuntimeExports.jsx(TryOnGenerationBadge, { tryOnStartedAt, t: t2 }),
|
|
20674
20697
|
isTryOnImage && onToggleLines && /* @__PURE__ */ jsxRuntimeExports.jsxs(
|
|
20675
20698
|
"button",
|
|
@@ -21314,7 +21337,17 @@ function SizeResultView({
|
|
|
21314
21337
|
const [selectedSize, setSelectedSize] = reactExports.useState(null);
|
|
21315
21338
|
const [showFullChart, setShowFullChart] = reactExports.useState(false);
|
|
21316
21339
|
const [showLines, setShowLines] = reactExports.useState(false);
|
|
21340
|
+
const [activeFitRows, setActiveFitRows] = reactExports.useState([]);
|
|
21317
21341
|
const [showVisualFit, setShowVisualFit] = reactExports.useState(false);
|
|
21342
|
+
const [zoomImageUrl, setZoomImageUrl] = reactExports.useState(null);
|
|
21343
|
+
reactExports.useEffect(() => {
|
|
21344
|
+
if (!zoomImageUrl) return;
|
|
21345
|
+
const onKey = (e) => {
|
|
21346
|
+
if (e.key === "Escape") setZoomImageUrl(null);
|
|
21347
|
+
};
|
|
21348
|
+
window.addEventListener("keydown", onKey);
|
|
21349
|
+
return () => window.removeEventListener("keydown", onKey);
|
|
21350
|
+
}, [zoomImageUrl]);
|
|
21318
21351
|
const [poseLines, setPoseLines] = reactExports.useState(null);
|
|
21319
21352
|
const [poseReady, setPoseReady] = reactExports.useState(false);
|
|
21320
21353
|
const [imgDims, setImgDims] = reactExports.useState({ w: 800, h: 1200 });
|
|
@@ -21526,6 +21559,69 @@ function SizeResultView({
|
|
|
21526
21559
|
/* @__PURE__ */ jsxRuntimeExports.jsx("div", { style: { fontSize: isMobile ? "12px" : "0.62vw", lineHeight: 1.45, color: "var(--ps-text-secondary)" }, children: t2("We noticed your entered details didn't match your photo. To give you accurate sizing, we measured your body directly from the photo.") })
|
|
21527
21560
|
] }) : null;
|
|
21528
21561
|
return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "ps-tryon-sr", children: [
|
|
21562
|
+
zoomImageUrl && typeof document !== "undefined" && reactDomExports.createPortal(
|
|
21563
|
+
/* @__PURE__ */ jsxRuntimeExports.jsxs(
|
|
21564
|
+
"div",
|
|
21565
|
+
{
|
|
21566
|
+
onClick: () => setZoomImageUrl(null),
|
|
21567
|
+
style: {
|
|
21568
|
+
position: "fixed",
|
|
21569
|
+
inset: 0,
|
|
21570
|
+
zIndex: 2147483647,
|
|
21571
|
+
background: "rgba(0,0,0,0.92)",
|
|
21572
|
+
display: "flex",
|
|
21573
|
+
alignItems: "center",
|
|
21574
|
+
justifyContent: "center",
|
|
21575
|
+
cursor: "zoom-out"
|
|
21576
|
+
},
|
|
21577
|
+
children: [
|
|
21578
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
21579
|
+
"img",
|
|
21580
|
+
{
|
|
21581
|
+
src: zoomImageUrl,
|
|
21582
|
+
alt: productTitle,
|
|
21583
|
+
style: {
|
|
21584
|
+
maxWidth: "100vw",
|
|
21585
|
+
maxHeight: "100vh",
|
|
21586
|
+
width: "auto",
|
|
21587
|
+
height: "auto",
|
|
21588
|
+
objectFit: "contain",
|
|
21589
|
+
display: "block"
|
|
21590
|
+
},
|
|
21591
|
+
onClick: (e) => e.stopPropagation()
|
|
21592
|
+
}
|
|
21593
|
+
),
|
|
21594
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
21595
|
+
"button",
|
|
21596
|
+
{
|
|
21597
|
+
type: "button",
|
|
21598
|
+
onClick: () => setZoomImageUrl(null),
|
|
21599
|
+
"aria-label": "Close",
|
|
21600
|
+
style: {
|
|
21601
|
+
position: "absolute",
|
|
21602
|
+
top: "1rem",
|
|
21603
|
+
right: "1rem",
|
|
21604
|
+
background: "rgba(255,255,255,0.16)",
|
|
21605
|
+
border: "1px solid rgba(255,255,255,0.32)",
|
|
21606
|
+
color: "white",
|
|
21607
|
+
width: "2.4rem",
|
|
21608
|
+
height: "2.4rem",
|
|
21609
|
+
borderRadius: "50%",
|
|
21610
|
+
fontSize: "1.4rem",
|
|
21611
|
+
lineHeight: 1,
|
|
21612
|
+
cursor: "pointer",
|
|
21613
|
+
display: "flex",
|
|
21614
|
+
alignItems: "center",
|
|
21615
|
+
justifyContent: "center"
|
|
21616
|
+
},
|
|
21617
|
+
children: "×"
|
|
21618
|
+
}
|
|
21619
|
+
)
|
|
21620
|
+
]
|
|
21621
|
+
}
|
|
21622
|
+
),
|
|
21623
|
+
document.body
|
|
21624
|
+
),
|
|
21529
21625
|
isMobile && isSizingOnly && /* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
21530
21626
|
MobileScanningView,
|
|
21531
21627
|
{
|
|
@@ -21660,7 +21756,9 @@ function SizeResultView({
|
|
|
21660
21756
|
src: tryOnProcessing && previewUrl ? previewUrl : resultImageUrl || productImage,
|
|
21661
21757
|
alt: productTitle,
|
|
21662
21758
|
className: "ps-tryon-v2-bg-img",
|
|
21663
|
-
onLoad: handleImgLoad
|
|
21759
|
+
onLoad: handleImgLoad,
|
|
21760
|
+
style: resultImageUrl && !tryOnProcessing ? { cursor: "zoom-in" } : void 0,
|
|
21761
|
+
onClick: resultImageUrl && !tryOnProcessing ? () => setZoomImageUrl(resultImageUrl) : void 0
|
|
21664
21762
|
}
|
|
21665
21763
|
),
|
|
21666
21764
|
tryOnProcessing && /* @__PURE__ */ jsxRuntimeExports.jsx(TryOnGenerationBadge, { tryOnStartedAt: tryOnStartedAt ?? null, t: t2 }),
|
|
@@ -21759,7 +21857,17 @@ function SizeResultView({
|
|
|
21759
21857
|
/* ── Desktop section picker: split layout — image left, image cards right ── */
|
|
21760
21858
|
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "ps-tryon-v2", children: [
|
|
21761
21859
|
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "ps-tryon-v2-bg", style: { position: "relative" }, children: [
|
|
21762
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
21860
|
+
/* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
21861
|
+
"img",
|
|
21862
|
+
{
|
|
21863
|
+
src: tryOnProcessing && previewUrl ? previewUrl : resultImageUrl || productImage,
|
|
21864
|
+
alt: productTitle,
|
|
21865
|
+
className: "ps-tryon-v2-bg-img",
|
|
21866
|
+
onLoad: handleImgLoad,
|
|
21867
|
+
style: resultImageUrl && !tryOnProcessing ? { cursor: "zoom-in" } : void 0,
|
|
21868
|
+
onClick: resultImageUrl && !tryOnProcessing ? () => setZoomImageUrl(resultImageUrl) : void 0
|
|
21869
|
+
}
|
|
21870
|
+
),
|
|
21763
21871
|
tryOnProcessing && /* @__PURE__ */ jsxRuntimeExports.jsx(TryOnGenerationBadge, { tryOnStartedAt: tryOnStartedAt ?? null, t: t2 }),
|
|
21764
21872
|
/* @__PURE__ */ jsxRuntimeExports.jsx(RegenScanOverlay, { active: !!tryOnProcessing }),
|
|
21765
21873
|
resultImageUrl && !tryOnProcessing && poseReady && poseLines && /* @__PURE__ */ jsxRuntimeExports.jsx(MeasurementOverlay, { lines: poseLines, fitRows: (() => {
|
|
@@ -21932,6 +22040,7 @@ function SizeResultView({
|
|
|
21932
22040
|
isMobile: true,
|
|
21933
22041
|
renderRaw: isAccessory,
|
|
21934
22042
|
allSizes: sizingResult?.allSizes,
|
|
22043
|
+
onActiveFitChange: setActiveFitRows,
|
|
21935
22044
|
isTryOnImage: !!resultImageUrl,
|
|
21936
22045
|
showLines,
|
|
21937
22046
|
onToggleLines: isAccessory ? void 0 : () => setShowLines(!showLines),
|
|
@@ -21959,12 +22068,23 @@ function SizeResultView({
|
|
|
21959
22068
|
src: tryOnProcessing && previewUrl ? previewUrl : resultImageUrl || productImage,
|
|
21960
22069
|
alt: productTitle,
|
|
21961
22070
|
className: "ps-tryon-v2-bg-img",
|
|
21962
|
-
onLoad: handleImgLoad
|
|
22071
|
+
onLoad: handleImgLoad,
|
|
22072
|
+
style: resultImageUrl && !tryOnProcessing ? { cursor: "zoom-in" } : void 0,
|
|
22073
|
+
onClick: resultImageUrl && !tryOnProcessing ? () => setZoomImageUrl(resultImageUrl) : void 0
|
|
21963
22074
|
}
|
|
21964
22075
|
),
|
|
21965
22076
|
tryOnProcessing && /* @__PURE__ */ jsxRuntimeExports.jsx(TryOnGenerationBadge, { tryOnStartedAt: tryOnStartedAt ?? null, t: t2 }),
|
|
21966
22077
|
/* @__PURE__ */ jsxRuntimeExports.jsx(RegenScanOverlay, { active: !!tryOnProcessing }),
|
|
21967
|
-
resultImageUrl && !tryOnProcessing && poseReady && poseLines && /* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
22078
|
+
resultImageUrl && !tryOnProcessing && poseReady && poseLines && /* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
22079
|
+
MeasurementOverlay,
|
|
22080
|
+
{
|
|
22081
|
+
lines: poseLines,
|
|
22082
|
+
fitRows: activeFitRows.length > 0 ? activeFitRows : (sizingResult?.matchDetails || []).map((m2) => ({ area: m2.measurement, userNum: parseFloat(m2.userValue) || 0, chartLabel: m2.chartRange || "", fit: m2.fit })),
|
|
22083
|
+
show: showLines,
|
|
22084
|
+
imgWidth: imgDims.w,
|
|
22085
|
+
imgHeight: imgDims.h
|
|
22086
|
+
}
|
|
22087
|
+
),
|
|
21968
22088
|
resultImageUrl && !tryOnProcessing && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { style: { position: "absolute", bottom: "0.5vw", left: "0.5vw", zIndex: 3, display: "flex", flexDirection: "column", gap: "0.3vw" }, children: [
|
|
21969
22089
|
!isAccessory && /* @__PURE__ */ jsxRuntimeExports.jsx("button", { className: "ps-tryon-sr-glass-btn", onClick: () => setShowLines(!showLines), children: showLines ? t2("Hide Fit") : t2("Show Fit") }),
|
|
21970
22090
|
/* @__PURE__ */ jsxRuntimeExports.jsx("button", { className: "ps-tryon-sr-glass-btn", onClick: handleDownload, children: t2("Download") })
|
|
@@ -22001,7 +22121,8 @@ function SizeResultView({
|
|
|
22001
22121
|
tryOnStartedAt,
|
|
22002
22122
|
t: t2,
|
|
22003
22123
|
renderRaw: isAccessory,
|
|
22004
|
-
allSizes: sizingResult?.allSizes
|
|
22124
|
+
allSizes: sizingResult?.allSizes,
|
|
22125
|
+
onActiveFitChange: setActiveFitRows
|
|
22005
22126
|
}
|
|
22006
22127
|
)
|
|
22007
22128
|
) : sizingResult?.found === false ? (
|
|
@@ -23272,7 +23393,7 @@ function CreateProfileWizard({ onSave, onCancel, apiUrl, apiKey, onPhotoPreview,
|
|
|
23272
23393
|
setPhotoStatus(t2("Analyzing photo…"));
|
|
23273
23394
|
try {
|
|
23274
23395
|
const ageCheckPromise = apiUrl && apiKey ? checkAgeBeforeUpload(file, apiUrl, apiKey) : Promise.resolve({ isAdult: true, confidence: "low" });
|
|
23275
|
-
const compressPromise = compressImage(file);
|
|
23396
|
+
const compressPromise = compressImage(file, { maxDimension: 1280, quality: 0.92 });
|
|
23276
23397
|
const ageResult = await ageCheckPromise;
|
|
23277
23398
|
if (!ageResult.isAdult) {
|
|
23278
23399
|
const reason = ageResult.reasoning?.trim() || t2("This photo appears to be of a minor. Please upload a photo of someone 18 or older.");
|
|
@@ -23520,6 +23641,7 @@ function CreateProfileWizard({ onSave, onCancel, apiUrl, apiKey, onPhotoPreview,
|
|
|
23520
23641
|
setEstimating(true);
|
|
23521
23642
|
const MIN_HOLD_MS = 6e3;
|
|
23522
23643
|
const minHold = new Promise((r2) => setTimeout(r2, MIN_HOLD_MS));
|
|
23644
|
+
let liveEstimates = null;
|
|
23523
23645
|
if (onEstimate && photoBase64) {
|
|
23524
23646
|
const heightRaw = unit === "in" ? (parseInt(heightFt, 10) || 0) * 12 + (parseInt(heightInch, 10) || 0) : parseFloat(heightVal);
|
|
23525
23647
|
try {
|
|
@@ -23533,6 +23655,7 @@ function CreateProfileWizard({ onSave, onCancel, apiUrl, apiKey, onPhotoPreview,
|
|
|
23533
23655
|
...a > 0 ? { age: a } : {}
|
|
23534
23656
|
});
|
|
23535
23657
|
if (result?.estimates) {
|
|
23658
|
+
liveEstimates = result.estimates;
|
|
23536
23659
|
setEstimateResults(result.estimates);
|
|
23537
23660
|
}
|
|
23538
23661
|
} catch {
|
|
@@ -23540,7 +23663,12 @@ function CreateProfileWizard({ onSave, onCancel, apiUrl, apiKey, onPhotoPreview,
|
|
|
23540
23663
|
}
|
|
23541
23664
|
await minHold;
|
|
23542
23665
|
setEstimating(false);
|
|
23543
|
-
|
|
23666
|
+
const payload2 = buildImagePayload();
|
|
23667
|
+
if (liveEstimates) {
|
|
23668
|
+
payload2.measurements = liveEstimates;
|
|
23669
|
+
payload2.measurementsUnit = "cm";
|
|
23670
|
+
}
|
|
23671
|
+
onSave(payload2);
|
|
23544
23672
|
return;
|
|
23545
23673
|
}
|
|
23546
23674
|
if (imageStep === "details") {
|
|
@@ -24531,20 +24659,6 @@ function CreateProfileWizard({ onSave, onCancel, apiUrl, apiKey, onPhotoPreview,
|
|
|
24531
24659
|
{ title: t2("FINALIZING RESULT"), desc: t2("Almost done — preparing your recommendation.") }
|
|
24532
24660
|
];
|
|
24533
24661
|
const stage = stages[Math.min(scanStageIdx, stages.length - 1)] ?? stages[0];
|
|
24534
|
-
const SKELETON_CONNECTIONS2 = [
|
|
24535
|
-
["leftShoulder", "rightShoulder"],
|
|
24536
|
-
["leftShoulder", "leftElbow"],
|
|
24537
|
-
["leftElbow", "leftWrist"],
|
|
24538
|
-
["rightShoulder", "rightElbow"],
|
|
24539
|
-
["rightElbow", "rightWrist"],
|
|
24540
|
-
["leftShoulder", "leftHip"],
|
|
24541
|
-
["rightShoulder", "rightHip"],
|
|
24542
|
-
["leftHip", "rightHip"],
|
|
24543
|
-
["leftHip", "leftKnee"],
|
|
24544
|
-
["leftKnee", "leftAnkle"],
|
|
24545
|
-
["rightHip", "rightKnee"],
|
|
24546
|
-
["rightKnee", "rightAnkle"]
|
|
24547
|
-
];
|
|
24548
24662
|
return /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "ps-cpw-fade-in", style: { display: "flex", flexDirection: "column", flex: 1, overflow: "auto" }, children: estimating ? /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { style: { display: "flex", gap: "1.2vw", padding: "1.5vw", flex: 1 }, children: [
|
|
24549
24663
|
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { style: {
|
|
24550
24664
|
flex: 1,
|
|
@@ -24573,40 +24687,12 @@ function CreateProfileWizard({ onSave, onCancel, apiUrl, apiKey, onPhotoPreview,
|
|
|
24573
24687
|
}
|
|
24574
24688
|
}
|
|
24575
24689
|
),
|
|
24576
|
-
scanLandmarks && /* @__PURE__ */ jsxRuntimeExports.
|
|
24577
|
-
|
|
24690
|
+
scanLandmarks && /* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
24691
|
+
PoseOverlay,
|
|
24578
24692
|
{
|
|
24579
|
-
|
|
24580
|
-
|
|
24581
|
-
|
|
24582
|
-
"aria-hidden": "true",
|
|
24583
|
-
children: [
|
|
24584
|
-
SKELETON_CONNECTIONS2.map(([a, b], i) => {
|
|
24585
|
-
const pa2 = scanLandmarks[a];
|
|
24586
|
-
const pb2 = scanLandmarks[b];
|
|
24587
|
-
if (!pa2 || !pb2) return null;
|
|
24588
|
-
return /* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
24589
|
-
"line",
|
|
24590
|
-
{
|
|
24591
|
-
x1: pa2.x * scanImgDims.w,
|
|
24592
|
-
y1: pa2.y * scanImgDims.h,
|
|
24593
|
-
x2: pb2.x * scanImgDims.w,
|
|
24594
|
-
y2: pb2.y * scanImgDims.h,
|
|
24595
|
-
stroke: "rgba(100,210,255,0.95)",
|
|
24596
|
-
strokeWidth: Math.max(3, scanImgDims.w / 180),
|
|
24597
|
-
strokeLinecap: "round"
|
|
24598
|
-
},
|
|
24599
|
-
`l-${i}`
|
|
24600
|
-
);
|
|
24601
|
-
}),
|
|
24602
|
-
Object.entries(scanLandmarks).map(([key, v2]) => {
|
|
24603
|
-
const p2 = v2;
|
|
24604
|
-
return /* @__PURE__ */ jsxRuntimeExports.jsxs("g", { children: [
|
|
24605
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx("circle", { cx: p2.x * scanImgDims.w, cy: p2.y * scanImgDims.h, r: Math.max(7, scanImgDims.w / 75), fill: "rgba(100,210,255,0.25)" }),
|
|
24606
|
-
/* @__PURE__ */ jsxRuntimeExports.jsx("circle", { cx: p2.x * scanImgDims.w, cy: p2.y * scanImgDims.h, r: Math.max(4, scanImgDims.w / 120), fill: "rgba(100,210,255,0.95)" })
|
|
24607
|
-
] }, key);
|
|
24608
|
-
})
|
|
24609
|
-
]
|
|
24693
|
+
landmarks: scanLandmarks,
|
|
24694
|
+
width: scanImgDims.w,
|
|
24695
|
+
height: scanImgDims.h
|
|
24610
24696
|
}
|
|
24611
24697
|
)
|
|
24612
24698
|
] }),
|
|
@@ -28435,6 +28521,30 @@ function PrimeStyleTryonInner({
|
|
|
28435
28521
|
sizeGuideData
|
|
28436
28522
|
}) {
|
|
28437
28523
|
const effectiveProductId = productId || productImage;
|
|
28524
|
+
const renderCountRef = reactExports.useRef(0);
|
|
28525
|
+
const mountIdRef = reactExports.useRef(null);
|
|
28526
|
+
if (mountIdRef.current === null && typeof window !== "undefined") {
|
|
28527
|
+
const w2 = window;
|
|
28528
|
+
w2.__psSdkDiag = w2.__psSdkDiag || { mounts: 0, effectFires: 0, tryonMounts: 0, tryonRenders: 0 };
|
|
28529
|
+
w2.__psSdkDiag.tryonMounts += 1;
|
|
28530
|
+
mountIdRef.current = w2.__psSdkDiag.tryonMounts;
|
|
28531
|
+
}
|
|
28532
|
+
renderCountRef.current += 1;
|
|
28533
|
+
if (typeof window !== "undefined") {
|
|
28534
|
+
window.__psSdkDiag.tryonRenders = (window.__psSdkDiag.tryonRenders || 0) + 1;
|
|
28535
|
+
}
|
|
28536
|
+
console.log(
|
|
28537
|
+
`[ps-sdk:tryon#${mountIdRef.current}] render #${renderCountRef.current}`,
|
|
28538
|
+
{
|
|
28539
|
+
productId: effectiveProductId,
|
|
28540
|
+
productImagesLen: productImages?.length ?? 0,
|
|
28541
|
+
productImagesRef: productImages
|
|
28542
|
+
}
|
|
28543
|
+
);
|
|
28544
|
+
reactExports.useEffect(() => {
|
|
28545
|
+
console.log(`[ps-sdk:tryon#${mountIdRef.current}] MOUNT`);
|
|
28546
|
+
return () => console.log(`[ps-sdk:tryon#${mountIdRef.current}] UNMOUNT`);
|
|
28547
|
+
}, []);
|
|
28438
28548
|
const [activeLocale, setActiveLocale] = reactExports.useState(() => locale || "");
|
|
28439
28549
|
reactExports.useEffect(() => {
|
|
28440
28550
|
if (locale !== void 0) setActiveLocale(locale);
|
|
@@ -28563,15 +28673,31 @@ function PrimeStyleTryonInner({
|
|
|
28563
28673
|
if (pollingRef.current) clearInterval(pollingRef.current);
|
|
28564
28674
|
};
|
|
28565
28675
|
}, [apiUrl]);
|
|
28676
|
+
const pickFireCountRef = reactExports.useRef(0);
|
|
28566
28677
|
reactExports.useEffect(() => {
|
|
28678
|
+
pickFireCountRef.current += 1;
|
|
28679
|
+
const fireN = pickFireCountRef.current;
|
|
28680
|
+
const reason = !productImages ? "no productImages" : productImages.length < 2 ? `productImages.length=${productImages.length}` : `len=${productImages.length} firstUrl=${productImages[0]?.slice(0, 40)}`;
|
|
28681
|
+
console.log(`[ps-sdk:pick] effect FIRE #${fireN} — ${reason}`, {
|
|
28682
|
+
productImagesRef: productImages,
|
|
28683
|
+
garmentReferenceImageSet: !!garmentReferenceImage,
|
|
28684
|
+
apiUrl,
|
|
28685
|
+
productTitle
|
|
28686
|
+
});
|
|
28567
28687
|
bestGarmentImageRef.current = null;
|
|
28568
28688
|
if (garmentReferenceImage) {
|
|
28569
28689
|
bestGarmentImageRef.current = garmentReferenceImage;
|
|
28690
|
+
console.log(`[ps-sdk:pick] #${fireN} early-return — using prop override`);
|
|
28691
|
+
return;
|
|
28692
|
+
}
|
|
28693
|
+
if (!productImages || productImages.length < 2) {
|
|
28694
|
+
console.log(`[ps-sdk:pick] #${fireN} early-return — too few images, no /pick-best call`);
|
|
28570
28695
|
return;
|
|
28571
28696
|
}
|
|
28572
|
-
if (!productImages || productImages.length < 2) return;
|
|
28573
28697
|
const baseUrl = getApiUrl(apiUrl);
|
|
28574
28698
|
const ctrl = new AbortController();
|
|
28699
|
+
const t0 = Date.now();
|
|
28700
|
+
console.log(`[ps-sdk:pick] #${fireN} → POST /api/catalog/pick-best-garment-image`);
|
|
28575
28701
|
fetch(`${baseUrl}/api/catalog/pick-best-garment-image`, {
|
|
28576
28702
|
method: "POST",
|
|
28577
28703
|
headers: { "Content-Type": "application/json" },
|
|
@@ -28580,11 +28706,17 @@ function PrimeStyleTryonInner({
|
|
|
28580
28706
|
}).then((r2) => r2.ok ? r2.json() : null).then((j) => {
|
|
28581
28707
|
if (j?.bestUrl) {
|
|
28582
28708
|
bestGarmentImageRef.current = j.bestUrl;
|
|
28583
|
-
console.log(`[ps-sdk]
|
|
28709
|
+
console.log(`[ps-sdk:pick] #${fireN} ← OK in ${Date.now() - t0}ms → ${j.bestUrl}`);
|
|
28710
|
+
} else {
|
|
28711
|
+
console.log(`[ps-sdk:pick] #${fireN} ← no bestUrl in response (${Date.now() - t0}ms)`);
|
|
28584
28712
|
}
|
|
28585
|
-
}).catch(() => {
|
|
28713
|
+
}).catch((e) => {
|
|
28714
|
+
console.log(`[ps-sdk:pick] #${fireN} ← aborted/network error in ${Date.now() - t0}ms`, e?.name || e);
|
|
28586
28715
|
});
|
|
28587
|
-
return () =>
|
|
28716
|
+
return () => {
|
|
28717
|
+
ctrl.abort();
|
|
28718
|
+
console.log(`[ps-sdk:pick] #${fireN} cleanup — aborted in-flight fetch`);
|
|
28719
|
+
};
|
|
28588
28720
|
}, [productImages, garmentReferenceImage, apiUrl, productTitle]);
|
|
28589
28721
|
const TARGET_SECONDS2 = 22;
|
|
28590
28722
|
const progressRef = reactExports.useRef(0);
|
|
@@ -29714,12 +29846,12 @@ function PrimeStyleTryonInner({
|
|
|
29714
29846
|
if (noFitFoundRef.current) return;
|
|
29715
29847
|
if (autoTryOnFiredRef.current) return;
|
|
29716
29848
|
if (tryOnProcessing || resultImageUrl) return;
|
|
29717
|
-
if (!sizingResult) return;
|
|
29849
|
+
if (!sizingLoading && !sizingResult) return;
|
|
29718
29850
|
const file = selectedFile || selectedFileRef.current;
|
|
29719
29851
|
if (!file) return;
|
|
29720
29852
|
autoTryOnFiredRef.current = true;
|
|
29721
29853
|
handleTryOnSubmit();
|
|
29722
|
-
}, [view, selectedFile, sizingResult, tryOnProcessing, resultImageUrl, handleTryOnSubmit]);
|
|
29854
|
+
}, [view, selectedFile, sizingLoading, sizingResult, tryOnProcessing, resultImageUrl, handleTryOnSubmit]);
|
|
29723
29855
|
const handleDownload = reactExports.useCallback(() => {
|
|
29724
29856
|
if (!resultImageUrl) return;
|
|
29725
29857
|
if (resultImageUrl.startsWith("data:")) {
|
|
@@ -30354,7 +30486,12 @@ function PrimeStyleTryonInner({
|
|
|
30354
30486
|
const heightUnitVal = newProfile.sizingUnit === "in" ? "in" : "cm";
|
|
30355
30487
|
const weightUnitVal = newProfile.sizingUnit === "in" ? "lbs" : "kg";
|
|
30356
30488
|
const photoBase64 = newProfile.photoBase64;
|
|
30357
|
-
const
|
|
30489
|
+
const measurementsObj = newProfile.measurements;
|
|
30490
|
+
const hasMeasurements = !!measurementsObj && Object.keys(measurementsObj).length > 0 && typeof measurementsObj.neckCircumference === "number" && measurementsObj.neckCircumference > 0;
|
|
30491
|
+
console.log(
|
|
30492
|
+
"[ps-sdk:saveProfile] hasMeasurements=" + hasMeasurements,
|
|
30493
|
+
measurementsObj ? { neck: measurementsObj.neckCircumference, chest: measurementsObj.chest, photoBase64Set: !!photoBase64 } : null
|
|
30494
|
+
);
|
|
30358
30495
|
if (!hasMeasurements && (photoBase64 || heightVal > 0 && weightVal > 0)) {
|
|
30359
30496
|
setEstimatingProfileIds((prev) => new Set(prev).add(newProfile.id));
|
|
30360
30497
|
const landmarksPromise = photoBase64 ? detectBodyLandmarks(photoBase64).catch(() => null) : Promise.resolve(null);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@primestyleai/tryon",
|
|
3
|
-
"version": "5.10.
|
|
3
|
+
"version": "5.10.172",
|
|
4
4
|
"description": "PrimeStyle Virtual Try-On SDK — React component & Web Component",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/primestyle-tryon.js",
|
|
@@ -64,5 +64,8 @@
|
|
|
64
64
|
"terser": "^5.31.0",
|
|
65
65
|
"typescript": "^5.5.0",
|
|
66
66
|
"vite": "^5.4.0"
|
|
67
|
+
},
|
|
68
|
+
"dependencies": {
|
|
69
|
+
"@primestyleai/tryon": "file:primestyleai-tryon-5.10.171.tgz"
|
|
67
70
|
}
|
|
68
71
|
}
|