Merge pull request #11 from techjarves/dev

add ModelManager component and infrastructure to support local model …
This commit is contained in:
Tech Jarves authored and GitHub committed 2026-06-07 04:43:07 +00:00
commit d0fcab8c40
10 files changed
+207 -56

No files matched your search

+2 -2
View File
@@ -86,9 +86,9 @@ Typical generation times for an image with **20 steps** (e.g. 512x512 resolution
## 🛠️ Troubleshooting
* **Reset Environment:** If a build fails or you want to clear dependencies, run `scripts/reset.ps1`. (This preserves your models and generated images).
* **Port Conflicts:** The application binds to ports `1420` (Frontend) and `8080` (Backend API). Ensure these are free.
* **Port Conflicts:** The frontend uses `1420` by default. The backend tries `8080` first, then automatically falls back to a free port if `8080` is already busy.
---
## 📝 License
This project is licensed under the MIT License - see the [LICENSE](LICENSE) file. Bundles [stable-diffusion.cpp](https://github.com/leejet/stable-diffusion.cpp) (MIT License). Model weights are subject to their respective creators' licenses.
This project is licensed under the MIT License - see the [LICENSE](LICENSE) file. Bundles [stable-diffusion.cpp](https://github.com/leejet/stable-diffusion.cpp) (MIT License). Model weights are subject to their respective creators' licenses.
+1
View File
@@ -0,0 +1 @@
import{i as D,t as i}from"./index-CL30r104.js";var _;(function(r){r.WINDOW_RESIZED="tauri://resize",r.WINDOW_MOVED="tauri://move",r.WINDOW_CLOSE_REQUESTED="tauri://close-requested",r.WINDOW_DESTROYED="tauri://destroyed",r.WINDOW_FOCUS="tauri://focus",r.WINDOW_BLUR="tauri://blur",r.WINDOW_SCALE_FACTOR_CHANGED="tauri://scale-change",r.WINDOW_THEME_CHANGED="tauri://theme-changed",r.WINDOW_CREATED="tauri://window-created",r.WINDOW_SUSPENDED="tauri://suspended",r.WINDOW_RESUMED="tauri://resumed",r.WEBVIEW_CREATED="tauri://webview-created",r.DRAG_ENTER="tauri://drag-enter",r.DRAG_OVER="tauri://drag-over",r.DRAG_DROP="tauri://drag-drop",r.DRAG_LEAVE="tauri://drag-leave"})(_||(_={}));async function l(r,a){window.__TAURI_EVENT_PLUGIN_INTERNALS__.unregisterListener(r,a),await D("plugin:event|unlisten",{event:r,eventId:a})}async function n(r,a,e){var t;const W=typeof e?.target=="string"?{kind:"AnyLabel",label:e.target}:(t=e?.target)!==null&&t!==void 0?t:{kind:"Any"};return D("plugin:event|listen",{event:r,target:W,handler:i(a)}).then(d=>async()=>l(r,d))}export{_ as TauriEvent,n as listen};
+121
View File
@@ -0,0 +1,121 @@
(function(){const b=document.createElement("link").relList;if(b&&b.supports&&b.supports("modulepreload"))return;for(const _ of document.querySelectorAll('link[rel="modulepreload"]'))r(_);new MutationObserver(_=>{for(const N of _)if(N.type==="childList")for(const G of N.addedNodes)G.tagName==="LINK"&&G.rel==="modulepreload"&&r(G)}).observe(document,{childList:!0,subtree:!0});function E(_){const N={};return _.integrity&&(N.integrity=_.integrity),_.referrerPolicy&&(N.referrerPolicy=_.referrerPolicy),_.crossOrigin==="use-credentials"?N.credentials="include":_.crossOrigin==="anonymous"?N.credentials="omit":N.credentials="same-origin",N}function r(_){if(_.ep)return;_.ep=!0;const N=E(_);fetch(_.href,N)}})();function sm(f){return f&&f.__esModule&&Object.prototype.hasOwnProperty.call(f,"default")?f.default:f}var Us={exports:{}},ei={};var Zd;function E0(){if(Zd)return ei;Zd=1;var f=Symbol.for("react.transitional.element"),b=Symbol.for("react.fragment");function E(r,_,N){var G=null;if(N!==void 0&&(G=""+N),_.key!==void 0&&(G=""+_.key),"key"in _){N={};for(var H in _)H!=="key"&&(N[H]=_[H])}else N=_;return _=N.ref,{$$typeof:f,type:r,key:G,ref:_!==void 0?_:null,props:N}}return ei.Fragment=b,ei.jsx=E,ei.jsxs=E,ei}var Vd;function N0(){return Vd||(Vd=1,Us.exports=E0()),Us.exports}var c=N0(),ws={exports:{}},ie={};var Kd;function A0(){if(Kd)return ie;Kd=1;var f=Symbol.for("react.transitional.element"),b=Symbol.for("react.portal"),E=Symbol.for("react.fragment"),r=Symbol.for("react.strict_mode"),_=Symbol.for("react.profiler"),N=Symbol.for("react.consumer"),G=Symbol.for("react.context"),H=Symbol.for("react.forward_ref"),B=Symbol.for("react.suspense"),y=Symbol.for("react.memo"),A=Symbol.for("react.lazy"),O=Symbol.for("react.activity"),L=Symbol.iterator;function Z(d){return d===null||typeof d!="object"?null:(d=L&&d[L]||d["@@iterator"],typeof d=="function"?d:null)}var V={isMounted:function(){return!1},enqueueForceUpdate:function(){},enqueueReplaceState:function(){},enqueueSetState:function(){}},W=Object.assign,ue={};function F(d,j,w){this.props=d,this.context=j,this.refs=ue,this.updater=w||V}F.prototype.isReactComponent={},F.prototype.setState=function(d,j){if(typeof d!="object"&&typeof d!="function"&&d!=null)throw Error("takes an object of state variables to update or a function which returns an object of state variables.");this.updater.enqueueSetState(this,d,j,"setState")},F.prototype.forceUpdate=function(d){this.updater.enqueueForceUpdate(this,d,"forceUpdate")};function Ce(){}Ce.prototype=F.prototype;function Te(d,j,w){this.props=d,this.context=j,this.refs=ue,this.updater=w||V}var Qe=Te.prototype=new Ce;Qe.constructor=Te,W(Qe,F.prototype),Qe.isPureReactComponent=!0;var Le=Array.isArray;function Me(){}var te={H:null,A:null,T:null,S:null},Se=Object.prototype.hasOwnProperty;function ft(d,j,w){var q=w.ref;return{$$typeof:f,type:d,key:j,ref:q!==void 0?q:null,props:w}}function _t(d,j){return ft(d.type,j,d.props)}function ye(d){return typeof d=="object"&&d!==null&&d.$$typeof===f}function Ve(d){var j={"=":"=0",":":"=2"};return"$"+d.replace(/[=:]/g,function(w){return j[w]})}var nt=/\/+/g;function Ke(d,j){return typeof d=="object"&&d!==null&&d.key!=null?Ve(""+d.key):j.toString(36)}function Ye(d){switch(d.status){case"fulfilled":return d.value;case"rejected":throw d.reason;default:switch(typeof d.status=="string"?d.then(Me,Me):(d.status="pending",d.then(function(j){d.status==="pending"&&(d.status="fulfilled",d.value=j)},function(j){d.status==="pending"&&(d.status="rejected",d.reason=j)})),d.status){case"fulfilled":return d.value;case"rejected":throw d.reason}}throw d}function x(d,j,w,q,J){var ae=typeof d;(ae==="undefined"||ae==="boolean")&&(d=null);var de=!1;if(d===null)de=!0;else switch(ae){case"bigint":case"string":case"number":de=!0;break;case"object":switch(d.$$typeof){case f:case b:de=!0;break;case A:return de=d._init,x(de(d._payload),j,w,q,J)}}if(de)return J=J(d),de=q===""?"."+Ke(d,0):q,Le(J)?(w="",de!=null&&(w=de.replace(nt,"$&/")+"/"),x(J,j,w,"",function(it){return it})):J!=null&&(ye(J)&&(J=_t(J,w+(J.key==null||d&&d.key===J.key?"":(""+J.key).replace(nt,"$&/")+"/")+de)),j.push(J)),1;de=0;var ke=q===""?".":q+":";if(Le(d))for(var Ee=0;Ee<d.length;Ee++)q=d[Ee],ae=ke+Ke(q,Ee),de+=x(q,j,w,ae,J);else if(Ee=Z(d),typeof Ee=="function")for(d=Ee.call(d),Ee=0;!(q=d.next()).done;)q=q.value,ae=ke+Ke(q,Ee++),de+=x(q,j,w,ae,J);else if(ae==="object"){if(typeof d.then=="function")return x(Ye(d),j,w,q,J);throw j=String(d),Error("Objects are not valid as a React child (found: "+(j==="[object Object]"?"object with keys {"+Object.keys(d).join(", ")+"}":j)+"). If you meant to render a collection of children, use an array instead.")}return de}function C(d,j,w){if(d==null)return d;var q=[],J=0;return x(d,q,"","",function(ae){return j.call(w,ae,J++)}),q}function K(d){if(d._status===-1){var j=d._result;j=j(),j.then(function(w){(d._status===0||d._status===-1)&&(d._status=1,d._result=w)},function(w){(d._status===0||d._status===-1)&&(d._status=2,d._result=w)}),dLine truncated
at`)?" (<anonymous>)":-1<l.stack.indexOf("@")?"@unknown:0:0":""}return`
`+ht+e+cl}var k=!1;function se(e,t){if(!e||k)return"";k=!0;var l=Error.prepareStackTrace;Error.prepareStackTrace=void 0;try{var a={DetermineComponentFrameRoot:function(){try{if(t){var M=function(){throw Error()};if(Object.defineProperty(M.prototype,"props",{set:function(){throw Error()}}),typeof Reflect=="object"&&Reflect.construct){try{Reflect.construct(M,[])}catch(S){var v=S}Reflect.construct(e,[],M)}else{try{M.call()}catch(S){v=S}e.call(M.prototype)}}else{try{throw Error()}catch(S){v=S}(M=e())&&typeof M.catch=="function"&&M.catch(function(){})}}catch(S){if(S&&v&&typeof S.stack=="string")return[S.stack,v.stack]}return[null,null]}};a.DetermineComponentFrameRoot.displayName="DetermineComponentFrameRoot";var n=Object.getOwnPropertyDescriptor(a.DetermineComponentFrameRoot,"name");n&&n.configurable&&Object.defineProperty(a.DetermineComponentFrameRoot,"name",{value:"DetermineComponentFrameRoot"});var i=a.DetermineComponentFrameRoot(),u=i[0],s=i[1];if(u&&s){var o=u.split(`
`),p=s.split(`
`);for(n=a=0;a<o.length&&!o[a].includes("DetermineComponentFrameRoot");)a++;for(;n<p.length&&!p[n].includes("DetermineComponentFrameRoot");)n++;if(a===o.length||n===p.length)for(a=o.length-1,n=p.length-1;1<=a&&0<=n&&o[a]!==p[n];)n--;for(;1<=a&&0<=n;a--,n--)if(o[a]!==p[n]){if(a!==1||n!==1)do if(a--,n--,0>n||o[a]!==p[n]){var z=`
`+o[a].replace(" at new "," at ");return e.displayName&&z.includes("<anonymous>")&&(z=z.replace("<anonymous>",e.displayName)),z}while(1<=a&&0<=n);break}}}finally{k=!1,Error.prepareStackTrace=l}return(l=e?e.displayName||e.name:"")?Ut(l):""}function D(e,t){switch(e.tag){case 26:case 27:case 5:return Ut(e.type);case 16:return Ut("Lazy");case 13:return e.child!==t&&t!==null?Ut("Suspense Fallback"):Ut("Suspense");case 19:return Ut("SuspenseList");case 0:case 15:return se(e.type,!1);case 11:return se(e.type.render,!1);case 1:return se(e.type,!0);case 31:return Ut("Activity");default:return""}}function R(e){try{var t="",l=null;do t+=D(e,l),l=e,e=e.return;while(e);return t}catch(a){return`
Error generating stack: `+a.message+`
`+a.stack}}var I=Object.prototype.hasOwnProperty,P=f.unstable_scheduleCallback,Tt=f.unstable_cancelCallback,It=f.unstable_shouldYield,sl=f.unstable_requestPaint,ut=f.unstable_now,Y=f.unstable_getCurrentPriorityLevel,le=f.unstable_ImmediatePriority,He=f.unstable_UserBlockingPriority,Pe=f.unstable_NormalPriority,jt=f.unstable_LowPriority,ll=f.unstable_IdlePriority,xa=f.log,_a=f.unstable_setDisableYieldValue,We=null,ne=null;function fe(e){if(typeof xa=="function"&&_a(e),ne&&typeof ne.setStrictMode=="function")try{ne.setStrictMode(We,e)}catch{}}var pe=Math.clz32?Math.clz32:Re,Ne=Math.log,ct=Math.LN2;function Re(e){return e>>>=0,e===0?32:31-(Ne(e)/ct|0)|0}var rt=256,St=262144,wt=4194304;function fl(e){var t=e&42;if(t!==0)return t;switch(e&-e){case 1:return 1;case 2:return 2;case 4:return 4;case 8:return 8;case 16:return 16;case 32:return 32;case 64:return 64;case 128:return 128;case 256:case 512:case 1024:case 2048:case 4096:case 8192:case 16384:case 32768:case 65536:case 131072:return e&261888;case 262144:case 524288:case 1048576:case 2097152:return e&3932160;case 4194304:case 8388608:case 16777216:case 33554432:return e&62914560;case 67108864:return 67108864;case 134217728:return 134217728;case 268435456:return 268435456;case 536870912:return 536870912;case 1073741824:return 0;default:return e}}function ii(e,t,l){var a=e.pendingLanes;if(a===0)return 0;var n=0,i=e.suspendedLanes,u=e.pingedLanes;e=e.warmLanes;var s=a&134217727;return s!==0?(a=s&~i,a!==0?n=fl(a):(u&=s,u!==0?n=fl(u):l||(l=s&~e,l!==0&&(n=fl(l))))):(s=a&~i,s!==0?n=fl(s):u!==0?n=fl(u):l||(l=a&~e,l!==0&&(n=fl(l)))),n===0?0:t!==0&&t!==n&&(t&i)===0&&(i=n&-n,l=t&-t,i>=l||i===32&&(l&4194048)!==0)?t:n}function fn(e,t){return(e.pendingLanes&~(e.suspendedLanes&~e.pingedLanes)&t)===0}function hm(e,t){switch(e){case 1:case 2:case 4:case 8:case 64:return t+250;case 16:case 32:case 128:case 256:case 512:case 1024:case 2048:case 4096:case 8192:case 16384:case 32768:case 65536:case 131072:case 262144:case 524288:case 1048576:case 2097152:return t+5e3;case 4194304:case 8388608:case 16777216:case 33554432:return-1;case 67108864:case 134217728:case 268435456:case 536870912:case 1073741824:return-1;default:return-1}}function Ks(){var e=wt;return wt<<=1,(wt&62914560)===0&&(wt=4194304),e}function xu(e){for(var t=[],l=0;31>l;l++)t.push(e);return t}function on(e,t){e.pendingLanes|=t,t!==268435456&&(e.suspendedLanes=0,e.pingedLanes=0,e.warmLanes=0)}function ym(e,t,l,a,n,i){var u=e.pendingLanes;e.pendingLanes=l,e.suspendedLanes=0,e.pingedLanes=0,e.warmLanes=0,e.expiredLanes&=l,e.entangledLanes&=l,e.errorRecoveryDisabledLanes&=l,e.shellSuspendCounter=0;var s=e.entanglements,o=e.expirationTimes,p=e.hiddenUpdates;for(l=u&~l;0<l;){var z=31-pe(l),M=1<<z;s[z]=0,o[z]=-1;var v=p[z];if(v!==null)for(p[z]=null,z=0;z<v.length;z++){var S=v[z];S!==null&&(S.lane&=-536870913)}l&=~M}a!==0&&Js(e,a,0),i!==0&&n===0&&e.tag!==0&&(e.suspendedLanes|=i&~(u&~t))}function Js(e,t,l){e.pendingLanes|=t,e.suspendedLanes&=~t;var a=31-pe(t);e.entangledLanes|=t,e.entanglements[a]=e.entanglements[a]|1073741824|l&261930}function $s(e,t){var l=e.entangledLanes|=t;for(e=e.entanglements;l;){var a=31-pe(l),n=1<<a;n&t|e[a]&t&&(e[a]|=t),l&=~n}}function Ws(e,t){var l=t&-t;return l=(l&42)!==0?1:_u(l),(l&(e.suspendedLanes|t))!==0?0:l}function _u(e){switch(e){case 2:e=1;break;case 8:e=4;break;case 32:e=16;break;case 256:case 512:case 1024:case 2048:case 4096:case 8192:case 16384:case 32768:case 65536:case 131072:case 262144:case 524288:case 1048576:case 2097152:case 4194304:case 8388608:case 16777216:case 33554432:e=128;break;case 268435456:e=134217728;break;default:e=0}return e}function ju(e){return e&=-e,2<e?8<e?(e&134217727)!==0?32:268435456:8:2}function Fs(){var e=C.p;return e!==0?e:(e=window.event,e===void 0?32:Gd(e.type))}function Is(e,t){var l=C.p;try{return C.p=e,t()}finally{C.p=l}}var Al=Math.random().toString(36).slice(2),yt="__reactFiber$"+Al,Mt="__reactProps$"+Al,ja="__reactContainer$"+Al,zu="__reactEvents$"+Al,gm="__reactListeners$"+Al,pm="__reactHandles$"+Al,Ps="__reactResources$"+Al,rn="__reactMarker$"+Al;function Tu(e){delete e[yt],delete e[Mt],delete e[zu],delete e[gm],delete e[pm]}function za(e){var t=e[yt];if(t)return t;for(var l=e.parentNode;l;){if(t=l[ja]||l[yt]){if(l=t.alternate,t.child!==null||l!==null&&l.child!==null)for(e=_d(e);e!==null;){if(l=e[yt])return l;e=_d(e)}return t}e=l,l=e.parentNode}return null}function Ta(e){if(e=e[yt]||e[ja]){var t=e.tag;if(t===5||t===6||t===13||t===31||t===26||t===27||t===3)return e}return null}function dn(e){var t=e.tag;if(t===5||t===26||t===27||t===6)return e.stateNode;throw Error(r(33))}function Ma(e){var t=e[Ps];return t||(t=e[Ps]={hoistableStyles:new Map,hoistableScripts:new Map}),t}function dt(e){e[rn]=!0}var ef=new Set,tf={};function aa(e,t){Ea(e,t),Ea(e+"Capture",t)}function Ea(e,t){for(tf[e]=t,e=0;e<t.length;e++)ef.add(t[e])}var vm=RegExp("^[:A-Z_a-z\\u00C0-\\u00D6\\u00D8-\\u00F6\\u00F8-\\u02FF\\u0370-\\u037D\\u037F-\\u1FFF\\u200C-\\u200D\\u2070-\\u218F\ Line truncated
`).replace($h,"")}function dd(e,t){return t=rd(t),rd(e)===t}function Ue(e,t,l,a,n,i){switch(l){case"children":typeof a=="string"?t==="body"||t==="textarea"&&a===""||Aa(e,a):(typeof a=="number"||typeof a=="bigint")&&t!=="body"&&Aa(e,""+a);break;case"className":ci(e,"class",a);break;case"tabIndex":ci(e,"tabindex",a);break;case"dir":case"role":case"viewBox":case"width":case"height":ci(e,l,a);break;case"style":rf(e,a,i);break;case"data":if(t!=="object"){ci(e,"data",a);break}case"src":case"href":if(a===""&&(t!=="a"||l!=="href")){e.removeAttribute(l);break}if(a==null||typeof a=="function"||typeof a=="symbol"||typeof a=="boolean"){e.removeAttribute(l);break}a=fi(""+a),e.setAttribute(l,a);break;case"action":case"formAction":if(typeof a=="function"){e.setAttribute(l,"javascript:throw new Error('A React form was unexpectedly submitted. If you called form.submit() manually, consider using form.requestSubmit() instead. If you\\'re trying to use event.stopPropagation() in a submit event handler, consider also calling event.preventDefault().')");break}else typeof i=="function"&&(l==="formAction"?(t!=="input"&&Ue(e,t,"name",n.name,n,null),Ue(e,t,"formEncType",n.formEncType,n,null),Ue(e,t,"formMethod",n.formMethod,n,null),Ue(e,t,"formTarget",n.formTarget,n,null)):(Ue(e,t,"encType",n.encType,n,null),Ue(e,t,"method",n.method,n,null),Ue(e,t,"target",n.target,n,null)));if(a==null||typeof a=="symbol"||typeof a=="boolean"){e.removeAttribute(l);break}a=fi(""+a),e.setAttribute(l,a);break;case"onClick":a!=null&&(e.onclick=rl);break;case"onScroll":a!=null&&he("scroll",e);break;case"onScrollEnd":a!=null&&he("scrollend",e);break;case"dangerouslySetInnerHTML":if(a!=null){if(typeof a!="object"||!("__html"in a))throw Error(r(61));if(l=a.__html,l!=null){if(n.children!=null)throw Error(r(60));e.innerHTML=l}}break;case"multiple":e.multiple=a&&typeof a!="function"&&typeof a!="symbol";break;case"muted":e.muted=a&&typeof a!="function"&&typeof a!="symbol";break;case"suppressContentEditableWarning":case"suppressHydrationWarning":case"defaultValue":case"defaultChecked":case"innerHTML":case"ref":break;case"autoFocus":break;case"xlinkHref":if(a==null||typeof a=="function"||typeof a=="boolean"||typeof a=="symbol"){e.removeAttribute("xlink:href");break}l=fi(""+a),e.setAttributeNS("http://www.w3.org/1999/xlink","xlink:href",l);break;case"contentEditable":case"spellCheck":case"draggable":case"value":case"autoReverse":case"externalResourcesRequired":case"focusable":case"preserveAlpha":a!=null&&typeof a!="function"&&typeof a!="symbol"?e.setAttribute(l,""+a):e.removeAttribute(l);break;case"inert":case"allowFullScreen":case"async":case"autoPlay":case"controls":case"default":case"defer":case"disabled":case"disablePictureInPicture":case"disableRemotePlayback":case"formNoValidate":case"hidden":case"loop":case"noModule":case"noValidate":case"open":case"playsInline":case"readOnly":case"required":case"reversed":case"scoped":case"seamless":case"itemScope":a&&typeof a!="function"&&typeof a!="symbol"?e.setAttribute(l,""):e.removeAttribute(l);break;case"capture":case"download":a===!0?e.setAttribute(l,""):a!==!1&&a!=null&&typeof a!="function"&&typeof a!="symbol"?e.setAttribute(l,a):e.removeAttribute(l);break;case"cols":case"rows":case"size":case"span":a!=null&&typeof a!="function"&&typeof a!="symbol"&&!isNaN(a)&&1<=a?e.setAttribute(l,a):e.removeAttribute(l);break;case"rowSpan":case"start":a==null||typeof a=="function"||typeof a=="symbol"||isNaN(a)?e.removeAttribute(l):e.setAttribute(l,a);break;case"popover":he("beforetoggle",e),he("toggle",e),ui(e,"popover",a);break;case"xlinkActuate":ol(e,"http://www.w3.org/1999/xlink","xlink:actuate",a);break;case"xlinkArcrole":ol(e,"http://www.w3.org/1999/xlink","xlink:arcrole",a);break;case"xlinkRole":ol(e,"http://www.w3.org/1999/xlink","xlink:role",a);break;case"xlinkShow":ol(e,"http://www.w3.org/1999/xlink","xlink:show",a);break;case"xlinkTitle":ol(e,"http://www.w3.org/1999/xlink","xlink:title",a);break;case"xlinkType":ol(e,"http://www.w3.org/1999/xlink","xlink:type",a);break;case"xmlBase":ol(e,"http://www.w3.org/XML/1998/namespace","xml:base",a);break;case"xmlLang":ol(e,"http://www.w3.org/XML/1998/namespace","xml:lang",a);break;case"xmlSpace":ol(e,"http://www.w3.org/XML/1998/namespace","xml:space",a);break;case"is":ui(e,"is",a);break;case"innerText":case"textContent":break;default:(!(2<l.length)||l[0]!=="o"&&l[0]!=="O"||l[1]!=="n"&&l[1]!=="N")&&(l=jm.get(l)||l,ui(e,l,a))}}function ys(e,t,l,a,n,i){switch(l){case"style":rf(e,a,i);break;case"dangerouslySetInnerHTML":if(a!=null){if(typeof a!="object"||!("__html"in a))throw Error(r(61));if(l=a.__html,l!=null){if(n.children!=null)throw Error(r(60));e.innerHTML=l}}break;case"children":typeof a=="string"?Aa(e,a):(typeof a=="number"||typeof a=="bigint")&&Aa(e,""+a);break;case"onScroll":a!=null&&he("scroll",e);break;case"onScrollEnd":a!=null&&he("scrollend",e);break;case"onClick":a!=null&&(e.onclick=rl);break;case"suppressContentEditableWarning":case"suppressHydrationWarning":case"inne Line truncated
<!-- Stars -->
<circle cx="80" cy="100" r="1.5" fill="#ffffff" opacity="0.6"/>
<circle cx="160" cy="60" r="1" fill="#ffffff" opacity="0.5"/>
<circle cx="280" cy="120" r="2" fill="#ffffff" opacity="0.8"/>
<circle cx="360" cy="80" r="1.5" fill="#ffffff" opacity="0.4"/>
<circle cx="440" cy="150" r="1" fill="#ffffff" opacity="0.7"/>
<circle cx="400" cy="280" r="2" fill="#ffffff" opacity="0.9"/>
<circle cx="100" cy="300" r="1.5" fill="#ffffff" opacity="0.5"/>
<!-- Moon -->
<circle cx="256" cy="220" r="90" fill="url(#moonGrad)" />
<circle cx="226" cy="190" r="80" fill="#0F172A" />
`):Z.includes("cat")||Z.includes("kitten")||Z.includes("feline")?(W="#4F46E5",ue="#7C3AED",Ce="Stylized Cat Portrait",F=`
<!-- Cat head & ears -->
<polygon points="176,280 146,160 216,210" fill="#312E81" />
<polygon points="336,280 366,160 296,210" fill="#312E81" />
<circle cx="256" cy="270" r="80" fill="#4338CA" />
<!-- Eyes -->
<ellipse cx="226" cy="260" rx="14" ry="8" fill="#10B981" />
<ellipse cx="286" cy="260" rx="14" ry="8" fill="#10B981" />
<circle cx="226" cy="260" r="4" fill="#000" />
<circle cx="286" cy="260" r="4" fill="#000" />
<!-- Nose & Whiskers -->
<polygon points="256,285 248,275 264,275" fill="#F43F5E" />
<line x1="226" y1="290" x2="166" y2="280" stroke="#E2E8F0" stroke-width="2" />
<line x1="226" y1="295" x2="156" y2="295" stroke="#E2E8F0" stroke-width="2" />
<line x1="286" y1="290" x2="346" y2="280" stroke="#E2E8F0" stroke-width="2" />
<line x1="286" y1="295" x2="356" y2="295" stroke="#E2E8F0" stroke-width="2" />
`):Z.includes("dog")||Z.includes("puppy")||Z.includes("canine")?(W="#D97706",ue="#B45309",Ce="Friendly Dog Portrait",F=`
<!-- Dog head & floppy ears -->
<ellipse cx="166" cy="260" rx="20" ry="60" fill="#78350F" />
<ellipse cx="346" cy="260" rx="20" ry="60" fill="#78350F" />
<circle cx="256" cy="250" r="70" fill="#92400E" />
<ellipse cx="256" cy="280" rx="40" ry="30" fill="#D97706" />
<!-- Eyes -->
<circle cx="226" cy="230" r="10" fill="#1E293B" />
<circle cx="286" cy="230" r="10" fill="#1E293B" />
<circle cx="222" cy="226" r="3" fill="#ffffff" />
<circle cx="282" cy="226" r="3" fill="#ffffff" />
<!-- Nose -->
<ellipse cx="256" cy="275" rx="16" ry="10" fill="#1E293B" />
`):Z.includes("city")||Z.includes("cyberpunk")||Z.includes("tokyo")||Z.includes("neon")?(W="#111827",ue="#311042",Ce="Neon Cyberpunk Skyline",F=`
<!-- Skyline -->
<rect x="60" y="160" width="80" height="352" fill="#1F2937" opacity="0.9"/>
<rect x="180" y="100" width="100" height="412" fill="#111827" />
<rect x="320" y="200" width="120" height="312" fill="#1F2937" opacity="0.85"/>
<rect x="100" y="240" width="110" height="272" fill="#374151" opacity="0.6"/>
<!-- Neon details -->
<line x1="230" y1="100" x2="230" y2="40" stroke="#EC4899" stroke-width="3" />
<rect x="200" y="140" width="60" height="15" fill="#10B981" opacity="0.7"/>
<rect x="200" y="170" width="60" height="15" fill="#10B981" opacity="0.7"/>
<rect x="200" y="200" width="60" height="15" fill="#3B82F6" opacity="0.7"/>
<rect x="70" y="200" width="15" height="100" fill="#F59E0B" opacity="0.5"/>
<rect x="340" y="250" width="20" height="150" fill="#EF4444" opacity="0.6"/>
`):Z.includes("car")||Z.includes("vehicle")||Z.includes("supercar")?(W="#DC2626",ue="#991B1B",Ce="Futuristic Concept Car",F=`
<!-- Road / Perspective grid -->
<line x1="256" y1="350" x2="0" y2="512" stroke="#4B5563" stroke-width="3" />
<line x1="256" y1="350" x2="512" y2="512" stroke="#4B5563" stroke-width="3" />
<!-- Car body -->
<polygon points="126,380 256,330 386,380 346,440 166,440" fill="#EF4444" />
<polygon points="176,370 256,340 336,370 326,390 186,390" fill="#1E293B" opacity="0.9" />
<!-- Headlights -->
<polygon points="136,410 176,415 166,425 136,420" fill="#FBBF24" />
<polygon points="376,410 336,415 346,425 376,420" fill="#FBBF24" />
<!-- Wheels -->
<ellipse cx="156" cy="440" rx="30" ry="15" fill="#111" />
<ellipse cx="356" cy="440" rx="30" ry="15" fill="#111" />
`):(W="#06B6D4",ue="#0891B2",Ce="Abstract Creative Concept",F=`
<path d="M0,256 C150,150 350,350 512,256 L512,512 L0,512 Z" fill="#0891B2" opacity="0.4" />
<path d="M0,350 C200,200 300,450 512,350 L512,512 L0,512 Z" fill="#0E7490" opacity="0.6" />
<circle cx="380" cy="180" r="50" fill="#F59E0B" opacity="0.8" />
`);const Te=`
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" width="512" height="512">
<defs>
<linearGradient id="bgGrad" x1="0%" y1="0%" x2="100%" y2="100%">
<stop offset="0%" stop-color="${W}" />
<stop offset="100%" stop-color="${ue}" />
</linearGradient>
<linearGradient id="moonGrad" x1="0%" y1="0%" x2="100%" y2="100%">
<stop offset="0%" stop-color="#FEF08A" />
<stop offset="100%" stop-color="#FDE047" />
</linearGradient>
</defs>
<!-- Background -->
<rect width="512" height="512" fill="url(#bgGrad)" />
<!-- Shapes -->
${F}
<!-- Premium UI Overlay Frame -->
<rect x="20" y="20" width="472" height="472" fill="none" stroke="#ffffff" stroke-width="1.5" opacity="0.15" rx="8"/>
<!-- Prompt & Details text -->
<rect x="40" y="410" width="432" height="62" fill="#0F172A" opacity="0.85" rx="6" />
<text x="60" y="435" fill="#F1F5F9" font-family="system-ui, -apple-system, sans-serif" font-size="13" font-weight="bold">${Ce}</text>
<text x="60" y="455" fill="#94A3B8" font-family="system-ui, -apple-system, sans-serif" font-size="11">Seed: ${V} • Steps: ${y.steps} • Scale: ${y.cfg_scale}</text>
</svg>
`,Le=`data:image/svg+xml;base64,${btoa(unescape(encodeURIComponent(Te)))}`,Me=parseFloat(((Date.now()-H)/1e3).toFixed(1));return{image:Le,seed:y.seed,duration_sec:Me}},O={prompt:y.prompt,negative_prompt:y.negative_prompt||"",n:1,size:`${y.width}x${y.height}`,response_format:"b64_json",steps:y.steps,cfg_scale:y.cfg_scale,seed:y.seed,sample_method:y.sampler};try{const L=await fetch(`${B}/v1/images/generations`,{method:"POST",headers:{"Content-Type":"application/json"},signal:G,body:JSON.stringify(O)});if(L.ok){const Z=await L.json(),V=Z?.data?.[0]?.b64_json??Z?.images?.[0];if(V){const W=parseFloat(((Date.now()-H)/1e3).toFixed(1));return{image:`data:image/png;base64,${V}`,seed:Z.data?.[0]?.seed??y.seed,duration_sec:W}}}else{let Z=`Generation failed (HTTP ${L.status})`;try{const V=await L.json();(V?.detail||V?.error?.message)&&(Z=V.detail||V.error.message)}catch{}throw new Error(Z)}}catch(L){if(L.name==="AbortError"||L.message.startsWith("Generation failed"))throw L;console.warn("Could not reach local server. Falling back to simulation mode.",L)}return await A()}async function Vy(f,b,E){if(f instanceof File){const H=f;if(!ta())throw new Error("File import requires the local image generator server.");return await Ky(H,b,E),{response:"Model imported successfully"}}if(la()){const{listen:H}=await Cy(async()=>{const{listen:y}=await import("./event-DtK1umGE.js");return{listen:y}},[]);let B=null;b&&(B=await H("import-progress",y=>{b(y.payload)}));try{return{response:await Sa("import_model_file",{sourcePath:f}),unlisten:B}}catch(y){throw B&&B(),y}}const r=f.split(/[\\/]/).pop()||"imported_model.gguf";console.log(`Web Mode: Simulating copying ${r} to USB models folder`);const _=40;for(let H=1;H<=_;H++){if(E?.aborted)throw new DOMException("The user aborted a request.","AbortError");await new Promise(O=>setTimeout(O,150));const B=H/_*100,y=40+Math.sin(H)*5+Math.random()*2,A=(_-H)*.15;b&&b({filename:r,progress:B,speed_mb_s:y,eta_secs:A,status:"Copying to USB..."})}const N=localStorage.getItem("imported-models"),G=N?JSON.parse(N):[];return G.includes(r)||(G.push({filename:r,sizeBytes:0,size:"Unknown"}),localStorage.setItem("imported-models",JSON.stringify(G))),{response:"Model imported successfully"}}function Ky(f,b,E){return new Promise((r,_)=>{const N=new XMLHttpRequest,G=Date.now();let H=!1;N.open("POST",`/api/import-model?filename=${encodeURIComponent(f.name)}`),N.setRequestHeader("Content-Type","application/octet-stream");const B=()=>{H=!0,N.abort()};E&&(E.aborted?B():E.addEventListener("abort",B,{once:!0})),N.upload.onprogress=y=>{if(!y.lengthComputable||!b)return;const A=Math.max(.1,(Date.now()-G)/1e3),O=y.loaded/A,L=Math.max(0,y.total-y.loaded);b({filename:f.name,progress:y.loaded/y.total*100,speed_mb_s:O/(1024*1024),eta_secs:L/Math.max(1,O),status:"Copying to models folder..."})},N.onload=()=>{let y={};try{y=JSON.parse(N.responseText||"{}")}catch{}N.status>=200&&N.status<300&&y.ok!==!1?(b&&b({filename:f.name,progress:100,speed_mb_s:0,eta_secs:0,status:"Import complete"}),r(y)):_(new Error(y.error||`Import failed (HTTP ${N.status})`))},N.onerror=()=>_(new Error("Import failed while uploading the file.")),N.onabort=()=>_(new DOMException(H?"Import cancelled by user.":"Import aborted.","AbortError")),N.send(f)})}async function Jy(f){if(la())return await Sa("delete_model_file",{filename:f});if(typeof window<"u"&&(window.location.hostname==="localhost"||window.location.hostname==="127.0.0.1"))try{const _=await fetch("/api/delete-model",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({filename:f})}),N=await _.json();if(!_.ok)throw new Error(N.error||"Failed to delete model file");return N}catch(_){throw console.error("Failed to delete model via local API:",_),_}const E=localStorage.getItem("imported-models");let r=E?JSON.parse(E):[];return r=r.filter(_=>ni(_).filename!==f),localStorage.setItem("imported-models",JSON.stringify(r)),`Simulated deletion of ${f} from browser session`}async function mm(){const f=await dm();try{return(await fetch(`${f}/v1/models`,{method:"GET"})).ok}catch{return!1}}async function $y(f=30){const b=la()||ta()?f:3;for(let E=0;E<b;E++){let r=!1;if(ta()?r=!!(await Nl()).ready:r=await mm(),r)return!0;await new Promise(_=>setTimeout(_,500))}return!1}async function Wy(f){try{return await(await fetch("/api/download-model",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({url:f})})).json()}catch(b){return console.error("Failed to start model download:",b),{ok:!1,error:b.message}}}async function Fy(){try{return await(await fetch("/api/cancel-download",{method:"POST"})).json()}catch(f){return console.error("Failed to cancel model download:",f),{ok:!1,error:f.message}}}async function um(){try{return await(await fetch("/api/download-progress")).json()}catch(f){return console.error("Failed to get download progress:",f),{active:!1,error:f.message}}}async function Iy(){try{return await(await fetch("/api/generation-progress")).json()}catch(f){ret Line truncated
Seed: ${f.seed}${f.duration_sec?`
Time: ${f.duration_sec.toFixed(1)}s`:""}`,children:[c.jsx("img",{src:f.url,className:"gallery-thumb",alt:"Thumbnail",loading:"lazy",decoding:"async"}),E&&c.jsx("div",{className:"gallery-select-badge",children:c.jsx(Ls,{size:14})})]})});function e1({prompt:f,setPrompt:b,negativePrompt:E,setNegativePrompt:r,constraints:_,setConstraints:N,activeModel:G,generatedImages:H,setGeneratedImages:B,isGenerating:y,setIsGenerating:A,generationProgress:O,setGenerationProgress:L,setActiveTab:Z,showAlert:V=async({message:ue})=>window.alert(ue),showConfirm:W=async({message:ue})=>window.confirm(ue)}){const[ue,F]=U.useState(null),[Ce,Te]=U.useState(null),[Qe,Le]=U.useState(null),[Me,te]=U.useState(!1),[Se,ft]=U.useState(0),[_t,ye]=U.useState(0),[Ve,nt]=U.useState(0),[Ke,Ye]=U.useState(""),[x,C]=U.useState(!1),[K,ge]=U.useState(!1),[re,d]=U.useState(!1),[j,w]=U.useState(null),[q,J]=U.useState(null),[ae,de]=U.useState(!1),[ke,Ee]=U.useState([]),it=U.useRef(null),ot=U.useRef(null),ht=U.useRef(!1),cl=async()=>{if(!f.trim())return;J(null),d(!0),w(null);let Y=!1;try{const ne=await Nl();if(ne&&ne.settings){const fe=ne.settings,pe=fe.model?fe.model.split(/[\\/]/).pop():null,Ne=G?G.split(/[\\/]/).pop():null;(pe!==Ne||parseInt(fe.steps)!==parseInt(_.steps)||Math.abs(parseFloat(fe.cfgScale)-parseFloat(_.cfgScale))>.05||fe.sampler!==_.sampler||parseInt(fe.threads)!==parseInt(_.threads)||!!fe.useGpu!=(_.useGpu!==!1)||(fe.backendType||(fe.useGpu===!1?"cpu":"auto"))!==(_.backendType||(_.useGpu===!1?"cpu":"auto")))&&(Y=!0)}else Y=!0}catch{Y=!0}if(Y)try{console.log("Settings out of sync, restarting backend..."),await Qs(G,_);let ne=!1,fe=null;for(let pe=0;pe<240;pe++){const Ne=await Nl();if(Ne.loading&&w({progress:Ne.loading.progress||0,phase:Ne.loading.phase||"Loading model...",speed:Ne.loading.speed||"",current:Ne.loading.current||0,total:Ne.loading.total||0,backendMode:Ne.loading.backendMode||Ne.settings?.backendMode||"",device:Ne.loading.device||Ne.settings?.backendDevice||""}),Ne.ready){ne=!0;break}if(Ne.error){fe=Ne.error;break}if(!Ne.running&&pe>3){fe="The backend process terminated immediately on startup.";break}await new Promise(ct=>setTimeout(ct,500))}if(!ne)throw new Error(fe||"Backend server failed to start after applying configuration changes.")}catch(ne){console.error("Failed to restart backend:",ne),J(ne.message||"Failed to sync settings with backend."),d(!1);return}d(!1),w(null),it.current&&clearInterval(it.current),A(!0),L(0),ft(0),nt(0),Ye(""),ht.current=!1,C(!1),de(!1),Le(null),F(null);const le=(_.width>=1024?140:35)*.04,He=_.width>=1024?140:35,Pe=(_.backendType||(_.useGpu===!1?"cpu":"auto"))!=="cpu";let jt=Pe?le:He,ll=!1,xa=0,_a=0,We=0;ye(Math.round(_.steps*jt)),it.current=setInterval(async()=>{We++,ft(We);try{const ne=await Iy();if(ne&&ne.active){const fe=ne.decoding||!1;de(fe);const pe=fe?ne.steps||_.steps||20:ne.step||0,Ne=ne.steps||_.steps||20,ct=fe?"":ne.speed||"";(fe||ne.steps>0)&&(ht.current=!0,nt(pe)),Ye(fe?"decoding":ct);let Re=0;fe?Re=99:Ne>0&&(Re=Math.round(pe/Ne*100)),Re=Math.min(99,Re),fe||ne.steps>0?L(Re):L(St=>Math.max(St,Re));let rt=0;if(fe)rt=3;else{const St=parseFloat(ct);if(!isNaN(St)&&St>0){const wt=Math.max(0,Ne-pe);ct.includes("it/s")?rt=wt/St:ct.includes("s/it")&&(rt=wt*St)}else{const wt=Pe?le:He;rt=Math.max(0,(Ne-pe)*wt)}}if(Pe&&!ll){const St=ct.includes("it/s")&&numericSpeed<.2,wt=ct.includes("s/it")&&numericSpeed>5;(St||wt)&&(ll=!0,C(!0))}ye(Math.round(rt))}else L(fe=>Math.min(15,fe+1)),ye(fe=>Math.max(1,fe-1))}catch(ne){console.warn("Failed to get generation progress, running simulation fallback:",ne),Pe&&!ll&&We>_.steps*le*1.3&&(ll=!0,C(!0),jt=He,xa=We,_a=Math.min(95,Math.round(We/(_.steps*le)*100)));let fe=Math.min(_.steps-1,Math.floor(We/jt)),pe=_.steps*jt;if(We>=pe){const Re=Math.ceil(We/Math.max(.5,fe));jt=Math.max(jt+15,Re+15),pe=_.steps*jt,fe=Math.min(_.steps-1,Math.floor(We/jt))}const Ne=Math.max(1,pe-We);ye(Math.round(Ne));let ct=0;if(ll){const Re=99-_a,rt=pe-xa;if(rt>0){const St=Math.min(1,(We-xa)/rt);if(St<.85)ct=Math.round(_a+St*Re);else{const wt=(We-(xa+rt*.85))/(jt*2),fl=1-Math.exp(-wt);ct=Math.min(99,Math.round(_a+.85*Re+.14*Re*fl))}}else ct=99}else if(We<pe*.85)ct=Math.round(We/pe*100);else{const Re=(We-pe*.85)/(jt*2),rt=1-Math.exp(-Re);ct=Math.min(99,Math.round(85+14*rt))}ht.current||L(Re=>Math.max(Re,ct))}},1e3);try{ot.current=new AbortController;const ne=await Zy(f,E,_,G,null,Re=>L(rt=>Math.max(rt,Re)),ot.current.signal);it.current&&clearInterval(it.current),L(100),nt(_.steps),ye(0),Ye("");const fe={prompt:f,negativePrompt:E,seed:ne.seed,steps:_.steps,cfgScale:_.cfgScale,width:_.width,height:_.height,sampler:_.sampler,model:G,duration_sec:ne.duration_sec,timestamp:new Date().toLocaleTimeString()};let pe=null,Ne=null;try{if(pe=await Xy(ne.image,fe),Ne=pe.url,!Ne)throw new Error("The local server saved the image but did not return a file URL.")}catch(Re){throw console.error("Generated image could not be saved:",Re),new Error(`Image generated but could not be saved to USB: ${Re.mLine truncated
${sl}
Update the UI status anyway?`,confirmLabel:"Update UI"})&&(b(D),r(!0)):await G({title:"Backend Not Running",message:`C++ backend server is not running on port ${Tt}.
To run locally, start:
${sl}
Proceed in Simulation Mode instead?`,confirmLabel:"Proceed"})&&(b(D),r(!1));return}ue(D),Ce({progress:0,phase:"Starting backend...",speed:"",current:0,total:0,model:D,backendMode:"",backendBinary:"",device:""});try{const It=await Qs(D,_);console.log(It);let sl=!1,ut=null;for(let Y=0;Y<240;Y++){const le=await Nl();if(le.loading&&Ce({progress:le.loading.progress||0,phase:le.loading.phase||"Loading model...",speed:le.loading.speed||"",current:le.loading.current||0,total:le.loading.total||0,model:le.loading.model||D,backendMode:le.loading.backendMode||le.settings?.backendMode||"",backendBinary:le.loading.backendBinary||le.settings?.backendBinary||"",device:le.loading.device||le.settings?.backendDevice||""}),le.ready){sl=!0;break}if(le.error){ut=le.error;break}if(!le.running&&Y>3){ut="The backend process terminated immediately on startup.";break}await new Promise(He=>setTimeout(He,500))}if(sl)Ce(Y=>({...Y||{},progress:100,phase:"Model ready",speed:"",current:Y?.current||0,total:Y?.total||0,model:D})),b(D),r(!0);else throw new Error(ut||`Model server failed to respond on port ${Tt}.`)}catch(It){console.error("Failed to load model:",It),N({title:"Model Load Failed",message:It.message||String(It),danger:!0})}finally{setTimeout(()=>Ce(null),800),ue(null)}},it=async()=>{const D=Ye;x(null),await ot(),D&&await Ee(D)},ot=async()=>{if(ye)return;Ve(!0),Ke({progress:10,phase:"Stopping backend process..."});const D=setInterval(async()=>{try{const R=await Nl();R.unloading?.active?Ke({progress:R.unloading.progress||50,phase:R.unloading.phase||"Unloading model..."}):Ke(I=>({progress:Math.min(95,I.progress+10),phase:I.phase||"Unloading model..."}))}catch{}},300);try{await Vs(),Ke({progress:100,phase:"Model unloaded"}),b(null),x(null),r(!1)}catch(R){console.error(R),N({title:"Unload Failed",message:R.message||String(R),danger:!0})}finally{clearInterval(D),setTimeout(()=>{Ve(!1),Ke({progress:0,phase:""})},500)}},ht=async D=>{if(await G({title:"Delete Model?",message:`Delete "${D}" from your drive?`,confirmLabel:"Delete",danger:!0}))try{await Jy(D),await w(),f===D&&await ot()}catch(R){console.error("Failed to delete model:",R),N({title:"Delete Failed",message:R.message||String(R),danger:!0})}},cl=async(D,R)=>{const I=new AbortController;_t(I),Qe(0),Me({filename:R,speed:"0.0",eta:0,status:"Initiating file copy..."});try{await Vy(D,P=>{Qe(Math.round(P.progress)),Me({filename:P.filename,speed:P.speed_mb_s.toFixed(1),eta:Math.round(P.eta_secs),status:P.status})},I.signal),await w()}catch(P){console.error("Import failed:",P),P.name!=="AbortError"&&N({title:"Import Failed",message:P.message||String(P),danger:!0})}finally{Qe(null),_t(null)}},Ut=()=>{ft&&ft.abort()},k=D=>{const R=D.target.files[0];R&&cl(R,R.name),D.target.value=""},se=!!y;return c.jsxs("div",{className:"workspace-area",children:[c.jsxs("div",{className:"workspace-title-section",children:[c.jsx("h2",{className:"workspace-title",children:"Model Manager"}),c.jsx("p",{className:"workspace-subtitle",children:"Manage local files and download recommended weights directly to your local models folder."})]}),f&&c.jsx("div",{className:"m3-card",style:{borderLeft:"4px solid var(--md-sys-color-primary)",background:"var(--md-sys-color-primary-container)",color:"var(--md-sys-color-on-primary-container)"},children:c.jsxs("div",{style:{display:"flex",justifyContent:"space-between",alignItems:"center"},children:[c.jsxs("div",{children:[c.jsxs("h4",{style:{fontWeight:700},children:["Active Model: ",f]}),c.jsx("p",{style:{fontSize:"0.85rem",marginTop:"2px",opacity:.9},children:"The local C++ stable-diffusion server is running. Real-time telemetry is displaying performance status in the top bar."}),(C.backendMode||C.backendDevice||C.backendBinary)&&c.jsxs("div",{style:{display:"flex",flexWrap:"wrap",gap:"8px",marginTop:"10px"},children:[C.backendMode&&c.jsxs("span",{className:"status-chip",style:{cursor:"default"},children:[c.jsx(bu,{size:14}),c.jsxs("span",{children:["Loaded on ",C.backendMode]})]}),C.backendDevice&&c.jsx("span",{className:"status-chip",style:{cursor:"default"},children:c.jsx("span",{children:C.backendDevice})}),C.backendBinary&&c.jsx("span",{className:"status-chip",style:{cursor:"default"},children:c.jsx("span",{children:C.backendBinary})})]})]}),c.jsxs("button",{className:"m3-btn m3-btn-error",onClick:ot,disabled:ye||W!==null,children:[ye?c.jsx(ea,{className:"progress-spinner",size:16}):c.jsx(li,{size:16}),c.jsx("span",{children:ye?"Unloading":"Unload Server"})]})]})}),ye&&c.jsxs("div",{className:"m3-card",style:{borderLeft:"4px solid var(--md-sys-color-error)",marginTop:"16px"},children:[c.jsx("h4",{style:{fontWeight:700,marginBottom:"8px"},children:"Unloading Model"}),c.jsxs("div",{className:"model-progress-section",style:{margin:"8px 0 0 0"},children:[c.jsxs("div",{className:"model-progress-label",children:[c.jsx("span",{children:nt.phase||"Unloading model..."}),c.jsxs("span",{children:[Math.round(nt.progress),"%"]})]}),c.jsx("div",{className:"model-progress-bar",child Line truncated
Models and generated outputs are not included.`,confirmLabel:"Clean",danger:!0})){ye(!0);try{await Ry(Se.map(D=>D.id)),await ae()}catch(D){N({title:"Cleanup Failed",message:D.message||String(D),danger:!0})}finally{ye(!1)}}},[Se,ae,N,_]);U.useEffect(()=>{async function k(){const se=await Yy();Ye(se)}k()},[]),U.useEffect(()=>{V?localStorage.setItem("active-model",V):localStorage.removeItem("active-model")},[V]),U.useEffect(()=>{let k=null;async function se(){const D=await Nl();if(!!D.ready){k&&clearInterval(k);const I=localStorage.getItem("active-model");if(I)W(I),F(!0);else{try{if(D&&D.settings&&D.settings.model){const P=D.settings.model.split(/[\\/]/).pop();W(P)}}catch{}F(!0)}}else if(!(typeof window<"u"&&window.__TAURI_INTERNALS__!==void 0)){const P=localStorage.getItem("active-model");P&&(W(P),F(!1))}}return se(),k=setInterval(se,2e3),()=>{k&&clearInterval(k)}},[]),U.useEffect(()=>{async function k(){try{const D=await Gy();w(R=>R.cpu_usage===D.cpu_usage&&R.ram_used_gb===D.ram_used_gb&&R.ram_total_gb===D.ram_total_gb&&R.gpu_name===D.gpu_name&&R.vram_used_gb===D.vram_used_gb&&R.vram_total_gb===D.vram_total_gb?R:D)}catch{}}k();const se=setInterval(k,1500);return()=>clearInterval(se)},[]),U.useEffect(()=>{if(V){const k=V.toLowerCase();k.includes("flux")||k.includes("schnell")?J(se=>({...se,steps:4,cfgScale:1,width:1024,height:1024})):k.includes("lightning")||k.includes("turbo")?J(se=>({...se,steps:4,cfgScale:1.5,width:1024,height:1024})):k.includes("sd15")?J(se=>({...se,steps:25,cfgScale:7,width:512,height:512})):k.includes("sd35")?J(se=>({...se,steps:20,cfgScale:4.5,width:1024,height:1024})):J(se=>({...se,steps:20,cfgScale:7,width:512,height:512}))}},[V]);const Ee=U.useMemo(()=>{switch(B){case"generator":return c.jsx(t1,{prompt:A,setPrompt:O,negativePrompt:L,setNegativePrompt:Z,constraints:q,setConstraints:J,activeModel:V,generatedImages:Ke,setGeneratedImages:Ye,isGenerating:x,setIsGenerating:C,generationProgress:K,setGenerationProgress:ge,setActiveTab:y,showAlert:N,showConfirm:_});case"models":return c.jsx(n1,{activeModel:V,setActiveModel:W,serverRunning:ue,setServerRunning:F,constraints:q,showAlert:N,showConfirm:_});case"constraints":return c.jsx(c1,{constraints:q,setConstraints:J,activeModel:V,specs:re,backendOptions:Qe});default:return null}},[B,A,L,q,V,Ke,x,K,ue,re,Qe,N,_]),it=U.useMemo(()=>c.jsx(My,{activeTab:B,setActiveTab:y,specs:re}),[B,re]),ot=U.useCallback(async()=>{if(!(!ue||Ce)){Te(!0);try{await Vs(),F(!1),W(null)}catch(k){console.error("Failed to stop server:",k),N({title:"Stop Server Failed",message:k.message||String(k),danger:!0})}finally{Te(!1)}}},[ue,Ce,N]),ht=[...Me?.stale?["Restart Local AI Image Generator so the local server loads the latest API."]:[],...Me?.issues||[]],cl=Se.reduce((k,se)=>k+Number(se.sizeBytes||0),0),Ut=!!(Me&&(ht.length>0||Se.length>0));return c.jsxs("div",{className:"app-container",children:[it,c.jsxs("div",{className:"main-content",children:[c.jsx(Ny,{telemetry:j,serverRunning:ue,activeModel:V,onStopServer:ot,isStoppingServer:Ce,theme:G,setTheme:H}),Ut&&c.jsxs("div",{className:`m3-card readiness-card ${Me?.stale||ht.length>0?"readiness-card-warning":""}`,children:[c.jsxs("div",{className:"readiness-header",children:[c.jsxs("div",{children:[c.jsx("h3",{className:"m3-card-title",style:{marginBottom:"4px"},children:Me?.stale?"Restart Required":ht.length>0?"System Readiness":"Safe Cleanup Available"}),c.jsx("p",{className:"m3-card-subtitle",style:{margin:0},children:Me?.stale?`Running server build: ${Me.build||"unknown"}`:ht.length>0?"Local AI Image Generator found setup items that may need attention.":`${Se.length} temporary item${Se.length===1?"":"s"} can be cleaned (${ai(cl)}).`})]}),c.jsxs("div",{className:"readiness-actions",children:[c.jsx("button",{className:"m3-btn m3-btn-outlined",onClick:ae,disabled:_t,children:_t?"Checking":"Refresh"}),c.jsx("button",{className:"m3-btn m3-btn-tonal",onClick:de,children:Ve?"Copied":"Copy Diagnostics"}),Se.length>0&&c.jsxs("button",{className:"m3-btn m3-btn-error",onClick:ke,disabled:_t,children:["Clean ",ai(cl)]})]})]}),ht.length>0&&c.jsx("div",{className:"readiness-issues",children:ht.slice(0,4).map(k=>c.jsx("span",{children:k},k))}),Se.length>0&&c.jsx("div",{className:"readiness-cleanup-list",children:Se.slice(0,3).map(k=>c.jsxs("span",{title:k.path,children:[k.name," · ",k.size," · ",k.reason]},k.id))})]}),Ee]}),b&&c.jsx("div",{className:"app-dialog-backdrop",role:"presentation",children:c.jsxs("div",{className:"app-dialog",role:"dialog","aria-modal":"true","aria-labelledby":"app-dialog-title",children:[c.jsx("h3",{id:"app-dialog-title",className:"app-dialog-title",children:b.title}),c.jsx("p",{className:"app-dialog-message",children:b.message}),c.jsxs("div",{className:"app-dialog-actions",children:[b.type==="confirm"&&c.jsx("button",{className:"m3-btn m3-btn-outlined",onClick:()=>r(!1),children:b.cancelLabel}),c.jsx("button",{className:`m3-btn ${b.danger?"m3-btn-error":"m3-btn-filled"}`,onClick:()=>r(!0),children:b.conf Line truncated
+1 -1
View File
@@ -8,7 +8,7 @@
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Outfit:wght@300;400;500;600;700&display=swap" rel="stylesheet">
<title>Local AI Image Generator</title>
<script type="module" crossorigin src="/assets/index-CBtRYlFE.js"></script>
<script type="module" crossorigin src="/assets/index-CL30r104.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-DW6G7wc2.css">
</head>
+1 -5
View File
@@ -1,5 +1,5 @@
import React, { memo, useState, useRef, useCallback } from "react";
import { Sparkles, Download, Copy, RefreshCw, Check, Sliders, Layers, Trash2 } from "lucide-react";
import { Sparkles, Download, Copy, RefreshCw, Check, Sliders, Trash2 } from "lucide-react";
import { generateImage, startServer, stopServer, waitForServerReady, getBackendStatus, getGenerationProgress, saveGeneratedOutput, deleteGeneratedOutputs } from "../services/api";
const GalleryItem = memo(({ img, idx, isSelected, onClick }) => {
@@ -600,10 +600,6 @@ function Generator({
<RefreshCw size={14} />
<span>Steps: {constraints.steps}</span>
</div>
<div className="status-chip" onClick={() => setActiveTab("constraints")}>
<Layers size={14} />
<span>CFG: {constraints.cfgScale.toFixed(1)}</span>
</div>
<div className="status-chip" onClick={() => setActiveTab("constraints")}>
<Sparkles size={14} />
<span>Sampler: {constraints.sampler}</span>
@@ -209,26 +209,6 @@ function ImageConstraints({ constraints, setConstraints, activeModel, specs, bac
</span>
</div>
{/* CFG Scale Slider */}
<div className="m3-slider-group" style={{ marginTop: "8px" }}>
<div className="m3-slider-header">
<span className="m3-slider-label">Prompt Strictness (Instruction obedience)</span>
<span className="m3-slider-value">{constraints.cfgScale.toFixed(1)}</span>
</div>
<input
type="range"
className="m3-slider"
value={constraints.cfgScale}
onChange={(e) => updateConstraint("cfgScale", parseFloat(e.target.value))}
min="1"
max="15"
step="0.5"
/>
<span style={{ fontSize: "0.75rem", color: "var(--md-sys-color-outline)", lineHeight: "1.3" }}>
How closely the AI follows your text prompt. Lower = more creative. Higher = more strict.
</span>
</div>
{/* Random Seed DNA */}
<div className="m3-text-field" style={{ marginTop: "8px" }}>
<label className="m3-text-field-label">Image Blueprint (DNA Seed)</label>
+5 -3
View File
@@ -250,10 +250,12 @@ function ModelManager({ activeModel, setActiveModel, serverRunning, setServerRun
const performLoadModel = async (modelId) => {
const isTauriDesktop = typeof window !== "undefined" && window.__TAURI_INTERNALS__ !== undefined;
const isLocalServerMode = typeof window !== "undefined" && (window.location.hostname === "localhost" || window.location.hostname === "127.0.0.1");
const backendStatus = await getBackendStatus();
const backendPort = backendStatus?.port || 8080;
if (!isTauriDesktop && !isLocalServerMode) {
const isAlreadyRunning = await pingServer();
const cmd = `.\\backend\\win\\vulkan\\sd-vulkan.exe --listen-port 8080 --model .\\models\\${modelId} --threads 8`;
const cmd = `.\\backend\\win\\vulkan\\sd-vulkan.exe --listen-port ${backendPort} --model .\\models\\${modelId} --threads 8`;
if (isAlreadyRunning) {
if (await showConfirm({ title: "Web Browser Mode", message: `To load "${modelId}" on the active GPU server, restart your terminal backend with:\n\n${cmd}\n\nUpdate the UI status anyway?`, confirmLabel: "Update UI" })) {
@@ -261,7 +263,7 @@ function ModelManager({ activeModel, setActiveModel, serverRunning, setServerRun
setServerRunning(true);
}
} else {
if (await showConfirm({ title: "Backend Not Running", message: `C++ backend server is not running on port 8080.\n\nTo run locally, start:\n\n${cmd}\n\nProceed in Simulation Mode instead?`, confirmLabel: "Proceed" })) {
if (await showConfirm({ title: "Backend Not Running", message: `C++ backend server is not running on port ${backendPort}.\n\nTo run locally, start:\n\n${cmd}\n\nProceed in Simulation Mode instead?`, confirmLabel: "Proceed" })) {
setActiveModel(modelId);
setServerRunning(false);
}
@@ -330,7 +332,7 @@ function ModelManager({ activeModel, setActiveModel, serverRunning, setServerRun
setActiveModel(modelId);
setServerRunning(true);
} else {
throw new Error(crashError || "Model server failed to respond on port 8080.");
throw new Error(crashError || `Model server failed to respond on port ${backendPort}.`);
}
} catch (e) {
console.error("Failed to load model:", e);
+27 -5
View File
@@ -8,12 +8,32 @@ export const isTauri = () => {
// Cached hardware specs
let cachedSpecs = null;
let cachedBackendPort = null;
export const EXPECTED_SERVER_BUILD = "polish-setup-v1";
const isLocalServerMode = () => {
return typeof window !== "undefined" && (window.location.hostname === "localhost" || window.location.hostname === "127.0.0.1");
};
async function getBackendPort() {
if (isLocalServerMode()) {
try {
const status = await getBackendStatus();
const port = Number(status?.port);
if (Number.isInteger(port) && port > 0) {
cachedBackendPort = port;
return port;
}
} catch (_) {}
}
if (cachedBackendPort) return cachedBackendPort;
return 8080;
}
async function getBackendBaseUrl() {
return `http://127.0.0.1:${await getBackendPort()}`;
}
export function formatBytes(bytes) {
const value = Number(bytes) || 0;
if (value <= 0) return "0 B";
@@ -208,10 +228,11 @@ export async function listLocalModels() {
// In web/portable mode this calls serve.cjs management API which restarts the
// sd-vulkan.exe process with --steps, --cfg-scale, --sampling-method flags.
export async function startServer(modelPath, constraints) {
const backendPort = await getBackendPort();
if (isTauri()) {
const launchParams = {
model_path: modelPath,
port: 8080,
port: backendPort,
use_gpu: constraints.useGpu !== false,
backend_type: constraints.backendType || (constraints.useGpu === false ? "cpu" : "auto"),
threads: constraints.threads || 8,
@@ -242,6 +263,9 @@ export async function startServer(modelPath, constraints) {
if (!res.ok || data.ok === false) {
throw new Error(data.error || `Backend restart failed (HTTP ${res.status})`);
}
if (Number.isInteger(Number(data.port)) && Number(data.port) > 0) {
cachedBackendPort = Number(data.port);
}
console.log("Backend restart:", data.message);
return data.message;
} catch (e) {
@@ -320,8 +344,7 @@ export async function generateImage(prompt, negativePrompt, constraints, activeM
console.log("Initiating image generation:", { prompt, negativePrompt, constraints, activeModelName });
const startTime = Date.now();
const port = 8080;
const baseUrl = `http://127.0.0.1:${port}`;
const baseUrl = await getBackendBaseUrl();
// Prepare payload based on standard stable-diffusion.cpp REST endpoint schemas
const payload = {
@@ -738,8 +761,7 @@ export async function deleteModel(filename) {
// Ping the server to check if it is active and responding
export async function pingServer() {
const port = 8080;
const baseUrl = `http://127.0.0.1:${port}`;
const baseUrl = await getBackendBaseUrl();
try {
const response = await fetch(`${baseUrl}/v1/models`, { method: "GET" });
return response.ok;
+38 -8
View File
@@ -11,8 +11,14 @@ const os = require("os");
const path = require("path");
const { spawn, spawnSync, execSync, exec } = require("child_process");
const PORT_FRONTEND = process.env.PORT ? parseInt(process.env.PORT) : 1420;
const PORT_BACKEND = 8080;
function readPort(value, fallback) {
const port = parseInt(value, 10);
return Number.isInteger(port) && port > 0 && port < 65536 ? port : fallback;
}
const PORT_FRONTEND = readPort(process.env.PORT || process.env.FRONTEND_PORT, 1420);
const PREFERRED_BACKEND_PORT = readPort(process.env.BACKEND_PORT || process.env.SD_BACKEND_PORT, 8080);
let PORT_BACKEND = PREFERRED_BACKEND_PORT;
const MAX_JSON_BODY_BYTES = 64 * 1024 * 1024;
const SERVER_BUILD = "polish-setup-v1";
const ROOT = path.join(__dirname, "..");
@@ -310,6 +316,21 @@ function checkPort(port) {
});
}
async function findAvailableBackendPort() {
const preferred = await checkPort(PREFERRED_BACKEND_PORT);
if (preferred.available) return PREFERRED_BACKEND_PORT;
for (let port = 28088; port <= 28120; port += 1) {
const candidate = await checkPort(port);
if (candidate.available) {
console.log(` [backend] Preferred port ${PREFERRED_BACKEND_PORT} is busy; using ${port} instead.`);
return port;
}
}
throw new Error(`No free backend port found. Tried ${PREFERRED_BACKEND_PORT} and 28088-28120.`);
}
function getSetupPaths() {
const appDir = path.join(ROOT, "app");
if (osPlatform === "win32") {
@@ -382,7 +403,9 @@ async function getHealth() {
backend: { ...(await checkPort(PORT_BACKEND)), expectedInUse: backendProc !== null },
};
ports.frontend.ok = !ports.frontend.available;
ports.backend.ok = backendProc !== null ? !ports.backend.available : ports.backend.available;
ports.backend.preferred = PREFERRED_BACKEND_PORT;
ports.backend.selected = PORT_BACKEND;
ports.backend.ok = true;
const issues = checks
.filter((check) => !check.ok && !["CUDA backend", "Vulkan backend", "Linux backend", "Mac backend"].includes(check.label))
@@ -390,8 +413,6 @@ async function getHealth() {
if (!backendInstalled) {
issues.push(`No ${osPlatform === "win32" ? "Windows" : osPlatform === "darwin" ? "macOS" : "Linux"} backend binary is installed.`);
}
if (!ports.backend.ok) issues.push(`Port ${PORT_BACKEND} is already in use by another process.`);
return {
ok: criticalOk && ports.backend.ok,
build: SERVER_BUILD,
@@ -657,7 +678,7 @@ function killBackend() {
});
}
function startBackend(settings = {}) {
async function startBackend(settings = {}) {
backendError = null;
currentSettings = { ...currentSettings, ...settings };
if (!currentSettings.model) currentSettings.model = getDefaultModel();
@@ -666,6 +687,8 @@ function startBackend(settings = {}) {
return;
}
PORT_BACKEND = await findAvailableBackendPort();
const resolvedBackendType = resolveBackendType(currentSettings.useGpu, currentSettings.backendType);
currentSettings.backendType = resolvedBackendType;
currentSettings.useGpu = resolvedBackendType !== "cpu";
@@ -1305,6 +1328,8 @@ const server = http.createServer(async (req, res) => {
return json(res, 200, {
ready: backendReady,
running: backendProc !== null,
port: PORT_BACKEND,
preferredPort: PREFERRED_BACKEND_PORT,
error: backendError,
loading: backendLoadState,
unloading: backendUnloadState,
@@ -1359,8 +1384,13 @@ const server = http.createServer(async (req, res) => {
}
if (typeof body.vae_tiling === "boolean") newSettings.vaeTiling = body.vae_tiling;
if (typeof body.vae_on_cpu === "boolean") newSettings.vaeOnCpu = body.vae_on_cpu;
startBackend(newSettings);
return json(res, 200, { ok: true, message: "Backend restarting...", settings: currentSettings });
try {
await startBackend(newSettings);
return json(res, 200, { ok: true, message: "Backend restarting...", settings: currentSettings, port: PORT_BACKEND });
} catch (err) {
backendError = err.message || String(err);
return json(res, 500, { ok: false, error: backendError, port: PORT_BACKEND });
}
}
// POST /api/stop-backend
+11 -12
View File
@@ -10,6 +10,7 @@ set SETUP=%~dp0scripts\setup.ps1
set CUDA_BACKEND=%APP%\backend\win\cuda\sd-cuda.exe
set VULKAN_BACKEND=%APP%\backend\win\vulkan\sd-vulkan.exe
set SERVE=%~dp0scripts\serve.cjs
if "%FRONTEND_PORT%"=="" set FRONTEND_PORT=1420
set SETUP_REASON=
set SETUP_MODE=Repair
@@ -49,9 +50,8 @@ echo.
echo Press any key to continue, or Ctrl+C to cancel.
pause >nul
:: Clear old local server processes before setup so app/tools/node-win can be replaced
for /f "tokens=5" %%a in ('netstat -aon 2^>nul ^| findstr ":8080 "') do taskkill /f /pid %%a >nul 2>nul
for /f "tokens=5" %%a in ('netstat -aon 2^>nul ^| findstr ":1420 "') do taskkill /f /pid %%a >nul 2>nul
:: Clear old frontend server process before setup so app/tools/node-win can be replaced
for /f "tokens=5" %%a in ('netstat -aon 2^>nul ^| findstr ":%FRONTEND_PORT% "') do taskkill /f /pid %%a >nul 2>nul
powershell -ExecutionPolicy Bypass -File "%SETUP%"
if errorlevel 1 (
@@ -73,10 +73,9 @@ echo LOCAL AI IMAGE GENERATOR ^| Launching...
echo ============================================================
echo.
:: Kill anything on our ports
echo Clearing ports 8080 and 1420...
for /f "tokens=5" %%a in ('netstat -aon 2^>nul ^| findstr ":8080 "') do taskkill /f /pid %%a >nul 2>nul
for /f "tokens=5" %%a in ('netstat -aon 2^>nul ^| findstr ":1420 "') do taskkill /f /pid %%a >nul 2>nul
:: Clear only the frontend port. The backend auto-selects a free port.
echo Clearing frontend port %FRONTEND_PORT%...
for /f "tokens=5" %%a in ('netstat -aon 2^>nul ^| findstr ":%FRONTEND_PORT% "') do taskkill /f /pid %%a >nul 2>nul
:: Start frontend server + backend manager (serve.cjs manages sd-vulkan.exe)
echo Starting Local AI Image Generator...
@@ -86,14 +85,14 @@ start "SD-Server" /min "%NODE%" "%SERVE%"
timeout /t 2 >nul
:: Open browser
echo Opening browser at http://localhost:1420
start http://localhost:1420
echo Opening browser at http://localhost:%FRONTEND_PORT%
start http://localhost:%FRONTEND_PORT%
echo.
echo ============================================================
echo Running!
echo Web UI: http://localhost:1420
echo GPU API: http://127.0.0.1:8080
echo Web UI: http://localhost:%FRONTEND_PORT%
echo GPU API: Auto-selected by the app (starts at 8080)
echo.
echo Close this window to stop all services.
echo ============================================================
@@ -103,4 +102,4 @@ pause >nul
:: Cleanup on exit
echo Shutting down...
taskkill /fi "WINDOWTITLE eq SD-Server*" /f >nul 2>nul
echo Done. Goodbye!
echo Done. Goodbye!