@primestyleai/tryon 1.2.1 → 1.2.2
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/README.md +13 -13
- package/dist/PrimeStyleTryon.d.ts +3 -0
- package/dist/{image-utils-usff6Qu8.js → image-utils-DtJVYQIA.js} +1 -1
- package/dist/primestyle-tryon.js +25 -3
- package/dist/react/index.d.ts +1 -1
- package/dist/react/index.js +64 -26
- package/dist/types.d.ts +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<p align="center">
|
|
2
|
-
<a href="https://
|
|
2
|
+
<a href="https://myaifitting.com">
|
|
3
3
|
<img src="https://cdn.jsdelivr.net/npm/@primestyleai/tryon/logo.svg" alt="PrimeStyle AI" width="180" />
|
|
4
4
|
</a>
|
|
5
5
|
</p>
|
|
@@ -22,10 +22,10 @@
|
|
|
22
22
|
</p>
|
|
23
23
|
|
|
24
24
|
<p align="center">
|
|
25
|
-
<a href="https://
|
|
26
|
-
<a href="https://
|
|
27
|
-
<a href="https://
|
|
28
|
-
<a href="https://
|
|
25
|
+
<a href="https://myaifitting.com/developer/demo">Live Demo</a> •
|
|
26
|
+
<a href="https://myaifitting.com/docs">Documentation</a> •
|
|
27
|
+
<a href="https://myaifitting.com/developer/dashboard/keys">Get API Key</a> •
|
|
28
|
+
<a href="https://myaifitting.com/pricing">Pricing</a>
|
|
29
29
|
</p>
|
|
30
30
|
|
|
31
31
|
---
|
|
@@ -51,7 +51,7 @@ npm install @primestyleai/tryon
|
|
|
51
51
|
|
|
52
52
|
### 2. Set Your API Key
|
|
53
53
|
|
|
54
|
-
Get your key from the [Developer Dashboard](https://
|
|
54
|
+
Get your key from the [Developer Dashboard](https://myaifitting.com/developer/dashboard/keys) and add it to your `.env.local`:
|
|
55
55
|
|
|
56
56
|
```env
|
|
57
57
|
NEXT_PUBLIC_PRIMESTYLE_API_KEY=ps_live_your_key_here
|
|
@@ -324,7 +324,7 @@ Priority: Tailwind/classNames classes > CSS variables (buttonStyles/modalStyles)
|
|
|
324
324
|
| Variable | Required | Default | Description |
|
|
325
325
|
|----------|----------|---------|-------------|
|
|
326
326
|
| `NEXT_PUBLIC_PRIMESTYLE_API_KEY` | Yes | — | Your PrimeStyle API key |
|
|
327
|
-
| `NEXT_PUBLIC_PRIMESTYLE_API_URL` | No | `https://
|
|
327
|
+
| `NEXT_PUBLIC_PRIMESTYLE_API_URL` | No | `https://myaifitting.com` | Custom API endpoint |
|
|
328
328
|
|
|
329
329
|
The component reads these automatically — no need to pass them as props.
|
|
330
330
|
|
|
@@ -340,7 +340,7 @@ Each virtual try-on consumes **tokens** from your account balance.
|
|
|
340
340
|
| **Token packs** | Pay-as-you-go, never expire |
|
|
341
341
|
| **Monthly plans** | Volume discounts for high-traffic stores |
|
|
342
342
|
|
|
343
|
-
Manage your balance and purchase tokens at [primestyleai.com/dashboard/billing](https://
|
|
343
|
+
Manage your balance and purchase tokens at [primestyleai.com/developer/dashboard/billing](https://myaifitting.com/developer/dashboard/billing).
|
|
344
344
|
|
|
345
345
|
If a try-on fails, tokens are **automatically refunded** to your account.
|
|
346
346
|
|
|
@@ -357,7 +357,7 @@ onComplete={(result) => {
|
|
|
357
357
|
|
|
358
358
|
## API Key Management
|
|
359
359
|
|
|
360
|
-
Create and manage your API keys at the [Developer Dashboard](https://
|
|
360
|
+
Create and manage your API keys at the [Developer Dashboard](https://myaifitting.com/developer/dashboard/keys).
|
|
361
361
|
|
|
362
362
|
- Keys start with `ps_live_` and are shown **once** at creation
|
|
363
363
|
- Set **allowed domains** to restrict where your key can be used
|
|
@@ -421,13 +421,13 @@ export default function ProductPage({ product }) {
|
|
|
421
421
|
|
|
422
422
|
## Need Help?
|
|
423
423
|
|
|
424
|
-
- [Live Demo](https://
|
|
425
|
-
- [Documentation](https://
|
|
426
|
-
- [Dashboard](https://
|
|
424
|
+
- [Live Demo](https://myaifitting.com/developer/demo) — See it in action
|
|
425
|
+
- [Documentation](https://myaifitting.com/docs) — Full API reference
|
|
426
|
+
- [Dashboard](https://myaifitting.com/developer/dashboard/keys) — Manage keys & tokens
|
|
427
427
|
- [Contact](mailto:support@primestyleai.com) — We're here to help
|
|
428
428
|
|
|
429
429
|
---
|
|
430
430
|
|
|
431
431
|
<p align="center">
|
|
432
|
-
Built with care by <a href="https://
|
|
432
|
+
Built with care by <a href="https://myaifitting.com">PrimeStyle AI</a>
|
|
433
433
|
</p>
|
|
@@ -22,6 +22,9 @@ export declare class PrimeStyleTryon extends HTMLElement {
|
|
|
22
22
|
setButtonStyles(styles: ButtonStyles): void;
|
|
23
23
|
/** Configure modal appearance programmatically */
|
|
24
24
|
setModalStyles(styles: ModalStyles): void;
|
|
25
|
+
private savedScrollY;
|
|
26
|
+
private lockBodyScroll;
|
|
27
|
+
private unlockBodyScroll;
|
|
25
28
|
/** Open the try-on modal */
|
|
26
29
|
open(): void;
|
|
27
30
|
/** Close the try-on modal */
|
package/dist/primestyle-tryon.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { A as ApiClient, S as SseClient, i as isValidImageFile, c as compressImage } from "./image-utils-
|
|
2
|
-
import { P } from "./image-utils-
|
|
1
|
+
import { A as ApiClient, S as SseClient, i as isValidImageFile, c as compressImage } from "./image-utils-DtJVYQIA.js";
|
|
2
|
+
import { P } from "./image-utils-DtJVYQIA.js";
|
|
3
3
|
function detectProductImage() {
|
|
4
4
|
const ogImage = document.querySelector(
|
|
5
5
|
'meta[property="og:image"]'
|
|
@@ -459,6 +459,7 @@ class PrimeStyleTryon extends HTMLElement {
|
|
|
459
459
|
this.productImageUrl = null;
|
|
460
460
|
this.buttonStyles = {};
|
|
461
461
|
this.modalStyles = {};
|
|
462
|
+
this.savedScrollY = 0;
|
|
462
463
|
this.shadow = this.attachShadow({ mode: "open" });
|
|
463
464
|
}
|
|
464
465
|
static get observedAttributes() {
|
|
@@ -512,15 +513,33 @@ class PrimeStyleTryon extends HTMLElement {
|
|
|
512
513
|
this.modalStyles = { ...this.modalStyles, ...styles };
|
|
513
514
|
this.applyModalStyles();
|
|
514
515
|
}
|
|
516
|
+
lockBodyScroll() {
|
|
517
|
+
this.savedScrollY = window.scrollY;
|
|
518
|
+
document.body.style.overflow = "hidden";
|
|
519
|
+
document.body.style.position = "fixed";
|
|
520
|
+
document.body.style.top = `-${this.savedScrollY}px`;
|
|
521
|
+
document.body.style.left = "0";
|
|
522
|
+
document.body.style.right = "0";
|
|
523
|
+
}
|
|
524
|
+
unlockBodyScroll() {
|
|
525
|
+
document.body.style.overflow = "";
|
|
526
|
+
document.body.style.position = "";
|
|
527
|
+
document.body.style.top = "";
|
|
528
|
+
document.body.style.left = "";
|
|
529
|
+
document.body.style.right = "";
|
|
530
|
+
window.scrollTo(0, this.savedScrollY);
|
|
531
|
+
}
|
|
515
532
|
/** Open the try-on modal */
|
|
516
533
|
open() {
|
|
517
534
|
this.state = "upload";
|
|
535
|
+
this.lockBodyScroll();
|
|
518
536
|
this.render();
|
|
519
537
|
this.emit("ps:open");
|
|
520
538
|
}
|
|
521
539
|
/** Close the try-on modal */
|
|
522
540
|
close() {
|
|
523
541
|
this.state = "idle";
|
|
542
|
+
this.unlockBodyScroll();
|
|
524
543
|
this.resetUpload();
|
|
525
544
|
this.render();
|
|
526
545
|
this.emit("ps:close");
|
|
@@ -567,6 +586,9 @@ class PrimeStyleTryon extends HTMLElement {
|
|
|
567
586
|
this.sseClient = new SseClient(this.apiClient.getStreamUrl());
|
|
568
587
|
}
|
|
569
588
|
cleanup() {
|
|
589
|
+
if (this.state !== "idle") {
|
|
590
|
+
this.unlockBodyScroll();
|
|
591
|
+
}
|
|
570
592
|
if (this.sseUnsubscribe) {
|
|
571
593
|
this.sseUnsubscribe();
|
|
572
594
|
this.sseUnsubscribe = null;
|
|
@@ -653,7 +675,7 @@ class PrimeStyleTryon extends HTMLElement {
|
|
|
653
675
|
if (this.showPoweredBy) {
|
|
654
676
|
const powered = document.createElement("div");
|
|
655
677
|
powered.className = "ps-powered";
|
|
656
|
-
powered.innerHTML = `Powered by <a href="https://
|
|
678
|
+
powered.innerHTML = `Powered by <a href="https://myaifitting.com" target="_blank" rel="noopener">PrimeStyle AI</a>`;
|
|
657
679
|
modal.appendChild(powered);
|
|
658
680
|
}
|
|
659
681
|
overlay.appendChild(modal);
|
package/dist/react/index.d.ts
CHANGED
|
@@ -5,7 +5,7 @@ export interface PrimeStyleTryonProps {
|
|
|
5
5
|
productImage: string;
|
|
6
6
|
/** Button text (defaults to "Virtual Try-On") */
|
|
7
7
|
buttonText?: string;
|
|
8
|
-
/** API base URL — defaults to NEXT_PUBLIC_PRIMESTYLE_API_URL env or https://
|
|
8
|
+
/** API base URL — defaults to NEXT_PUBLIC_PRIMESTYLE_API_URL env or https://myaifitting.com */
|
|
9
9
|
apiUrl?: string;
|
|
10
10
|
/** Show "Powered by PrimeStyle" in modal footer (defaults to true) */
|
|
11
11
|
showPoweredBy?: boolean;
|
package/dist/react/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { jsxs, jsx, Fragment } from "react/jsx-runtime";
|
|
3
3
|
import { useState, useRef, useEffect, useCallback } from "react";
|
|
4
|
-
import { A as ApiClient, S as SseClient, i as isValidImageFile, c as compressImage, P as PrimeStyleError } from "../image-utils-
|
|
4
|
+
import { A as ApiClient, S as SseClient, i as isValidImageFile, c as compressImage, P as PrimeStyleError } from "../image-utils-DtJVYQIA.js";
|
|
5
5
|
function cx(base, override) {
|
|
6
6
|
return override ? `${base} ${override}` : base;
|
|
7
7
|
}
|
|
@@ -94,7 +94,7 @@ function getApiKey() {
|
|
|
94
94
|
return key;
|
|
95
95
|
}
|
|
96
96
|
function getApiUrl(override) {
|
|
97
|
-
return override || process.env.NEXT_PUBLIC_PRIMESTYLE_API_URL || "https://
|
|
97
|
+
return override || process.env.NEXT_PUBLIC_PRIMESTYLE_API_URL || "https://myaifitting.com";
|
|
98
98
|
}
|
|
99
99
|
function PrimeStyleTryon({
|
|
100
100
|
productImage,
|
|
@@ -143,6 +143,24 @@ function PrimeStyleTryon({
|
|
|
143
143
|
if (previewUrl) URL.revokeObjectURL(previewUrl);
|
|
144
144
|
};
|
|
145
145
|
}, [previewUrl]);
|
|
146
|
+
useEffect(() => {
|
|
147
|
+
if (view !== "idle") {
|
|
148
|
+
const scrollY = window.scrollY;
|
|
149
|
+
document.body.style.overflow = "hidden";
|
|
150
|
+
document.body.style.position = "fixed";
|
|
151
|
+
document.body.style.top = `-${scrollY}px`;
|
|
152
|
+
document.body.style.left = "0";
|
|
153
|
+
document.body.style.right = "0";
|
|
154
|
+
return () => {
|
|
155
|
+
document.body.style.overflow = "";
|
|
156
|
+
document.body.style.position = "";
|
|
157
|
+
document.body.style.top = "";
|
|
158
|
+
document.body.style.left = "";
|
|
159
|
+
document.body.style.right = "";
|
|
160
|
+
window.scrollTo(0, scrollY);
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
}, [view]);
|
|
146
164
|
const handleOpen = useCallback(() => {
|
|
147
165
|
setView("upload");
|
|
148
166
|
onOpen?.();
|
|
@@ -187,26 +205,41 @@ function PrimeStyleTryon({
|
|
|
187
205
|
if (previewUrl) URL.revokeObjectURL(previewUrl);
|
|
188
206
|
setPreviewUrl(null);
|
|
189
207
|
}, [previewUrl]);
|
|
208
|
+
const completedRef = useRef(false);
|
|
209
|
+
const cleanupJob = useCallback(() => {
|
|
210
|
+
if (pollingRef.current) {
|
|
211
|
+
clearInterval(pollingRef.current);
|
|
212
|
+
pollingRef.current = null;
|
|
213
|
+
}
|
|
214
|
+
unsubRef.current?.();
|
|
215
|
+
unsubRef.current = null;
|
|
216
|
+
}, []);
|
|
190
217
|
const handleVtoUpdate = useCallback(
|
|
191
|
-
(update
|
|
218
|
+
(update) => {
|
|
192
219
|
if (update.status === "completed" && update.imageUrl) {
|
|
193
220
|
setResultImageUrl((prev) => {
|
|
194
|
-
if (prev
|
|
221
|
+
if (!prev || prev.startsWith("data:")) return update.imageUrl;
|
|
195
222
|
if (!update.imageUrl.startsWith("data:")) return update.imageUrl;
|
|
196
223
|
return prev;
|
|
197
224
|
});
|
|
198
|
-
if (
|
|
225
|
+
if (!completedRef.current) {
|
|
226
|
+
completedRef.current = true;
|
|
227
|
+
cleanupJob();
|
|
199
228
|
setView("result");
|
|
200
229
|
onComplete?.({ jobId: update.galleryId, imageUrl: update.imageUrl });
|
|
201
230
|
}
|
|
202
231
|
} else if (update.status === "failed") {
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
232
|
+
if (!completedRef.current) {
|
|
233
|
+
completedRef.current = true;
|
|
234
|
+
cleanupJob();
|
|
235
|
+
const msg = update.error || "Try-on generation failed";
|
|
236
|
+
setErrorMessage(msg);
|
|
237
|
+
setView("error");
|
|
238
|
+
onError?.({ message: msg });
|
|
239
|
+
}
|
|
207
240
|
}
|
|
208
241
|
},
|
|
209
|
-
[onComplete, onError]
|
|
242
|
+
[onComplete, onError, cleanupJob]
|
|
210
243
|
);
|
|
211
244
|
const handleSubmit = useCallback(async () => {
|
|
212
245
|
if (!selectedFile || !apiRef.current || !sseRef.current) {
|
|
@@ -216,6 +249,7 @@ function PrimeStyleTryon({
|
|
|
216
249
|
onError?.({ message: msg, code: "SDK_NOT_CONFIGURED" });
|
|
217
250
|
return;
|
|
218
251
|
}
|
|
252
|
+
completedRef.current = false;
|
|
219
253
|
setView("processing");
|
|
220
254
|
try {
|
|
221
255
|
const modelImage = await compressImage(selectedFile);
|
|
@@ -226,33 +260,35 @@ function PrimeStyleTryon({
|
|
|
226
260
|
onProcessing?.(response.jobId);
|
|
227
261
|
unsubRef.current = sseRef.current.onJob(
|
|
228
262
|
response.jobId,
|
|
229
|
-
(update) => handleVtoUpdate(update
|
|
263
|
+
(update) => handleVtoUpdate(update)
|
|
230
264
|
);
|
|
231
265
|
let attempts = 0;
|
|
232
266
|
pollingRef.current = setInterval(async () => {
|
|
267
|
+
if (completedRef.current) {
|
|
268
|
+
if (pollingRef.current) clearInterval(pollingRef.current);
|
|
269
|
+
pollingRef.current = null;
|
|
270
|
+
return;
|
|
271
|
+
}
|
|
233
272
|
attempts++;
|
|
234
273
|
if (attempts > 60) {
|
|
235
274
|
if (pollingRef.current) clearInterval(pollingRef.current);
|
|
275
|
+
pollingRef.current = null;
|
|
236
276
|
return;
|
|
237
277
|
}
|
|
238
278
|
try {
|
|
239
279
|
const status = await apiRef.current.getStatus(response.jobId);
|
|
240
280
|
if (status.status === "completed" || status.status === "failed") {
|
|
241
|
-
handleVtoUpdate(
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
},
|
|
249
|
-
view
|
|
250
|
-
);
|
|
251
|
-
if (pollingRef.current) clearInterval(pollingRef.current);
|
|
281
|
+
handleVtoUpdate({
|
|
282
|
+
galleryId: response.jobId,
|
|
283
|
+
status: status.status,
|
|
284
|
+
imageUrl: status.imageUrl,
|
|
285
|
+
error: status.status === "failed" ? status.message : null,
|
|
286
|
+
timestamp: Date.now()
|
|
287
|
+
});
|
|
252
288
|
}
|
|
253
289
|
} catch {
|
|
254
290
|
}
|
|
255
|
-
},
|
|
291
|
+
}, 3e3);
|
|
256
292
|
} catch (err) {
|
|
257
293
|
const message = err instanceof Error ? err.message : "Failed to start try-on";
|
|
258
294
|
const code = err instanceof PrimeStyleError ? err.code : void 0;
|
|
@@ -260,7 +296,7 @@ function PrimeStyleTryon({
|
|
|
260
296
|
setView("error");
|
|
261
297
|
onError?.({ message, code });
|
|
262
298
|
}
|
|
263
|
-
}, [selectedFile, productImage, onProcessing, onError, handleVtoUpdate
|
|
299
|
+
}, [selectedFile, productImage, onProcessing, onError, handleVtoUpdate]);
|
|
264
300
|
const handleDownload = useCallback(() => {
|
|
265
301
|
if (!resultImageUrl) return;
|
|
266
302
|
if (resultImageUrl.startsWith("data:")) {
|
|
@@ -280,13 +316,15 @@ function PrimeStyleTryon({
|
|
|
280
316
|
}
|
|
281
317
|
}, [resultImageUrl]);
|
|
282
318
|
const handleRetry = useCallback(() => {
|
|
319
|
+
completedRef.current = false;
|
|
320
|
+
cleanupJob();
|
|
283
321
|
setSelectedFile(null);
|
|
284
322
|
if (previewUrl) URL.revokeObjectURL(previewUrl);
|
|
285
323
|
setPreviewUrl(null);
|
|
286
324
|
setResultImageUrl(null);
|
|
287
325
|
setErrorMessage(null);
|
|
288
326
|
setView("upload");
|
|
289
|
-
}, [previewUrl]);
|
|
327
|
+
}, [previewUrl, cleanupJob]);
|
|
290
328
|
const rootVars = {
|
|
291
329
|
"--ps-btn-bg": btnS.backgroundColor,
|
|
292
330
|
"--ps-btn-color": btnS.textColor,
|
|
@@ -510,7 +548,7 @@ function PrimeStyleTryon({
|
|
|
510
548
|
/* @__PURE__ */ jsx(
|
|
511
549
|
"a",
|
|
512
550
|
{
|
|
513
|
-
href: "https://
|
|
551
|
+
href: "https://myaifitting.com",
|
|
514
552
|
target: "_blank",
|
|
515
553
|
rel: "noopener noreferrer",
|
|
516
554
|
children: "PrimeStyle AI"
|
package/dist/types.d.ts
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
export interface PrimeStyleConfig {
|
|
3
3
|
/** Your API key (starts with ps_live_) */
|
|
4
4
|
apiKey: string;
|
|
5
|
-
/** API base URL (defaults to https://
|
|
5
|
+
/** API base URL (defaults to https://myaifitting.com) */
|
|
6
6
|
apiUrl?: string;
|
|
7
7
|
/** Product image URL to try on */
|
|
8
8
|
productImage?: string;
|