@primestyleai/tryon 3.0.0 → 3.1.1
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 +123 -35
- package/package.json +1 -1
package/dist/react/index.js
CHANGED
|
@@ -187,11 +187,9 @@ function PrimeStyleTryonInner({
|
|
|
187
187
|
const [resultImageUrl, setResultImageUrl] = useState(null);
|
|
188
188
|
const [errorMessage, setErrorMessage] = useState(null);
|
|
189
189
|
const [dragOver, setDragOver] = useState(false);
|
|
190
|
-
const countdownRef = useRef(25);
|
|
191
|
-
const countdownElRef = useRef(null);
|
|
192
|
-
const countdownCircleRef = useRef(null);
|
|
193
190
|
const [sizingMethod, setSizingMethod] = useState(null);
|
|
194
191
|
const [sizingResult, setSizingResult] = useState(null);
|
|
192
|
+
const [sizingLoading, setSizingLoading] = useState(false);
|
|
195
193
|
const [sizeGuide, setSizeGuide] = useState(null);
|
|
196
194
|
const [sizeGuideFetching, setSizeGuideFetching] = useState(false);
|
|
197
195
|
const sizeGuideFetchedRef = useRef(false);
|
|
@@ -202,6 +200,7 @@ function PrimeStyleTryonInner({
|
|
|
202
200
|
const [weightUnit, setWeightUnit] = useState(imperial ? "lbs" : "kg");
|
|
203
201
|
const formRef = useRef({});
|
|
204
202
|
const [formGender, setFormGender] = useState("male");
|
|
203
|
+
const [formKey, setFormKey] = useState(0);
|
|
205
204
|
const [profiles, setProfiles] = useState(() => lsGet("profiles", []));
|
|
206
205
|
const [history, setHistory] = useState(() => lsGet("history", []));
|
|
207
206
|
const [activeProfileId, setActiveProfileId] = useState(null);
|
|
@@ -240,15 +239,33 @@ function PrimeStyleTryonInner({
|
|
|
240
239
|
if (pollingRef.current) clearInterval(pollingRef.current);
|
|
241
240
|
};
|
|
242
241
|
}, [apiUrl]);
|
|
242
|
+
const progressRef = useRef(0);
|
|
243
|
+
const progressBarRef = useRef(null);
|
|
244
|
+
const progressTextRef = useRef(null);
|
|
245
|
+
const progressStatusRef = useRef(null);
|
|
243
246
|
useEffect(() => {
|
|
244
247
|
if (view === "processing") {
|
|
245
|
-
|
|
248
|
+
progressRef.current = 0;
|
|
249
|
+
const statuses = [
|
|
250
|
+
{ at: 0, text: "Preparing your image..." },
|
|
251
|
+
{ at: 15, text: "Analyzing body proportions..." },
|
|
252
|
+
{ at: 30, text: "Matching garment to your photo..." },
|
|
253
|
+
{ at: 50, text: "Generating virtual try-on..." },
|
|
254
|
+
{ at: 75, text: "Refining details..." },
|
|
255
|
+
{ at: 90, text: "Almost there..." }
|
|
256
|
+
];
|
|
246
257
|
const interval = setInterval(() => {
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
258
|
+
const p = progressRef.current;
|
|
259
|
+
const increment = p < 30 ? 1.2 : p < 60 ? 0.8 : p < 80 ? 0.4 : p < 95 ? 0.15 : 0;
|
|
260
|
+
progressRef.current = Math.min(p + increment, 95);
|
|
261
|
+
const val = Math.round(progressRef.current);
|
|
262
|
+
if (progressBarRef.current) progressBarRef.current.style.width = `${val}%`;
|
|
263
|
+
if (progressTextRef.current) progressTextRef.current.textContent = `${val}%`;
|
|
264
|
+
if (progressStatusRef.current) {
|
|
265
|
+
const status = [...statuses].reverse().find((s) => val >= s.at);
|
|
266
|
+
if (status) progressStatusRef.current.textContent = status.text;
|
|
267
|
+
}
|
|
268
|
+
}, 200);
|
|
252
269
|
return () => clearInterval(interval);
|
|
253
270
|
}
|
|
254
271
|
}, [view]);
|
|
@@ -332,6 +349,7 @@ function PrimeStyleTryonInner({
|
|
|
332
349
|
setErrorMessage(null);
|
|
333
350
|
setSizingMethod(null);
|
|
334
351
|
setSizingResult(null);
|
|
352
|
+
setSizingLoading(false);
|
|
335
353
|
setSizeGuide(null);
|
|
336
354
|
setProfileSaved(false);
|
|
337
355
|
formRef.current = {};
|
|
@@ -385,8 +403,12 @@ function PrimeStyleTryonInner({
|
|
|
385
403
|
});
|
|
386
404
|
if (!completedRef.current) {
|
|
387
405
|
completedRef.current = true;
|
|
406
|
+
progressRef.current = 100;
|
|
407
|
+
if (progressBarRef.current) progressBarRef.current.style.width = "100%";
|
|
408
|
+
if (progressTextRef.current) progressTextRef.current.textContent = "100%";
|
|
409
|
+
if (progressStatusRef.current) progressStatusRef.current.textContent = "Complete!";
|
|
388
410
|
cleanupJob();
|
|
389
|
-
setView("result");
|
|
411
|
+
setTimeout(() => setView("result"), 400);
|
|
390
412
|
onComplete?.({ jobId: update.galleryId, imageUrl: update.imageUrl });
|
|
391
413
|
}
|
|
392
414
|
} else if (update.status === "failed") {
|
|
@@ -439,8 +461,13 @@ function PrimeStyleTryonInner({
|
|
|
439
461
|
if (res.ok) {
|
|
440
462
|
const data = await res.json();
|
|
441
463
|
setSizingResult(data);
|
|
464
|
+
} else {
|
|
465
|
+
console.warn("[PrimeStyle] Sizing API error:", res.status, await res.text().catch(() => ""));
|
|
442
466
|
}
|
|
443
|
-
} catch {
|
|
467
|
+
} catch (err) {
|
|
468
|
+
console.warn("[PrimeStyle] Sizing request failed:", err);
|
|
469
|
+
} finally {
|
|
470
|
+
setSizingLoading(false);
|
|
444
471
|
}
|
|
445
472
|
}, [apiUrl, sizingMethod, sizingCountry, heightUnit, weightUnit, sizingUnit, sizeGuide, productTitle]);
|
|
446
473
|
const handleSubmit = useCallback(async () => {
|
|
@@ -453,7 +480,10 @@ function PrimeStyleTryonInner({
|
|
|
453
480
|
}
|
|
454
481
|
completedRef.current = false;
|
|
455
482
|
setView("processing");
|
|
456
|
-
if (sizingMethod)
|
|
483
|
+
if (sizingMethod) {
|
|
484
|
+
setSizingLoading(true);
|
|
485
|
+
submitSizing();
|
|
486
|
+
}
|
|
457
487
|
try {
|
|
458
488
|
const modelImage = await compressImage(selectedFile);
|
|
459
489
|
const response = await apiRef.current.submitTryOn(modelImage, productImage);
|
|
@@ -516,6 +546,7 @@ function PrimeStyleTryonInner({
|
|
|
516
546
|
setErrorMessage(null);
|
|
517
547
|
setSizingMethod(null);
|
|
518
548
|
setSizingResult(null);
|
|
549
|
+
setSizingLoading(false);
|
|
519
550
|
setProfileSaved(false);
|
|
520
551
|
setView("upload");
|
|
521
552
|
}, [previewUrl, cleanupJob]);
|
|
@@ -541,6 +572,11 @@ function PrimeStyleTryonInner({
|
|
|
541
572
|
if (p.fitPreference) fd.fitPreference = p.fitPreference;
|
|
542
573
|
formRef.current = fd;
|
|
543
574
|
setFormGender(fd.gender || "male");
|
|
575
|
+
if (p.country) setSizingCountry(p.country);
|
|
576
|
+
if (p.sizingUnit) setSizingUnit(p.sizingUnit);
|
|
577
|
+
if (p.heightUnit) setHeightUnit(p.heightUnit);
|
|
578
|
+
if (p.weightUnit) setWeightUnit(p.weightUnit);
|
|
579
|
+
setFormKey((k) => k + 1);
|
|
544
580
|
}, [profiles]);
|
|
545
581
|
const saveProfile = useCallback((name) => {
|
|
546
582
|
const id = activeProfileId || `p_${Date.now()}`;
|
|
@@ -563,6 +599,10 @@ function PrimeStyleTryonInner({
|
|
|
563
599
|
shoeUS: formRef.current.shoeUS,
|
|
564
600
|
shoeUK: formRef.current.shoeUK,
|
|
565
601
|
fitPreference: formRef.current.fitPreference,
|
|
602
|
+
country: sizingCountry,
|
|
603
|
+
sizingUnit,
|
|
604
|
+
heightUnit,
|
|
605
|
+
weightUnit,
|
|
566
606
|
createdAt: Date.now()
|
|
567
607
|
};
|
|
568
608
|
setProfiles((prev) => {
|
|
@@ -576,7 +616,7 @@ function PrimeStyleTryonInner({
|
|
|
576
616
|
});
|
|
577
617
|
setActiveProfileId(id);
|
|
578
618
|
setProfileSaved(true);
|
|
579
|
-
}, [activeProfileId]);
|
|
619
|
+
}, [activeProfileId, sizingCountry, sizingUnit, heightUnit, weightUnit]);
|
|
580
620
|
const saveHistoryEntry = useCallback(() => {
|
|
581
621
|
const entry = {
|
|
582
622
|
id: `h_${Date.now()}`,
|
|
@@ -827,7 +867,11 @@ function PrimeStyleTryonInner({
|
|
|
827
867
|
/* @__PURE__ */ jsx("label", { children: "Sizing region" }),
|
|
828
868
|
/* @__PURE__ */ jsx("select", { className: "ps-tryon-country-select", value: sizingCountry, onChange: (e) => setSizingCountry(e.target.value), children: SIZING_COUNTRIES.map((c) => /* @__PURE__ */ jsx("option", { value: c.code, children: c.label }, c.code)) })
|
|
829
869
|
] }),
|
|
830
|
-
sizingMethod === "exact" && /* @__PURE__ */ jsx(Fragment, { children: /* @__PURE__ */ jsx(UnitToggle, { options: [{ label: "cm", value: "cm" }, { label: "in", value: "in" }], value: sizingUnit, onChange:
|
|
870
|
+
sizingMethod === "exact" && /* @__PURE__ */ jsx(Fragment, { children: /* @__PURE__ */ jsx(UnitToggle, { options: [{ label: "cm", value: "cm" }, { label: "in", value: "in" }], value: sizingUnit, onChange: (v) => {
|
|
871
|
+
setSizingUnit(v);
|
|
872
|
+
setHeightUnit(v === "cm" ? "cm" : "ft");
|
|
873
|
+
setWeightUnit(v === "cm" ? "kg" : "lbs");
|
|
874
|
+
} }) }),
|
|
831
875
|
/* @__PURE__ */ jsxs("div", { className: "ps-tryon-input-row", children: [
|
|
832
876
|
/* @__PURE__ */ jsx("label", { children: "Height" }),
|
|
833
877
|
heightUnit === "ft" ? /* @__PURE__ */ jsxs("div", { className: "ps-tryon-height-ft", children: [
|
|
@@ -883,27 +927,34 @@ function PrimeStyleTryonInner({
|
|
|
883
927
|
"Get My Size & Try On ",
|
|
884
928
|
/* @__PURE__ */ jsx(ArrowRightIcon, {})
|
|
885
929
|
] })
|
|
886
|
-
] }, `form-${formGender}-${sizingUnit}-${heightUnit}-${sizingCountry}`);
|
|
930
|
+
] }, `form-${formGender}-${sizingUnit}-${heightUnit}-${sizingCountry}-${formKey}`);
|
|
887
931
|
}
|
|
888
932
|
function ProcessingView() {
|
|
889
933
|
return /* @__PURE__ */ jsxs("div", { className: "ps-tryon-processing", children: [
|
|
890
|
-
/* @__PURE__ */ jsxs("div", { className: "ps-tryon-
|
|
891
|
-
/* @__PURE__ */
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
934
|
+
/* @__PURE__ */ jsxs("div", { className: "ps-tryon-processing-image-wrap", children: [
|
|
935
|
+
previewUrl && /* @__PURE__ */ jsx("img", { src: previewUrl, alt: "Your photo", className: "ps-tryon-processing-model" }),
|
|
936
|
+
/* @__PURE__ */ jsx("div", { className: "ps-tryon-scan-line" }),
|
|
937
|
+
/* @__PURE__ */ jsx("div", { className: "ps-tryon-scan-overlay" })
|
|
938
|
+
] }),
|
|
939
|
+
/* @__PURE__ */ jsxs("div", { className: "ps-tryon-progress-section", children: [
|
|
940
|
+
/* @__PURE__ */ jsx("div", { className: "ps-tryon-progress-bar-wrap", children: /* @__PURE__ */ jsx("div", { ref: progressBarRef, className: "ps-tryon-progress-bar-fill", style: { width: "0%" } }) }),
|
|
941
|
+
/* @__PURE__ */ jsx("span", { ref: progressTextRef, className: "ps-tryon-progress-pct", children: "0%" })
|
|
896
942
|
] }),
|
|
897
|
-
/* @__PURE__ */ jsx("
|
|
943
|
+
/* @__PURE__ */ jsx("div", { ref: progressStatusRef, className: cx("ps-tryon-processing-text", cn.processingText), children: "Preparing your image..." }),
|
|
898
944
|
/* @__PURE__ */ jsx("p", { className: cx("ps-tryon-processing-sub", cn.processingSubText), children: "This usually takes 15-25 seconds" })
|
|
899
945
|
] });
|
|
900
946
|
}
|
|
901
947
|
function ResultView() {
|
|
902
|
-
const
|
|
948
|
+
const hasSizing = !!sizingResult || sizingLoading;
|
|
949
|
+
const hasBoth = !!resultImageUrl && hasSizing;
|
|
903
950
|
const [profileName, setProfileName] = useState("");
|
|
904
951
|
return /* @__PURE__ */ jsxs("div", { className: `ps-tryon-result-layout${hasBoth ? " ps-tryon-result-split" : ""}`, children: [
|
|
905
952
|
resultImageUrl && /* @__PURE__ */ jsx("div", { className: "ps-tryon-result-image-col", children: /* @__PURE__ */ jsx("img", { src: resultImageUrl, alt: "Try-on result", className: cn.resultImage }) }),
|
|
906
953
|
/* @__PURE__ */ jsxs("div", { className: "ps-tryon-result-info-col", children: [
|
|
954
|
+
sizingLoading && !sizingResult && /* @__PURE__ */ jsxs("div", { className: "ps-tryon-size-recommend ps-tryon-sizing-loading", children: [
|
|
955
|
+
/* @__PURE__ */ jsx("div", { className: "ps-tryon-size-loading-spinner" }),
|
|
956
|
+
/* @__PURE__ */ jsx("p", { className: "ps-tryon-size-reasoning", children: "Analyzing your size..." })
|
|
957
|
+
] }),
|
|
907
958
|
sizingResult && /* @__PURE__ */ jsxs("div", { className: "ps-tryon-size-recommend", children: [
|
|
908
959
|
/* @__PURE__ */ jsx("div", { className: "ps-tryon-size-badge", children: sizingResult.recommendedSize }),
|
|
909
960
|
/* @__PURE__ */ jsxs("div", { className: "ps-tryon-size-confidence", children: [
|
|
@@ -1324,19 +1375,49 @@ const STYLES = `
|
|
|
1324
1375
|
.ps-tryon-disclaimer { font-size: 11px; color: #666; margin: 4px 0 0; }
|
|
1325
1376
|
|
|
1326
1377
|
/* Processing */
|
|
1327
|
-
.ps-tryon-processing { text-align: center; padding:
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1378
|
+
.ps-tryon-processing { text-align: center; padding: 24px; display: flex; flex-direction: column; align-items: center; }
|
|
1379
|
+
|
|
1380
|
+
.ps-tryon-processing-image-wrap {
|
|
1381
|
+
position: relative; width: 200px; height: 260px; margin: 0 auto 24px;
|
|
1382
|
+
border-radius: 16px; overflow: hidden; border: 2px solid #333;
|
|
1332
1383
|
}
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
.ps-tryon-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1384
|
+
.ps-tryon-processing-model {
|
|
1385
|
+
width: 100%; height: 100%; object-fit: cover; display: block;
|
|
1386
|
+
}
|
|
1387
|
+
.ps-tryon-scan-overlay {
|
|
1388
|
+
position: absolute; inset: 0;
|
|
1389
|
+
background: linear-gradient(180deg, rgba(187,148,92,0.05) 0%, transparent 40%, transparent 60%, rgba(187,148,92,0.05) 100%);
|
|
1390
|
+
pointer-events: none;
|
|
1391
|
+
}
|
|
1392
|
+
.ps-tryon-scan-line {
|
|
1393
|
+
position: absolute; left: 0; right: 0; height: 3px;
|
|
1394
|
+
background: linear-gradient(90deg, transparent, #bb945c 20%, #d6ba7d 50%, #bb945c 80%, transparent);
|
|
1395
|
+
box-shadow: 0 0 15px rgba(187,148,92,0.6), 0 0 40px rgba(187,148,92,0.2);
|
|
1396
|
+
animation: ps-scan 2.5s ease-in-out infinite;
|
|
1397
|
+
pointer-events: none; z-index: 2;
|
|
1398
|
+
}
|
|
1399
|
+
@keyframes ps-scan {
|
|
1400
|
+
0% { top: 0; opacity: 0; }
|
|
1401
|
+
5% { opacity: 1; }
|
|
1402
|
+
95% { opacity: 1; }
|
|
1403
|
+
100% { top: calc(100% - 3px); opacity: 0; }
|
|
1404
|
+
}
|
|
1405
|
+
|
|
1406
|
+
.ps-tryon-progress-section {
|
|
1407
|
+
display: flex; align-items: center; gap: 12px; width: 100%; max-width: 300px; margin-bottom: 16px;
|
|
1408
|
+
}
|
|
1409
|
+
.ps-tryon-progress-bar-wrap {
|
|
1410
|
+
flex: 1; height: 6px; background: #333; border-radius: 3px; overflow: hidden;
|
|
1411
|
+
}
|
|
1412
|
+
.ps-tryon-progress-bar-fill {
|
|
1413
|
+
height: 100%; background: linear-gradient(90deg, #bb945c, #d6ba7d);
|
|
1414
|
+
border-radius: 3px; transition: width 0.3s ease;
|
|
1415
|
+
}
|
|
1416
|
+
.ps-tryon-progress-pct {
|
|
1417
|
+
font-size: 13px; font-weight: 700; color: #bb945c; min-width: 36px; text-align: right;
|
|
1418
|
+
font-variant-numeric: tabular-nums;
|
|
1419
|
+
}
|
|
1420
|
+
|
|
1340
1421
|
@keyframes ps-scale-in { from { opacity: 0; transform: scale(0.8); } to { opacity: 1; transform: scale(1); } }
|
|
1341
1422
|
.ps-tryon-processing-text { font-size: 14px; color: #fff; margin: 0 0 4px; }
|
|
1342
1423
|
.ps-tryon-processing-sub { font-size: 12px; color: #999; margin: 0; }
|
|
@@ -1366,6 +1447,13 @@ const STYLES = `
|
|
|
1366
1447
|
background: linear-gradient(135deg, #bb945c, #d6ba7d);
|
|
1367
1448
|
color: #111; font-size: 20px; font-weight: 700; margin-bottom: 10px;
|
|
1368
1449
|
}
|
|
1450
|
+
.ps-tryon-sizing-loading { text-align: center; padding: 20px 0; }
|
|
1451
|
+
.ps-tryon-size-loading-spinner {
|
|
1452
|
+
width: 36px; height: 36px; border: 3px solid #333;
|
|
1453
|
+
border-top-color: #bb945c; border-radius: 50%;
|
|
1454
|
+
animation: ps-spin 0.8s linear infinite; margin: 0 auto 12px;
|
|
1455
|
+
}
|
|
1456
|
+
@keyframes ps-spin { to { transform: rotate(360deg); } }
|
|
1369
1457
|
.ps-tryon-size-confidence { font-size: 12px; color: #999; margin-bottom: 8px; }
|
|
1370
1458
|
.ps-conf-high { color: #4ade80; } .ps-conf-medium { color: #bb945c; } .ps-conf-low { color: #ef4444; }
|
|
1371
1459
|
.ps-tryon-size-reasoning { font-size: 13px; color: #ccc; line-height: 1.5; margin-bottom: 12px; }
|