@primestyleai/tryon 5.10.171 → 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 -103
- 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,6 +23663,12 @@ function CreateProfileWizard({ onSave, onCancel, apiUrl, apiKey, onPhotoPreview,
|
|
|
23540
23663
|
}
|
|
23541
23664
|
await minHold;
|
|
23542
23665
|
setEstimating(false);
|
|
23666
|
+
const payload2 = buildImagePayload();
|
|
23667
|
+
if (liveEstimates) {
|
|
23668
|
+
payload2.measurements = liveEstimates;
|
|
23669
|
+
payload2.measurementsUnit = "cm";
|
|
23670
|
+
}
|
|
23671
|
+
onSave(payload2);
|
|
23543
23672
|
return;
|
|
23544
23673
|
}
|
|
23545
23674
|
if (imageStep === "details") {
|
|
@@ -24530,20 +24659,6 @@ function CreateProfileWizard({ onSave, onCancel, apiUrl, apiKey, onPhotoPreview,
|
|
|
24530
24659
|
{ title: t2("FINALIZING RESULT"), desc: t2("Almost done — preparing your recommendation.") }
|
|
24531
24660
|
];
|
|
24532
24661
|
const stage = stages[Math.min(scanStageIdx, stages.length - 1)] ?? stages[0];
|
|
24533
|
-
const SKELETON_CONNECTIONS2 = [
|
|
24534
|
-
["leftShoulder", "rightShoulder"],
|
|
24535
|
-
["leftShoulder", "leftElbow"],
|
|
24536
|
-
["leftElbow", "leftWrist"],
|
|
24537
|
-
["rightShoulder", "rightElbow"],
|
|
24538
|
-
["rightElbow", "rightWrist"],
|
|
24539
|
-
["leftShoulder", "leftHip"],
|
|
24540
|
-
["rightShoulder", "rightHip"],
|
|
24541
|
-
["leftHip", "rightHip"],
|
|
24542
|
-
["leftHip", "leftKnee"],
|
|
24543
|
-
["leftKnee", "leftAnkle"],
|
|
24544
|
-
["rightHip", "rightKnee"],
|
|
24545
|
-
["rightKnee", "rightAnkle"]
|
|
24546
|
-
];
|
|
24547
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: [
|
|
24548
24663
|
/* @__PURE__ */ jsxRuntimeExports.jsxs("div", { style: {
|
|
24549
24664
|
flex: 1,
|
|
@@ -24572,40 +24687,12 @@ function CreateProfileWizard({ onSave, onCancel, apiUrl, apiKey, onPhotoPreview,
|
|
|
24572
24687
|
}
|
|
24573
24688
|
}
|
|
24574
24689
|
),
|
|
24575
|
-
scanLandmarks && /* @__PURE__ */ jsxRuntimeExports.
|
|
24576
|
-
|
|
24690
|
+
scanLandmarks && /* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
24691
|
+
PoseOverlay,
|
|
24577
24692
|
{
|
|
24578
|
-
|
|
24579
|
-
|
|
24580
|
-
|
|
24581
|
-
"aria-hidden": "true",
|
|
24582
|
-
children: [
|
|
24583
|
-
SKELETON_CONNECTIONS2.map(([a, b], i) => {
|
|
24584
|
-
const pa2 = scanLandmarks[a];
|
|
24585
|
-
const pb2 = scanLandmarks[b];
|
|
24586
|
-
if (!pa2 || !pb2) return null;
|
|
24587
|
-
return /* @__PURE__ */ jsxRuntimeExports.jsx(
|
|
24588
|
-
"line",
|
|
24589
|
-
{
|
|
24590
|
-
x1: pa2.x * scanImgDims.w,
|
|
24591
|
-
y1: pa2.y * scanImgDims.h,
|
|
24592
|
-
x2: pb2.x * scanImgDims.w,
|
|
24593
|
-
y2: pb2.y * scanImgDims.h,
|
|
24594
|
-
stroke: "rgba(100,210,255,0.95)",
|
|
24595
|
-
strokeWidth: Math.max(3, scanImgDims.w / 180),
|
|
24596
|
-
strokeLinecap: "round"
|
|
24597
|
-
},
|
|
24598
|
-
`l-${i}`
|
|
24599
|
-
);
|
|
24600
|
-
}),
|
|
24601
|
-
Object.entries(scanLandmarks).map(([key, v2]) => {
|
|
24602
|
-
const p2 = v2;
|
|
24603
|
-
return /* @__PURE__ */ jsxRuntimeExports.jsxs("g", { children: [
|
|
24604
|
-
/* @__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)" }),
|
|
24605
|
-
/* @__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)" })
|
|
24606
|
-
] }, key);
|
|
24607
|
-
})
|
|
24608
|
-
]
|
|
24693
|
+
landmarks: scanLandmarks,
|
|
24694
|
+
width: scanImgDims.w,
|
|
24695
|
+
height: scanImgDims.h
|
|
24609
24696
|
}
|
|
24610
24697
|
)
|
|
24611
24698
|
] }),
|
|
@@ -28434,6 +28521,30 @@ function PrimeStyleTryonInner({
|
|
|
28434
28521
|
sizeGuideData
|
|
28435
28522
|
}) {
|
|
28436
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
|
+
}, []);
|
|
28437
28548
|
const [activeLocale, setActiveLocale] = reactExports.useState(() => locale || "");
|
|
28438
28549
|
reactExports.useEffect(() => {
|
|
28439
28550
|
if (locale !== void 0) setActiveLocale(locale);
|
|
@@ -28562,15 +28673,31 @@ function PrimeStyleTryonInner({
|
|
|
28562
28673
|
if (pollingRef.current) clearInterval(pollingRef.current);
|
|
28563
28674
|
};
|
|
28564
28675
|
}, [apiUrl]);
|
|
28676
|
+
const pickFireCountRef = reactExports.useRef(0);
|
|
28565
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
|
+
});
|
|
28566
28687
|
bestGarmentImageRef.current = null;
|
|
28567
28688
|
if (garmentReferenceImage) {
|
|
28568
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`);
|
|
28569
28695
|
return;
|
|
28570
28696
|
}
|
|
28571
|
-
if (!productImages || productImages.length < 2) return;
|
|
28572
28697
|
const baseUrl = getApiUrl(apiUrl);
|
|
28573
28698
|
const ctrl = new AbortController();
|
|
28699
|
+
const t0 = Date.now();
|
|
28700
|
+
console.log(`[ps-sdk:pick] #${fireN} → POST /api/catalog/pick-best-garment-image`);
|
|
28574
28701
|
fetch(`${baseUrl}/api/catalog/pick-best-garment-image`, {
|
|
28575
28702
|
method: "POST",
|
|
28576
28703
|
headers: { "Content-Type": "application/json" },
|
|
@@ -28579,11 +28706,17 @@ function PrimeStyleTryonInner({
|
|
|
28579
28706
|
}).then((r2) => r2.ok ? r2.json() : null).then((j) => {
|
|
28580
28707
|
if (j?.bestUrl) {
|
|
28581
28708
|
bestGarmentImageRef.current = j.bestUrl;
|
|
28582
|
-
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)`);
|
|
28583
28712
|
}
|
|
28584
|
-
}).catch(() => {
|
|
28713
|
+
}).catch((e) => {
|
|
28714
|
+
console.log(`[ps-sdk:pick] #${fireN} ← aborted/network error in ${Date.now() - t0}ms`, e?.name || e);
|
|
28585
28715
|
});
|
|
28586
|
-
return () =>
|
|
28716
|
+
return () => {
|
|
28717
|
+
ctrl.abort();
|
|
28718
|
+
console.log(`[ps-sdk:pick] #${fireN} cleanup — aborted in-flight fetch`);
|
|
28719
|
+
};
|
|
28587
28720
|
}, [productImages, garmentReferenceImage, apiUrl, productTitle]);
|
|
28588
28721
|
const TARGET_SECONDS2 = 22;
|
|
28589
28722
|
const progressRef = reactExports.useRef(0);
|
|
@@ -29713,12 +29846,12 @@ function PrimeStyleTryonInner({
|
|
|
29713
29846
|
if (noFitFoundRef.current) return;
|
|
29714
29847
|
if (autoTryOnFiredRef.current) return;
|
|
29715
29848
|
if (tryOnProcessing || resultImageUrl) return;
|
|
29716
|
-
if (!sizingResult) return;
|
|
29849
|
+
if (!sizingLoading && !sizingResult) return;
|
|
29717
29850
|
const file = selectedFile || selectedFileRef.current;
|
|
29718
29851
|
if (!file) return;
|
|
29719
29852
|
autoTryOnFiredRef.current = true;
|
|
29720
29853
|
handleTryOnSubmit();
|
|
29721
|
-
}, [view, selectedFile, sizingResult, tryOnProcessing, resultImageUrl, handleTryOnSubmit]);
|
|
29854
|
+
}, [view, selectedFile, sizingLoading, sizingResult, tryOnProcessing, resultImageUrl, handleTryOnSubmit]);
|
|
29722
29855
|
const handleDownload = reactExports.useCallback(() => {
|
|
29723
29856
|
if (!resultImageUrl) return;
|
|
29724
29857
|
if (resultImageUrl.startsWith("data:")) {
|
|
@@ -30353,7 +30486,12 @@ function PrimeStyleTryonInner({
|
|
|
30353
30486
|
const heightUnitVal = newProfile.sizingUnit === "in" ? "in" : "cm";
|
|
30354
30487
|
const weightUnitVal = newProfile.sizingUnit === "in" ? "lbs" : "kg";
|
|
30355
30488
|
const photoBase64 = newProfile.photoBase64;
|
|
30356
|
-
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
|
+
);
|
|
30357
30495
|
if (!hasMeasurements && (photoBase64 || heightVal > 0 && weightVal > 0)) {
|
|
30358
30496
|
setEstimatingProfileIds((prev) => new Set(prev).add(newProfile.id));
|
|
30359
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
|
}
|