import type { CSSProperties } from "react"; import type { ButtonStyles, ModalStyles, PrimeStyleClassNames, MatchDetail as MatchDetailType, SizingResult as SizingResultType, SavedTryOnResult as SavedTryOnResultType, SizeGuideField as SizeGuideFieldType, NormalizedSection } from "../types"; export type BodyType = "slim" | "athletic" | "average" | "stocky" | "plus"; export type ChestProfile = "narrow" | "average" | "broad"; export type MidsectionProfile = "flat" | "average" | "round"; export type HipProfile = "narrow" | "average" | "full"; export type PrimeStyleOutfitSlot = "top" | "bottom" | "dress" | "outerwear" | "shoe" | "bag" | "accessory"; export interface PrimeStyleOutfitItem { slot: PrimeStyleOutfitSlot; productId: string; title: string; image: string; displayImage?: string; url?: string; color?: string; garmentType?: string; recommendedSize?: string; selected?: boolean; alternatives?: Array>; } export interface PrimeStyleOutfitLook { id: string; label?: string; items: PrimeStyleOutfitItem[]; } export type MatchDetail = MatchDetailType; export type SizingResult = SizingResultType; export type SavedTryOnResult = SavedTryOnResultType; export type SizeGuideField = SizeGuideFieldType; /** Full body measurements computed by the LLM (cm or in based on measurementsUnit) */ export interface ProfileMeasurements { chest?: number; bust?: number; waist?: number; hips?: number; shoulderWidth?: number; sleeveLength?: number; inseam?: number; neckCircumference?: number; headCircumference?: number; thighCircumference?: number; wristCircumference?: number; footLengthCm?: number; height?: number; /** Allow other fields the backend may return */ [key: string]: number | undefined; } /** A cached size recommendation for a specific product */ export interface SizeHistoryEntry { productId: string; productTitle: string; productImage?: string; recommendedSize: string; confidence?: string; /** For multi-section products: { "Jacket Size": "50", "Pants": "34", ... } */ sections?: Record; /** Full per-section data (size/length/availableSizes/...) — preserved so * cached recommendations carry the same detail as a fresh API response. */ sectionsFull?: Record; /** Top-level recommended length (single-piece products that have a separate * length axis like Regular / Long / Short). */ recommendedLength?: string; savedAt: number; } export interface Profile { id: string; name: string; gender: "male" | "female"; height?: number; weight?: number; age?: number; /** @deprecated Use `height` */ heightCm?: number; /** @deprecated Use `weight` */ weightKg?: number; chestProfile?: ChestProfile; midsectionProfile?: MidsectionProfile; hipProfile?: HipProfile; bandSize?: string; cupSize?: string; braSizeRegion?: string; photoBase64?: string; /** Optional uploaded profile photo URL/data URL used by remote profile sync. */ photoUrl?: string; /** Internal sync flag used when the shopper explicitly removes their profile photo. */ profilePhotoRemoved?: boolean; measurements?: ProfileMeasurements; measurementsUnit?: "cm" | "in"; sizeHistory?: SizeHistoryEntry[]; chest?: number; bust?: number; waist?: number; hips?: number; shoulderWidth?: number; sleeveLength?: number; inseam?: number; neckCircumference?: number; footLengthCm?: number; shoeEU?: string; shoeUS?: string; shoeUK?: string; fitPreference?: string; country?: string; sizingUnit?: string; heightUnit?: string; weightUnit?: string; customMeasurements?: Record; createdAt?: number; lastUsedAt?: number; /** Bumped any time measurements/answers change — used to invalidate sizeHistory cache */ lastEditedAt?: number; } export interface ProfileCompletionDraft { source?: "size-result" | string; createdAt?: number; productId?: string; productTitle?: string; gender?: "male" | "female"; height?: number; weight?: number; heightUnit?: "cm" | "in" | "ft" | string; weightUnit?: "kg" | "lbs" | string; sizingUnit?: "cm" | "in" | string; age?: number; chestProfile?: ChestProfile; midsectionProfile?: MidsectionProfile; hipProfile?: HipProfile; bandSize?: string; cupSize?: string; braSizeRegion?: string; photoBase64?: string; photoUrl?: string; measurements?: ProfileMeasurements; measurementsUnit?: "cm" | "in"; customMeasurements?: Record; } export interface HistoryEntry { id: string; productId?: string; productTitle?: string; productUrl?: string; jobId?: string; productImage?: string; productImages?: string[]; productCarouselItems?: Array<{ image: string; title?: string; href?: string; }>; resultImageUrl?: string; recommendedSize?: string; confidence?: string; reasoning?: string; internationalSizes?: Record; matchDetails?: MatchDetail[]; profileName?: string; date: number; /** Full sizing result for restoring the result view */ sizingResult?: SizingResultType | null; /** Size guide snapshot for restoring charts & multi-section layout */ sizeGuide?: { found: boolean; title?: string; headers?: string[]; rows?: string[][]; sections?: Record; sectionImages?: Record; } | null; /** Snapshot of the inputs/estimates needed to complete a fit profile from a restored result. */ profileCompletionDraft?: ProfileCompletionDraft; /** True when the user's model photo for this entry is stored as a Blob in * IndexedDB (key = entry.id). Restored as a File on history click so the * user does not need to re-upload before clicking Try On. Photos live in * IndexedDB rather than in this localStorage object to avoid the ~5–10 MB * quota. Old entries without a photo set this false/undefined. */ hasPhoto?: boolean; /** True when the try-on RESULT image is stored as a Blob in IndexedDB * (key = `result-`). Restored as an object URL on history * click so the result survives even if the original URL (Cloudinary * / data:) expired or was lost. */ hasResult?: boolean; /** What the user actually selected when they ran the try-on (per section). */ selectedSizes?: Array<{ sectionName: string; selectedSize: string; selectedLength?: string; displayLabel: string; isOverride: boolean; }>; } export interface PrimeStyleAddToBagPayload { productId?: string; productTitle?: string; productUrl?: string; recommendedSize?: string; sizingResult?: SizingResultType | null; resultImageUrl?: string | null; historyEntryId?: string; selectedSizes?: HistoryEntry["selectedSizes"]; } export interface SizeGuide { found: boolean; title?: string; headers?: string[]; rows?: string[][]; requiredFields?: SizeGuideField[]; sections?: Record; /** Mapping of section name → product image URL (classified by AI) */ sectionImages?: Record; } export type ViewState = "idle" | "welcome" | "body-profile" | "estimation-review" | "size-result" | "upload" | "photo-guide" | "processing" | "result" | "error" | "no-chart" | "profiles"; export type DrawerType = "profiles" | "history" | null; export interface PrimeStyleTryonProps { productImage: string; /** Optional gallery of product photos for the active color/variant. * * - Shown as an auto-cycling carousel on the single-garment * SizeResultView while the try-on image is being generated. * - At try-on time the SDK runs MediaPipe BlazePose on every entry in * parallel and forwards the highest-scoring "model wearing the * garment" shot to Gemini as image 2. Flat-lays / close-up details * score ~0 and are skipped. The first image still shows in the UI * unchanged. */ productImages?: string[]; /** Optional carousel items shown in the size-result card. * Used for "complete the look" recommendations. Falls back to productImages. */ productCarouselItems?: Array<{ image: string; title?: string; href?: string; }>; /** Optional explicit override — if you already know which image in the * gallery is the model-wearing-the-garment shot, pass it here and the * SDK skips the MediaPipe-based auto-pick. Falls through to auto-pick * (or to `productImage`) when omitted. */ garmentReferenceImage?: string; /** Optional clean product/detail image. When omitted, the backend picker * selects one from productImages and sends it as Gemini's extra design reference. */ garmentDetailImage?: string; productTitle?: string; /** Absolute product page URL, used by restored history results to continue shopping. */ productUrl?: string; /** Stable product identifier — used to cache size recommendations per (profile, product) */ productId?: string; /** Optional product category metadata used to select the correct sizing flow. */ productCategory?: string; /** Optional target product gender metadata. Prevents AI fallback when the host already knows it. */ productGender?: string; /** Optional product subcategory metadata used to select accessory flows like belts, hats, and eyewear. */ productSubcategory?: string; /** Merchant/product-type label used before keyword fallback. */ productType?: string; /** Optional product vendor/brand metadata forwarded to backend context. */ productVendor?: string; /** Explicit fit category override, e.g. apparel, belt, shoe, hat, sunglasses. */ productFitType?: string; /** Product tags used as sizing-flow detection signals. */ productTags?: string[] | string; /** Alias for productTags for hosts that already use a `tags` property. */ tags?: string[] | string; /** Free-text product description (e.g. "Brown slip maxi dress with spaghetti straps"). * Forwarded to the backend try-on prompt so Gemini knows the garment context. */ productDescription?: string; /** Selects the product source used by Outfit Builder. `ai-stylist` expects * authenticated host-supplied looks and never falls back to another store's catalog. */ outfitBuilderSource?: "sdk" | "ai-stylist"; /** Host-supplied AI Stylist looks matched around the current pinned PDP item. * `undefined` means loading; an empty array means no compatible look was found. */ instantOutfitLooks?: PrimeStyleOutfitLook[]; /** Material / fabric composition (e.g. "60% Recycled Polyester / 40% Polyester", * "100% Cotton", "Wool blend"). Forwarded to the prompt so Gemini drapes * the fabric correctly without guessing from the image alone. */ productMaterial?: string; buttonText?: string; /** When true, a completed try-on for this product turns the storefront button into a saved fit result. */ limitOneColorTryOnPerProduct?: boolean; /** Shopify setting: one shopper IP can only generate this product up to the configured cap. */ tryOnProductIpLimitEnabled?: boolean; /** Shopify setting: max generations per shopper IP/product. */ tryOnProductIpLimitMaxAttempts?: number; apiUrl?: string; showPoweredBy?: boolean; /** Show the default camera icon in the button. Defaults to true. Set to false to hide it. */ showIcon?: boolean; /** Custom icon element to replace the default camera icon. Pass any React node (e.g. an or ). */ buttonIcon?: React.ReactNode; /** BCP-47 locale tag for UI translations (e.g. "en", "ja", "pt-BR"). Auto-detects from browser if omitted. */ locale?: string; buttonStyles?: ButtonStyles; modalStyles?: ModalStyles; classNames?: PrimeStyleClassNames; className?: string; style?: CSSProperties; /** Optional portal target for modal/dropdown overlays. */ portalContainer?: Element | DocumentFragment | null; /** Optional Microsoft Clarity project id. Defaults to PrimeStyleAI's SDK replay project. */ clarityProjectId?: string; onOpen?: () => void; onClose?: () => void; onUpload?: (file: File) => void; onProcessing?: (jobId: string) => void; onComplete?: (result: { jobId: string; imageUrl: string; }) => void; onError?: (error: { message: string; code?: string; }) => void; /** Called when the shopper clicks Add to Bag on the SDK result screen. */ onAddToBag?: (payload: PrimeStyleAddToBagPayload) => void | Promise; /** Optional result-screen CTA label overrides. */ addToBagLabel?: string; continueShoppingLabel?: string; backToProductPageLabel?: string; /** Size guide data — pass as-is, any format (JSON object, array, HTML). Structured formats are parsed instantly client-side; unrecognised formats fall back to AI. */ sizeGuideData?: unknown; }