@primestyleai/tryon 1.0.1 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +235 -307
- package/dist/image-utils-usff6Qu8.js +186 -0
- package/dist/{primestyle-tryon.es.js → primestyle-tryon.js} +3 -180
- package/dist/react/index.d.ts +39 -0
- package/dist/react/index.js +771 -0
- package/dist/types.d.ts +55 -0
- package/package.json +25 -8
- package/dist/primestyle-tryon.umd.js +0 -1
package/dist/types.d.ts
CHANGED
|
@@ -75,6 +75,61 @@ export interface TryOnStatus {
|
|
|
75
75
|
imageUrl: string | null;
|
|
76
76
|
message: string;
|
|
77
77
|
}
|
|
78
|
+
/** Class name overrides for styling with Tailwind or custom CSS */
|
|
79
|
+
export interface PrimeStyleClassNames {
|
|
80
|
+
/** Root wrapper element */
|
|
81
|
+
root?: string;
|
|
82
|
+
/** Trigger button */
|
|
83
|
+
button?: string;
|
|
84
|
+
/** Modal overlay backdrop */
|
|
85
|
+
overlay?: string;
|
|
86
|
+
/** Modal container */
|
|
87
|
+
modal?: string;
|
|
88
|
+
/** Modal header bar */
|
|
89
|
+
header?: string;
|
|
90
|
+
/** Modal title text */
|
|
91
|
+
title?: string;
|
|
92
|
+
/** Close (X) button */
|
|
93
|
+
closeButton?: string;
|
|
94
|
+
/** Modal body area */
|
|
95
|
+
body?: string;
|
|
96
|
+
/** Upload drop zone */
|
|
97
|
+
uploadZone?: string;
|
|
98
|
+
/** Upload primary text */
|
|
99
|
+
uploadText?: string;
|
|
100
|
+
/** Upload hint text (file types) */
|
|
101
|
+
uploadHint?: string;
|
|
102
|
+
/** Preview image container */
|
|
103
|
+
preview?: string;
|
|
104
|
+
/** Preview image element */
|
|
105
|
+
previewImage?: string;
|
|
106
|
+
/** Remove preview button */
|
|
107
|
+
removeButton?: string;
|
|
108
|
+
/** Submit / "Try It On" button */
|
|
109
|
+
submitButton?: string;
|
|
110
|
+
/** Processing spinner element */
|
|
111
|
+
spinner?: string;
|
|
112
|
+
/** Processing main text */
|
|
113
|
+
processingText?: string;
|
|
114
|
+
/** Processing sub text */
|
|
115
|
+
processingSubText?: string;
|
|
116
|
+
/** Result container */
|
|
117
|
+
result?: string;
|
|
118
|
+
/** Result image element */
|
|
119
|
+
resultImage?: string;
|
|
120
|
+
/** Result actions container */
|
|
121
|
+
resultActions?: string;
|
|
122
|
+
/** Download button */
|
|
123
|
+
downloadButton?: string;
|
|
124
|
+
/** Retry / "Try Another" button */
|
|
125
|
+
retryButton?: string;
|
|
126
|
+
/** Error container */
|
|
127
|
+
error?: string;
|
|
128
|
+
/** Error text */
|
|
129
|
+
errorText?: string;
|
|
130
|
+
/** Powered by footer */
|
|
131
|
+
poweredBy?: string;
|
|
132
|
+
}
|
|
78
133
|
/** Custom events emitted by the component */
|
|
79
134
|
export interface PrimeStyleEvents {
|
|
80
135
|
"ps:open": CustomEvent<void>;
|
package/package.json
CHANGED
|
@@ -1,16 +1,19 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@primestyleai/tryon",
|
|
3
|
-
"version": "1.0
|
|
4
|
-
"description": "PrimeStyle Virtual Try-On Web Component
|
|
3
|
+
"version": "1.2.0",
|
|
4
|
+
"description": "PrimeStyle Virtual Try-On SDK — React component & Web Component",
|
|
5
5
|
"type": "module",
|
|
6
|
-
"main": "dist/primestyle-tryon.
|
|
7
|
-
"module": "dist/primestyle-tryon.
|
|
6
|
+
"main": "dist/primestyle-tryon.js",
|
|
7
|
+
"module": "dist/primestyle-tryon.js",
|
|
8
8
|
"types": "dist/index.d.ts",
|
|
9
9
|
"exports": {
|
|
10
10
|
".": {
|
|
11
|
-
"import": "./dist/primestyle-tryon.
|
|
12
|
-
"require": "./dist/primestyle-tryon.umd.js",
|
|
11
|
+
"import": "./dist/primestyle-tryon.js",
|
|
13
12
|
"types": "./dist/index.d.ts"
|
|
13
|
+
},
|
|
14
|
+
"./react": {
|
|
15
|
+
"import": "./dist/react/index.js",
|
|
16
|
+
"types": "./dist/react/index.d.ts"
|
|
14
17
|
}
|
|
15
18
|
},
|
|
16
19
|
"files": [
|
|
@@ -24,6 +27,7 @@
|
|
|
24
27
|
},
|
|
25
28
|
"keywords": [
|
|
26
29
|
"virtual-try-on",
|
|
30
|
+
"react",
|
|
27
31
|
"web-component",
|
|
28
32
|
"fashion",
|
|
29
33
|
"ai",
|
|
@@ -31,9 +35,22 @@
|
|
|
31
35
|
],
|
|
32
36
|
"author": "PrimeStyle AI",
|
|
33
37
|
"license": "MIT",
|
|
38
|
+
"peerDependencies": {
|
|
39
|
+
"react": ">=18.0.0",
|
|
40
|
+
"react-dom": ">=18.0.0"
|
|
41
|
+
},
|
|
42
|
+
"peerDependenciesMeta": {
|
|
43
|
+
"react": {
|
|
44
|
+
"optional": true
|
|
45
|
+
},
|
|
46
|
+
"react-dom": {
|
|
47
|
+
"optional": true
|
|
48
|
+
}
|
|
49
|
+
},
|
|
34
50
|
"devDependencies": {
|
|
51
|
+
"@types/react": "^18.3.28",
|
|
52
|
+
"terser": "^5.31.0",
|
|
35
53
|
"typescript": "^5.5.0",
|
|
36
|
-
"vite": "^5.4.0"
|
|
37
|
-
"terser": "^5.31.0"
|
|
54
|
+
"vite": "^5.4.0"
|
|
38
55
|
}
|
|
39
56
|
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?t(exports):"function"==typeof define&&define.amd?define(["exports"],t):t((e="undefined"!=typeof globalThis?globalThis:e||self).PrimeStyleTryon={})}(this,function(e){"use strict";class t{constructor(e,t){this.apiKey=e,this.baseUrl=(t||"https://api.primestyleai.com").replace(/\/+$/,"")}get headers(){return{"Content-Type":"application/json",Authorization:`Bearer ${this.apiKey}`}}async submitTryOn(e,t){const s=await fetch(`${this.baseUrl}/api/v1/tryon`,{method:"POST",headers:this.headers,body:JSON.stringify({modelImage:e,garmentImage:t})});if(!s.ok){const e=await s.json().catch(()=>({}));if(402===s.status)throw new n(e.message||"Insufficient tokens","INSUFFICIENT_TOKENS");throw new n(e.message||"Failed to submit try-on","API_ERROR")}return s.json()}async getStatus(e){const t=await fetch(`${this.baseUrl}/api/v1/tryon/status/${e}`,{headers:this.headers});if(!t.ok){const e=await t.json().catch(()=>({}));throw new n(e.message||"Failed to get status","API_ERROR")}return t.json()}getStreamUrl(){return`${this.baseUrl}/api/v1/tryon/stream?key=${encodeURIComponent(this.apiKey)}`}}class n extends Error{constructor(e,t){super(e),this.name="PrimeStyleError",this.code=t}}class s{constructor(e){this.eventSource=null,this.listeners=new Map,this.reconnectTimer=null,this.reconnectAttempts=0,this.maxReconnectAttempts=5,this.streamUrl=e}connect(){this.eventSource||(this.eventSource=new EventSource(this.streamUrl),this.eventSource.addEventListener("vto-update",e=>{try{const t=JSON.parse(e.data);this.emit(t.galleryId,t)}catch{}}),this.eventSource.onopen=()=>{this.reconnectAttempts=0},this.eventSource.onerror=()=>{this.eventSource?.close(),this.eventSource=null,this.scheduleReconnect()})}scheduleReconnect(){if(this.reconnectAttempts>=this.maxReconnectAttempts)return;if(0===this.listeners.size)return;const e=Math.min(1e3*2**this.reconnectAttempts,3e4);this.reconnectAttempts++,this.reconnectTimer=setTimeout(()=>{this.connect()},e)}onJob(e,t){return this.listeners.has(e)||this.listeners.set(e,new Set),this.listeners.get(e).add(t),this.eventSource||this.connect(),()=>{const n=this.listeners.get(e);n&&(n.delete(t),0===n.size&&this.listeners.delete(e)),0===this.listeners.size&&this.disconnect()}}emit(e,t){const n=this.listeners.get(e);n&&n.forEach(e=>e(t))}disconnect(){this.reconnectTimer&&(clearTimeout(this.reconnectTimer),this.reconnectTimer=null),this.eventSource&&(this.eventSource.close(),this.eventSource=null),this.listeners.clear(),this.reconnectAttempts=0}}function r(){const e=document.querySelector('meta[property="og:image"]');if(e?.content)return e.content;const t=document.querySelectorAll('script[type="application/ld+json"]');for(const s of t)try{const e=o(JSON.parse(s.textContent||""));if(e)return e}catch{}const n=["[data-product-image] img","[data-product-image]",".product-image img",".product-gallery img","#product-image",".product__media img",".product-single__photo img",".woocommerce-product-gallery img",".product-media img"];for(const s of n){const e=document.querySelector(s);if(e){const t=e.src||e.dataset.src||e.dataset.zoom;if(t)return t}}return null}function o(e){if(!e||"object"!=typeof e)return null;if(Array.isArray(e)){for(const t of e){const e=o(t);if(e)return e}return null}const t=e;if("Product"===t["@type"]||"IndividualProduct"===t["@type"]){const e=t.image;if("string"==typeof e)return e;if(Array.isArray(e)&&"string"==typeof e[0])return e[0];if(e&&"object"==typeof e){const t=e;if("string"==typeof t.url)return t.url;if("string"==typeof t.contentUrl)return t.contentUrl}}return Array.isArray(t["@graph"])?o(t["@graph"]):null}const i=1024;function a(e){return new Promise((t,n)=>{const s=new FileReader;s.onload=()=>{const e=new Image;e.onload=()=>{try{const s=document.createElement("canvas");let{width:r,height:o}=e;(r>i||o>i)&&(r>o?(o=Math.round(o*i/r),r=i):(r=Math.round(r*i/o),o=i)),s.width=r,s.height=o;const a=s.getContext("2d");if(!a)return void n(new Error("Canvas context not available"));a.drawImage(e,0,0,r,o);const l=s.toDataURL("image/jpeg",.85);t(l)}catch(s){n(s)}},e.onerror=()=>n(new Error("Failed to load image")),e.src=s.result},s.onerror=()=>n(new Error("Failed to read file")),s.readAsDataURL(e)})}function l(e){return["image/jpeg","image/png","image/webp"].includes(e.type)}const p={camera:'<svg viewBox="0 0 24 24"><path d="M23 19a2 2 0 0 1-2 2H3a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h4l2-3h6l2 3h4a2 2 0 0 1 2 2z"/><circle cx="12" cy="13" r="4"/></svg>',upload:'<svg viewBox="0 0 24 24"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><polyline points="17 8 12 3 7 8"/><line x1="12" y1="3" x2="12" y2="15"/></svg>',x:'<svg viewBox="0 0 24 24"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg>',alert:'<svg viewBox="0 0 24 24"><circle cx="12" cy="12" r="10"/><line x1="12" y1="8" x2="12" y2="12"/><line x1="12" y1="16" x2="12.01" y2="16"/></svg>'};class d extends HTMLElement{constructor(){super(),this.apiClient=null,this.sseClient=null,this.sseUnsubscribe=null,this.state="idle",this.selectedFile=null,this.previewUrl=null,this.resultImageUrl=null,this.errorMessage=null,this.currentJobId=null,this.productImageUrl=null,this.buttonStyles={},this.modalStyles={},this.shadow=this.attachShadow({mode:"open"})}static get observedAttributes(){return["api-key","api-url","product-image","button-text","locale","show-powered-by","button-styles","modal-styles"]}connectedCallback(){this.init(),this.render()}disconnectedCallback(){this.cleanup()}attributeChangedCallback(e,t,n){if("api-key"!==e&&"api-url"!==e||this.initApi(),"product-image"===e&&(this.productImageUrl=n),"button-styles"===e)try{this.buttonStyles=JSON.parse(n)}catch{}if("modal-styles"===e)try{this.modalStyles=JSON.parse(n)}catch{}this.isConnected&&this.render()}setButtonStyles(e){this.buttonStyles={...this.buttonStyles,...e},this.applyButtonStyles()}setModalStyles(e){this.modalStyles={...this.modalStyles,...e},this.applyModalStyles()}open(){this.state="upload",this.render(),this.emit("ps:open")}close(){this.state="idle",this.resetUpload(),this.render(),this.emit("ps:close")}detectProduct(){const e=r();return e&&(this.productImageUrl=e,this.emit("ps:product-detected",{imageUrl:e})),e}init(){const e=this.getAttribute("button-styles");if(e)try{this.buttonStyles=JSON.parse(e)}catch{}const t=this.getAttribute("modal-styles");if(t)try{this.modalStyles=JSON.parse(t)}catch{}this.productImageUrl=this.getAttribute("product-image")||null,this.productImageUrl||(this.productImageUrl=r(),this.productImageUrl&&this.emit("ps:product-detected",{imageUrl:this.productImageUrl})),this.initApi()}initApi(){const e=this.getAttribute("api-key");if(!e)return;const n=this.getAttribute("api-url")||void 0;this.apiClient=new t(e,n),this.sseClient=new s(this.apiClient.getStreamUrl())}cleanup(){this.sseUnsubscribe&&(this.sseUnsubscribe(),this.sseUnsubscribe=null),this.sseClient&&(this.sseClient.disconnect(),this.sseClient=null),this.previewUrl&&URL.revokeObjectURL(this.previewUrl)}emit(e,t){this.dispatchEvent(new CustomEvent(e,{bubbles:!0,composed:!0,detail:t}))}get buttonText(){return this.getAttribute("button-text")||"Virtual Try-On"}get showPoweredBy(){return"false"!==this.getAttribute("show-powered-by")}render(){this.shadow.innerHTML="";const e=document.createElement("style");e.textContent="\n :host {\n display: inline-block;\n --ps-primary: #bb945c;\n --ps-primary-hover: #a07d4e;\n --ps-text: #ffffff;\n --ps-text-secondary: #999999;\n --ps-bg: #111211;\n --ps-bg-secondary: #1a1b1a;\n --ps-border: #333333;\n --ps-radius: 12px;\n --ps-font: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;\n --ps-overlay: rgba(0, 0, 0, 0.6);\n --ps-error: #ef4444;\n --ps-success: #22c55e;\n }\n\n * {\n box-sizing: border-box;\n margin: 0;\n padding: 0;\n }\n\n /* ── Button ─────────────────────────────── */\n .ps-button {\n display: inline-flex;\n align-items: center;\n gap: 8px;\n padding: var(--ps-btn-padding, 12px 24px);\n background: var(--ps-btn-bg, var(--ps-primary));\n color: var(--ps-btn-color, #111211);\n font-family: var(--ps-btn-font, var(--ps-font));\n font-size: var(--ps-btn-font-size, 14px);\n font-weight: var(--ps-btn-font-weight, 600);\n border: var(--ps-btn-border, none);\n border-radius: var(--ps-btn-radius, 8px);\n cursor: pointer;\n transition: all 0.2s ease;\n width: var(--ps-btn-width, auto);\n height: var(--ps-btn-height, auto);\n box-shadow: var(--ps-btn-shadow, none);\n line-height: 1;\n white-space: nowrap;\n }\n\n .ps-button:hover {\n background: var(--ps-btn-hover-bg, var(--ps-primary-hover));\n color: var(--ps-btn-hover-color, var(--ps-btn-color, #111211));\n transform: translateY(-1px);\n }\n\n .ps-button:active {\n transform: translateY(0);\n }\n\n .ps-button svg {\n width: var(--ps-btn-icon-size, 18px);\n height: var(--ps-btn-icon-size, 18px);\n fill: none;\n stroke: var(--ps-btn-icon-color, currentColor);\n stroke-width: 2;\n stroke-linecap: round;\n stroke-linejoin: round;\n }\n\n /* ── Modal Overlay ──────────────────────── */\n .ps-overlay {\n position: fixed;\n inset: 0;\n background: var(--ps-modal-overlay, var(--ps-overlay));\n display: flex;\n align-items: center;\n justify-content: center;\n z-index: 999999;\n opacity: 0;\n visibility: hidden;\n transition: opacity 0.25s ease, visibility 0.25s ease;\n padding: 16px;\n }\n\n .ps-overlay.ps-open {\n opacity: 1;\n visibility: visible;\n }\n\n /* ── Modal ──────────────────────────────── */\n .ps-modal {\n background: var(--ps-modal-bg, var(--ps-bg));\n color: var(--ps-modal-color, var(--ps-text));\n border-radius: var(--ps-modal-radius, var(--ps-radius));\n width: var(--ps-modal-width, 100%);\n max-width: var(--ps-modal-max-width, 480px);\n max-height: 90vh;\n overflow-y: auto;\n font-family: var(--ps-modal-font, var(--ps-font));\n position: relative;\n transform: translateY(20px) scale(0.97);\n transition: transform 0.25s ease;\n box-shadow: 0 25px 50px rgba(0, 0, 0, 0.4);\n }\n\n .ps-open .ps-modal {\n transform: translateY(0) scale(1);\n }\n\n /* ── Modal Header ───────────────────────── */\n .ps-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 20px 24px;\n background: var(--ps-modal-header-bg, var(--ps-bg-secondary));\n border-bottom: 1px solid var(--ps-border);\n border-radius: var(--ps-modal-radius, var(--ps-radius)) var(--ps-modal-radius, var(--ps-radius)) 0 0;\n }\n\n .ps-header-title {\n font-size: 16px;\n font-weight: 600;\n color: var(--ps-modal-header-color, var(--ps-text));\n }\n\n .ps-close {\n width: 32px;\n height: 32px;\n display: flex;\n align-items: center;\n justify-content: center;\n background: none;\n border: none;\n color: var(--ps-modal-close-color, var(--ps-text-secondary));\n cursor: pointer;\n border-radius: 6px;\n transition: background 0.15s;\n }\n\n .ps-close:hover {\n background: rgba(255, 255, 255, 0.1);\n }\n\n .ps-close svg {\n width: 20px;\n height: 20px;\n stroke: currentColor;\n stroke-width: 2;\n fill: none;\n }\n\n /* ── Modal Body ─────────────────────────── */\n .ps-body {\n padding: 24px;\n }\n\n /* ── Upload Zone ─────────────────────────── */\n .ps-upload-zone {\n border: 2px dashed var(--ps-upload-border, var(--ps-border));\n border-radius: var(--ps-radius);\n padding: 40px 24px;\n text-align: center;\n cursor: pointer;\n transition: all 0.2s ease;\n background: var(--ps-upload-bg, transparent);\n }\n\n .ps-upload-zone:hover,\n .ps-upload-zone.ps-drag-over {\n border-color: var(--ps-primary);\n background: rgba(187, 148, 92, 0.05);\n }\n\n .ps-upload-zone input {\n display: none;\n }\n\n .ps-upload-icon {\n width: 48px;\n height: 48px;\n margin: 0 auto 12px;\n stroke: var(--ps-upload-icon-color, var(--ps-primary));\n fill: none;\n stroke-width: 1.5;\n }\n\n .ps-upload-text {\n font-size: 14px;\n color: var(--ps-upload-color, var(--ps-text));\n margin-bottom: 4px;\n }\n\n .ps-upload-hint {\n font-size: 12px;\n color: var(--ps-text-secondary);\n }\n\n /* ── Preview ────────────────────────────── */\n .ps-preview {\n margin-top: 16px;\n position: relative;\n }\n\n .ps-preview img {\n width: 100%;\n border-radius: var(--ps-radius);\n display: block;\n }\n\n .ps-preview-remove {\n position: absolute;\n top: 8px;\n right: 8px;\n width: 28px;\n height: 28px;\n border-radius: 50%;\n background: rgba(0, 0, 0, 0.6);\n border: none;\n color: white;\n cursor: pointer;\n display: flex;\n align-items: center;\n justify-content: center;\n font-size: 16px;\n transition: background 0.15s;\n }\n\n .ps-preview-remove:hover {\n background: rgba(0, 0, 0, 0.8);\n }\n\n /* ── Submit Button ──────────────────────── */\n .ps-submit {\n width: 100%;\n padding: 14px;\n margin-top: 20px;\n background: var(--ps-modal-primary-bg, var(--ps-primary));\n color: var(--ps-modal-primary-color, #111211);\n font-family: var(--ps-modal-font, var(--ps-font));\n font-size: 14px;\n font-weight: 600;\n border: none;\n border-radius: var(--ps-modal-primary-radius, 8px);\n cursor: pointer;\n transition: all 0.2s ease;\n display: flex;\n align-items: center;\n justify-content: center;\n gap: 8px;\n }\n\n .ps-submit:hover:not(:disabled) {\n opacity: 0.9;\n transform: translateY(-1px);\n }\n\n .ps-submit:disabled {\n opacity: 0.5;\n cursor: not-allowed;\n }\n\n /* ── Processing State ───────────────────── */\n .ps-processing {\n text-align: center;\n padding: 40px 24px;\n }\n\n .ps-spinner {\n width: 48px;\n height: 48px;\n border: 3px solid var(--ps-border);\n border-top-color: var(--ps-loader, var(--ps-primary));\n border-radius: 50%;\n animation: ps-spin 0.8s linear infinite;\n margin: 0 auto 16px;\n }\n\n @keyframes ps-spin {\n to { transform: rotate(360deg); }\n }\n\n .ps-processing-text {\n font-size: 14px;\n color: var(--ps-text);\n margin-bottom: 4px;\n }\n\n .ps-processing-sub {\n font-size: 12px;\n color: var(--ps-text-secondary);\n }\n\n /* ── Result ─────────────────────────────── */\n .ps-result {\n text-align: center;\n }\n\n .ps-result img {\n width: 100%;\n border-radius: var(--ps-result-radius, var(--ps-radius));\n display: block;\n margin-bottom: 16px;\n }\n\n .ps-result-actions {\n display: flex;\n gap: 8px;\n }\n\n .ps-result-actions button {\n flex: 1;\n padding: 12px;\n font-family: var(--ps-modal-font, var(--ps-font));\n font-size: 13px;\n font-weight: 600;\n border-radius: 8px;\n cursor: pointer;\n transition: all 0.2s;\n border: none;\n }\n\n .ps-btn-download {\n background: var(--ps-primary);\n color: #111211;\n }\n\n .ps-btn-download:hover {\n opacity: 0.9;\n }\n\n .ps-btn-retry {\n background: rgba(255, 255, 255, 0.1);\n color: var(--ps-text);\n border: 1px solid var(--ps-border) !important;\n }\n\n .ps-btn-retry:hover {\n background: rgba(255, 255, 255, 0.15);\n }\n\n /* ── Error State ────────────────────────── */\n .ps-error {\n text-align: center;\n padding: 24px;\n }\n\n .ps-error-icon {\n width: 48px;\n height: 48px;\n margin: 0 auto 12px;\n stroke: var(--ps-error);\n fill: none;\n stroke-width: 1.5;\n }\n\n .ps-error-text {\n font-size: 14px;\n color: var(--ps-error);\n margin-bottom: 16px;\n }\n\n /* ── Powered By ─────────────────────────── */\n .ps-powered {\n text-align: center;\n padding: 12px 24px 16px;\n font-size: 11px;\n color: var(--ps-text-secondary);\n }\n\n .ps-powered a {\n color: var(--ps-primary);\n text-decoration: none;\n }\n\n .ps-powered a:hover {\n text-decoration: underline;\n }\n ",this.shadow.appendChild(e);const t=this.createButton();if(this.shadow.appendChild(t),"idle"!==this.state){const e=this.createModal();this.shadow.appendChild(e),requestAnimationFrame(()=>e.classList.add("ps-open"))}this.applyButtonStyles(),this.applyModalStyles()}createButton(){const e=document.createElement("button");return e.className="ps-button",e.innerHTML=`${p.camera}<span>${this.buttonText}</span>`,e.addEventListener("click",()=>this.open()),e}createModal(){const e=document.createElement("div");e.className="ps-overlay",e.addEventListener("click",t=>{t.target===e&&this.close()});const t=document.createElement("div");t.className="ps-modal";const n=document.createElement("div");n.className="ps-header",n.innerHTML='\n <span class="ps-header-title">Virtual Try-On</span>\n ';const s=document.createElement("button");s.className="ps-close",s.innerHTML=p.x,s.addEventListener("click",()=>this.close()),n.appendChild(s),t.appendChild(n);const r=document.createElement("div");switch(r.className="ps-body",this.state){case"upload":r.appendChild(this.createUploadView());break;case"processing":r.appendChild(this.createProcessingView());break;case"result":r.appendChild(this.createResultView());break;case"error":r.appendChild(this.createErrorView())}if(t.appendChild(r),this.showPoweredBy){const e=document.createElement("div");e.className="ps-powered",e.innerHTML='Powered by <a href="https://primestyleai.com" target="_blank" rel="noopener">PrimeStyle AI</a>',t.appendChild(e)}return e.appendChild(t),e}createUploadView(){const e=document.createDocumentFragment();if(this.selectedFile&&this.previewUrl){const t=document.createElement("div");t.className="ps-preview";const n=document.createElement("img");n.src=this.previewUrl,n.alt="Your photo",t.appendChild(n);const s=document.createElement("button");s.className="ps-preview-remove",s.textContent="×",s.addEventListener("click",()=>{this.resetUpload(),this.render()}),t.appendChild(s),e.appendChild(t);const r=document.createElement("button");r.className="ps-submit",r.textContent="Try It On",r.addEventListener("click",()=>this.handleSubmit()),e.appendChild(r)}else{const t=document.createElement("div");t.className="ps-upload-zone";const n=document.createElement("input");n.type="file",n.accept="image/jpeg,image/png,image/webp",n.addEventListener("change",e=>{const t=e.target.files?.[0];t&&this.handleFileSelect(t)}),t.innerHTML=`\n <svg class="ps-upload-icon" viewBox="0 0 24 24">${p.upload.replace(/<\/?svg[^>]*>/g,"")}</svg>\n <p class="ps-upload-text">Drop your photo here or click to upload</p>\n <p class="ps-upload-hint">JPEG, PNG or WebP (max 10MB)</p>\n `,t.appendChild(n),t.addEventListener("click",()=>n.click()),t.addEventListener("dragover",e=>{e.preventDefault(),t.classList.add("ps-drag-over")}),t.addEventListener("dragleave",()=>{t.classList.remove("ps-drag-over")}),t.addEventListener("drop",e=>{e.preventDefault(),t.classList.remove("ps-drag-over");const n=e.dataTransfer?.files?.[0];n&&this.handleFileSelect(n)}),e.appendChild(t)}return e}createProcessingView(){const e=document.createElement("div");return e.className="ps-processing",e.innerHTML='\n <div class="ps-spinner"></div>\n <p class="ps-processing-text">Generating your try-on...</p>\n <p class="ps-processing-sub">This usually takes 15-20 seconds</p>\n ',e}createResultView(){const e=document.createElement("div");if(e.className="ps-result",this.resultImageUrl){const t=document.createElement("img");t.src=this.resultImageUrl,t.alt="Try-on result",e.appendChild(t)}const t=document.createElement("div");t.className="ps-result-actions";const n=document.createElement("button");n.className="ps-btn-download",n.textContent="Download",n.addEventListener("click",()=>this.handleDownload()),t.appendChild(n);const s=document.createElement("button");return s.className="ps-btn-retry",s.textContent="Try Another",s.addEventListener("click",()=>{this.resetUpload(),this.state="upload",this.render()}),t.appendChild(s),e.appendChild(t),e}createErrorView(){const e=document.createElement("div");e.className="ps-error",e.innerHTML=`\n <svg class="ps-error-icon" viewBox="0 0 24 24">${p.alert.replace(/<\/?svg[^>]*>/g,"")}</svg>\n <p class="ps-error-text">${this.errorMessage||"Something went wrong"}</p>\n `;const t=document.createElement("button");return t.className="ps-submit",t.textContent="Try Again",t.addEventListener("click",()=>{this.state="upload",this.errorMessage=null,this.render()}),e.appendChild(t),e}handleFileSelect(e){return l(e)?e.size>10485760?(this.errorMessage="Image must be under 10MB.",this.state="error",void this.render()):(this.selectedFile=e,this.previewUrl=URL.createObjectURL(e),this.emit("ps:upload",{file:e}),void this.render()):(this.errorMessage="Please upload a JPEG, PNG, or WebP image.",this.state="error",void this.render())}async handleSubmit(){if(!this.selectedFile||!this.apiClient||!this.sseClient)return this.errorMessage="SDK not configured. Please provide an API key.",this.state="error",void this.render();if(!this.productImageUrl)return this.errorMessage="No product image found. Please set the product-image attribute.",this.state="error",void this.render();this.state="processing",this.render();try{const e=await a(this.selectedFile),t=await this.apiClient.submitTryOn(e,this.productImageUrl);this.currentJobId=t.jobId,this.emit("ps:processing",{jobId:t.jobId}),this.sseUnsubscribe=this.sseClient.onJob(t.jobId,e=>this.handleVtoUpdate(e)),this.startPolling(t.jobId)}catch(e){const t=e instanceof Error?e.message:"Failed to start try-on";this.errorMessage=t,this.state="error",this.emit("ps:error",{message:t,code:e?.code}),this.render()}}handleVtoUpdate(e){"completed"===e.status&&e.imageUrl?("result"!==this.state||this.resultImageUrl?.startsWith("data:")&&!e.imageUrl.startsWith("data:"))&&(this.resultImageUrl=e.imageUrl,this.state="result",this.emit("ps:complete",{jobId:e.galleryId,imageUrl:e.imageUrl}),this.render()):"failed"===e.status&&(this.errorMessage=e.error||"Try-on generation failed",this.state="error",this.emit("ps:error",{message:this.errorMessage}),this.render())}startPolling(e){let t=0;const n=setInterval(async()=>{if(t++,t>60||"result"===this.state||"idle"===this.state)clearInterval(n);else if(this.apiClient)try{const t=await this.apiClient.getStatus(e);"completed"===t.status&&t.imageUrl?("processing"===this.state&&this.handleVtoUpdate({galleryId:e,status:"completed",imageUrl:t.imageUrl,error:null,timestamp:Date.now()}),clearInterval(n)):"failed"===t.status&&("processing"===this.state&&this.handleVtoUpdate({galleryId:e,status:"failed",imageUrl:null,error:t.message,timestamp:Date.now()}),clearInterval(n))}catch{}else clearInterval(n)},2e3)}handleDownload(){if(!this.resultImageUrl)return;const e=document.createElement("a");e.href=this.resultImageUrl,e.download=`primestyle-tryon-${Date.now()}.png`,e.target="_blank",this.resultImageUrl.startsWith("data:")?e.click():fetch(this.resultImageUrl).then(e=>e.blob()).then(t=>{const n=URL.createObjectURL(t);e.href=n,e.click(),setTimeout(()=>URL.revokeObjectURL(n),100)}).catch(()=>{window.open(this.resultImageUrl,"_blank")})}resetUpload(){this.previewUrl&&URL.revokeObjectURL(this.previewUrl),this.selectedFile=null,this.previewUrl=null,this.resultImageUrl=null,this.errorMessage=null,this.currentJobId=null,this.sseUnsubscribe&&(this.sseUnsubscribe(),this.sseUnsubscribe=null)}applyButtonStyles(){if(!this.shadow.querySelector(".ps-button"))return;const e={backgroundColor:"--ps-btn-bg",textColor:"--ps-btn-color",borderRadius:"--ps-btn-radius",fontSize:"--ps-btn-font-size",fontFamily:"--ps-btn-font",fontWeight:"--ps-btn-font-weight",padding:"--ps-btn-padding",border:"--ps-btn-border",width:"--ps-btn-width",height:"--ps-btn-height",hoverBackgroundColor:"--ps-btn-hover-bg",hoverTextColor:"--ps-btn-hover-color",iconSize:"--ps-btn-icon-size",iconColor:"--ps-btn-icon-color",boxShadow:"--ps-btn-shadow"};for(const[t,n]of Object.entries(e)){const e=this.buttonStyles[t];e&&this.style.setProperty(n,e)}}applyModalStyles(){const e={overlayColor:"--ps-modal-overlay",backgroundColor:"--ps-modal-bg",textColor:"--ps-modal-color",borderRadius:"--ps-modal-radius",width:"--ps-modal-width",maxWidth:"--ps-modal-max-width",fontFamily:"--ps-modal-font",headerBackgroundColor:"--ps-modal-header-bg",headerTextColor:"--ps-modal-header-color",closeButtonColor:"--ps-modal-close-color",uploadBorderColor:"--ps-upload-border",uploadBackgroundColor:"--ps-upload-bg",uploadTextColor:"--ps-upload-color",uploadIconColor:"--ps-upload-icon-color",primaryButtonBackgroundColor:"--ps-modal-primary-bg",primaryButtonTextColor:"--ps-modal-primary-color",primaryButtonBorderRadius:"--ps-modal-primary-radius",loaderColor:"--ps-loader",resultBorderRadius:"--ps-result-radius"};for(const[t,n]of Object.entries(e)){const e=this.modalStyles[t];e&&this.style.setProperty(n,e)}}}"undefined"==typeof window||customElements.get("primestyle-tryon")||customElements.define("primestyle-tryon",d),e.ApiClient=t,e.PrimeStyleError=n,e.PrimeStyleTryon=d,e.SseClient=s,e.compressImage=a,e.detectProductImage=r,e.isValidImageFile=l,Object.defineProperty(e,Symbol.toStringTag,{value:"Module"})});
|