styllar-react-plugin 1.0.1 → 1.0.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/dist/styllar-react-plugin.js +403 -346
- package/dist/styllar-react-plugin.umd.cjs +10 -10
- package/package.json +1 -1
- package/src/StyllarPlugin.jsx +126 -75
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
(function(
|
|
1
|
+
(function(W,_){typeof exports=="object"&&typeof module<"u"?_(exports,require("react")):typeof define=="function"&&define.amd?define(["exports","react"],_):(W=typeof globalThis<"u"?globalThis:W||self,_(W.StyllarReactPlugin={},W.React))})(this,function(W,_){"use strict";var re={exports:{}},J={};/**
|
|
2
2
|
* @license React
|
|
3
3
|
* react-jsx-runtime.production.min.js
|
|
4
4
|
*
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
*
|
|
7
7
|
* This source code is licensed under the MIT license found in the
|
|
8
8
|
* LICENSE file in the root directory of this source tree.
|
|
9
|
-
*/var
|
|
9
|
+
*/var me;function Me(){if(me)return J;me=1;var j=_,E=Symbol.for("react.element"),f=Symbol.for("react.fragment"),g=Object.prototype.hasOwnProperty,S=j.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.ReactCurrentOwner,N={key:!0,ref:!0,__self:!0,__source:!0};function I(D,y,A){var h,C={},k=null,z=null;A!==void 0&&(k=""+A),y.key!==void 0&&(k=""+y.key),y.ref!==void 0&&(z=y.ref);for(h in y)g.call(y,h)&&!N.hasOwnProperty(h)&&(C[h]=y[h]);if(D&&D.defaultProps)for(h in y=D.defaultProps,y)C[h]===void 0&&(C[h]=y[h]);return{$$typeof:E,type:D,key:k,ref:z,props:C,_owner:S.current}}return J.Fragment=f,J.jsx=I,J.jsxs=I,J}var K={};/**
|
|
10
10
|
* @license React
|
|
11
11
|
* react-jsx-runtime.development.js
|
|
12
12
|
*
|
|
@@ -14,17 +14,17 @@
|
|
|
14
14
|
*
|
|
15
15
|
* This source code is licensed under the MIT license found in the
|
|
16
16
|
* LICENSE file in the root directory of this source tree.
|
|
17
|
-
*/var
|
|
18
|
-
`+
|
|
19
|
-
`),
|
|
20
|
-
`),
|
|
21
|
-
`+
|
|
17
|
+
*/var ge;function Ue(){return ge||(ge=1,process.env.NODE_ENV!=="production"&&function(){var j=_,E=Symbol.for("react.element"),f=Symbol.for("react.portal"),g=Symbol.for("react.fragment"),S=Symbol.for("react.strict_mode"),N=Symbol.for("react.profiler"),I=Symbol.for("react.provider"),D=Symbol.for("react.context"),y=Symbol.for("react.forward_ref"),A=Symbol.for("react.suspense"),h=Symbol.for("react.suspense_list"),C=Symbol.for("react.memo"),k=Symbol.for("react.lazy"),z=Symbol.for("react.offscreen"),Y=Symbol.iterator,X="@@iterator";function G(e){if(e===null||typeof e!="object")return null;var r=Y&&e[Y]||e[X];return typeof r=="function"?r:null}var F=j.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED;function x(e){{for(var r=arguments.length,t=new Array(r>1?r-1:0),n=1;n<r;n++)t[n-1]=arguments[n];ne("error",e,t)}}function ne(e,r,t){{var n=F.ReactDebugCurrentFrame,l=n.getStackAddendum();l!==""&&(r+="%s",t=t.concat([l]));var c=t.map(function(s){return String(s)});c.unshift("Warning: "+r),Function.prototype.apply.call(console[e],console,c)}}var ae=!1,oe=!1,q=!1,se=!1,i=!1,u;u=Symbol.for("react.module.reference");function p(e){return!!(typeof e=="string"||typeof e=="function"||e===g||e===N||i||e===S||e===A||e===h||se||e===z||ae||oe||q||typeof e=="object"&&e!==null&&(e.$$typeof===k||e.$$typeof===C||e.$$typeof===I||e.$$typeof===D||e.$$typeof===y||e.$$typeof===u||e.getModuleId!==void 0))}function T(e,r,t){var n=e.displayName;if(n)return n;var l=r.displayName||r.name||"";return l!==""?t+"("+l+")":t}function w(e){return e.displayName||"Context"}function v(e){if(e==null)return null;if(typeof e.tag=="number"&&x("Received an unexpected object in getComponentNameFromType(). This is likely a bug in React. Please file an issue."),typeof e=="function")return e.displayName||e.name||null;if(typeof e=="string")return e;switch(e){case g:return"Fragment";case f:return"Portal";case N:return"Profiler";case S:return"StrictMode";case A:return"Suspense";case h:return"SuspenseList"}if(typeof e=="object")switch(e.$$typeof){case D:var r=e;return w(r)+".Consumer";case I:var t=e;return w(t._context)+".Provider";case y:return T(e,e.render,"ForwardRef");case C:var n=e.displayName||null;return n!==null?n:v(e.type)||"Memo";case k:{var l=e,c=l._payload,s=l._init;try{return v(s(c))}catch{return null}}}return null}var b=Object.assign,L=0,he,be,ye,xe,_e,Ee,Se;function we(){}we.__reactDisabledLog=!0;function Xe(){{if(L===0){he=console.log,be=console.info,ye=console.warn,xe=console.error,_e=console.group,Ee=console.groupCollapsed,Se=console.groupEnd;var e={configurable:!0,enumerable:!0,value:we,writable:!0};Object.defineProperties(console,{info:e,log:e,warn:e,error:e,group:e,groupCollapsed:e,groupEnd:e})}L++}}function Ge(){{if(L--,L===0){var e={configurable:!0,enumerable:!0,writable:!0};Object.defineProperties(console,{log:b({},e,{value:he}),info:b({},e,{value:be}),warn:b({},e,{value:ye}),error:b({},e,{value:xe}),group:b({},e,{value:_e}),groupCollapsed:b({},e,{value:Ee}),groupEnd:b({},e,{value:Se})})}L<0&&x("disabledDepth fell below zero. This is a bug in React. Please file an issue.")}}var ie=F.ReactCurrentDispatcher,le;function $(e,r,t){{if(le===void 0)try{throw Error()}catch(l){var n=l.stack.trim().match(/\n( *(at )?)/);le=n&&n[1]||""}return`
|
|
18
|
+
`+le+e}}var ce=!1,Z;{var He=typeof WeakMap=="function"?WeakMap:Map;Z=new He}function Re(e,r){if(!e||ce)return"";{var t=Z.get(e);if(t!==void 0)return t}var n;ce=!0;var l=Error.prepareStackTrace;Error.prepareStackTrace=void 0;var c;c=ie.current,ie.current=null,Xe();try{if(r){var s=function(){throw Error()};if(Object.defineProperty(s.prototype,"props",{set:function(){throw Error()}}),typeof Reflect=="object"&&Reflect.construct){try{Reflect.construct(s,[])}catch(P){n=P}Reflect.construct(e,[],s)}else{try{s.call()}catch(P){n=P}e.call(s.prototype)}}else{try{throw Error()}catch(P){n=P}e()}}catch(P){if(P&&n&&typeof P.stack=="string"){for(var o=P.stack.split(`
|
|
19
|
+
`),R=n.stack.split(`
|
|
20
|
+
`),d=o.length-1,m=R.length-1;d>=1&&m>=0&&o[d]!==R[m];)m--;for(;d>=1&&m>=0;d--,m--)if(o[d]!==R[m]){if(d!==1||m!==1)do if(d--,m--,m<0||o[d]!==R[m]){var O=`
|
|
21
|
+
`+o[d].replace(" at new "," at ");return e.displayName&&O.includes("<anonymous>")&&(O=O.replace("<anonymous>",e.displayName)),typeof e=="function"&&Z.set(e,O),O}while(d>=1&&m>=0);break}}}finally{ce=!1,ie.current=c,Ge(),Error.prepareStackTrace=l}var V=e?e.displayName||e.name:"",U=V?$(V):"";return typeof e=="function"&&Z.set(e,U),U}function qe(e,r,t){return Re(e,!1)}function $e(e){var r=e.prototype;return!!(r&&r.isReactComponent)}function Q(e,r,t){if(e==null)return"";if(typeof e=="function")return Re(e,$e(e));if(typeof e=="string")return $(e);switch(e){case A:return $("Suspense");case h:return $("SuspenseList")}if(typeof e=="object")switch(e.$$typeof){case y:return qe(e.render);case C:return Q(e.type,r,t);case k:{var n=e,l=n._payload,c=n._init;try{return Q(c(l),r,t)}catch{}}}return""}var H=Object.prototype.hasOwnProperty,je={},Te=F.ReactDebugCurrentFrame;function ee(e){if(e){var r=e._owner,t=Q(e.type,e._source,r?r.type:null);Te.setExtraStackFrame(t)}else Te.setExtraStackFrame(null)}function Ze(e,r,t,n,l){{var c=Function.call.bind(H);for(var s in e)if(c(e,s)){var o=void 0;try{if(typeof e[s]!="function"){var R=Error((n||"React class")+": "+t+" type `"+s+"` is invalid; it must be a function, usually from the `prop-types` package, but received `"+typeof e[s]+"`.This often happens because of typos such as `PropTypes.function` instead of `PropTypes.func`.");throw R.name="Invariant Violation",R}o=e[s](r,s,n,t,null,"SECRET_DO_NOT_PASS_THIS_OR_YOU_WILL_BE_FIRED")}catch(d){o=d}o&&!(o instanceof Error)&&(ee(l),x("%s: type specification of %s `%s` is invalid; the type checker function must return `null` or an `Error` but returned a %s. You may have forgotten to pass an argument to the type checker creator (arrayOf, instanceOf, objectOf, oneOf, oneOfType, and shape all require an argument).",n||"React class",t,s,typeof o),ee(null)),o instanceof Error&&!(o.message in je)&&(je[o.message]=!0,ee(l),x("Failed %s type: %s",t,o.message),ee(null))}}}var Qe=Array.isArray;function ue(e){return Qe(e)}function er(e){{var r=typeof Symbol=="function"&&Symbol.toStringTag,t=r&&e[Symbol.toStringTag]||e.constructor.name||"Object";return t}}function rr(e){try{return Pe(e),!1}catch{return!0}}function Pe(e){return""+e}function Ce(e){if(rr(e))return x("The provided key is an unsupported type %s. This value must be coerced to a string before before using it here.",er(e)),Pe(e)}var ke=F.ReactCurrentOwner,tr={key:!0,ref:!0,__self:!0,__source:!0},Oe,De;function nr(e){if(H.call(e,"ref")){var r=Object.getOwnPropertyDescriptor(e,"ref").get;if(r&&r.isReactWarning)return!1}return e.ref!==void 0}function ar(e){if(H.call(e,"key")){var r=Object.getOwnPropertyDescriptor(e,"key").get;if(r&&r.isReactWarning)return!1}return e.key!==void 0}function or(e,r){typeof e.ref=="string"&&ke.current}function sr(e,r){{var t=function(){Oe||(Oe=!0,x("%s: `key` is not a prop. Trying to access it will result in `undefined` being returned. If you need to access the same value within the child component, you should pass it as a different prop. (https://reactjs.org/link/special-props)",r))};t.isReactWarning=!0,Object.defineProperty(e,"key",{get:t,configurable:!0})}}function ir(e,r){{var t=function(){De||(De=!0,x("%s: `ref` is not a prop. Trying to access it will result in `undefined` being returned. If you need to access the same value within the child component, you should pass it as a different prop. (https://reactjs.org/link/special-props)",r))};t.isReactWarning=!0,Object.defineProperty(e,"ref",{get:t,configurable:!0})}}var lr=function(e,r,t,n,l,c,s){var o={$$typeof:E,type:e,key:r,ref:t,props:s,_owner:c};return o._store={},Object.defineProperty(o._store,"validated",{configurable:!1,enumerable:!1,writable:!0,value:!1}),Object.defineProperty(o,"_self",{configurable:!1,enumerable:!1,writable:!1,value:n}),Object.defineProperty(o,"_source",{configurable:!1,enumerable:!1,writable:!1,value:l}),Object.freeze&&(Object.freeze(o.props),Object.freeze(o)),o};function cr(e,r,t,n,l){{var c,s={},o=null,R=null;t!==void 0&&(Ce(t),o=""+t),ar(r)&&(Ce(r.key),o=""+r.key),nr(r)&&(R=r.ref,or(r,l));for(c in r)H.call(r,c)&&!tr.hasOwnProperty(c)&&(s[c]=r[c]);if(e&&e.defaultProps){var d=e.defaultProps;for(c in d)s[c]===void 0&&(s[c]=d[c])}if(o||R){var m=typeof e=="function"?e.displayName||e.name||"Unknown":e;o&&sr(s,m),R&&ir(s,m)}return lr(e,o,R,l,n,ke.current,s)}}var fe=F.ReactCurrentOwner,Ne=F.ReactDebugCurrentFrame;function B(e){if(e){var r=e._owner,t=Q(e.type,e._source,r?r.type:null);Ne.setExtraStackFrame(t)}else Ne.setExtraStackFrame(null)}var de;de=!1;function pe(e){return typeof e=="object"&&e!==null&&e.$$typeof===E}function Ae(){{if(fe.current){var e=v(fe.current.type);if(e)return`
|
|
22
22
|
|
|
23
|
-
Check the render method of \``+e+"`."}return""}}function
|
|
23
|
+
Check the render method of \``+e+"`."}return""}}function ur(e){return""}var Fe={};function fr(e){{var r=Ae();if(!r){var t=typeof e=="string"?e:e.displayName||e.name;t&&(r=`
|
|
24
24
|
|
|
25
|
-
Check the top-level render call using <`+t+">.")}return r}}function
|
|
25
|
+
Check the top-level render call using <`+t+">.")}return r}}function Ie(e,r){{if(!e._store||e._store.validated||e.key!=null)return;e._store.validated=!0;var t=fr(r);if(Fe[t])return;Fe[t]=!0;var n="";e&&e._owner&&e._owner!==fe.current&&(n=" It was passed a child from "+v(e._owner.type)+"."),B(e),x('Each child in a list should have a unique "key" prop.%s%s See https://reactjs.org/link/warning-keys for more information.',t,n),B(null)}}function ze(e,r){{if(typeof e!="object")return;if(ue(e))for(var t=0;t<e.length;t++){var n=e[t];pe(n)&&Ie(n,r)}else if(pe(e))e._store&&(e._store.validated=!0);else if(e){var l=G(e);if(typeof l=="function"&&l!==e.entries)for(var c=l.call(e),s;!(s=c.next()).done;)pe(s.value)&&Ie(s.value,r)}}}function dr(e){{var r=e.type;if(r==null||typeof r=="string")return;var t;if(typeof r=="function")t=r.propTypes;else if(typeof r=="object"&&(r.$$typeof===y||r.$$typeof===C))t=r.propTypes;else return;if(t){var n=v(r);Ze(t,e.props,"prop",n,e)}else if(r.PropTypes!==void 0&&!de){de=!0;var l=v(r);x("Component %s declared `PropTypes` instead of `propTypes`. Did you misspell the property assignment?",l||"Unknown")}typeof r.getDefaultProps=="function"&&!r.getDefaultProps.isReactClassApproved&&x("getDefaultProps is only used on classic React.createClass definitions. Use a static property named `defaultProps` instead.")}}function pr(e){{for(var r=Object.keys(e.props),t=0;t<r.length;t++){var n=r[t];if(n!=="children"&&n!=="key"){B(e),x("Invalid prop `%s` supplied to `React.Fragment`. React.Fragment can only have `key` and `children` props.",n),B(null);break}}e.ref!==null&&(B(e),x("Invalid attribute `ref` supplied to `React.Fragment`."),B(null))}}var Le={};function We(e,r,t,n,l,c){{var s=p(e);if(!s){var o="";(e===void 0||typeof e=="object"&&e!==null&&Object.keys(e).length===0)&&(o+=" You likely forgot to export your component from the file it's defined in, or you might have mixed up default and named imports.");var R=ur();R?o+=R:o+=Ae();var d;e===null?d="null":ue(e)?d="array":e!==void 0&&e.$$typeof===E?(d="<"+(v(e.type)||"Unknown")+" />",o=" Did you accidentally export a JSX literal instead of a component?"):d=typeof e,x("React.jsx: type is invalid -- expected a string (for built-in components) or a class/function (for composite components) but got: %s.%s",d,o)}var m=cr(e,r,t,l,c);if(m==null)return m;if(s){var O=r.children;if(O!==void 0)if(n)if(ue(O)){for(var V=0;V<O.length;V++)ze(O[V],e);Object.freeze&&Object.freeze(O)}else x("React.jsx: Static children should always be an array. You are likely explicitly calling React.jsxs or React.jsxDEV. Use the Babel transform instead.");else ze(O,e)}if(H.call(r,"key")){var U=v(e),P=Object.keys(r).filter(function(yr){return yr!=="key"}),ve=P.length>0?"{key: someKey, "+P.join(": ..., ")+": ...}":"{key: someKey}";if(!Le[U+ve]){var br=P.length>0?"{"+P.join(": ..., ")+": ...}":"{}";x(`A props object containing a "key" prop is being spread into JSX:
|
|
26
26
|
let props = %s;
|
|
27
27
|
<%s {...props} />
|
|
28
28
|
React keys must be passed directly to JSX without using spread:
|
|
29
29
|
let props = %s;
|
|
30
|
-
<%s key={someKey} {...props} />`,
|
|
30
|
+
<%s key={someKey} {...props} />`,ve,U,br,U),Le[U+ve]=!0}}return e===g?pr(m):dr(m),m}}function vr(e,r,t){return We(e,r,t,!0)}function mr(e,r,t){return We(e,r,t,!1)}var gr=mr,hr=vr;K.Fragment=g,K.jsx=gr,K.jsxs=hr}()),K}process.env.NODE_ENV==="production"?re.exports=Me():re.exports=Ue();var a=re.exports;const Ye="StyllarDB",M="user_profile",te=()=>new Promise((j,E)=>{const f=indexedDB.open(Ye,1);f.onupgradeneeded=g=>g.target.result.createObjectStore(M),f.onsuccess=()=>j(f.result),f.onerror=()=>E(f.error)}),Be=async j=>{const E=await te();return new Promise((f,g)=>{const N=E.transaction(M,"readwrite").objectStore(M).put(j,"userData");N.onsuccess=()=>f(),N.onerror=()=>g(N.error)})},Ve=async()=>{const j=await te();return new Promise((E,f)=>{const S=j.transaction(M,"readonly").objectStore(M).get("userData");S.onsuccess=()=>E(S.result),S.onerror=()=>f(S.error)})},Je=async()=>{const j=await te();return new Promise((E,f)=>{const S=j.transaction(M,"readwrite").objectStore(M).delete("userData");S.onsuccess=()=>E(),S.onerror=()=>f(S.error)})},Ke=()=>{const[j,E]=_.useState(!1),[f,g]=_.useState("idle"),[S,N]=_.useState(""),[I,D]=_.useState(null),[y,A]=_.useState(!1),[h,C]=_.useState(""),[k,z]=_.useState(""),[Y,X]=_.useState(null),[G,F]=_.useState(null),x="http://136.113.197.77:5000/predict/size-from-chart";_.useEffect(()=>{(async()=>{try{const u=await Ve();u&&u.frontImage&&u.sideImage&&(C(u.height),z(u.weight),X(u.frontImage),F(u.sideImage),A(!0),console.log("Styllar: Loaded user profile from memory."))}catch(u){console.error("Styllar: Failed to load profile",u)}})()},[]);const ne=i=>{if(!i)return;const u=()=>{const p=document.querySelectorAll('button, .size-swatch, .size-option, li[role="radio"], div[data-value]');for(let T of p){const w=T.innerText.trim().toUpperCase(),v=String(i).trim().toUpperCase();if(w===v||w===`SIZE ${v}`)return T.click(),!0}return!1};if(!u()){let p=0;const T=setInterval(()=>{p++,(u()||p>10)&&clearInterval(T)},500)}};function ae(){var v;let i=0,u=0;const p=["pant","jeans","trouser","skirt","short","legging","jogger","bottom","denim","cargo"],T=["shirt","top","tee","blouse","jacket","coat","hoodie","sweater","vest","cardigan","dress","t-shirt"],w=(document.title+" "+(((v=document.querySelector("h1"))==null?void 0:v.innerText)||"")).toLowerCase();return p.forEach(b=>{w.includes(b)&&(u+=2)}),T.forEach(b=>{w.includes(b)&&(i+=2)}),u>i?"bottom":"top"}function oe(){const i={chart_type:"none",chart_content:null,available_sizes:[]},u=document.querySelector("table");if(u&&/chest|waist|bust|hip|size/i.test(u.innerText))return i.chart_type="html_table",i.chart_content=u.outerHTML,i;const p=document.querySelectorAll("img");for(let v of p){const b=v.src.toLowerCase(),L=(v.alt||"").toLowerCase();if(b.includes("size")&&b.includes("chart")||L.includes("size")&&L.includes("guide"))return i.chart_type="image_url",i.chart_content=v.src,i}const T=document.querySelector('.size-guide, #size-chart, .measurement-guide, [class*="SizeGuide"]');return T?(i.chart_type="raw_text",i.chart_content=T.innerText,i):(document.querySelectorAll("button, span, li").forEach(v=>{const b=v.innerText.trim();/^(XS|S|M|L|XL|XXL|[0-9]{2})$/i.test(b)&&b.length<4&&(i.available_sizes.includes(b)||i.available_sizes.push(b))}),i)}const q=async()=>{if(!Y||!G||!h||!k){alert("Please fill all fields.");return}g("loading"),await Be({height:h,weight:k,frontImage:Y,sideImage:G}),A(!0);const i=ae(),u=oe(),p=new FormData;p.append("front_image",Y),p.append("side_image",G),p.append("height",h),p.append("weight",k),p.append("product_type",i),p.append("chart_type",u.chart_type),p.append("chart_content",u.chart_content),p.append("available_sizes",JSON.stringify(u.available_sizes));try{const w=await(await fetch(x,{method:"POST",body:p})).json();if(w.error)throw new Error(w.error);D(w),g("success"),w.recommended_size&&ne(w.recommended_size)}catch(T){N(T.message),g("error")}},se=async()=>{await Je(),C(""),z(""),X(null),F(null),A(!1),D(null),g("idle")};return a.jsxs("div",{className:"fixed bottom-6 right-6 z-[999999]",children:[a.jsx("button",{onClick:()=>E(!j),className:"bg-black text-white px-7 py-3.5 rounded-full shadow-xl font-bold text-sm hover:opacity-80 transition-opacity",children:"Styllar"}),j&&a.jsxs("div",{className:"absolute bottom-20 right-0 w-[330px] bg-white rounded-2xl p-6 shadow-2xl border border-gray-100 text-black",children:[a.jsxs("div",{className:"flex justify-between items-center mb-4",children:[a.jsx("h3",{className:"m-0 text-lg font-bold",children:"Smart Fit Assistant"}),a.jsx("button",{onClick:()=>E(!1),className:"text-gray-400 hover:text-black text-2xl leading-none",children:"×"})]}),f==="loading"&&a.jsxs("div",{className:"flex flex-col items-center justify-center py-8",children:[a.jsx("div",{className:"w-8 h-8 border-4 border-gray-200 border-t-black rounded-full animate-spin mb-4"}),a.jsx("p",{className:"text-sm text-gray-600",children:"Analyzing body & page data..."})]}),f==="success"&&I&&a.jsxs("div",{className:"text-center py-6",children:[a.jsx("div",{className:"text-sm text-green-600 font-bold tracking-wider",children:"MATCH FOUND"}),a.jsx("div",{className:"text-5xl font-black my-4 text-black",children:I.recommended_size}),a.jsxs("div",{className:"text-xs text-gray-500 mb-6",children:["Confidence: ",I.confidence]}),a.jsx("button",{onClick:()=>{g("idle"),D(null)},className:"text-blue-600 text-sm underline hover:text-blue-800",children:"Scan Another Product"})]}),f==="error"&&a.jsxs("div",{className:"py-4",children:[a.jsxs("p",{className:"text-red-500 text-sm mb-4",children:["Error: ",S]}),a.jsx("button",{onClick:()=>g("idle"),className:"w-full bg-black text-white py-3 rounded-xl font-bold text-sm",children:"Try Again"})]}),f==="idle"&&y&&a.jsxs("div",{className:"py-4",children:[a.jsxs("div",{className:"bg-gray-50 border border-gray-200 rounded-xl p-4 mb-5 text-center",children:[a.jsx("span",{className:"block text-xl mb-2",children:"👤"}),a.jsx("h4",{className:"font-bold text-sm mb-1",children:"Body Profile Saved"}),a.jsxs("p",{className:"text-xs text-gray-500",children:[h,"cm | ",k,"kg"]})]}),a.jsx("button",{onClick:q,className:"w-full bg-black text-white py-3.5 rounded-xl font-bold text-sm hover:opacity-80 transition-opacity mb-3",children:"⚡ Auto-Scan This Product"}),a.jsx("button",{onClick:se,className:"w-full text-gray-500 text-xs underline hover:text-black",children:"Edit Profile / Upload New Photos"})]}),f==="idle"&&!y&&a.jsxs("div",{children:[a.jsx("p",{className:"text-sm text-gray-600 mb-5 leading-relaxed",children:"Upload photos for a precision body scan."}),a.jsxs("div",{className:"flex gap-3 mb-4",children:[a.jsx("input",{type:"number",placeholder:"Height (cm)",value:h,onChange:i=>C(i.target.value),className:"w-1/2 p-2.5 border border-gray-300 rounded-lg outline-none focus:border-black text-sm"}),a.jsx("input",{type:"number",placeholder:"Weight (kg)",value:k,onChange:i=>z(i.target.value),className:"w-1/2 p-2.5 border border-gray-300 rounded-lg outline-none focus:border-black text-sm"})]}),a.jsxs("div",{className:"mb-3",children:[a.jsx("label",{className:"text-[11px] font-bold text-gray-400 uppercase block mb-1",children:"Front Image"}),a.jsx("input",{type:"file",accept:"image/*",onChange:i=>X(i.target.files[0]),className:"w-full text-xs file:mr-3 file:py-1.5 file:px-3 file:rounded-md file:border-0 file:text-xs file:bg-gray-100 file:text-black hover:file:bg-gray-200"})]}),a.jsxs("div",{className:"mb-5",children:[a.jsx("label",{className:"text-[11px] font-bold text-gray-400 uppercase block mb-1",children:"Side Image"}),a.jsx("input",{type:"file",accept:"image/*",onChange:i=>F(i.target.files[0]),className:"w-full text-xs file:mr-3 file:py-1.5 file:px-3 file:rounded-md file:border-0 file:text-xs file:bg-gray-100 file:text-black hover:file:bg-gray-200"})]}),a.jsx("button",{onClick:q,className:"w-full bg-black text-white py-3.5 rounded-xl font-bold text-sm hover:opacity-80 transition-opacity",children:"Save Profile & Scan"})]})]})]})};W.StyllarPlugin=Ke,Object.defineProperty(W,Symbol.toStringTag,{value:"Module"})});
|
package/package.json
CHANGED
package/src/StyllarPlugin.jsx
CHANGED
|
@@ -1,13 +1,56 @@
|
|
|
1
1
|
import React, { useState, useEffect } from 'react';
|
|
2
2
|
|
|
3
|
+
// --- 1. INDEXED-DB HELPERS (For storing large image files safely) ---
|
|
4
|
+
const DB_NAME = 'StyllarDB';
|
|
5
|
+
const STORE_NAME = 'user_profile';
|
|
6
|
+
|
|
7
|
+
const openDB = () => {
|
|
8
|
+
return new Promise((resolve, reject) => {
|
|
9
|
+
const request = indexedDB.open(DB_NAME, 1);
|
|
10
|
+
request.onupgradeneeded = (e) => e.target.result.createObjectStore(STORE_NAME);
|
|
11
|
+
request.onsuccess = () => resolve(request.result);
|
|
12
|
+
request.onerror = () => reject(request.error);
|
|
13
|
+
});
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
const saveProfileToDB = async (profileData) => {
|
|
17
|
+
const db = await openDB();
|
|
18
|
+
return new Promise((resolve, reject) => {
|
|
19
|
+
const tx = db.transaction(STORE_NAME, 'readwrite');
|
|
20
|
+
const request = tx.objectStore(STORE_NAME).put(profileData, 'userData');
|
|
21
|
+
request.onsuccess = () => resolve();
|
|
22
|
+
request.onerror = () => reject(request.error);
|
|
23
|
+
});
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
const getProfileFromDB = async () => {
|
|
27
|
+
const db = await openDB();
|
|
28
|
+
return new Promise((resolve, reject) => {
|
|
29
|
+
const tx = db.transaction(STORE_NAME, 'readonly');
|
|
30
|
+
const request = tx.objectStore(STORE_NAME).get('userData');
|
|
31
|
+
request.onsuccess = () => resolve(request.result);
|
|
32
|
+
request.onerror = () => reject(request.error);
|
|
33
|
+
});
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
const clearProfileFromDB = async () => {
|
|
37
|
+
const db = await openDB();
|
|
38
|
+
return new Promise((resolve, reject) => {
|
|
39
|
+
const tx = db.transaction(STORE_NAME, 'readwrite');
|
|
40
|
+
const request = tx.objectStore(STORE_NAME).delete('userData');
|
|
41
|
+
request.onsuccess = () => resolve();
|
|
42
|
+
request.onerror = () => reject(request.error);
|
|
43
|
+
});
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
// --- MAIN COMPONENT ---
|
|
3
47
|
const StyllarPlugin = () => {
|
|
4
|
-
// --- 1. REACT STATE MANAGEMENT ---
|
|
5
48
|
const [isOpen, setIsOpen] = useState(false);
|
|
6
|
-
const [status, setStatus] = useState('idle');
|
|
49
|
+
const [status, setStatus] = useState('idle');
|
|
7
50
|
const [errorMsg, setErrorMsg] = useState('');
|
|
8
51
|
const [result, setResult] = useState(null);
|
|
52
|
+
const [hasSavedProfile, setHasSavedProfile] = useState(false);
|
|
9
53
|
|
|
10
|
-
// Form states
|
|
11
54
|
const [height, setHeight] = useState('');
|
|
12
55
|
const [weight, setWeight] = useState('');
|
|
13
56
|
const [frontImage, setFrontImage] = useState(null);
|
|
@@ -15,54 +58,52 @@ const StyllarPlugin = () => {
|
|
|
15
58
|
|
|
16
59
|
const API_ENDPOINT = "http://136.113.197.77:5000/predict/size-from-chart";
|
|
17
60
|
|
|
18
|
-
// ---
|
|
61
|
+
// --- 2. LOAD SAVED PROFILE ON MOUNT ---
|
|
62
|
+
useEffect(() => {
|
|
63
|
+
const loadUserData = async () => {
|
|
64
|
+
try {
|
|
65
|
+
const savedData = await getProfileFromDB();
|
|
66
|
+
if (savedData && savedData.frontImage && savedData.sideImage) {
|
|
67
|
+
setHeight(savedData.height);
|
|
68
|
+
setWeight(savedData.weight);
|
|
69
|
+
setFrontImage(savedData.frontImage);
|
|
70
|
+
setSideImage(savedData.sideImage);
|
|
71
|
+
setHasSavedProfile(true);
|
|
72
|
+
console.log("Styllar: Loaded user profile from memory.");
|
|
73
|
+
}
|
|
74
|
+
} catch (err) {
|
|
75
|
+
console.error("Styllar: Failed to load profile", err);
|
|
76
|
+
}
|
|
77
|
+
};
|
|
78
|
+
loadUserData();
|
|
79
|
+
}, []);
|
|
80
|
+
|
|
81
|
+
// --- 3. AUTO-SELECT FUNCTION ---
|
|
19
82
|
const autoSelectSize = (targetSize) => {
|
|
20
83
|
if (!targetSize) return;
|
|
21
|
-
|
|
22
84
|
const trySelect = () => {
|
|
23
|
-
// Look for common size elements on e-commerce sites
|
|
24
85
|
const sizeElements = document.querySelectorAll('button, .size-swatch, .size-option, li[role="radio"], div[data-value]');
|
|
25
|
-
|
|
26
86
|
for (let el of sizeElements) {
|
|
27
87
|
const text = el.innerText.trim().toUpperCase();
|
|
28
88
|
const recommended = String(targetSize).trim().toUpperCase();
|
|
29
|
-
|
|
30
|
-
// Check for exact match or formats like "SIZE L"
|
|
31
89
|
if (text === recommended || text === `SIZE ${recommended}`) {
|
|
32
90
|
el.click();
|
|
33
|
-
|
|
34
|
-
return true; // Found and clicked
|
|
91
|
+
return true;
|
|
35
92
|
}
|
|
36
93
|
}
|
|
37
|
-
return false;
|
|
94
|
+
return false;
|
|
38
95
|
};
|
|
39
96
|
|
|
40
|
-
// Try immediately. If it fails (DOM loading dynamically), poll for up to 5 seconds.
|
|
41
97
|
if (!trySelect()) {
|
|
42
98
|
let attempts = 0;
|
|
43
99
|
const interval = setInterval(() => {
|
|
44
100
|
attempts++;
|
|
45
|
-
if (trySelect() || attempts > 10)
|
|
46
|
-
clearInterval(interval);
|
|
47
|
-
}
|
|
101
|
+
if (trySelect() || attempts > 10) clearInterval(interval);
|
|
48
102
|
}, 500);
|
|
49
103
|
}
|
|
50
104
|
};
|
|
51
105
|
|
|
52
|
-
// ---
|
|
53
|
-
useEffect(() => {
|
|
54
|
-
if (typeof chrome !== "undefined" && chrome.storage) {
|
|
55
|
-
chrome.storage.local.get(["styllar_last_size"], (data) => {
|
|
56
|
-
if (data.styllar_last_size) {
|
|
57
|
-
console.log("Styllar: Found saved size:", data.styllar_last_size);
|
|
58
|
-
// Wait a brief moment for the host site to render its UI
|
|
59
|
-
setTimeout(() => autoSelectSize(data.styllar_last_size), 1000);
|
|
60
|
-
}
|
|
61
|
-
});
|
|
62
|
-
}
|
|
63
|
-
}, []);
|
|
64
|
-
|
|
65
|
-
// --- 2. INTELLIGENCE & SCRAPING (Unchanged logic) ---
|
|
106
|
+
// --- 4. SCRAPING LOGIC ---
|
|
66
107
|
function detectProductCategory() {
|
|
67
108
|
let scoreTop = 0; let scoreBottom = 0;
|
|
68
109
|
const bottomKeywords = ['pant', 'jeans', 'trouser', 'skirt', 'short', 'legging', 'jogger', 'bottom', 'denim', 'cargo'];
|
|
@@ -77,28 +118,19 @@ const StyllarPlugin = () => {
|
|
|
77
118
|
const data = { chart_type: 'none', chart_content: null, available_sizes: [] };
|
|
78
119
|
const table = document.querySelector('table');
|
|
79
120
|
if (table && /chest|waist|bust|hip|size/i.test(table.innerText)) {
|
|
80
|
-
data.chart_type = 'html_table';
|
|
81
|
-
data.chart_content = table.outerHTML;
|
|
82
|
-
return data;
|
|
121
|
+
data.chart_type = 'html_table'; data.chart_content = table.outerHTML; return data;
|
|
83
122
|
}
|
|
84
123
|
const allImages = document.querySelectorAll('img');
|
|
85
124
|
for (let img of allImages) {
|
|
86
|
-
const src = img.src.toLowerCase();
|
|
87
|
-
const alt = (img.alt || "").toLowerCase();
|
|
125
|
+
const src = img.src.toLowerCase(); const alt = (img.alt || "").toLowerCase();
|
|
88
126
|
if ((src.includes('size') && src.includes('chart')) || (alt.includes('size') && alt.includes('guide'))) {
|
|
89
|
-
data.chart_type = 'image_url';
|
|
90
|
-
data.chart_content = img.src;
|
|
91
|
-
return data;
|
|
127
|
+
data.chart_type = 'image_url'; data.chart_content = img.src; return data;
|
|
92
128
|
}
|
|
93
129
|
}
|
|
94
|
-
|
|
95
130
|
const sizeGuideDiv = document.querySelector('.size-guide, #size-chart, .measurement-guide, [class*="SizeGuide"]');
|
|
96
131
|
if (sizeGuideDiv) {
|
|
97
|
-
data.chart_type = 'raw_text';
|
|
98
|
-
data.chart_content = sizeGuideDiv.innerText;
|
|
99
|
-
return data;
|
|
132
|
+
data.chart_type = 'raw_text'; data.chart_content = sizeGuideDiv.innerText; return data;
|
|
100
133
|
}
|
|
101
|
-
|
|
102
134
|
const buttons = document.querySelectorAll('button, span, li');
|
|
103
135
|
buttons.forEach(el => {
|
|
104
136
|
const txt = el.innerText.trim();
|
|
@@ -109,14 +141,18 @@ const StyllarPlugin = () => {
|
|
|
109
141
|
return data;
|
|
110
142
|
}
|
|
111
143
|
|
|
112
|
-
// ---
|
|
144
|
+
// --- 5. SUBMIT HANDLER ---
|
|
113
145
|
const runBodyScan = async () => {
|
|
114
146
|
if (!frontImage || !sideImage || !height || !weight) {
|
|
115
|
-
alert("Please fill all fields.");
|
|
116
|
-
return;
|
|
147
|
+
alert("Please fill all fields."); return;
|
|
117
148
|
}
|
|
118
149
|
|
|
119
150
|
setStatus('loading');
|
|
151
|
+
|
|
152
|
+
// Save profile to IndexedDB so they don't have to enter it again on the next page
|
|
153
|
+
await saveProfileToDB({ height, weight, frontImage, sideImage });
|
|
154
|
+
setHasSavedProfile(true);
|
|
155
|
+
|
|
120
156
|
const category = detectProductCategory();
|
|
121
157
|
const pageData = scanPageForSizeData();
|
|
122
158
|
|
|
@@ -138,25 +174,26 @@ const StyllarPlugin = () => {
|
|
|
138
174
|
setResult(data);
|
|
139
175
|
setStatus('success');
|
|
140
176
|
|
|
141
|
-
// --- NEW: Trigger Auto-Select & Save to Storage ---
|
|
142
177
|
if (data.recommended_size) {
|
|
143
178
|
autoSelectSize(data.recommended_size);
|
|
144
|
-
|
|
145
|
-
if (typeof chrome !== "undefined" && chrome.storage) {
|
|
146
|
-
chrome.storage.local.set({ styllar_last_size: data.recommended_size });
|
|
147
|
-
}
|
|
148
179
|
}
|
|
149
|
-
|
|
150
180
|
} catch (e) {
|
|
151
181
|
setErrorMsg(e.message);
|
|
152
182
|
setStatus('error');
|
|
153
183
|
}
|
|
154
184
|
};
|
|
155
185
|
|
|
156
|
-
|
|
186
|
+
const handleClearProfile = async () => {
|
|
187
|
+
await clearProfileFromDB();
|
|
188
|
+
setHeight(''); setWeight(''); setFrontImage(null); setSideImage(null);
|
|
189
|
+
setHasSavedProfile(false);
|
|
190
|
+
setResult(null);
|
|
191
|
+
setStatus('idle');
|
|
192
|
+
};
|
|
193
|
+
|
|
194
|
+
// --- 6. UI RENDER ---
|
|
157
195
|
return (
|
|
158
196
|
<div className="fixed bottom-6 right-6 z-[999999]">
|
|
159
|
-
{/* The Trigger Button */}
|
|
160
197
|
<button
|
|
161
198
|
onClick={() => setIsOpen(!isOpen)}
|
|
162
199
|
className="bg-black text-white px-7 py-3.5 rounded-full shadow-xl font-bold text-sm hover:opacity-80 transition-opacity"
|
|
@@ -164,17 +201,14 @@ const StyllarPlugin = () => {
|
|
|
164
201
|
Styllar
|
|
165
202
|
</button>
|
|
166
203
|
|
|
167
|
-
{/* The Modal Window */}
|
|
168
204
|
{isOpen && (
|
|
169
205
|
<div className="absolute bottom-20 right-0 w-[330px] bg-white rounded-2xl p-6 shadow-2xl border border-gray-100 text-black">
|
|
170
206
|
|
|
171
|
-
{/* Header */}
|
|
172
207
|
<div className="flex justify-between items-center mb-4">
|
|
173
208
|
<h3 className="m-0 text-lg font-bold">Smart Fit Assistant</h3>
|
|
174
209
|
<button onClick={() => setIsOpen(false)} className="text-gray-400 hover:text-black text-2xl leading-none">×</button>
|
|
175
210
|
</div>
|
|
176
211
|
|
|
177
|
-
{/* Loading State */}
|
|
178
212
|
{status === 'loading' && (
|
|
179
213
|
<div className="flex flex-col items-center justify-center py-8">
|
|
180
214
|
<div className="w-8 h-8 border-4 border-gray-200 border-t-black rounded-full animate-spin mb-4"></div>
|
|
@@ -182,7 +216,6 @@ const StyllarPlugin = () => {
|
|
|
182
216
|
</div>
|
|
183
217
|
)}
|
|
184
218
|
|
|
185
|
-
{/* Success State */}
|
|
186
219
|
{status === 'success' && result && (
|
|
187
220
|
<div className="text-center py-6">
|
|
188
221
|
<div className="text-sm text-green-600 font-bold tracking-wider">MATCH FOUND</div>
|
|
@@ -192,12 +225,11 @@ const StyllarPlugin = () => {
|
|
|
192
225
|
onClick={() => { setStatus('idle'); setResult(null); }}
|
|
193
226
|
className="text-blue-600 text-sm underline hover:text-blue-800"
|
|
194
227
|
>
|
|
195
|
-
|
|
228
|
+
Scan Another Product
|
|
196
229
|
</button>
|
|
197
230
|
</div>
|
|
198
231
|
)}
|
|
199
232
|
|
|
200
|
-
{/* Error State */}
|
|
201
233
|
{status === 'error' && (
|
|
202
234
|
<div className="py-4">
|
|
203
235
|
<p className="text-red-500 text-sm mb-4">Error: {errorMsg}</p>
|
|
@@ -210,8 +242,35 @@ const StyllarPlugin = () => {
|
|
|
210
242
|
</div>
|
|
211
243
|
)}
|
|
212
244
|
|
|
213
|
-
{/*
|
|
214
|
-
{status === 'idle' && (
|
|
245
|
+
{/* NEW: Display this if they have a saved profile */}
|
|
246
|
+
{status === 'idle' && hasSavedProfile && (
|
|
247
|
+
<div className="py-4">
|
|
248
|
+
<div className="bg-gray-50 border border-gray-200 rounded-xl p-4 mb-5 text-center">
|
|
249
|
+
<span className="block text-xl mb-2">👤</span>
|
|
250
|
+
<h4 className="font-bold text-sm mb-1">Body Profile Saved</h4>
|
|
251
|
+
<p className="text-xs text-gray-500">
|
|
252
|
+
{height}cm | {weight}kg
|
|
253
|
+
</p>
|
|
254
|
+
</div>
|
|
255
|
+
|
|
256
|
+
<button
|
|
257
|
+
onClick={runBodyScan}
|
|
258
|
+
className="w-full bg-black text-white py-3.5 rounded-xl font-bold text-sm hover:opacity-80 transition-opacity mb-3"
|
|
259
|
+
>
|
|
260
|
+
⚡ Auto-Scan This Product
|
|
261
|
+
</button>
|
|
262
|
+
|
|
263
|
+
<button
|
|
264
|
+
onClick={handleClearProfile}
|
|
265
|
+
className="w-full text-gray-500 text-xs underline hover:text-black"
|
|
266
|
+
>
|
|
267
|
+
Edit Profile / Upload New Photos
|
|
268
|
+
</button>
|
|
269
|
+
</div>
|
|
270
|
+
)}
|
|
271
|
+
|
|
272
|
+
{/* Original form, only visible if NO saved profile exists */}
|
|
273
|
+
{status === 'idle' && !hasSavedProfile && (
|
|
215
274
|
<div>
|
|
216
275
|
<p className="text-sm text-gray-600 mb-5 leading-relaxed">
|
|
217
276
|
Upload photos for a precision body scan.
|
|
@@ -219,16 +278,12 @@ const StyllarPlugin = () => {
|
|
|
219
278
|
|
|
220
279
|
<div className="flex gap-3 mb-4">
|
|
221
280
|
<input
|
|
222
|
-
type="number"
|
|
223
|
-
placeholder="Height (cm)"
|
|
224
|
-
value={height}
|
|
281
|
+
type="number" placeholder="Height (cm)" value={height}
|
|
225
282
|
onChange={(e) => setHeight(e.target.value)}
|
|
226
283
|
className="w-1/2 p-2.5 border border-gray-300 rounded-lg outline-none focus:border-black text-sm"
|
|
227
284
|
/>
|
|
228
285
|
<input
|
|
229
|
-
type="number"
|
|
230
|
-
placeholder="Weight (kg)"
|
|
231
|
-
value={weight}
|
|
286
|
+
type="number" placeholder="Weight (kg)" value={weight}
|
|
232
287
|
onChange={(e) => setWeight(e.target.value)}
|
|
233
288
|
className="w-1/2 p-2.5 border border-gray-300 rounded-lg outline-none focus:border-black text-sm"
|
|
234
289
|
/>
|
|
@@ -237,9 +292,7 @@ const StyllarPlugin = () => {
|
|
|
237
292
|
<div className="mb-3">
|
|
238
293
|
<label className="text-[11px] font-bold text-gray-400 uppercase block mb-1">Front Image</label>
|
|
239
294
|
<input
|
|
240
|
-
type="file"
|
|
241
|
-
accept="image/*"
|
|
242
|
-
onChange={(e) => setFrontImage(e.target.files[0])}
|
|
295
|
+
type="file" accept="image/*" onChange={(e) => setFrontImage(e.target.files[0])}
|
|
243
296
|
className="w-full text-xs file:mr-3 file:py-1.5 file:px-3 file:rounded-md file:border-0 file:text-xs file:bg-gray-100 file:text-black hover:file:bg-gray-200"
|
|
244
297
|
/>
|
|
245
298
|
</div>
|
|
@@ -247,9 +300,7 @@ const StyllarPlugin = () => {
|
|
|
247
300
|
<div className="mb-5">
|
|
248
301
|
<label className="text-[11px] font-bold text-gray-400 uppercase block mb-1">Side Image</label>
|
|
249
302
|
<input
|
|
250
|
-
type="file"
|
|
251
|
-
accept="image/*"
|
|
252
|
-
onChange={(e) => setSideImage(e.target.files[0])}
|
|
303
|
+
type="file" accept="image/*" onChange={(e) => setSideImage(e.target.files[0])}
|
|
253
304
|
className="w-full text-xs file:mr-3 file:py-1.5 file:px-3 file:rounded-md file:border-0 file:text-xs file:bg-gray-100 file:text-black hover:file:bg-gray-200"
|
|
254
305
|
/>
|
|
255
306
|
</div>
|
|
@@ -258,7 +309,7 @@ const StyllarPlugin = () => {
|
|
|
258
309
|
onClick={runBodyScan}
|
|
259
310
|
className="w-full bg-black text-white py-3.5 rounded-xl font-bold text-sm hover:opacity-80 transition-opacity"
|
|
260
311
|
>
|
|
261
|
-
|
|
312
|
+
Save Profile & Scan
|
|
262
313
|
</button>
|
|
263
314
|
</div>
|
|
264
315
|
)}
|