@primestyleai/tryon 3.7.0 → 3.9.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/dist/PrimeStyleTryon.d.ts +1 -0
- package/dist/i18n/cs.d.ts +5 -0
- package/dist/i18n/da.d.ts +5 -0
- package/dist/i18n/de.d.ts +5 -0
- package/dist/i18n/en.d.ts +9 -0
- package/dist/i18n/es.d.ts +5 -0
- package/dist/i18n/fi.d.ts +5 -0
- package/dist/i18n/fr.d.ts +5 -0
- package/dist/i18n/index.d.ts +37 -0
- package/dist/i18n/it.d.ts +5 -0
- package/dist/i18n/ja.d.ts +5 -0
- package/dist/i18n/ko.d.ts +5 -0
- package/dist/i18n/nb.d.ts +5 -0
- package/dist/i18n/nl.d.ts +5 -0
- package/dist/i18n/pl.d.ts +5 -0
- package/dist/i18n/pt-br.d.ts +5 -0
- package/dist/i18n/pt-pt.d.ts +5 -0
- package/dist/i18n/sv.d.ts +5 -0
- package/dist/i18n/th.d.ts +5 -0
- package/dist/i18n/tr.d.ts +5 -0
- package/dist/i18n/zh-cn.d.ts +5 -0
- package/dist/i18n/zh-tw.d.ts +5 -0
- package/dist/index-B0KE3c8S.js +2951 -0
- package/dist/index.d.ts +2 -0
- package/dist/primestyle-tryon.js +35 -24
- package/dist/react/index.d.ts +3 -1
- package/dist/react/index.js +618 -221
- package/dist/sizing/constants.d.ts +13 -0
- package/dist/sizing/normalizer.d.ts +35 -0
- package/dist/types.d.ts +47 -0
- package/package.json +1 -1
- package/dist/image-utils-C9bJ1zKO.js +0 -186
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Header alias mapping — maps common size guide column headers
|
|
3
|
+
* to standardised measurement field keys.
|
|
4
|
+
*/
|
|
5
|
+
export declare const HEADER_ALIASES: Record<string, string>;
|
|
6
|
+
/** Human-readable labels for each measurement key */
|
|
7
|
+
export declare const FIELD_LABELS: Record<string, string>;
|
|
8
|
+
/** Placeholder values shown in form inputs */
|
|
9
|
+
export declare const FIELD_PLACEHOLDERS: Record<string, string>;
|
|
10
|
+
/** Regex to detect size label strings (S/M/L/XL or numeric 28-52) */
|
|
11
|
+
export declare const SIZE_LABEL_PATTERN: RegExp;
|
|
12
|
+
/** Keys that are shoe-related */
|
|
13
|
+
export declare const SHOE_FIELD_KEYS: Set<string>;
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
export interface SizeGuideField {
|
|
2
|
+
key: string;
|
|
3
|
+
label: string;
|
|
4
|
+
required: boolean;
|
|
5
|
+
unit: "cm" | "size" | string;
|
|
6
|
+
placeholder?: string;
|
|
7
|
+
category: "body" | "shoe" | "other";
|
|
8
|
+
}
|
|
9
|
+
export interface NormalizedSection {
|
|
10
|
+
headers: string[];
|
|
11
|
+
rows: string[][];
|
|
12
|
+
requiredFields: SizeGuideField[];
|
|
13
|
+
}
|
|
14
|
+
export interface NormalizedSizeGuide {
|
|
15
|
+
found: true;
|
|
16
|
+
title?: string;
|
|
17
|
+
headers: string[];
|
|
18
|
+
rows: string[][];
|
|
19
|
+
requiredFields: SizeGuideField[];
|
|
20
|
+
sections?: Record<string, NormalizedSection>;
|
|
21
|
+
}
|
|
22
|
+
export type NormalizerResult = {
|
|
23
|
+
success: true;
|
|
24
|
+
data: NormalizedSizeGuide;
|
|
25
|
+
} | {
|
|
26
|
+
success: false;
|
|
27
|
+
reason: string;
|
|
28
|
+
};
|
|
29
|
+
export declare function deriveRequiredFields(headers: string[]): SizeGuideField[];
|
|
30
|
+
/**
|
|
31
|
+
* Attempts to normalise `sizeGuideData` (any format) into a structured
|
|
32
|
+
* `NormalizedSizeGuide`. Returns `{ success: false }` for unrecognised
|
|
33
|
+
* formats (HTML, images, free-form text) so the caller can fall back to AI.
|
|
34
|
+
*/
|
|
35
|
+
export declare function normalizeSizeGuide(input: unknown): NormalizerResult;
|
package/dist/types.d.ts
CHANGED
|
@@ -137,6 +137,53 @@ export interface SizeGuideData {
|
|
|
137
137
|
headers: string[];
|
|
138
138
|
rows: string[][];
|
|
139
139
|
}
|
|
140
|
+
/** A measurement field descriptor used by the sizing form */
|
|
141
|
+
export interface SizeGuideField {
|
|
142
|
+
key: string;
|
|
143
|
+
label: string;
|
|
144
|
+
required: boolean;
|
|
145
|
+
unit: "cm" | "size" | string;
|
|
146
|
+
placeholder?: string;
|
|
147
|
+
category: "body" | "shoe" | "other";
|
|
148
|
+
}
|
|
149
|
+
/** A normalised section within a multi-garment size guide (e.g. tuxedo) */
|
|
150
|
+
export interface NormalizedSection {
|
|
151
|
+
headers: string[];
|
|
152
|
+
rows: string[][];
|
|
153
|
+
requiredFields: SizeGuideField[];
|
|
154
|
+
}
|
|
155
|
+
/** Fully normalised size guide ready for deterministic comparison */
|
|
156
|
+
export interface NormalizedSizeGuide {
|
|
157
|
+
found: true;
|
|
158
|
+
title?: string;
|
|
159
|
+
headers: string[];
|
|
160
|
+
rows: string[][];
|
|
161
|
+
requiredFields: SizeGuideField[];
|
|
162
|
+
sections?: Record<string, NormalizedSection>;
|
|
163
|
+
}
|
|
164
|
+
/** Match detail for a single measurement in a sizing recommendation */
|
|
165
|
+
export interface MatchDetail {
|
|
166
|
+
measurement: string;
|
|
167
|
+
userValue: string;
|
|
168
|
+
chartRange: string;
|
|
169
|
+
fit: "good" | "tight" | "loose";
|
|
170
|
+
}
|
|
171
|
+
/** Per-section recommendation result (for multi-garment products) */
|
|
172
|
+
export interface SectionRecommendation {
|
|
173
|
+
recommendedSize: string;
|
|
174
|
+
matchDetails: MatchDetail[];
|
|
175
|
+
}
|
|
176
|
+
/** Full sizing recommendation result */
|
|
177
|
+
export interface SizingResult {
|
|
178
|
+
recommendedSize: string;
|
|
179
|
+
recommendedLength?: string | null;
|
|
180
|
+
confidence: "high" | "medium" | "low" | string;
|
|
181
|
+
reasoning: string;
|
|
182
|
+
internationalSizes?: Record<string, string>;
|
|
183
|
+
matchDetails?: MatchDetail[];
|
|
184
|
+
method?: "deterministic" | "ai";
|
|
185
|
+
sections?: Record<string, SectionRecommendation>;
|
|
186
|
+
}
|
|
140
187
|
/** Custom events emitted by the component */
|
|
141
188
|
export interface PrimeStyleEvents {
|
|
142
189
|
"ps:open": CustomEvent<void>;
|
package/package.json
CHANGED
|
@@ -1,186 +0,0 @@
|
|
|
1
|
-
const DEFAULT_API_URL = "https://myaifitting.com";
|
|
2
|
-
class ApiClient {
|
|
3
|
-
constructor(apiKey, apiUrl) {
|
|
4
|
-
this.apiKey = apiKey;
|
|
5
|
-
this.baseUrl = (apiUrl || DEFAULT_API_URL).replace(/\/+$/, "");
|
|
6
|
-
}
|
|
7
|
-
get headers() {
|
|
8
|
-
return {
|
|
9
|
-
"Content-Type": "application/json",
|
|
10
|
-
Authorization: `Bearer ${this.apiKey}`
|
|
11
|
-
};
|
|
12
|
-
}
|
|
13
|
-
async submitTryOn(modelImage, garmentImage) {
|
|
14
|
-
const res = await fetch(`${this.baseUrl}/api/v1/tryon`, {
|
|
15
|
-
method: "POST",
|
|
16
|
-
headers: this.headers,
|
|
17
|
-
body: JSON.stringify({ modelImage, garmentImage })
|
|
18
|
-
});
|
|
19
|
-
if (!res.ok) {
|
|
20
|
-
const data = await res.json().catch(() => ({}));
|
|
21
|
-
if (res.status === 402) {
|
|
22
|
-
throw new PrimeStyleError(
|
|
23
|
-
data.message || "Insufficient try-ons",
|
|
24
|
-
"INSUFFICIENT_BALANCE"
|
|
25
|
-
);
|
|
26
|
-
}
|
|
27
|
-
throw new PrimeStyleError(
|
|
28
|
-
data.message || "Failed to submit try-on",
|
|
29
|
-
"API_ERROR"
|
|
30
|
-
);
|
|
31
|
-
}
|
|
32
|
-
return res.json();
|
|
33
|
-
}
|
|
34
|
-
async getStatus(jobId) {
|
|
35
|
-
const res = await fetch(`${this.baseUrl}/api/v1/tryon/status/${jobId}`, {
|
|
36
|
-
headers: this.headers
|
|
37
|
-
});
|
|
38
|
-
if (!res.ok) {
|
|
39
|
-
const data = await res.json().catch(() => ({}));
|
|
40
|
-
throw new PrimeStyleError(
|
|
41
|
-
data.message || "Failed to get status",
|
|
42
|
-
"API_ERROR"
|
|
43
|
-
);
|
|
44
|
-
}
|
|
45
|
-
return res.json();
|
|
46
|
-
}
|
|
47
|
-
getStreamUrl() {
|
|
48
|
-
return `${this.baseUrl}/api/v1/tryon/stream?key=${encodeURIComponent(this.apiKey)}`;
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
class PrimeStyleError extends Error {
|
|
52
|
-
constructor(message, code) {
|
|
53
|
-
super(message);
|
|
54
|
-
this.name = "PrimeStyleError";
|
|
55
|
-
this.code = code;
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
class SseClient {
|
|
59
|
-
constructor(streamUrl) {
|
|
60
|
-
this.eventSource = null;
|
|
61
|
-
this.listeners = /* @__PURE__ */ new Map();
|
|
62
|
-
this.reconnectTimer = null;
|
|
63
|
-
this.reconnectAttempts = 0;
|
|
64
|
-
this.maxReconnectAttempts = 5;
|
|
65
|
-
this.streamUrl = streamUrl;
|
|
66
|
-
}
|
|
67
|
-
connect() {
|
|
68
|
-
if (this.eventSource) return;
|
|
69
|
-
this.eventSource = new EventSource(this.streamUrl);
|
|
70
|
-
this.eventSource.addEventListener("vto-update", (event) => {
|
|
71
|
-
try {
|
|
72
|
-
const data = JSON.parse(event.data);
|
|
73
|
-
this.emit(data.galleryId, data);
|
|
74
|
-
} catch {
|
|
75
|
-
}
|
|
76
|
-
});
|
|
77
|
-
this.eventSource.onopen = () => {
|
|
78
|
-
this.reconnectAttempts = 0;
|
|
79
|
-
};
|
|
80
|
-
this.eventSource.onerror = () => {
|
|
81
|
-
this.eventSource?.close();
|
|
82
|
-
this.eventSource = null;
|
|
83
|
-
this.scheduleReconnect();
|
|
84
|
-
};
|
|
85
|
-
}
|
|
86
|
-
scheduleReconnect() {
|
|
87
|
-
if (this.reconnectAttempts >= this.maxReconnectAttempts) return;
|
|
88
|
-
if (this.listeners.size === 0) return;
|
|
89
|
-
const delay = Math.min(1e3 * 2 ** this.reconnectAttempts, 3e4);
|
|
90
|
-
this.reconnectAttempts++;
|
|
91
|
-
this.reconnectTimer = setTimeout(() => {
|
|
92
|
-
this.connect();
|
|
93
|
-
}, delay);
|
|
94
|
-
}
|
|
95
|
-
onJob(jobId, callback) {
|
|
96
|
-
if (!this.listeners.has(jobId)) {
|
|
97
|
-
this.listeners.set(jobId, /* @__PURE__ */ new Set());
|
|
98
|
-
}
|
|
99
|
-
this.listeners.get(jobId).add(callback);
|
|
100
|
-
if (!this.eventSource) {
|
|
101
|
-
this.connect();
|
|
102
|
-
}
|
|
103
|
-
return () => {
|
|
104
|
-
const jobListeners = this.listeners.get(jobId);
|
|
105
|
-
if (jobListeners) {
|
|
106
|
-
jobListeners.delete(callback);
|
|
107
|
-
if (jobListeners.size === 0) {
|
|
108
|
-
this.listeners.delete(jobId);
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
if (this.listeners.size === 0) {
|
|
112
|
-
this.disconnect();
|
|
113
|
-
}
|
|
114
|
-
};
|
|
115
|
-
}
|
|
116
|
-
emit(jobId, update) {
|
|
117
|
-
const callbacks = this.listeners.get(jobId);
|
|
118
|
-
if (callbacks) {
|
|
119
|
-
callbacks.forEach((cb) => cb(update));
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
disconnect() {
|
|
123
|
-
if (this.reconnectTimer) {
|
|
124
|
-
clearTimeout(this.reconnectTimer);
|
|
125
|
-
this.reconnectTimer = null;
|
|
126
|
-
}
|
|
127
|
-
if (this.eventSource) {
|
|
128
|
-
this.eventSource.close();
|
|
129
|
-
this.eventSource = null;
|
|
130
|
-
}
|
|
131
|
-
this.listeners.clear();
|
|
132
|
-
this.reconnectAttempts = 0;
|
|
133
|
-
}
|
|
134
|
-
}
|
|
135
|
-
const MAX_DIMENSION = 512;
|
|
136
|
-
const JPEG_QUALITY = 0.65;
|
|
137
|
-
function compressImage(file) {
|
|
138
|
-
return new Promise((resolve, reject) => {
|
|
139
|
-
const reader = new FileReader();
|
|
140
|
-
reader.onload = () => {
|
|
141
|
-
const img = new Image();
|
|
142
|
-
img.onload = () => {
|
|
143
|
-
try {
|
|
144
|
-
const canvas = document.createElement("canvas");
|
|
145
|
-
let { width, height } = img;
|
|
146
|
-
if (width > MAX_DIMENSION || height > MAX_DIMENSION) {
|
|
147
|
-
if (width > height) {
|
|
148
|
-
height = Math.round(height * MAX_DIMENSION / width);
|
|
149
|
-
width = MAX_DIMENSION;
|
|
150
|
-
} else {
|
|
151
|
-
width = Math.round(width * MAX_DIMENSION / height);
|
|
152
|
-
height = MAX_DIMENSION;
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
|
-
canvas.width = width;
|
|
156
|
-
canvas.height = height;
|
|
157
|
-
const ctx = canvas.getContext("2d");
|
|
158
|
-
if (!ctx) {
|
|
159
|
-
reject(new Error("Canvas context not available"));
|
|
160
|
-
return;
|
|
161
|
-
}
|
|
162
|
-
ctx.drawImage(img, 0, 0, width, height);
|
|
163
|
-
const dataUrl = canvas.toDataURL("image/jpeg", JPEG_QUALITY);
|
|
164
|
-
resolve(dataUrl);
|
|
165
|
-
} catch (err) {
|
|
166
|
-
reject(err);
|
|
167
|
-
}
|
|
168
|
-
};
|
|
169
|
-
img.onerror = () => reject(new Error("Failed to load image"));
|
|
170
|
-
img.src = reader.result;
|
|
171
|
-
};
|
|
172
|
-
reader.onerror = () => reject(new Error("Failed to read file"));
|
|
173
|
-
reader.readAsDataURL(file);
|
|
174
|
-
});
|
|
175
|
-
}
|
|
176
|
-
function isValidImageFile(file) {
|
|
177
|
-
const accepted = ["image/jpeg", "image/png", "image/webp"];
|
|
178
|
-
return accepted.includes(file.type);
|
|
179
|
-
}
|
|
180
|
-
export {
|
|
181
|
-
ApiClient as A,
|
|
182
|
-
PrimeStyleError as P,
|
|
183
|
-
SseClient as S,
|
|
184
|
-
compressImage as c,
|
|
185
|
-
isValidImageFile as i
|
|
186
|
-
};
|