From 402ad238eeec42caa43297d00a12a46504fcf7a1 Mon Sep 17 00:00:00 2001 From: Immac Date: Wed, 4 Mar 2026 08:56:40 -0600 Subject: [PATCH] feat: auto-refresh node previews when image-writing nodes execute --- dist/immac_style_mixer/main.js | 4 ++-- ui/src/main.tsx | 29 +++++++++++++++++++++++++++++ 2 files changed, 31 insertions(+), 2 deletions(-) diff --git a/dist/immac_style_mixer/main.js b/dist/immac_style_mixer/main.js index a80c8df..dbc79c4 100644 --- a/dist/immac_style_mixer/main.js +++ b/dist/immac_style_mixer/main.js @@ -1,5 +1,5 @@ const __vite__mapDeps=(i,m=__vite__mapDeps,d=(m.f||(m.f=["immac_style_mixer/StyleMixerPanel-wHdKd5z8.js","immac_style_mixer/vendor-DPfS0z4g.js"])))=>i.map(i=>d[i]); -import{r as j,a as U,R as T}from"./vendor-DPfS0z4g.js";import{app as P}from"/scripts/app.js";const A="modulepreload",B=function(x){return"/"+x},C={},N=function(y,S,h){let e=Promise.resolve();if(S&&S.length>0){document.getElementsByTagName("link");const r=document.querySelector("meta[property=csp-nonce]"),i=(r==null?void 0:r.nonce)||(r==null?void 0:r.getAttribute("nonce"));e=Promise.allSettled(S.map(t=>{if(t=B(t),t in C)return;C[t]=!0;const o=t.endsWith(".css"),s=o?'[rel="stylesheet"]':"";if(document.querySelector(`link[href="${t}"]${s}`))return;const n=document.createElement("link");if(n.rel=o?"stylesheet":A,o||(n.as="script"),n.crossOrigin="",n.href=t,i&&n.setAttribute("nonce",i),document.head.appendChild(n),o)return new Promise((d,m)=>{n.addEventListener("load",d),n.addEventListener("error",()=>m(new Error(`Unable to preload CSS for ${t}`)))})}))}function a(r){const i=new Event("vite:preloadError",{cancelable:!0});if(i.payload=r,window.dispatchEvent(i),!i.defaultPrevented)throw r}return e.then(r=>{for(const i of r||[])i.status==="rejected"&&a(i.reason);return y().catch(a)})};var M={exports:{}},R={};/** +import{r as z,a as B,R as U}from"./vendor-DPfS0z4g.js";import{app as R}from"/scripts/app.js";const N="modulepreload",D=function(_){return"/"+_},j={},F=function(f,k,x){let S=Promise.resolve();if(k&&k.length>0){document.getElementsByTagName("link");const l=document.querySelector("meta[property=csp-nonce]"),e=(l==null?void 0:l.nonce)||(l==null?void 0:l.getAttribute("nonce"));S=Promise.allSettled(k.map(i=>{if(i=D(i),i in j)return;j[i]=!0;const s=i.endsWith(".css"),r=s?'[rel="stylesheet"]':"";if(document.querySelector(`link[href="${i}"]${r}`))return;const t=document.createElement("link");if(t.rel=s?"stylesheet":N,s||(t.as="script"),t.crossOrigin="",t.href=i,e&&t.setAttribute("nonce",e),document.head.appendChild(t),s)return new Promise((n,a)=>{t.addEventListener("load",n),t.addEventListener("error",()=>a(new Error(`Unable to preload CSS for ${i}`)))})}))}function P(l){const e=new Event("vite:preloadError",{cancelable:!0});if(e.payload=l,window.dispatchEvent(e),!e.defaultPrevented)throw l}return S.then(l=>{for(const e of l||[])e.status==="rejected"&&P(e.reason);return f().catch(P)})};var A={exports:{}},C={};/** * @license React * react-jsx-runtime.production.min.js * @@ -7,4 +7,4 @@ import{r as j,a as U,R as T}from"./vendor-DPfS0z4g.js";import{app as P}from"/scr * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. - */var L=j,D=Symbol.for("react.element"),F=Symbol.for("react.fragment"),Z=Object.prototype.hasOwnProperty,J=L.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.ReactCurrentOwner,H={key:!0,ref:!0,__self:!0,__source:!0};function z(x,y,S){var h,e={},a=null,r=null;S!==void 0&&(a=""+S),y.key!==void 0&&(a=""+y.key),y.ref!==void 0&&(r=y.ref);for(h in y)Z.call(y,h)&&!H.hasOwnProperty(h)&&(e[h]=y[h]);if(x&&x.defaultProps)for(h in y=x.defaultProps,y)e[h]===void 0&&(e[h]=y[h]);return{$$typeof:D,type:x,key:a,ref:r,props:e,_owner:J.current}}R.Fragment=F;R.jsx=z;R.jsxs=z;M.exports=R;var E=M.exports,$={},O=U;$.createRoot=O.createRoot,$.hydrateRoot=O.hydrateRoot;const W=T.lazy(()=>N(()=>import("./StyleMixerPanel-wHdKd5z8.js"),__vite__mapDeps([0,1])));async function q(){P.extensionManager.registerSidebarTab({id:"immac-style-mixer",icon:"pi pi-palette",title:"Style Mixer",tooltip:"Immac Style Mixer",type:"custom",render:e=>{const a=document.createElement("div");a.id="immac-style-mixer-root",a.style.position="absolute",a.style.inset="0",a.style.overflow="hidden",e.style.position="relative",e.style.height="100%",e.appendChild(a),$.createRoot(a).render(E.jsx(T.StrictMode,{children:E.jsx(j.Suspense,{fallback:E.jsx("div",{style:{padding:"1rem"},children:"Loading…"}),children:E.jsx(W,{})})}))}});const x="/immac_style_mixer/api/data";async function y(){const e=await fetch(x);if(!e.ok){alert(`Backup failed: HTTP ${e.status}`);return}const a=await e.json(),r=new Blob([JSON.stringify(a,null,2)],{type:"application/json"}),i=URL.createObjectURL(r),t=document.createElement("a");t.href=i,t.download=`style_mixer_backup_${new Date().toISOString().slice(0,10)}.json`,t.click(),URL.revokeObjectURL(i)}function S(){const e=document.createElement("a");e.href="/immac_style_mixer/api/backup.zip",e.download=`style_mixer_backup_${new Date().toISOString().slice(0,10)}.zip`,e.click()}function h(){const e=document.createElement("input");e.type="file",e.accept=".json,.zip,application/json,application/zip",e.onchange=()=>{var i;const a=(i=e.files)==null?void 0:i[0];if(!a)return;if(a.name.endsWith(".zip")||a.type==="application/zip")a.arrayBuffer().then(async t=>{var o,s;try{const n=await fetch("/immac_style_mixer/api/restore.zip",{method:"POST",headers:{"Content-Type":"application/zip"},body:t});if(!n.ok){const m=await n.text();throw new Error(`HTTP ${n.status}: ${m.slice(0,300)}`)}const d=await n.json();try{(s=(o=P)==null?void 0:o.toast)==null||s.add({severity:"success",summary:"Style Mixer",detail:`ZIP restored: ${d.styles} styles, ${d.mixes} mixes, ${d.images} images. Reload the Style Mixer panel to see changes.`,life:6e3})}catch{}}catch(n){alert(`Failed to restore ZIP backup: ${n.message}`)}}).catch(t=>alert(`Could not read file: ${t.message}`));else{const t=new FileReader;t.onload=async o=>{var s,n,d;try{const m=JSON.parse((s=o.target)==null?void 0:s.result);if(!Array.isArray(m.styles)||!Array.isArray(m.mixes))throw new Error("Invalid backup: missing styles or mixes arrays.");const g=await fetch(x,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(m)});if(!g.ok)throw new Error(`Save failed: HTTP ${g.status}`);try{(d=(n=P)==null?void 0:n.toast)==null||d.add({severity:"success",summary:"Style Mixer",detail:`Backup restored: ${m.styles.length} styles, ${m.mixes.length} mixes. Reload the Style Mixer panel to see changes.`,life:6e3})}catch{}}catch(m){alert(`Failed to restore backup: ${m.message}`)}},t.readAsText(a)}},e.click()}P.registerExtension({name:"ImmacStyleMixer",settings:[{id:"ImmacStyleMixer.BackupRestore",name:"Style Mixer — Backup & Restore",type:(e,a,r)=>{const i=document.createElement("div");i.style.cssText="display:flex;gap:8px;align-items:center";const t=document.createElement("button");t.textContent="⬇ Download backup (JSON)",t.title="Save all styles and mixes to a JSON file",t.style.cssText="cursor:pointer;padding:4px 10px;border-radius:4px",t.onclick=()=>y().catch(console.error);const o=document.createElement("button");o.textContent="⬇ Download ZIP (with images)",o.title="Save styles, mixes and all images to a ZIP file",o.style.cssText="cursor:pointer;padding:4px 10px;border-radius:4px",o.onclick=()=>S();const s=document.createElement("button");return s.textContent="⬆ Restore (JSON or ZIP)",s.title="Restore from a JSON or ZIP backup — auto-detected",s.style.cssText="cursor:pointer;padding:4px 10px;border-radius:4px",s.onclick=()=>h(),i.appendChild(t),i.appendChild(o),i.appendChild(s),i},defaultValue:null}],nodeCreated(e){var I;if(e.comfyClass!=="PickMixImmacStyleMixer"&&e.comfyClass!=="StylePickImmacStyleMixer")return;const a=e.comfyClass==="StylePickImmacStyleMixer",r=a?"style":"mix",i=(I=e.widgets)==null?void 0:I.find(l=>l.name===r);if(!i)return;const t=document.createElement("img");t.style.cssText="width:100%;height:100%;display:none;object-fit:contain;border-radius:4px";const o=document.createElement("div");o.style.cssText="padding:4px;box-sizing:border-box",o.appendChild(t);let s=0;e.addDOMWidget("immac_preview","div",o,{serialize:!1,getMinHeight:()=>s,getHeight:()=>s});function n(l,p){var w,_,k,u;const f=((w=e.size)==null?void 0:w[0])??300,v=p/l;s=Math.max(80,Math.min(Math.round((f-8)*v),600));const c=(_=e.computeSize)==null?void 0:_.call(e);c&&e.setSize([Math.max(e.size[0],c[0]),Math.max(e.size[1],c[1])]),(k=e.onResize)==null||k.call(e,e.size),(u=e.graph)==null||u.setDirtyCanvas(!0,!0)}function d(){var l;s=0,t.style.display="none",(l=e.graph)==null||l.setDirtyCanvas(!0,!0)}function m(l){t.onload=()=>{t.style.display="block",n(t.naturalWidth,t.naturalHeight)},t.onerror=()=>{d()},t.src=l}async function g(l){var p;try{const f=await fetch("/immac_style_mixer/api/data");if(!f.ok)return;const v=await f.json();if(a){const c=(v.styles??[]).find(_=>_.name===l);if(!(c!=null&&c.image_filename)){d();return}const w=c.image_updated_at?`&t=${c.image_updated_at}`:"";m(`/view?filename=${encodeURIComponent(c.image_filename)}&subfolder=immac_style_mixer%2Fstyles&type=input${w}`)}else{const c=(v.mixes??[]).find(_=>_.name===l);let w="";if(c!=null&&c.image_filename){const _=c.image_updated_at?`&t=${c.image_updated_at}`:"";w=`/view?filename=${encodeURIComponent(c.image_filename)}&subfolder=immac_style_mixer%2Fmixes&type=input${_}`}else if((p=c==null?void 0:c.styles)!=null&&p.length){const _=Object.fromEntries((v.styles??[]).map(u=>[u.id,u])),k=c.styles.filter(u=>u.enabled!==!1).map(u=>_[u.style_id]).find(u=>u==null?void 0:u.image_filename);if(k){const u=k.image_updated_at?`&t=${k.image_updated_at}`:"";w=`/view?filename=${encodeURIComponent(k.image_filename)}&subfolder=immac_style_mixer%2Fstyles&type=input${u}`}}if(!w){d();return}m(w)}}catch(f){console.error("[ImmacStyleMixer] Preview update failed",f)}}const b=i.callback;i.callback=function(...l){var f;b==null||b.apply(this,l);const p=(f=e.widgets)==null?void 0:f.find(v=>v.name===r);g(String((p==null?void 0:p.value)??""))},e._immacUpdatePreview=l=>{var f;const p=(f=e.widgets)==null?void 0:f.find(v=>v.name===r);g(l??String((p==null?void 0:p.value)??""))},e._immacPreviewScheduled=!0,requestAnimationFrame(()=>{var p;if(!e._immacPreviewScheduled)return;const l=(p=e.widgets)==null?void 0:p.find(f=>f.name===r);l!=null&&l.value&&g(String(l.value))})},loadedGraphNode(e){var o;if(typeof e._immacUpdatePreview!="function")return;e._immacPreviewScheduled=!1;const a=e.comfyClass==="StylePickImmacStyleMixer",r=a?"style":"mix",i=(o=e.widgets)==null?void 0:o.find(s=>s.name===r),t=String((i==null?void 0:i.value)??"");requestAnimationFrame(()=>{t==="(no styles saved)"||t==="(no mixes saved)"?fetch("/immac_style_mixer/api/data").then(s=>s.json()).then(s=>{var m,g;const n=a?s.styles??[]:s.mixes??[],d=(m=e.widgets)==null?void 0:m.find(b=>b.name===r);n.length>0&&d&&(d.value=n[0].name,Array.isArray((g=d.options)==null?void 0:g.values)&&(d.options.values=n.map(b=>b.name))),e._immacUpdatePreview()}).catch(()=>e._immacUpdatePreview()):e._immacUpdatePreview(t)})}})}q().catch(console.error);export{E as j}; + */var Z=z,J=Symbol.for("react.element"),H=Symbol.for("react.fragment"),W=Object.prototype.hasOwnProperty,q=Z.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.ReactCurrentOwner,G={key:!0,ref:!0,__self:!0,__source:!0};function L(_,f,k){var x,S={},P=null,l=null;k!==void 0&&(P=""+k),f.key!==void 0&&(P=""+f.key),f.ref!==void 0&&(l=f.ref);for(x in f)W.call(f,x)&&!G.hasOwnProperty(x)&&(S[x]=f[x]);if(_&&_.defaultProps)for(x in f=_.defaultProps,f)S[x]===void 0&&(S[x]=f[x]);return{$$typeof:J,type:_,key:P,ref:l,props:S,_owner:q.current}}C.Fragment=H;C.jsx=L;C.jsxs=L;A.exports=C;var $=A.exports,M={},T=B;M.createRoot=T.createRoot,M.hydrateRoot=T.hydrateRoot;const V=U.lazy(()=>F(()=>import("./StyleMixerPanel-wHdKd5z8.js"),__vite__mapDeps([0,1])));async function Y(){var l;R.extensionManager.registerSidebarTab({id:"immac-style-mixer",icon:"pi pi-palette",title:"Style Mixer",tooltip:"Immac Style Mixer",type:"custom",render:e=>{const i=document.createElement("div");i.id="immac-style-mixer-root",i.style.position="absolute",i.style.inset="0",i.style.overflow="hidden",e.style.position="relative",e.style.height="100%",e.appendChild(i),M.createRoot(i).render($.jsx(U.StrictMode,{children:$.jsx(z.Suspense,{fallback:$.jsx("div",{style:{padding:"1rem"},children:"Loading…"}),children:$.jsx(V,{})})}))}});const _="/immac_style_mixer/api/data";async function f(){const e=await fetch(_);if(!e.ok){alert(`Backup failed: HTTP ${e.status}`);return}const i=await e.json(),s=new Blob([JSON.stringify(i,null,2)],{type:"application/json"}),r=URL.createObjectURL(s),t=document.createElement("a");t.href=r,t.download=`style_mixer_backup_${new Date().toISOString().slice(0,10)}.json`,t.click(),URL.revokeObjectURL(r)}function k(){const e=document.createElement("a");e.href="/immac_style_mixer/api/backup.zip",e.download=`style_mixer_backup_${new Date().toISOString().slice(0,10)}.zip`,e.click()}function x(){const e=document.createElement("input");e.type="file",e.accept=".json,.zip,application/json,application/zip",e.onchange=()=>{var r;const i=(r=e.files)==null?void 0:r[0];if(!i)return;if(i.name.endsWith(".zip")||i.type==="application/zip")i.arrayBuffer().then(async t=>{var n,a;try{const d=await fetch("/immac_style_mixer/api/restore.zip",{method:"POST",headers:{"Content-Type":"application/zip"},body:t});if(!d.ok){const m=await d.text();throw new Error(`HTTP ${d.status}: ${m.slice(0,300)}`)}const p=await d.json();try{(a=(n=R)==null?void 0:n.toast)==null||a.add({severity:"success",summary:"Style Mixer",detail:`ZIP restored: ${p.styles} styles, ${p.mixes} mixes, ${p.images} images. Reload the Style Mixer panel to see changes.`,life:6e3})}catch{}}catch(d){alert(`Failed to restore ZIP backup: ${d.message}`)}}).catch(t=>alert(`Could not read file: ${t.message}`));else{const t=new FileReader;t.onload=async n=>{var a,d,p;try{const m=JSON.parse((a=n.target)==null?void 0:a.result);if(!Array.isArray(m.styles)||!Array.isArray(m.mixes))throw new Error("Invalid backup: missing styles or mixes arrays.");const w=await fetch(_,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(m)});if(!w.ok)throw new Error(`Save failed: HTTP ${w.status}`);try{(p=(d=R)==null?void 0:d.toast)==null||p.add({severity:"success",summary:"Style Mixer",detail:`Backup restored: ${m.styles.length} styles, ${m.mixes.length} mixes. Reload the Style Mixer panel to see changes.`,life:6e3})}catch{}}catch(m){alert(`Failed to restore backup: ${m.message}`)}},t.readAsText(i)}},e.click()}R.registerExtension({name:"ImmacStyleMixer",settings:[{id:"ImmacStyleMixer.BackupRestore",name:"Style Mixer — Backup & Restore",type:(e,i,s)=>{const r=document.createElement("div");r.style.cssText="display:flex;gap:8px;align-items:center";const t=document.createElement("button");t.textContent="⬇ Download backup (JSON)",t.title="Save all styles and mixes to a JSON file",t.style.cssText="cursor:pointer;padding:4px 10px;border-radius:4px",t.onclick=()=>f().catch(console.error);const n=document.createElement("button");n.textContent="⬇ Download ZIP (with images)",n.title="Save styles, mixes and all images to a ZIP file",n.style.cssText="cursor:pointer;padding:4px 10px;border-radius:4px",n.onclick=()=>k();const a=document.createElement("button");return a.textContent="⬆ Restore (JSON or ZIP)",a.title="Restore from a JSON or ZIP backup — auto-detected",a.style.cssText="cursor:pointer;padding:4px 10px;border-radius:4px",a.onclick=()=>x(),r.appendChild(t),r.appendChild(n),r.appendChild(a),r},defaultValue:null}],nodeCreated(e){var O;if(e.comfyClass!=="PickMixImmacStyleMixer"&&e.comfyClass!=="StylePickImmacStyleMixer")return;const i=e.comfyClass==="StylePickImmacStyleMixer",s=i?"style":"mix",r=(O=e.widgets)==null?void 0:O.find(c=>c.name===s);if(!r)return;const t=document.createElement("img");t.style.cssText="width:100%;height:100%;display:none;object-fit:contain;border-radius:4px";const n=document.createElement("div");n.style.cssText="padding:4px;box-sizing:border-box",n.appendChild(t);let a=0;e.addDOMWidget("immac_preview","div",n,{serialize:!1,getMinHeight:()=>a,getHeight:()=>a});function d(c,u){var b,g,I,y;const h=((b=e.size)==null?void 0:b[0])??300,v=u/c;a=Math.max(80,Math.min(Math.round((h-8)*v),600));const o=(g=e.computeSize)==null?void 0:g.call(e);o&&e.setSize([Math.max(e.size[0],o[0]),Math.max(e.size[1],o[1])]),(I=e.onResize)==null||I.call(e,e.size),(y=e.graph)==null||y.setDirtyCanvas(!0,!0)}function p(){var c;a=0,t.style.display="none",(c=e.graph)==null||c.setDirtyCanvas(!0,!0)}function m(c){t.onload=()=>{t.style.display="block",d(t.naturalWidth,t.naturalHeight)},t.onerror=()=>{p()},t.src=c}async function w(c){var u;try{const h=await fetch("/immac_style_mixer/api/data");if(!h.ok)return;const v=await h.json();if(i){const o=(v.styles??[]).find(g=>g.name===c);if(!(o!=null&&o.image_filename)){p();return}const b=o.image_updated_at?`&t=${o.image_updated_at}`:"";m(`/view?filename=${encodeURIComponent(o.image_filename)}&subfolder=immac_style_mixer%2Fstyles&type=input${b}`)}else{const o=(v.mixes??[]).find(g=>g.name===c);let b="";if(o!=null&&o.image_filename){const g=o.image_updated_at?`&t=${o.image_updated_at}`:"";b=`/view?filename=${encodeURIComponent(o.image_filename)}&subfolder=immac_style_mixer%2Fmixes&type=input${g}`}else if((u=o==null?void 0:o.styles)!=null&&u.length){const g=Object.fromEntries((v.styles??[]).map(y=>[y.id,y])),I=o.styles.filter(y=>y.enabled!==!1).map(y=>g[y.style_id]).find(y=>y==null?void 0:y.image_filename);if(I){const y=I.image_updated_at?`&t=${I.image_updated_at}`:"";b=`/view?filename=${encodeURIComponent(I.image_filename)}&subfolder=immac_style_mixer%2Fstyles&type=input${y}`}}if(!b){p();return}m(b)}}catch(h){console.error("[ImmacStyleMixer] Preview update failed",h)}}const E=r.callback;r.callback=function(...c){var h;E==null||E.apply(this,c);const u=(h=e.widgets)==null?void 0:h.find(v=>v.name===s);w(String((u==null?void 0:u.value)??""))},e._immacUpdatePreview=c=>{var h;const u=(h=e.widgets)==null?void 0:h.find(v=>v.name===s);w(c??String((u==null?void 0:u.value)??""))},e._immacPreviewScheduled=!0,requestAnimationFrame(()=>{var u;if(!e._immacPreviewScheduled)return;const c=(u=e.widgets)==null?void 0:u.find(h=>h.name===s);c!=null&&c.value&&w(String(c.value))})},loadedGraphNode(e){var n;if(typeof e._immacUpdatePreview!="function")return;e._immacPreviewScheduled=!1;const i=e.comfyClass==="StylePickImmacStyleMixer",s=i?"style":"mix",r=(n=e.widgets)==null?void 0:n.find(a=>a.name===s),t=String((r==null?void 0:r.value)??"");requestAnimationFrame(()=>{t==="(no styles saved)"||t==="(no mixes saved)"?fetch("/immac_style_mixer/api/data").then(a=>a.json()).then(a=>{var m,w;const d=i?a.styles??[]:a.mixes??[],p=(m=e.widgets)==null?void 0:m.find(E=>E.name===s);d.length>0&&p&&(p.value=d[0].name,Array.isArray((w=p.options)==null?void 0:w.values)&&(p.options.values=d.map(E=>E.name))),e._immacUpdatePreview()}).catch(()=>e._immacUpdatePreview()):e._immacUpdatePreview(t)})}});const S=new Set(["SaveMixImmacStyleMixer","StyleCreateImmacStyleMixer","StyleModifyImmacStyleMixer"]);function P(){var i;const e=((i=R.graph)==null?void 0:i._nodes)??[];for(const s of e)typeof s._immacUpdatePreview=="function"&&s._immacUpdatePreview()}(l=R.api)==null||l.addEventListener("executed",e=>{var r,t,n;const i=(r=e==null?void 0:e.detail)==null?void 0:r.node;if(i==null)return;const s=(n=(t=R.graph)==null?void 0:t._nodes_by_id)==null?void 0:n[i];S.has(s==null?void 0:s.comfyClass)&&P()})}Y().catch(console.error);export{$ as j}; diff --git a/ui/src/main.tsx b/ui/src/main.tsx index d53af73..39986f5 100644 --- a/ui/src/main.tsx +++ b/ui/src/main.tsx @@ -314,6 +314,35 @@ async function init(): Promise { }) }, }) + + // ── Refresh previews after image-writing nodes finish executing ─────────── + // SaveMixImmacStyleMixer, StyleCreateImmacStyleMixer, and + // StyleModifyImmacStyleMixer can all update an image_filename field. + // When any of them finishes, re-run _immacUpdatePreview on every + // PickMix / StylePick node so their thumbnails stay current. + const IMAGE_WRITING_CLASSES = new Set([ + 'SaveMixImmacStyleMixer', + 'StyleCreateImmacStyleMixer', + 'StyleModifyImmacStyleMixer', + ]) + + function refreshAllPreviews() { + const nodes: any[] = (app as any).graph?._nodes ?? [] + for (const n of nodes) { + if (typeof n._immacUpdatePreview === 'function') { + n._immacUpdatePreview() + } + } + } + + ;(app as any).api?.addEventListener('executed', (ev: any) => { + const nodeId = ev?.detail?.node + if (nodeId == null) return + const executedNode = (app as any).graph?._nodes_by_id?.[nodeId] + if (IMAGE_WRITING_CLASSES.has(executedNode?.comfyClass)) { + refreshAllPreviews() + } + }) } init().catch(console.error)