From f5f4e050c97b2c5fae25722d3e28cf519d644413 Mon Sep 17 00:00:00 2001 From: Immac Date: Wed, 4 Mar 2026 23:52:50 -0600 Subject: [PATCH] refactor: rename Style Pick \u2192 Pick Style, Style Blend \u2192 Blend Style (verb-first node names) --- __init__.py | 8 +- ...t3_8kWe.js => StyleMixerPanel-CMNQxf7y.js} | 2 +- dist/immac_style_mixer/main.js | 4 +- src/immac_tools/save_mix_node.py | 111 ++++++++++++++---- src/immac_tools/style_blend_node.py | 8 +- src/immac_tools/style_pick_node.py | 8 +- ui/src/components/StyleCard.tsx | 2 +- ui/src/main.tsx | 10 +- 8 files changed, 110 insertions(+), 43 deletions(-) rename dist/immac_style_mixer/{StyleMixerPanel-Dt3_8kWe.js => StyleMixerPanel-CMNQxf7y.js} (99%) diff --git a/__init__.py b/__init__.py index 8db7371..217e2b5 100644 --- a/__init__.py +++ b/__init__.py @@ -9,9 +9,9 @@ from comfy_api.latest import ComfyExtension, io from .src.immac_tools.api import register_routes from .src.immac_tools.style_mix_node import PickMixNode, StyleMixNode -from .src.immac_tools.style_pick_node import StylePickNode +from .src.immac_tools.style_pick_node import PickStyleNode from .src.immac_tools.style_weight_node import StyleWeightNode -from .src.immac_tools.style_blend_node import StyleBlendNode +from .src.immac_tools.style_blend_node import BlendStyleNode from .src.immac_tools.save_mix_node import SaveMixNode from .src.immac_tools.save_style_node import SaveStyleNode @@ -20,9 +20,9 @@ class ImmacStyleMixerExtension(ComfyExtension): async def get_node_list(self) -> list[type[io.ComfyNode]]: return [ PickMixNode, - StylePickNode, + PickStyleNode, StyleWeightNode, - StyleBlendNode, + BlendStyleNode, SaveMixNode, SaveStyleNode, ] diff --git a/dist/immac_style_mixer/StyleMixerPanel-Dt3_8kWe.js b/dist/immac_style_mixer/StyleMixerPanel-CMNQxf7y.js similarity index 99% rename from dist/immac_style_mixer/StyleMixerPanel-Dt3_8kWe.js rename to dist/immac_style_mixer/StyleMixerPanel-CMNQxf7y.js index 635627c..2960eb7 100644 --- a/dist/immac_style_mixer/StyleMixerPanel-Dt3_8kWe.js +++ b/dist/immac_style_mixer/StyleMixerPanel-CMNQxf7y.js @@ -1,4 +1,4 @@ import{j as e}from"./main.js";import{r as o,b as me}from"./vendor-DPfS0z4g.js";import"/scripts/app.js";import"/scripts/api.js";const oe={styles:[],mixes:[],current_mix_id:null},ae="/immac_style_mixer/api/data";function xe(){const[t,s]=o.useState(oe),[p,c]=o.useState(!0),[g,y]=o.useState(null),[f,C]=o.useState(!1),I=o.useRef(oe),D=o.useRef(null),N=o.useCallback(()=>fetch(ae).then(m=>{if(!m.ok)throw new Error(`HTTP ${m.status}`);return m.json()}).then(m=>{I.current=m,s(m)}).catch(m=>y(String(m))),[]);o.useEffect(()=>{N().finally(()=>c(!1))},[]),o.useEffect(()=>{const m=()=>{N().catch(()=>{})};return window.addEventListener("immac:execution_success",m),()=>window.removeEventListener("immac:execution_success",m)},[N]);const _=o.useCallback((m,v)=>{var R,H,F,x,S,O,T,B;const w=I.current,j=typeof m=="function"?m(w):m;I.current=j,s(j);const u=j.mixes.length-w.mixes.length,k=j.styles.length-w.styles.length;if(u<0||k<0){try{(H=(R=window.app)==null?void 0:R.refreshComboInNodes)==null||H.call(R),(x=(F=window.app)==null?void 0:F.toast)==null||x.add({severity:"warn",summary:"Style Mixer",detail:"A mix or style was deleted — node combo lists have been refreshed.",life:4e3})}catch(n){console.warn("[ImmacStyleMixer] refreshComboInNodes/toast failed",n)}C(!1)}else if(u>0||k>0){const n=k>0&&w.styles.length===0,a=u>0&&w.mixes.length===0;if(n||a){try{(O=(S=window.app)==null?void 0:S.refreshComboInNodes)==null||O.call(S),(B=(T=window.app)==null?void 0:T.toast)==null||B.add({severity:"info",summary:"Style Mixer",detail:"First item added — node combo lists refreshed.",life:4e3})}catch(r){console.warn("[ImmacStyleMixer] refreshComboInNodes/toast failed",r)}C(!1)}else v!=null&&v.silent||C(!0)}D.current&&clearTimeout(D.current),D.current=setTimeout(()=>{fetch(ae,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(j)}).then(()=>{var n,a,r;try{(r=(a=(n=window.app)==null?void 0:n.graph)==null?void 0:a.nodes)==null||r.forEach(z=>{typeof z._immacUpdatePreview=="function"&&z._immacUpdatePreview()})}catch{}}).catch(n=>console.error("[ImmacStyleMixer] Save failed",n))},400)},[]),b=o.useCallback(()=>{var m,v,w,j,u;try{(v=(m=window.app)==null?void 0:m.refreshComboInNodes)==null||v.call(m),(u=(j=(w=window.app)==null?void 0:w.graph)==null?void 0:j.nodes)==null||u.forEach(k=>{typeof k._immacUpdatePreview=="function"&&k._immacUpdatePreview()})}catch(k){console.warn("[ImmacStyleMixer] refreshComboInNodes failed",k)}C(!1)},[]);return{data:t,loading:p,error:g,update:_,pendingRefresh:f,refreshNodes:b}}async function ge(t){const s=new FormData;s.append("image",t),s.append("subfolder","immac_style_mixer/styles"),s.append("type","input");const p=await fetch("/upload/image",{method:"POST",body:s});if(!p.ok)throw new Error(`Upload failed: HTTP ${p.status}`);return(await p.json()).name}function Q(t,s){const p=s?`&t=${s}`:"";return`/view?filename=${encodeURIComponent(t)}&subfolder=immac_style_mixer%2Fstyles&type=input${p}`}async function he(t){const s=new FormData;s.append("image",t),s.append("subfolder","immac_style_mixer/mixes"),s.append("type","input");const p=await fetch("/upload/image",{method:"POST",body:s});if(!p.ok)throw new Error(`Upload failed: HTTP ${p.status}`);return(await p.json()).name}function re(t,s){const p=s?`&t=${s}`:"";return`/view?filename=${encodeURIComponent(t)}&subfolder=immac_style_mixer%2Fmixes&type=input${p}`}function ne({src:t,alt:s,onClose:p}){return o.useEffect(()=>{function c(g){g.key==="Escape"&&p()}return window.addEventListener("keydown",c),()=>window.removeEventListener("keydown",c)},[p]),me.createPortal(e.jsxs("div",{onClick:p,style:{position:"fixed",inset:0,zIndex:99999,background:"rgba(0,0,0,0.85)",display:"flex",alignItems:"center",justifyContent:"center",cursor:"zoom-out"},children:[e.jsx("img",{src:t,alt:s??"",onClick:c=>c.stopPropagation(),style:{maxWidth:"90vw",maxHeight:"90vh",objectFit:"contain",borderRadius:8,boxShadow:"0 8px 40px rgba(0,0,0,0.7)",cursor:"default"}}),e.jsx("button",{onClick:p,title:"Close",style:{position:"fixed",top:16,right:20,background:"none",border:"none",color:"#fff",fontSize:28,cursor:"pointer",lineHeight:1,opacity:.8},children:"✕"})]}),document.body)}function pe({value:t,onChange:s,dragMin:p=0,dragMax:c=1,step:g=.01,pixelsPerStep:y=2,width:f=72,decimals:C=2}){const[I,D]=o.useState(!1),[N,_]=o.useState(""),b=o.useRef(null),m=o.useCallback(()=>{const x=parseFloat(N);isNaN(x)||s(x),D(!1)},[N,s]);o.useEffect(()=>{var x,S;I&&((x=b.current)==null||x.focus(),(S=b.current)==null||S.select())},[I]);const v=o.useRef(!1),w=o.useRef(0),j=o.useRef(0),u=o.useRef(!1),k=o.useCallback(x=>{I||(x.currentTarget.setPointerCapture(x.pointerId),v.current=!0,u.current=!1,w.current=x.clientX,j.current=Math.max(p,Math.min(c,t)))},[I,t,p,c]),R=o.useCallback(x=>{if(!v.current)return;const S=x.clientX-w.current;Math.abs(S)>2&&(u.current=!0);const O=S/y,T=j.current+O*g,B=Math.round(T/g)*g,n=parseFloat(Math.max(p,Math.min(c,B)).toFixed(C));s(n)},[g,y,C,p,c,s]),H=o.useCallback(()=>{v.current&&!u.current&&(_(t.toFixed(C)),D(!0)),v.current=!1},[t,C]),F=Math.max(0,Math.min(1,(t-p)/(c-p)))*100;return I?e.jsx("input",{ref:b,type:"text",inputMode:"decimal",value:N,onChange:x=>_(x.target.value),onBlur:m,onKeyDown:x=>{x.key==="Enter"&&m(),x.key==="Escape"&&D(!1)},style:{width:f,background:"var(--p-surface-ground, #141414)",border:"1px solid var(--p-primary-color, #6c6)",borderRadius:4,color:"inherit",padding:"2px 4px",fontSize:12,boxSizing:"border-box",textAlign:"center",outline:"none"}}):e.jsxs("div",{draggable:!1,onMouseDown:x=>x.stopPropagation(),onDragStart:x=>x.stopPropagation(),style:{position:"relative",width:f,height:20,borderRadius:4,overflow:"hidden",cursor:"ew-resize",userSelect:"none",flexShrink:0,border:"1px solid var(--p-surface-border, #555)",background:"var(--p-surface-ground, #141414)",boxSizing:"border-box"},onPointerDown:k,onPointerMove:R,onPointerUp:H,children:[e.jsx("div",{style:{position:"absolute",inset:0,width:`${F}%`,background:"var(--p-primary-color, #4a7a4a)",opacity:.55,pointerEvents:"none"}}),e.jsx("span",{style:{position:"absolute",inset:0,display:"flex",alignItems:"center",justifyContent:"center",fontSize:11,color:"var(--p-text-color, #ddd)",pointerEvents:"none",fontVariantNumeric:"tabular-nums"},children:t.toFixed(C)})]})}function ye({mix:t,styles:s,isActive:p,isDirty:c,onActivate:g,onUpdate:y,onDelete:f,onDuplicate:C,onRefreshCache:I}){function D(i){i.target.closest("button, input, select, textarea, a")||g()}const[N,_]=o.useState(!1),[b,m]=o.useState(t.name),[v,w]=o.useState(!1),[j,u]=o.useState(!1),[k,R]=o.useState(!1),[H,F]=o.useState(!1),[x,S]=o.useState(!1),[O,T]=o.useState(!1),B=o.useRef(null);function n(){const i=t.styles.filter(l=>l.enabled&&(l.weight??1)>1e-9).map(l=>{var P;const h=((P=s.find($=>$.id===l.style_id))==null?void 0:P.value)??"";if(!h)return"";const E=l.weight??1;return Math.abs(E-1)<1e-6?h:`(${h}:${E.toFixed(2)})`}).filter(Boolean);i.length!==0&&navigator.clipboard.writeText(i.join(", ")).then(()=>{S(!0),setTimeout(()=>S(!1),1500)})}async function a(i){if(i.type.startsWith("image/")){w(!0);try{const l=await he(i);y({...t,image_filename:l,image_updated_at:Date.now()})}catch(l){console.error("[ImmacStyleMixer] Mix image upload failed",l)}finally{w(!1)}}}function r(){_(!1),b.trim()!==t.name&&y({...t,name:b.trim()||t.name})}function z(i,l){const h=t.styles.map((E,P)=>P===i?{...E,...l}:E);y({...t,styles:h},{silent:!0})}function L(i){y({...t,styles:t.styles.filter((l,h)=>h!==i)},{silent:!0})}function U(i){t.styles.some(l=>l.style_id===i)||y({...t,styles:[...t.styles,{style_id:i,weight:1,enabled:!0}]},{silent:!0})}const d=s.filter(i=>!t.styles.some(l=>l.style_id===i.id));function M(){return{draggable:!0,onDragStart:i=>{i.dataTransfer.effectAllowed="copy",i.dataTransfer.setData("application/x-immac-mix-name",t.name)},onDragEnd:i=>{var K,X,Y,G,J,q,ie;const l=(K=window.app)==null?void 0:K.canvas;if(!l)return;const h=l.canvas;if(!h||document.elementFromPoint(i.clientX,i.clientY)!==h)return;const E=l.convertEventToCanvasOffset(i),P=window.LiteGraph,$=P==null?void 0:P.createNode("PickMixImmacStyleMixer");if(!$)return;$.pos=E,(Y=(X=window.app)==null?void 0:X.graph)==null||Y.add($);const A=(G=$.widgets)==null?void 0:G.find(fe=>fe.name==="mix");A&&(A.value=t.name,(J=A.callback)==null||J.call(A,t.name,null,null,null,$)),(ie=(q=window.app)==null?void 0:q.graph)==null||ie.setDirtyCanvas(!0,!0)}}}return e.jsxs("div",{style:{border:`2px solid ${O?"#88aaff":p?"var(--p-primary-color, #6c6)":"var(--p-surface-border, #444)"}`,borderRadius:8,padding:10,background:O?"rgba(100,130,255,0.07)":"var(--p-surface-section, #1e1e1e)",display:"flex",flexDirection:"column",gap:8,width:"100%",position:"relative",overflow:"hidden",transition:"border-color 0.15s, background 0.15s"},onClick:D,onDragOver:i=>{i.dataTransfer.types.includes("application/x-immac-style-id")&&(i.preventDefault(),i.dataTransfer.dropEffect="copy",T(!0))},onDragLeave:i=>{i.currentTarget.contains(i.relatedTarget)||T(!1)},onDrop:i=>{T(!1);const l=i.dataTransfer.getData("application/x-immac-style-id");l&&(i.preventDefault(),i.stopPropagation(),U(l))},children:[e.jsxs("div",{style:{display:"flex",alignItems:"center",gap:6},children:[e.jsx("span",{...M(),title:"Drag to canvas",style:{cursor:"grab",color:"#666",fontSize:14,flexShrink:0,lineHeight:1,userSelect:"none"},children:"⠿"}),N?e.jsx("input",{autoFocus:!0,value:b,onChange:i=>m(i.target.value),onBlur:r,onKeyDown:i=>{i.key==="Enter"&&r(),i.key==="Escape"&&_(!1)},style:le}):e.jsx("span",{title:"Double-click to rename",onDoubleClick:()=>{m(t.name),_(!0)},style:{fontWeight:600,flex:1,cursor:"text",fontSize:13},children:t.name}),c&&e.jsx("button",{title:"Pending node-cache changes — click to reload",onClick:i=>{i.stopPropagation(),I()},style:{...W,color:"#f5a623",fontSize:14,animation:"immac-pulse 1.8s ease-in-out infinite"},children:e.jsx("i",{className:"pi pi-exclamation-circle"})}),e.jsx("button",{title:t.favorite?"Remove bookmark":"Bookmark",onClick:()=>y({...t,favorite:!t.favorite}),style:W,children:e.jsx("i",{className:t.favorite?"pi pi-bookmark-fill":"pi pi-bookmark"})}),e.jsx("button",{title:"Duplicate mix",onClick:C,style:{...W,color:"var(--p-text-muted-color, #888)"},children:e.jsx("i",{className:"pi pi-copy"})}),(()=>{const i=t.styles.some(l=>{var h;return l.enabled&&((h=s.find(E=>E.id===l.style_id))==null?void 0:h.value)});return e.jsx("button",{title:i?"Copy combined prompt":"No enabled styles with prompt text",onClick:l=>{l.stopPropagation(),n()},disabled:!i,style:{...W,color:x?"#6c6":"var(--p-text-muted-color, #888)",opacity:i?1:.35},children:e.jsx("i",{className:x?"pi pi-check":"pi pi-clipboard"})})})(),e.jsx("button",{title:"Delete mix",onClick:()=>R(!0),style:{...W,color:"var(--p-text-muted-color, #888)"},children:e.jsx("i",{className:"pi pi-trash"})})]}),k&&e.jsxs("div",{style:{position:"absolute",inset:0,background:"rgba(20,20,20,0.92)",display:"flex",flexDirection:"column",alignItems:"center",justifyContent:"center",gap:12,zIndex:10},children:[e.jsx("i",{className:"pi pi-trash",style:{fontSize:22,color:"#e55"}}),e.jsxs("span",{style:{fontSize:13,color:"#ddd",fontWeight:600},children:['Delete "',t.name,'"?']}),e.jsxs("div",{style:{display:"flex",gap:8},children:[e.jsx("button",{onClick:()=>R(!1),style:{...se},children:"Cancel"}),e.jsx("button",{onClick:f,style:{...se,background:"#c33",color:"#fff",borderColor:"#c33"},children:"Delete"})]})]}),e.jsxs("div",{style:{position:"relative",width:"100%",paddingBottom:"100%"},onMouseEnter:()=>F(!0),onMouseLeave:()=>F(!1),children:[e.jsx("div",{title:"Click or drop an image to set mix cover",onClick:()=>{var i;return(i=B.current)==null?void 0:i.click()},onDragOver:i=>i.preventDefault(),onDrop:i=>{i.preventDefault();const l=i.dataTransfer.files[0];l&&a(l)},style:{position:"absolute",inset:0,borderRadius:6,border:"1px dashed var(--p-surface-border, #555)",overflow:"hidden",cursor:"pointer",display:"flex",alignItems:"center",justifyContent:"center",background:"var(--p-surface-ground, #141414)",fontSize:11,color:"#888",flexShrink:0},children:v?"Uploading…":t.image_filename?e.jsx("img",{src:re(t.image_filename,t.image_updated_at),alt:t.name,style:{width:"100%",height:"100%",objectFit:"contain",transition:"transform 0.3s ease",transform:H?"scale(1.05)":"scale(1)"}}):"+ cover image"}),!v&&t.image_filename&&H&&e.jsx("button",{title:"View full size",onClick:i=>{i.stopPropagation(),u(!0)},style:be,children:e.jsx("i",{className:"pi pi-search-plus"})})]}),j&&t.image_filename&&e.jsx(ne,{src:re(t.image_filename,t.image_updated_at),alt:t.name,onClose:()=>u(!1)}),e.jsx("input",{ref:B,type:"file",accept:"image/*",style:{display:"none"},onChange:i=>{var h;const l=(h=i.target.files)==null?void 0:h[0];l&&a(l),i.target.value=""}}),e.jsxs("div",{style:{display:"flex",flexDirection:"column",gap:6,maxHeight:240,overflowY:"auto"},children:[t.styles.length===0&&e.jsx("div",{style:{fontSize:11,color:"#666"},children:"No styles added yet."}),t.styles.map((i,l)=>{const h=s.find(E=>E.id===i.style_id);return e.jsxs("div",{style:{display:"flex",alignItems:"center",gap:6},children:[e.jsx("button",{title:i.enabled?"Disable":"Enable",onClick:()=>z(l,{enabled:!i.enabled}),style:{...W,fontSize:11,padding:"2px 5px",border:"1px solid var(--p-surface-border, #555)",borderRadius:4,background:i.enabled?"var(--p-primary-color, #557755)":"transparent",color:i.enabled?"#fff":"#888",minWidth:32},children:i.enabled?"ON":"OFF"}),e.jsx("span",{style:{fontSize:12,flex:1,overflow:"hidden",textOverflow:"ellipsis",whiteSpace:"nowrap"},children:(h==null?void 0:h.name)??i.style_id}),e.jsx(pe,{value:i.weight,onChange:E=>z(l,{weight:E}),step:.01,width:64}),e.jsx("button",{title:"Remove from mix",onClick:()=>L(l),style:{...W,color:"#e55",fontSize:13},children:"✕"})]},i.style_id)})]}),e.jsxs("select",{value:"",disabled:d.length===0,onChange:i=>{i.target.value&&U(i.target.value)},style:{...le,cursor:d.length===0?"not-allowed":"pointer",opacity:d.length===0?.45:1},title:d.length===0?"All library styles are already in this mix":"Add a style to this mix",children:[e.jsx("option",{value:"",children:d.length===0?"— all styles already added —":"+ add style…"}),d.map(i=>e.jsx("option",{value:i.id,children:i.name},i.id))]})]})}const se={background:"transparent",border:"1px solid #666",borderRadius:5,color:"#ccc",cursor:"pointer",fontSize:12,padding:"4px 12px"},W={background:"none",border:"none",cursor:"pointer",fontSize:16,padding:"0 2px",color:"inherit",lineHeight:1,flexShrink:0},be={position:"absolute",top:6,left:6,background:"rgba(255,255,255,0.92)",border:"none",borderRadius:6,cursor:"pointer",fontSize:13,padding:"3px 6px",lineHeight:1,color:"#222",boxShadow:"0 1px 4px rgba(0,0,0,0.25)"},le={background:"var(--p-surface-ground, #141414)",border:"1px solid var(--p-surface-border, #444)",borderRadius:4,color:"inherit",padding:"2px 4px",fontSize:12,boxSizing:"border-box"};(()=>{if(document.getElementById("immac-pulse-style"))return;const t=document.createElement("style");t.id="immac-pulse-style",t.textContent=`@keyframes immac-pulse { 0%,100% { opacity:1; transform:scale(1); } 50% { opacity:0.55; transform:scale(1.15); } -}`,document.head.appendChild(t)})();function ve({style:t,onUpdate:s,onDelete:p,inCurrentMix:c,onAddToMix:g,onRemoveFromMix:y,isDirty:f,onRefreshCache:C}){const[I,D]=o.useState(!1),[N,_]=o.useState(!1),[b,m]=o.useState(t.name),[v,w]=o.useState(t.value),[j,u]=o.useState(!1),[k,R]=o.useState(!1),[H,F]=o.useState(!1),[x,S]=o.useState(!1),[O,T]=o.useState(!1),B=o.useRef(null),n=!!(g||y);function a(){t.value&&navigator.clipboard.writeText(t.value).then(()=>{T(!0),setTimeout(()=>T(!1),1500)})}function r(){D(!1),b.trim()!==t.name&&s({...t,name:b.trim()||t.name})}function z(){_(!1),v!==t.value&&s({...t,value:v})}async function L(d){if(d.type.startsWith("image/")){u(!0);try{const M=await ge(d);s({...t,image_filename:M,image_updated_at:Date.now()})}catch(M){console.error("[ImmacStyleMixer] Image upload failed",M)}finally{u(!1)}}}const U={draggable:!0,onDragStart:d=>{d.dataTransfer.setData("application/x-immac-style-id",t.id),d.dataTransfer.setData("application/x-immac-style-name",t.name),d.dataTransfer.effectAllowed="copy"},onDragEnd:d=>{var $,A,K,X,Y,G,J;const M=($=window.app)==null?void 0:$.canvas;if(!M)return;const i=M.canvas;if(!i||document.elementFromPoint(d.clientX,d.clientY)!==i)return;const l=M.convertEventToCanvasOffset(d),h=window.LiteGraph,E=h==null?void 0:h.createNode("StylePickImmacStyleMixer");if(!E)return;E.pos=l,(K=(A=window.app)==null?void 0:A.graph)==null||K.add(E);const P=(X=E.widgets)==null?void 0:X.find(q=>q.name==="style");P&&(P.value=t.name,(Y=P.callback)==null||Y.call(P,t.name,null,null,null,E)),(J=(G=window.app)==null?void 0:G.graph)==null||J.setDirtyCanvas(!0,!0)}};return e.jsxs("div",{style:{border:"1px solid var(--p-surface-border, #444)",borderRadius:8,padding:8,width:"100%",background:"var(--p-surface-section, #1e1e1e)",display:"flex",flexDirection:"column",gap:6,position:"relative",overflow:"hidden"},children:[e.jsxs("div",{style:{display:"flex",justifyContent:"space-between",alignItems:"center"},children:[e.jsx("span",{...U,title:"Drag to canvas",style:{cursor:"grab",color:"#666",fontSize:14,lineHeight:1,userSelect:"none",flexShrink:0},children:"⠿"}),e.jsx("button",{title:t.favorite?"Remove bookmark":"Bookmark",onClick:()=>s({...t,favorite:!t.favorite}),style:V,children:e.jsx("i",{className:t.favorite?"pi pi-bookmark-fill":"pi pi-bookmark"})}),e.jsxs("div",{style:{display:"flex",gap:2},children:[c?e.jsx("button",{title:"Remove from current mix",onClick:y,style:{...V,color:"#e66"},children:e.jsx("i",{className:"pi pi-minus"})}):e.jsx("button",{title:n?"Add to current mix":"No active mix",onClick:g,disabled:!n,style:{...V,color:"var(--p-text-muted-color, #888)",opacity:n?1:.35},children:e.jsx("i",{className:"pi pi-plus"})}),e.jsx("button",{title:t.value?"Copy prompt text":"No prompt text to copy",onClick:a,disabled:!t.value,style:{...V,color:O?"#6c6":"var(--p-text-muted-color, #888)",opacity:t.value?1:.35},children:e.jsx("i",{className:O?"pi pi-check":"pi pi-clipboard"})}),f&&e.jsx("button",{title:"Node combo lists may be stale — click to refresh",onClick:C,style:{...V,color:"#f5a623",fontSize:14,animation:"immac-pulse 1.8s ease-in-out infinite"},children:e.jsx("i",{className:"pi pi-exclamation-circle"})}),e.jsx("button",{title:"Delete style",onClick:()=>F(!0),style:{...V,color:"var(--p-text-muted-color, #888)"},children:e.jsx("i",{className:"pi pi-trash"})})]})]}),H&&e.jsxs("div",{style:{position:"absolute",inset:0,background:"rgba(20,20,20,0.92)",display:"flex",flexDirection:"column",alignItems:"center",justifyContent:"center",gap:12,zIndex:10},children:[e.jsx("i",{className:"pi pi-trash",style:{fontSize:22,color:"#e55"}}),e.jsxs("span",{style:{fontSize:13,color:"#ddd",fontWeight:600},children:['Delete "',t.name,'"?']}),e.jsxs("div",{style:{display:"flex",gap:8},children:[e.jsx("button",{onClick:()=>F(!1),style:{...ce},children:"Cancel"}),e.jsx("button",{onClick:p,style:{...ce,background:"#c33",color:"#fff",borderColor:"#c33"},children:"Delete"})]})]}),e.jsxs("div",{style:{position:"relative",width:"100%",paddingBottom:"100%"},onMouseEnter:()=>S(!0),onMouseLeave:()=>S(!1),children:[e.jsx("div",{title:"Click or drop to upload image",onClick:()=>{var d;return(d=B.current)==null?void 0:d.click()},onDragOver:d=>d.preventDefault(),onDrop:d=>{d.preventDefault();const M=d.dataTransfer.files[0];M&&L(M)},style:{position:"absolute",inset:0,borderRadius:6,border:"1px dashed var(--p-surface-border, #555)",overflow:"hidden",cursor:"pointer",display:"flex",alignItems:"center",justifyContent:"center",background:"var(--p-surface-ground, #141414)",fontSize:11,color:"#888"},children:j?"Uploading…":t.image_filename?e.jsx("img",{src:Q(t.image_filename,t.image_updated_at),alt:t.name,style:{width:"100%",height:"100%",objectFit:"contain",transition:"transform 0.3s ease",transform:x?"scale(1.05)":"scale(1)"}}):"+ image"}),!j&&t.image_filename&&x&&e.jsx("button",{title:"View full size",onClick:d=>{d.stopPropagation(),R(!0)},style:ke,children:e.jsx("i",{className:"pi pi-search-plus"})})]}),k&&t.image_filename&&e.jsx(ne,{src:Q(t.image_filename,t.image_updated_at),alt:t.name,onClose:()=>R(!1)}),e.jsx("input",{ref:B,type:"file",accept:"image/*",style:{display:"none"},onChange:d=>{var i;const M=(i=d.target.files)==null?void 0:i[0];M&&L(M),d.target.value=""}}),I?e.jsx("input",{autoFocus:!0,value:b,onChange:d=>m(d.target.value),onBlur:r,onKeyDown:d=>{d.key==="Enter"&&r(),d.key==="Escape"&&D(!1)},style:de}):e.jsx("div",{title:"Click to rename",onDoubleClick:()=>{m(t.name),D(!0)},style:{fontWeight:600,fontSize:13,cursor:"text",wordBreak:"break-word"},children:t.name}),N?e.jsx("textarea",{autoFocus:!0,value:v,onChange:d=>w(d.target.value),onBlur:z,onKeyDown:d=>{d.key==="Escape"&&_(!1)},rows:3,style:{...de,resize:"vertical"}}):e.jsx("div",{title:"Click to edit prompt text",onDoubleClick:()=>{w(t.value),_(!0)},style:{fontSize:11,color:"#aaa",cursor:"text",wordBreak:"break-word",minHeight:32},children:t.value||e.jsx("span",{style:{color:"#555"},children:"double-click to add prompt…"})})]})}const ce={background:"transparent",border:"1px solid #666",borderRadius:5,color:"#ccc",cursor:"pointer",fontSize:12,padding:"4px 12px"},V={background:"none",border:"none",cursor:"pointer",fontSize:16,padding:"0 2px",color:"inherit",lineHeight:1},ke={position:"absolute",top:6,left:6,background:"rgba(255,255,255,0.92)",border:"none",borderRadius:6,cursor:"pointer",fontSize:13,padding:"3px 6px",lineHeight:1,color:"#222",boxShadow:"0 1px 4px rgba(0,0,0,0.25)"},de={width:"100%",boxSizing:"border-box",background:"var(--p-surface-ground, #141414)",border:"1px solid var(--p-primary-color, #6c6)",borderRadius:4,color:"inherit",padding:"2px 4px",fontSize:12};function we({styles:t,onUpdate:s,onDelete:p,onAdd:c,currentMixStyleIds:g,onAddToMix:y,onRemoveFromMix:f,isDirty:C,onRefreshCache:I}){const[D,N]=o.useState(""),[_,b]=o.useState(!1);function m(){const u=D.trim();u&&(c(u),N(""),b(!1))}const v=t.filter(u=>u.favorite),w=t.filter(u=>!u.favorite),j=[...v,...w];return e.jsx("div",{style:{display:"flex",flexDirection:"column",gap:10},children:e.jsxs("div",{style:{display:"grid",gridTemplateColumns:"repeat(auto-fill, minmax(180px, 1fr))",gap:10},children:[j.map(u=>e.jsx(ve,{style:u,onUpdate:s,onDelete:()=>p(u.id),inCurrentMix:g==null?void 0:g.has(u.id),onAddToMix:y?()=>y(u.id):void 0,onRemoveFromMix:f?()=>f(u.id):void 0,isDirty:C,onRefreshCache:I},u.id)),_?e.jsxs("div",{style:{border:"1px dashed var(--p-primary-color, #6c6)",borderRadius:8,padding:10,display:"flex",flexDirection:"column",gap:8,background:"var(--p-surface-section, #1e1e1e)",flexShrink:0},children:[e.jsx("input",{autoFocus:!0,placeholder:"Style name…",value:D,onChange:u=>N(u.target.value),onKeyDown:u=>{u.key==="Enter"&&m(),u.key==="Escape"&&b(!1)},style:je}),e.jsxs("div",{style:{display:"flex",gap:6},children:[e.jsx("button",{onClick:m,style:ue,children:"Add"}),e.jsx("button",{onClick:()=>{b(!1),N("")},style:{...ue,background:"transparent",color:"#aaa"},children:"Cancel"})]})]}):e.jsx("button",{title:"Add a new style",onClick:()=>b(!0),style:{width:"100%",minHeight:80,border:"1px dashed var(--p-surface-border, #555)",borderRadius:8,background:"transparent",color:"#666",cursor:"pointer",fontSize:24},children:"+"})]})})}const je={width:"100%",boxSizing:"border-box",background:"var(--p-surface-ground, #141414)",border:"1px solid var(--p-primary-color, #6c6)",borderRadius:4,color:"inherit",padding:"4px 6px",fontSize:13},ue={flex:1,background:"var(--p-primary-color, #557755)",border:"none",borderRadius:4,color:"#fff",cursor:"pointer",padding:"4px 8px",fontSize:12};function Z(){return crypto.randomUUID()}function ee({title:t,children:s,defaultOpen:p=!0,grow:c=!0}){const[g,y]=o.useState(p);return e.jsxs("div",{style:{display:"flex",flexDirection:"column",flex:g&&c?"1 1 0":"0 0 auto",minHeight:0,borderBottom:"1px solid var(--p-surface-border, #333)"},children:[e.jsxs("button",{onClick:()=>y(f=>!f),style:{display:"flex",alignItems:"center",gap:6,background:"none",border:"none",cursor:"pointer",padding:"8px 0",width:"100%",textAlign:"left",color:"#888",flexShrink:0},children:[e.jsx("i",{className:`pi ${g?"pi-chevron-down":"pi-chevron-right"}`,style:{fontSize:10,flexShrink:0}}),e.jsx("span",{style:{fontWeight:700,fontSize:12,textTransform:"uppercase",letterSpacing:"0.08em"},children:t})]}),g&&e.jsx("div",{style:{flex:1,minHeight:0,overflowY:"auto",paddingBottom:8},children:s})]})}function ze(){const{data:t,loading:s,error:p,update:c,pendingRefresh:g,refreshNodes:y}=xe(),f=t.mixes.find(n=>n.id===t.current_mix_id)??null,[C,I]=o.useState(null),[D,N]=o.useState(null),[_,b]=o.useState(!1),m=o.useCallback(n=>{const a={id:Z(),name:n,value:"",favorite:!1,image_filename:null};c(r=>({...r,styles:[...r.styles,a]}))},[c]),v=o.useCallback(n=>{c(a=>({...a,styles:a.styles.map(r=>r.id===n.id?n:r)}))},[c]),w=o.useCallback(n=>{c(a=>({...a,styles:a.styles.filter(r=>r.id!==n),mixes:a.mixes.map(r=>({...r,styles:r.styles.filter(z=>z.style_id!==n)}))}))},[c]),j=o.useCallback(n=>{const a={...n,id:Z(),name:`${n.name} (copy)`,favorite:!1};c(r=>{const z=r.mixes.findIndex(U=>U.id===n.id),L=[...r.mixes];return L.splice(z+1,0,a),{...r,mixes:L}})},[c]),u=o.useCallback(()=>{const n={id:Z(),name:`Mix ${t.mixes.length+1}`,favorite:!1,image_filename:null,styles:[]};c(a=>({...a,mixes:[...a.mixes,n]}))},[t.mixes.length,c]),k=o.useCallback((n,a)=>{c(r=>({...r,mixes:r.mixes.map(z=>z.id===n.id?n:z)}),a)},[c]),R=o.useCallback(n=>{f&&(f.styles.some(a=>a.style_id===n)||k({...f,styles:[...f.styles,{style_id:n,weight:1,enabled:!0}]},{silent:!0}))},[f,k]),H=o.useCallback(n=>{f&&k({...f,styles:f.styles.filter(a=>a.style_id!==n)},{silent:!0})},[f,k]),F=o.useCallback(n=>{c(a=>({...a,mixes:a.mixes.filter(r=>r.id!==n),current_mix_id:a.current_mix_id===n?null:a.current_mix_id}))},[c]),x=o.useCallback(n=>{c(a=>({...a,current_mix_id:n}))},[c]),S=o.useCallback((n,a,r)=>{f&&k({...f,styles:f.styles.map(z=>z.style_id===n?{...z,...a}:z)},r)},[f,k]);if(s)return e.jsx("div",{style:te,children:"Loading…"});if(p)return e.jsxs("div",{style:{...te,color:"#e55"},children:["Error: ",p]});const O=t.mixes.filter(n=>n.favorite),T=t.mixes.filter(n=>!n.favorite),B=[...O,...T];return e.jsxs("div",{style:te,children:[e.jsx(ee,{title:"Current Mix",grow:!1,children:f?e.jsxs("div",{style:{display:"flex",flexDirection:"column",gap:6},children:[e.jsx("div",{style:{fontWeight:600,fontSize:14},children:f.name}),e.jsxs("div",{style:{display:"flex",gap:8,flexWrap:"nowrap",overflowX:"auto",borderRadius:6,border:`1px dashed ${_?"#88aaff":"transparent"}`,background:_?"rgba(100,130,255,0.07)":"transparent",padding:_?4:0,transition:"border-color 0.15s, background 0.15s, padding 0.1s",minHeight:40,paddingBottom:6},onDragOver:n=>{n.dataTransfer.types.includes("application/x-immac-style-id")&&(n.preventDefault(),n.dataTransfer.dropEffect="copy",b(!0))},onDragLeave:n=>{n.currentTarget.contains(n.relatedTarget)||b(!1)},onDrop:n=>{b(!1);const a=n.dataTransfer.getData("application/x-immac-style-id");a&&(n.preventDefault(),R(a))},children:[f.styles.length===0&&!_&&e.jsx("div",{style:{fontSize:12,color:"#666"},children:"No styles in this mix yet."}),f.styles.map(n=>{const a=t.styles.find(r=>r.id===n.style_id);return e.jsxs("div",{onMouseEnter:()=>N(n.style_id),onMouseLeave:()=>N(null),onDoubleClick:()=>S(n.style_id,{enabled:!n.enabled},{silent:!0}),style:{border:`1px solid ${n.enabled?"var(--p-primary-color, #6c6)":"var(--p-surface-border, #444)"}`,borderRadius:6,overflow:"hidden",background:"var(--p-surface-section, #1e1e1e)",opacity:n.enabled?1:.45,fontSize:12,display:"flex",flexDirection:"column",alignItems:"center",width:180,flexShrink:0},children:[e.jsxs("div",{style:{position:"relative",width:"100%",paddingBottom:"100%"},children:[e.jsx("div",{style:{position:"absolute",inset:0,background:"var(--p-surface-ground, #141414)",display:"flex",alignItems:"center",justifyContent:"center"},children:a!=null&&a.image_filename?e.jsx("img",{src:Q(a.image_filename,a.image_updated_at),alt:a.name,style:{width:"100%",height:"100%",objectFit:"contain",display:"block",transition:"transform 0.3s ease",transform:D===n.style_id?"scale(1.05)":"scale(1)"}}):e.jsx("i",{className:"pi pi-image",style:{fontSize:20,color:"#555"}})}),(a==null?void 0:a.image_filename)&&D===n.style_id&&e.jsx("button",{title:"View full size",onClick:()=>I(Q(a.image_filename,a.image_updated_at)),style:{position:"absolute",top:6,left:6,background:"rgba(255,255,255,0.92)",border:"none",borderRadius:6,cursor:"pointer",fontSize:13,padding:"3px 6px",lineHeight:1,color:"#222",boxShadow:"0 1px 4px rgba(0,0,0,0.25)"},children:e.jsx("i",{className:"pi pi-search-plus"})})]}),e.jsxs("div",{style:{padding:"4px 6px",width:"100%",boxSizing:"border-box",display:"flex",flexDirection:"column",gap:4},children:[e.jsx("div",{style:{fontWeight:600,whiteSpace:"nowrap",overflow:"hidden",textOverflow:"ellipsis",textAlign:"center",fontSize:12},children:(a==null?void 0:a.name)??"?"}),e.jsxs("div",{style:{display:"flex",alignItems:"center",gap:4},children:[e.jsx("button",{title:n.enabled?"Disable style":"Enable style",onClick:r=>{r.stopPropagation(),S(n.style_id,{enabled:!n.enabled},{silent:!0})},style:{background:n.enabled?"var(--p-primary-color, #557755)":"transparent",border:"1px solid var(--p-surface-border, #555)",borderRadius:4,color:n.enabled?"#fff":"#888",cursor:"pointer",fontSize:10,padding:"1px 4px",lineHeight:1,flexShrink:0},children:n.enabled?"ON":"OFF"}),e.jsx("div",{style:{flex:1,minWidth:0},onClick:r=>r.stopPropagation(),children:e.jsx(pe,{value:n.weight,onChange:r=>S(n.style_id,{weight:r},{silent:!0}),step:.01,width:"100%"})}),e.jsx("button",{title:"Remove from mix",onClick:r=>{r.stopPropagation(),H(n.style_id)},style:{background:"none",border:"none",cursor:"pointer",color:"#e66",fontSize:13,padding:"0 2px",lineHeight:1,flexShrink:0},children:"✕"})]})]})]},n.style_id)})]})]}):e.jsx("div",{style:{fontSize:12,color:"#666"},children:"No mix active — select one from Mixes below."})}),e.jsx(ee,{title:"Mixes",children:e.jsxs("div",{style:{display:"flex",flexDirection:"row",flexWrap:"wrap",gap:10,paddingBottom:4},children:[B.map(n=>e.jsx("div",{style:{flex:"0 0 280px"},children:e.jsx(ye,{mix:n,styles:t.styles,isActive:n.id===t.current_mix_id,isDirty:g,onActivate:()=>x(n.id),onUpdate:k,onDelete:()=>F(n.id),onDuplicate:()=>j(n),onRefreshCache:y})},n.id)),e.jsx("button",{title:"Add a new mix",onClick:u,style:{flex:"0 0 120px",minHeight:80,border:"1px dashed var(--p-surface-border, #555)",borderRadius:8,background:"transparent",color:"#666",cursor:"pointer",fontSize:22},children:"+"})]})}),e.jsx(ee,{title:"Styles",children:e.jsx(we,{styles:t.styles,onUpdate:v,onDelete:w,onAdd:m,currentMixStyleIds:f?new Set(f.styles.map(n=>n.style_id)):void 0,onAddToMix:f?R:void 0,onRemoveFromMix:f?H:void 0,isDirty:g,onRefreshCache:y})}),C&&e.jsx(ne,{src:C,onClose:()=>I(null)})]})}const te={position:"absolute",inset:0,padding:"0 12px",display:"flex",flexDirection:"column",overflow:"hidden",color:"var(--p-text-color, #eee)",fontSize:13,boxSizing:"border-box"};export{ze as default}; +}`,document.head.appendChild(t)})();function ve({style:t,onUpdate:s,onDelete:p,inCurrentMix:c,onAddToMix:g,onRemoveFromMix:y,isDirty:f,onRefreshCache:C}){const[I,D]=o.useState(!1),[N,_]=o.useState(!1),[b,m]=o.useState(t.name),[v,w]=o.useState(t.value),[j,u]=o.useState(!1),[k,R]=o.useState(!1),[H,F]=o.useState(!1),[x,S]=o.useState(!1),[O,T]=o.useState(!1),B=o.useRef(null),n=!!(g||y);function a(){t.value&&navigator.clipboard.writeText(t.value).then(()=>{T(!0),setTimeout(()=>T(!1),1500)})}function r(){D(!1),b.trim()!==t.name&&s({...t,name:b.trim()||t.name})}function z(){_(!1),v!==t.value&&s({...t,value:v})}async function L(d){if(d.type.startsWith("image/")){u(!0);try{const M=await ge(d);s({...t,image_filename:M,image_updated_at:Date.now()})}catch(M){console.error("[ImmacStyleMixer] Image upload failed",M)}finally{u(!1)}}}const U={draggable:!0,onDragStart:d=>{d.dataTransfer.setData("application/x-immac-style-id",t.id),d.dataTransfer.setData("application/x-immac-style-name",t.name),d.dataTransfer.effectAllowed="copy"},onDragEnd:d=>{var $,A,K,X,Y,G,J;const M=($=window.app)==null?void 0:$.canvas;if(!M)return;const i=M.canvas;if(!i||document.elementFromPoint(d.clientX,d.clientY)!==i)return;const l=M.convertEventToCanvasOffset(d),h=window.LiteGraph,E=h==null?void 0:h.createNode("PickStyleImmacStyleMixer");if(!E)return;E.pos=l,(K=(A=window.app)==null?void 0:A.graph)==null||K.add(E);const P=(X=E.widgets)==null?void 0:X.find(q=>q.name==="style");P&&(P.value=t.name,(Y=P.callback)==null||Y.call(P,t.name,null,null,null,E)),(J=(G=window.app)==null?void 0:G.graph)==null||J.setDirtyCanvas(!0,!0)}};return e.jsxs("div",{style:{border:"1px solid var(--p-surface-border, #444)",borderRadius:8,padding:8,width:"100%",background:"var(--p-surface-section, #1e1e1e)",display:"flex",flexDirection:"column",gap:6,position:"relative",overflow:"hidden"},children:[e.jsxs("div",{style:{display:"flex",justifyContent:"space-between",alignItems:"center"},children:[e.jsx("span",{...U,title:"Drag to canvas",style:{cursor:"grab",color:"#666",fontSize:14,lineHeight:1,userSelect:"none",flexShrink:0},children:"⠿"}),e.jsx("button",{title:t.favorite?"Remove bookmark":"Bookmark",onClick:()=>s({...t,favorite:!t.favorite}),style:V,children:e.jsx("i",{className:t.favorite?"pi pi-bookmark-fill":"pi pi-bookmark"})}),e.jsxs("div",{style:{display:"flex",gap:2},children:[c?e.jsx("button",{title:"Remove from current mix",onClick:y,style:{...V,color:"#e66"},children:e.jsx("i",{className:"pi pi-minus"})}):e.jsx("button",{title:n?"Add to current mix":"No active mix",onClick:g,disabled:!n,style:{...V,color:"var(--p-text-muted-color, #888)",opacity:n?1:.35},children:e.jsx("i",{className:"pi pi-plus"})}),e.jsx("button",{title:t.value?"Copy prompt text":"No prompt text to copy",onClick:a,disabled:!t.value,style:{...V,color:O?"#6c6":"var(--p-text-muted-color, #888)",opacity:t.value?1:.35},children:e.jsx("i",{className:O?"pi pi-check":"pi pi-clipboard"})}),f&&e.jsx("button",{title:"Node combo lists may be stale — click to refresh",onClick:C,style:{...V,color:"#f5a623",fontSize:14,animation:"immac-pulse 1.8s ease-in-out infinite"},children:e.jsx("i",{className:"pi pi-exclamation-circle"})}),e.jsx("button",{title:"Delete style",onClick:()=>F(!0),style:{...V,color:"var(--p-text-muted-color, #888)"},children:e.jsx("i",{className:"pi pi-trash"})})]})]}),H&&e.jsxs("div",{style:{position:"absolute",inset:0,background:"rgba(20,20,20,0.92)",display:"flex",flexDirection:"column",alignItems:"center",justifyContent:"center",gap:12,zIndex:10},children:[e.jsx("i",{className:"pi pi-trash",style:{fontSize:22,color:"#e55"}}),e.jsxs("span",{style:{fontSize:13,color:"#ddd",fontWeight:600},children:['Delete "',t.name,'"?']}),e.jsxs("div",{style:{display:"flex",gap:8},children:[e.jsx("button",{onClick:()=>F(!1),style:{...ce},children:"Cancel"}),e.jsx("button",{onClick:p,style:{...ce,background:"#c33",color:"#fff",borderColor:"#c33"},children:"Delete"})]})]}),e.jsxs("div",{style:{position:"relative",width:"100%",paddingBottom:"100%"},onMouseEnter:()=>S(!0),onMouseLeave:()=>S(!1),children:[e.jsx("div",{title:"Click or drop to upload image",onClick:()=>{var d;return(d=B.current)==null?void 0:d.click()},onDragOver:d=>d.preventDefault(),onDrop:d=>{d.preventDefault();const M=d.dataTransfer.files[0];M&&L(M)},style:{position:"absolute",inset:0,borderRadius:6,border:"1px dashed var(--p-surface-border, #555)",overflow:"hidden",cursor:"pointer",display:"flex",alignItems:"center",justifyContent:"center",background:"var(--p-surface-ground, #141414)",fontSize:11,color:"#888"},children:j?"Uploading…":t.image_filename?e.jsx("img",{src:Q(t.image_filename,t.image_updated_at),alt:t.name,style:{width:"100%",height:"100%",objectFit:"contain",transition:"transform 0.3s ease",transform:x?"scale(1.05)":"scale(1)"}}):"+ image"}),!j&&t.image_filename&&x&&e.jsx("button",{title:"View full size",onClick:d=>{d.stopPropagation(),R(!0)},style:ke,children:e.jsx("i",{className:"pi pi-search-plus"})})]}),k&&t.image_filename&&e.jsx(ne,{src:Q(t.image_filename,t.image_updated_at),alt:t.name,onClose:()=>R(!1)}),e.jsx("input",{ref:B,type:"file",accept:"image/*",style:{display:"none"},onChange:d=>{var i;const M=(i=d.target.files)==null?void 0:i[0];M&&L(M),d.target.value=""}}),I?e.jsx("input",{autoFocus:!0,value:b,onChange:d=>m(d.target.value),onBlur:r,onKeyDown:d=>{d.key==="Enter"&&r(),d.key==="Escape"&&D(!1)},style:de}):e.jsx("div",{title:"Click to rename",onDoubleClick:()=>{m(t.name),D(!0)},style:{fontWeight:600,fontSize:13,cursor:"text",wordBreak:"break-word"},children:t.name}),N?e.jsx("textarea",{autoFocus:!0,value:v,onChange:d=>w(d.target.value),onBlur:z,onKeyDown:d=>{d.key==="Escape"&&_(!1)},rows:3,style:{...de,resize:"vertical"}}):e.jsx("div",{title:"Click to edit prompt text",onDoubleClick:()=>{w(t.value),_(!0)},style:{fontSize:11,color:"#aaa",cursor:"text",wordBreak:"break-word",minHeight:32},children:t.value||e.jsx("span",{style:{color:"#555"},children:"double-click to add prompt…"})})]})}const ce={background:"transparent",border:"1px solid #666",borderRadius:5,color:"#ccc",cursor:"pointer",fontSize:12,padding:"4px 12px"},V={background:"none",border:"none",cursor:"pointer",fontSize:16,padding:"0 2px",color:"inherit",lineHeight:1},ke={position:"absolute",top:6,left:6,background:"rgba(255,255,255,0.92)",border:"none",borderRadius:6,cursor:"pointer",fontSize:13,padding:"3px 6px",lineHeight:1,color:"#222",boxShadow:"0 1px 4px rgba(0,0,0,0.25)"},de={width:"100%",boxSizing:"border-box",background:"var(--p-surface-ground, #141414)",border:"1px solid var(--p-primary-color, #6c6)",borderRadius:4,color:"inherit",padding:"2px 4px",fontSize:12};function we({styles:t,onUpdate:s,onDelete:p,onAdd:c,currentMixStyleIds:g,onAddToMix:y,onRemoveFromMix:f,isDirty:C,onRefreshCache:I}){const[D,N]=o.useState(""),[_,b]=o.useState(!1);function m(){const u=D.trim();u&&(c(u),N(""),b(!1))}const v=t.filter(u=>u.favorite),w=t.filter(u=>!u.favorite),j=[...v,...w];return e.jsx("div",{style:{display:"flex",flexDirection:"column",gap:10},children:e.jsxs("div",{style:{display:"grid",gridTemplateColumns:"repeat(auto-fill, minmax(180px, 1fr))",gap:10},children:[j.map(u=>e.jsx(ve,{style:u,onUpdate:s,onDelete:()=>p(u.id),inCurrentMix:g==null?void 0:g.has(u.id),onAddToMix:y?()=>y(u.id):void 0,onRemoveFromMix:f?()=>f(u.id):void 0,isDirty:C,onRefreshCache:I},u.id)),_?e.jsxs("div",{style:{border:"1px dashed var(--p-primary-color, #6c6)",borderRadius:8,padding:10,display:"flex",flexDirection:"column",gap:8,background:"var(--p-surface-section, #1e1e1e)",flexShrink:0},children:[e.jsx("input",{autoFocus:!0,placeholder:"Style name…",value:D,onChange:u=>N(u.target.value),onKeyDown:u=>{u.key==="Enter"&&m(),u.key==="Escape"&&b(!1)},style:je}),e.jsxs("div",{style:{display:"flex",gap:6},children:[e.jsx("button",{onClick:m,style:ue,children:"Add"}),e.jsx("button",{onClick:()=>{b(!1),N("")},style:{...ue,background:"transparent",color:"#aaa"},children:"Cancel"})]})]}):e.jsx("button",{title:"Add a new style",onClick:()=>b(!0),style:{width:"100%",minHeight:80,border:"1px dashed var(--p-surface-border, #555)",borderRadius:8,background:"transparent",color:"#666",cursor:"pointer",fontSize:24},children:"+"})]})})}const je={width:"100%",boxSizing:"border-box",background:"var(--p-surface-ground, #141414)",border:"1px solid var(--p-primary-color, #6c6)",borderRadius:4,color:"inherit",padding:"4px 6px",fontSize:13},ue={flex:1,background:"var(--p-primary-color, #557755)",border:"none",borderRadius:4,color:"#fff",cursor:"pointer",padding:"4px 8px",fontSize:12};function Z(){return crypto.randomUUID()}function ee({title:t,children:s,defaultOpen:p=!0,grow:c=!0}){const[g,y]=o.useState(p);return e.jsxs("div",{style:{display:"flex",flexDirection:"column",flex:g&&c?"1 1 0":"0 0 auto",minHeight:0,borderBottom:"1px solid var(--p-surface-border, #333)"},children:[e.jsxs("button",{onClick:()=>y(f=>!f),style:{display:"flex",alignItems:"center",gap:6,background:"none",border:"none",cursor:"pointer",padding:"8px 0",width:"100%",textAlign:"left",color:"#888",flexShrink:0},children:[e.jsx("i",{className:`pi ${g?"pi-chevron-down":"pi-chevron-right"}`,style:{fontSize:10,flexShrink:0}}),e.jsx("span",{style:{fontWeight:700,fontSize:12,textTransform:"uppercase",letterSpacing:"0.08em"},children:t})]}),g&&e.jsx("div",{style:{flex:1,minHeight:0,overflowY:"auto",paddingBottom:8},children:s})]})}function ze(){const{data:t,loading:s,error:p,update:c,pendingRefresh:g,refreshNodes:y}=xe(),f=t.mixes.find(n=>n.id===t.current_mix_id)??null,[C,I]=o.useState(null),[D,N]=o.useState(null),[_,b]=o.useState(!1),m=o.useCallback(n=>{const a={id:Z(),name:n,value:"",favorite:!1,image_filename:null};c(r=>({...r,styles:[...r.styles,a]}))},[c]),v=o.useCallback(n=>{c(a=>({...a,styles:a.styles.map(r=>r.id===n.id?n:r)}))},[c]),w=o.useCallback(n=>{c(a=>({...a,styles:a.styles.filter(r=>r.id!==n),mixes:a.mixes.map(r=>({...r,styles:r.styles.filter(z=>z.style_id!==n)}))}))},[c]),j=o.useCallback(n=>{const a={...n,id:Z(),name:`${n.name} (copy)`,favorite:!1};c(r=>{const z=r.mixes.findIndex(U=>U.id===n.id),L=[...r.mixes];return L.splice(z+1,0,a),{...r,mixes:L}})},[c]),u=o.useCallback(()=>{const n={id:Z(),name:`Mix ${t.mixes.length+1}`,favorite:!1,image_filename:null,styles:[]};c(a=>({...a,mixes:[...a.mixes,n]}))},[t.mixes.length,c]),k=o.useCallback((n,a)=>{c(r=>({...r,mixes:r.mixes.map(z=>z.id===n.id?n:z)}),a)},[c]),R=o.useCallback(n=>{f&&(f.styles.some(a=>a.style_id===n)||k({...f,styles:[...f.styles,{style_id:n,weight:1,enabled:!0}]},{silent:!0}))},[f,k]),H=o.useCallback(n=>{f&&k({...f,styles:f.styles.filter(a=>a.style_id!==n)},{silent:!0})},[f,k]),F=o.useCallback(n=>{c(a=>({...a,mixes:a.mixes.filter(r=>r.id!==n),current_mix_id:a.current_mix_id===n?null:a.current_mix_id}))},[c]),x=o.useCallback(n=>{c(a=>({...a,current_mix_id:n}))},[c]),S=o.useCallback((n,a,r)=>{f&&k({...f,styles:f.styles.map(z=>z.style_id===n?{...z,...a}:z)},r)},[f,k]);if(s)return e.jsx("div",{style:te,children:"Loading…"});if(p)return e.jsxs("div",{style:{...te,color:"#e55"},children:["Error: ",p]});const O=t.mixes.filter(n=>n.favorite),T=t.mixes.filter(n=>!n.favorite),B=[...O,...T];return e.jsxs("div",{style:te,children:[e.jsx(ee,{title:"Current Mix",grow:!1,children:f?e.jsxs("div",{style:{display:"flex",flexDirection:"column",gap:6},children:[e.jsx("div",{style:{fontWeight:600,fontSize:14},children:f.name}),e.jsxs("div",{style:{display:"flex",gap:8,flexWrap:"nowrap",overflowX:"auto",borderRadius:6,border:`1px dashed ${_?"#88aaff":"transparent"}`,background:_?"rgba(100,130,255,0.07)":"transparent",padding:_?4:0,transition:"border-color 0.15s, background 0.15s, padding 0.1s",minHeight:40,paddingBottom:6},onDragOver:n=>{n.dataTransfer.types.includes("application/x-immac-style-id")&&(n.preventDefault(),n.dataTransfer.dropEffect="copy",b(!0))},onDragLeave:n=>{n.currentTarget.contains(n.relatedTarget)||b(!1)},onDrop:n=>{b(!1);const a=n.dataTransfer.getData("application/x-immac-style-id");a&&(n.preventDefault(),R(a))},children:[f.styles.length===0&&!_&&e.jsx("div",{style:{fontSize:12,color:"#666"},children:"No styles in this mix yet."}),f.styles.map(n=>{const a=t.styles.find(r=>r.id===n.style_id);return e.jsxs("div",{onMouseEnter:()=>N(n.style_id),onMouseLeave:()=>N(null),onDoubleClick:()=>S(n.style_id,{enabled:!n.enabled},{silent:!0}),style:{border:`1px solid ${n.enabled?"var(--p-primary-color, #6c6)":"var(--p-surface-border, #444)"}`,borderRadius:6,overflow:"hidden",background:"var(--p-surface-section, #1e1e1e)",opacity:n.enabled?1:.45,fontSize:12,display:"flex",flexDirection:"column",alignItems:"center",width:180,flexShrink:0},children:[e.jsxs("div",{style:{position:"relative",width:"100%",paddingBottom:"100%"},children:[e.jsx("div",{style:{position:"absolute",inset:0,background:"var(--p-surface-ground, #141414)",display:"flex",alignItems:"center",justifyContent:"center"},children:a!=null&&a.image_filename?e.jsx("img",{src:Q(a.image_filename,a.image_updated_at),alt:a.name,style:{width:"100%",height:"100%",objectFit:"contain",display:"block",transition:"transform 0.3s ease",transform:D===n.style_id?"scale(1.05)":"scale(1)"}}):e.jsx("i",{className:"pi pi-image",style:{fontSize:20,color:"#555"}})}),(a==null?void 0:a.image_filename)&&D===n.style_id&&e.jsx("button",{title:"View full size",onClick:()=>I(Q(a.image_filename,a.image_updated_at)),style:{position:"absolute",top:6,left:6,background:"rgba(255,255,255,0.92)",border:"none",borderRadius:6,cursor:"pointer",fontSize:13,padding:"3px 6px",lineHeight:1,color:"#222",boxShadow:"0 1px 4px rgba(0,0,0,0.25)"},children:e.jsx("i",{className:"pi pi-search-plus"})})]}),e.jsxs("div",{style:{padding:"4px 6px",width:"100%",boxSizing:"border-box",display:"flex",flexDirection:"column",gap:4},children:[e.jsx("div",{style:{fontWeight:600,whiteSpace:"nowrap",overflow:"hidden",textOverflow:"ellipsis",textAlign:"center",fontSize:12},children:(a==null?void 0:a.name)??"?"}),e.jsxs("div",{style:{display:"flex",alignItems:"center",gap:4},children:[e.jsx("button",{title:n.enabled?"Disable style":"Enable style",onClick:r=>{r.stopPropagation(),S(n.style_id,{enabled:!n.enabled},{silent:!0})},style:{background:n.enabled?"var(--p-primary-color, #557755)":"transparent",border:"1px solid var(--p-surface-border, #555)",borderRadius:4,color:n.enabled?"#fff":"#888",cursor:"pointer",fontSize:10,padding:"1px 4px",lineHeight:1,flexShrink:0},children:n.enabled?"ON":"OFF"}),e.jsx("div",{style:{flex:1,minWidth:0},onClick:r=>r.stopPropagation(),children:e.jsx(pe,{value:n.weight,onChange:r=>S(n.style_id,{weight:r},{silent:!0}),step:.01,width:"100%"})}),e.jsx("button",{title:"Remove from mix",onClick:r=>{r.stopPropagation(),H(n.style_id)},style:{background:"none",border:"none",cursor:"pointer",color:"#e66",fontSize:13,padding:"0 2px",lineHeight:1,flexShrink:0},children:"✕"})]})]})]},n.style_id)})]})]}):e.jsx("div",{style:{fontSize:12,color:"#666"},children:"No mix active — select one from Mixes below."})}),e.jsx(ee,{title:"Mixes",children:e.jsxs("div",{style:{display:"flex",flexDirection:"row",flexWrap:"wrap",gap:10,paddingBottom:4},children:[B.map(n=>e.jsx("div",{style:{flex:"0 0 280px"},children:e.jsx(ye,{mix:n,styles:t.styles,isActive:n.id===t.current_mix_id,isDirty:g,onActivate:()=>x(n.id),onUpdate:k,onDelete:()=>F(n.id),onDuplicate:()=>j(n),onRefreshCache:y})},n.id)),e.jsx("button",{title:"Add a new mix",onClick:u,style:{flex:"0 0 120px",minHeight:80,border:"1px dashed var(--p-surface-border, #555)",borderRadius:8,background:"transparent",color:"#666",cursor:"pointer",fontSize:22},children:"+"})]})}),e.jsx(ee,{title:"Styles",children:e.jsx(we,{styles:t.styles,onUpdate:v,onDelete:w,onAdd:m,currentMixStyleIds:f?new Set(f.styles.map(n=>n.style_id)):void 0,onAddToMix:f?R:void 0,onRemoveFromMix:f?H:void 0,isDirty:g,onRefreshCache:y})}),C&&e.jsx(ne,{src:C,onClose:()=>I(null)})]})}const te={position:"absolute",inset:0,padding:"0 12px",display:"flex",flexDirection:"column",overflow:"hidden",color:"var(--p-text-color, #eee)",fontSize:13,boxSizing:"border-box"};export{ze as default}; diff --git a/dist/immac_style_mixer/main.js b/dist/immac_style_mixer/main.js index cc942fe..44e9ad7 100644 --- a/dist/immac_style_mixer/main.js +++ b/dist/immac_style_mixer/main.js @@ -1,4 +1,4 @@ -const __vite__mapDeps=(i,m=__vite__mapDeps,d=(m.f||(m.f=["immac_style_mixer/StyleMixerPanel-Dt3_8kWe.js","immac_style_mixer/vendor-DPfS0z4g.js"])))=>i.map(i=>d[i]); +const __vite__mapDeps=(i,m=__vite__mapDeps,d=(m.f||(m.f=["immac_style_mixer/StyleMixerPanel-CMNQxf7y.js","immac_style_mixer/vendor-DPfS0z4g.js"])))=>i.map(i=>d[i]); import{r as T,a as A,R as M}from"./vendor-DPfS0z4g.js";import{app as E}from"/scripts/app.js";import{api as B}from"/scripts/api.js";const L="modulepreload",N=function(x){return"/"+x},O={},D=function(f,b,h){let g=Promise.resolve();if(b&&b.length>0){document.getElementsByTagName("link");const t=document.querySelector("meta[property=csp-nonce]"),s=(t==null?void 0:t.nonce)||(t==null?void 0:t.getAttribute("nonce"));g=Promise.allSettled(b.map(r=>{if(r=N(r),r in O)return;O[r]=!0;const i=r.endsWith(".css"),c=i?'[rel="stylesheet"]':"";if(document.querySelector(`link[href="${r}"]${c}`))return;const a=document.createElement("link");if(a.rel=i?"stylesheet":L,i||(a.as="script"),a.crossOrigin="",a.href=r,s&&a.setAttribute("nonce",s),document.head.appendChild(a),i)return new Promise((l,m)=>{a.addEventListener("load",l),a.addEventListener("error",()=>m(new Error(`Unable to preload CSS for ${r}`)))})}))}function e(t){const s=new Event("vite:preloadError",{cancelable:!0});if(s.payload=t,window.dispatchEvent(s),!s.defaultPrevented)throw t}return g.then(t=>{for(const s of t||[])s.status==="rejected"&&e(s.reason);return f().catch(e)})};var z={exports:{}},$={};/** * @license React * react-jsx-runtime.production.min.js @@ -7,4 +7,4 @@ import{r as T,a as A,R as M}from"./vendor-DPfS0z4g.js";import{app as E}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 F=T,Z=Symbol.for("react.element"),J=Symbol.for("react.fragment"),H=Object.prototype.hasOwnProperty,W=F.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.ReactCurrentOwner,q={key:!0,ref:!0,__self:!0,__source:!0};function U(x,f,b){var h,g={},e=null,t=null;b!==void 0&&(e=""+b),f.key!==void 0&&(e=""+f.key),f.ref!==void 0&&(t=f.ref);for(h in f)H.call(f,h)&&!q.hasOwnProperty(h)&&(g[h]=f[h]);if(x&&x.defaultProps)for(h in f=x.defaultProps,f)g[h]===void 0&&(g[h]=f[h]);return{$$typeof:Z,type:x,key:e,ref:t,props:g,_owner:W.current}}$.Fragment=J;$.jsx=U;$.jsxs=U;z.exports=$;var R=z.exports,I={},j=A;I.createRoot=j.createRoot,I.hydrateRoot=j.hydrateRoot;const V=M.lazy(()=>D(()=>import("./StyleMixerPanel-Dt3_8kWe.js"),__vite__mapDeps([0,1])));async function G(){E.extensionManager.registerSidebarTab({id:"immac-style-mixer",icon:"pi pi-palette",title:"Style Mixer",tooltip:"Immac Style Mixer",type:"custom",render:e=>{const t=document.createElement("div");t.id="immac-style-mixer-root",t.style.position="absolute",t.style.inset="0",t.style.overflow="hidden",e.style.position="relative",e.style.height="100%",e.appendChild(t),I.createRoot(t).render(R.jsx(M.StrictMode,{children:R.jsx(T.Suspense,{fallback:R.jsx("div",{style:{padding:"1rem"},children:"Loading…"}),children:R.jsx(V,{})})}))}});const x="/immac_style_mixer/api/data";async function f(){const e=await fetch(x);if(!e.ok){alert(`Backup failed: HTTP ${e.status}`);return}const t=await e.json(),s=new Blob([JSON.stringify(t,null,2)],{type:"application/json"}),r=URL.createObjectURL(s),i=document.createElement("a");i.href=r,i.download=`style_mixer_backup_${new Date().toISOString().slice(0,10)}.json`,i.click(),URL.revokeObjectURL(r)}function b(){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 r;const t=(r=e.files)==null?void 0:r[0];if(!t)return;if(t.name.endsWith(".zip")||t.type==="application/zip")t.arrayBuffer().then(async i=>{var c,a;try{const l=await fetch("/immac_style_mixer/api/restore.zip",{method:"POST",headers:{"Content-Type":"application/zip"},body:i});if(!l.ok){const d=await l.text();throw new Error(`HTTP ${l.status}: ${d.slice(0,300)}`)}const m=await l.json();try{(a=(c=E)==null?void 0:c.toast)==null||a.add({severity:"success",summary:"Style Mixer",detail:`ZIP restored: ${m.styles} styles, ${m.mixes} mixes, ${m.images} images. Reload the Style Mixer panel to see changes.`,life:6e3})}catch{}}catch(l){alert(`Failed to restore ZIP backup: ${l.message}`)}}).catch(i=>alert(`Could not read file: ${i.message}`));else{const i=new FileReader;i.onload=async c=>{var a,l,m;try{const d=JSON.parse((a=c.target)==null?void 0:a.result);if(!Array.isArray(d.styles)||!Array.isArray(d.mixes))throw new Error("Invalid backup: missing styles or mixes arrays.");const v=await fetch(x,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(d)});if(!v.ok)throw new Error(`Save failed: HTTP ${v.status}`);try{(m=(l=E)==null?void 0:l.toast)==null||m.add({severity:"success",summary:"Style Mixer",detail:`Backup restored: ${d.styles.length} styles, ${d.mixes.length} mixes. Reload the Style Mixer panel to see changes.`,life:6e3})}catch{}}catch(d){alert(`Failed to restore backup: ${d.message}`)}},i.readAsText(t)}},e.click()}E.registerExtension({name:"ImmacStyleMixer",settings:[{id:"ImmacStyleMixer.BackupRestore",name:"Style Mixer — Backup & Restore",type:(e,t,s)=>{const r=document.createElement("div");r.style.cssText="display:flex;gap:8px;align-items:center";const i=document.createElement("button");i.textContent="⬇ Download backup (JSON)",i.title="Save all styles and mixes to a JSON file",i.style.cssText="cursor:pointer;padding:4px 10px;border-radius:4px",i.onclick=()=>f().catch(console.error);const c=document.createElement("button");c.textContent="⬇ Download ZIP (with images)",c.title="Save styles, mixes and all images to a ZIP file",c.style.cssText="cursor:pointer;padding:4px 10px;border-radius:4px",c.onclick=()=>b();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=()=>h(),r.appendChild(i),r.appendChild(c),r.appendChild(a),r},defaultValue:null}],nodeCreated(e){var C;if(e.comfyClass!=="PickMixImmacStyleMixer"&&e.comfyClass!=="StylePickImmacStyleMixer")return;const t=e.comfyClass==="StylePickImmacStyleMixer",s=t?"style":"mix",r=(C=e.widgets)==null?void 0:C.find(o=>o.name===s);if(!r)return;const i=document.createElement("img");i.style.cssText="width:100%;height:100%;display:none;object-fit:contain;border-radius:4px";const c=document.createElement("div");c.style.cssText="padding:4px;box-sizing:border-box",c.appendChild(i);let a=0;e.addDOMWidget("immac_preview","div",c,{serialize:!1,getMinHeight:()=>a,getHeight:()=>a});function l(o,p){var S,_,P,u;const y=((S=e.size)==null?void 0:S[0])??300,w=p/o;a=Math.max(80,Math.min(Math.round((y-8)*w),600));const n=(_=e.computeSize)==null?void 0:_.call(e);n&&e.setSize([Math.max(e.size[0],n[0]),Math.max(e.size[1],n[1])]),(P=e.onResize)==null||P.call(e,e.size),(u=e.graph)==null||u.setDirtyCanvas(!0,!0)}function m(){var o;a=0,i.style.display="none",(o=e.graph)==null||o.setDirtyCanvas(!0,!0)}function d(o){i.onload=()=>{i.style.display="block",l(i.naturalWidth,i.naturalHeight)},i.onerror=()=>{m()},i.src=o}async function v(o){var p;try{const y=await fetch("/immac_style_mixer/api/data");if(!y.ok)return;const w=await y.json();if(t){const n=(w.styles??[]).find(_=>_.name===o);if(!(n!=null&&n.image_filename)){m();return}const S=n.image_updated_at?`&t=${n.image_updated_at}`:"";d(`/view?filename=${encodeURIComponent(n.image_filename)}&subfolder=immac_style_mixer%2Fstyles&type=input${S}`)}else{const n=(w.mixes??[]).find(_=>_.name===o);let S="";if(n!=null&&n.image_filename){const _=n.image_updated_at?`&t=${n.image_updated_at}`:"";S=`/view?filename=${encodeURIComponent(n.image_filename)}&subfolder=immac_style_mixer%2Fmixes&type=input${_}`}else if((p=n==null?void 0:n.styles)!=null&&p.length){const _=Object.fromEntries((w.styles??[]).map(u=>[u.id,u])),P=n.styles.filter(u=>u.enabled!==!1).map(u=>_[u.style_id]).find(u=>u==null?void 0:u.image_filename);if(P){const u=P.image_updated_at?`&t=${P.image_updated_at}`:"";S=`/view?filename=${encodeURIComponent(P.image_filename)}&subfolder=immac_style_mixer%2Fstyles&type=input${u}`}}if(!S){m();return}d(S)}}catch(y){console.error("[ImmacStyleMixer] Preview update failed",y)}}const k=r.callback;r.callback=function(...o){var y;k==null||k.apply(this,o);const p=(y=e.widgets)==null?void 0:y.find(w=>w.name===s);v(String((p==null?void 0:p.value)??""))},e._immacUpdatePreview=o=>{var y;const p=(y=e.widgets)==null?void 0:y.find(w=>w.name===s);v(o??String((p==null?void 0:p.value)??""))},e._immacPreviewScheduled=!0,requestAnimationFrame(()=>{var p;if(!e._immacPreviewScheduled)return;const o=(p=e.widgets)==null?void 0:p.find(y=>y.name===s);o!=null&&o.value&&v(String(o.value))})},loadedGraphNode(e){var c;if(typeof e._immacUpdatePreview!="function")return;e._immacPreviewScheduled=!1;const t=e.comfyClass==="StylePickImmacStyleMixer",s=t?"style":"mix",r=(c=e.widgets)==null?void 0:c.find(a=>a.name===s),i=String((r==null?void 0:r.value)??"");requestAnimationFrame(()=>{i==="(no styles saved)"||i==="(no mixes saved)"?fetch("/immac_style_mixer/api/data").then(a=>a.json()).then(a=>{var d,v;const l=t?a.styles??[]:a.mixes??[],m=(d=e.widgets)==null?void 0:d.find(k=>k.name===s);l.length>0&&m&&(m.value=l[0].name,Array.isArray((v=m.options)==null?void 0:v.values)&&(m.options.values=l.map(k=>k.name))),e._immacUpdatePreview()}).catch(()=>e._immacUpdatePreview()):e._immacUpdatePreview(i)})}});function g(){var t;const e=((t=E.graph)==null?void 0:t._nodes)??[];for(const s of e)typeof s._immacUpdatePreview=="function"&&s._immacUpdatePreview()}B.addEventListener("execution_success",()=>{g(),window.dispatchEvent(new CustomEvent("immac:execution_success"))})}G().catch(console.error);export{R as j}; + */var F=T,Z=Symbol.for("react.element"),J=Symbol.for("react.fragment"),H=Object.prototype.hasOwnProperty,W=F.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.ReactCurrentOwner,q={key:!0,ref:!0,__self:!0,__source:!0};function U(x,f,b){var h,g={},e=null,t=null;b!==void 0&&(e=""+b),f.key!==void 0&&(e=""+f.key),f.ref!==void 0&&(t=f.ref);for(h in f)H.call(f,h)&&!q.hasOwnProperty(h)&&(g[h]=f[h]);if(x&&x.defaultProps)for(h in f=x.defaultProps,f)g[h]===void 0&&(g[h]=f[h]);return{$$typeof:Z,type:x,key:e,ref:t,props:g,_owner:W.current}}$.Fragment=J;$.jsx=U;$.jsxs=U;z.exports=$;var R=z.exports,I={},j=A;I.createRoot=j.createRoot,I.hydrateRoot=j.hydrateRoot;const V=M.lazy(()=>D(()=>import("./StyleMixerPanel-CMNQxf7y.js"),__vite__mapDeps([0,1])));async function G(){E.extensionManager.registerSidebarTab({id:"immac-style-mixer",icon:"pi pi-palette",title:"Style Mixer",tooltip:"Immac Style Mixer",type:"custom",render:e=>{const t=document.createElement("div");t.id="immac-style-mixer-root",t.style.position="absolute",t.style.inset="0",t.style.overflow="hidden",e.style.position="relative",e.style.height="100%",e.appendChild(t),I.createRoot(t).render(R.jsx(M.StrictMode,{children:R.jsx(T.Suspense,{fallback:R.jsx("div",{style:{padding:"1rem"},children:"Loading…"}),children:R.jsx(V,{})})}))}});const x="/immac_style_mixer/api/data";async function f(){const e=await fetch(x);if(!e.ok){alert(`Backup failed: HTTP ${e.status}`);return}const t=await e.json(),s=new Blob([JSON.stringify(t,null,2)],{type:"application/json"}),r=URL.createObjectURL(s),i=document.createElement("a");i.href=r,i.download=`style_mixer_backup_${new Date().toISOString().slice(0,10)}.json`,i.click(),URL.revokeObjectURL(r)}function b(){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 r;const t=(r=e.files)==null?void 0:r[0];if(!t)return;if(t.name.endsWith(".zip")||t.type==="application/zip")t.arrayBuffer().then(async i=>{var c,a;try{const l=await fetch("/immac_style_mixer/api/restore.zip",{method:"POST",headers:{"Content-Type":"application/zip"},body:i});if(!l.ok){const d=await l.text();throw new Error(`HTTP ${l.status}: ${d.slice(0,300)}`)}const m=await l.json();try{(a=(c=E)==null?void 0:c.toast)==null||a.add({severity:"success",summary:"Style Mixer",detail:`ZIP restored: ${m.styles} styles, ${m.mixes} mixes, ${m.images} images. Reload the Style Mixer panel to see changes.`,life:6e3})}catch{}}catch(l){alert(`Failed to restore ZIP backup: ${l.message}`)}}).catch(i=>alert(`Could not read file: ${i.message}`));else{const i=new FileReader;i.onload=async c=>{var a,l,m;try{const d=JSON.parse((a=c.target)==null?void 0:a.result);if(!Array.isArray(d.styles)||!Array.isArray(d.mixes))throw new Error("Invalid backup: missing styles or mixes arrays.");const v=await fetch(x,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(d)});if(!v.ok)throw new Error(`Save failed: HTTP ${v.status}`);try{(m=(l=E)==null?void 0:l.toast)==null||m.add({severity:"success",summary:"Style Mixer",detail:`Backup restored: ${d.styles.length} styles, ${d.mixes.length} mixes. Reload the Style Mixer panel to see changes.`,life:6e3})}catch{}}catch(d){alert(`Failed to restore backup: ${d.message}`)}},i.readAsText(t)}},e.click()}E.registerExtension({name:"ImmacStyleMixer",settings:[{id:"ImmacStyleMixer.BackupRestore",name:"Style Mixer — Backup & Restore",type:(e,t,s)=>{const r=document.createElement("div");r.style.cssText="display:flex;gap:8px;align-items:center";const i=document.createElement("button");i.textContent="⬇ Download backup (JSON)",i.title="Save all styles and mixes to a JSON file",i.style.cssText="cursor:pointer;padding:4px 10px;border-radius:4px",i.onclick=()=>f().catch(console.error);const c=document.createElement("button");c.textContent="⬇ Download ZIP (with images)",c.title="Save styles, mixes and all images to a ZIP file",c.style.cssText="cursor:pointer;padding:4px 10px;border-radius:4px",c.onclick=()=>b();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=()=>h(),r.appendChild(i),r.appendChild(c),r.appendChild(a),r},defaultValue:null}],nodeCreated(e){var C;if(e.comfyClass!=="PickMixImmacStyleMixer"&&e.comfyClass!=="PickStyleImmacStyleMixer")return;const t=e.comfyClass==="PickStyleImmacStyleMixer",s=t?"style":"mix",r=(C=e.widgets)==null?void 0:C.find(o=>o.name===s);if(!r)return;const i=document.createElement("img");i.style.cssText="width:100%;height:100%;display:none;object-fit:contain;border-radius:4px";const c=document.createElement("div");c.style.cssText="padding:4px;box-sizing:border-box",c.appendChild(i);let a=0;e.addDOMWidget("immac_preview","div",c,{serialize:!1,getMinHeight:()=>a,getHeight:()=>a});function l(o,p){var S,_,P,u;const y=((S=e.size)==null?void 0:S[0])??300,w=p/o;a=Math.max(80,Math.min(Math.round((y-8)*w),600));const n=(_=e.computeSize)==null?void 0:_.call(e);n&&e.setSize([Math.max(e.size[0],n[0]),Math.max(e.size[1],n[1])]),(P=e.onResize)==null||P.call(e,e.size),(u=e.graph)==null||u.setDirtyCanvas(!0,!0)}function m(){var o;a=0,i.style.display="none",(o=e.graph)==null||o.setDirtyCanvas(!0,!0)}function d(o){i.onload=()=>{i.style.display="block",l(i.naturalWidth,i.naturalHeight)},i.onerror=()=>{m()},i.src=o}async function v(o){var p;try{const y=await fetch("/immac_style_mixer/api/data");if(!y.ok)return;const w=await y.json();if(t){const n=(w.styles??[]).find(_=>_.name===o);if(!(n!=null&&n.image_filename)){m();return}const S=n.image_updated_at?`&t=${n.image_updated_at}`:"";d(`/view?filename=${encodeURIComponent(n.image_filename)}&subfolder=immac_style_mixer%2Fstyles&type=input${S}`)}else{const n=(w.mixes??[]).find(_=>_.name===o);let S="";if(n!=null&&n.image_filename){const _=n.image_updated_at?`&t=${n.image_updated_at}`:"";S=`/view?filename=${encodeURIComponent(n.image_filename)}&subfolder=immac_style_mixer%2Fmixes&type=input${_}`}else if((p=n==null?void 0:n.styles)!=null&&p.length){const _=Object.fromEntries((w.styles??[]).map(u=>[u.id,u])),P=n.styles.filter(u=>u.enabled!==!1).map(u=>_[u.style_id]).find(u=>u==null?void 0:u.image_filename);if(P){const u=P.image_updated_at?`&t=${P.image_updated_at}`:"";S=`/view?filename=${encodeURIComponent(P.image_filename)}&subfolder=immac_style_mixer%2Fstyles&type=input${u}`}}if(!S){m();return}d(S)}}catch(y){console.error("[ImmacStyleMixer] Preview update failed",y)}}const k=r.callback;r.callback=function(...o){var y;k==null||k.apply(this,o);const p=(y=e.widgets)==null?void 0:y.find(w=>w.name===s);v(String((p==null?void 0:p.value)??""))},e._immacUpdatePreview=o=>{var y;const p=(y=e.widgets)==null?void 0:y.find(w=>w.name===s);v(o??String((p==null?void 0:p.value)??""))},e._immacPreviewScheduled=!0,requestAnimationFrame(()=>{var p;if(!e._immacPreviewScheduled)return;const o=(p=e.widgets)==null?void 0:p.find(y=>y.name===s);o!=null&&o.value&&v(String(o.value))})},loadedGraphNode(e){var c;if(typeof e._immacUpdatePreview!="function")return;e._immacPreviewScheduled=!1;const t=e.comfyClass==="PickStyleImmacStyleMixer",s=t?"style":"mix",r=(c=e.widgets)==null?void 0:c.find(a=>a.name===s),i=String((r==null?void 0:r.value)??"");requestAnimationFrame(()=>{i==="(no styles saved)"||i==="(no mixes saved)"?fetch("/immac_style_mixer/api/data").then(a=>a.json()).then(a=>{var d,v;const l=t?a.styles??[]:a.mixes??[],m=(d=e.widgets)==null?void 0:d.find(k=>k.name===s);l.length>0&&m&&(m.value=l[0].name,Array.isArray((v=m.options)==null?void 0:v.values)&&(m.options.values=l.map(k=>k.name))),e._immacUpdatePreview()}).catch(()=>e._immacUpdatePreview()):e._immacUpdatePreview(i)})}});function g(){var t;const e=((t=E.graph)==null?void 0:t._nodes)??[];for(const s of e)typeof s._immacUpdatePreview=="function"&&s._immacUpdatePreview()}B.addEventListener("execution_success",()=>{g(),window.dispatchEvent(new CustomEvent("immac:execution_success"))})}G().catch(console.error);export{R as j}; diff --git a/src/immac_tools/save_mix_node.py b/src/immac_tools/save_mix_node.py index 4a5dbc6..939ef17 100644 --- a/src/immac_tools/save_mix_node.py +++ b/src/immac_tools/save_mix_node.py @@ -47,11 +47,19 @@ _MODE = [ ] -def _update_mix(mix: dict, mix_styles: list, example_image, name: str) -> None: - """Patch styles and optionally the image on an existing mix dict in place.""" +def _update_mix( + mix: dict, + mix_styles: list, + example_image, + name: str, + favorite: bool | None = None, +) -> None: + """Patch styles and optionally the image/name/favorite on an existing mix dict in place.""" mix["styles"] = mix_styles if name: mix["name"] = name + if favorite is not None: + mix["favorite"] = favorite if example_image is not None: mix["image_filename"] = _save_mix_image( example_image, @@ -61,27 +69,64 @@ def _update_mix(mix: dict, mix_styles: list, example_image, name: str) -> None: mix["image_updated_at"] = int(time.time()) +def _derive_name(mix_styles: list, data: dict) -> str: + """Build a 'Quick Mix (A, B, C)' label from the style names in blend order. + + Unresolved style_ids fall back to the first 8 hex chars of the id. + """ + style_index = {s["id"]: s.get("name", "") for s in data.get("styles", [])} + parts = [] + for entry in mix_styles: + sid = entry.get("style_id", "") + parts.append(style_index.get(sid) or sid[:8]) + joined = ", ".join(parts) if parts else "empty" + return f"Quick Mix ({joined})" + + class SaveMixNode(io.ComfyNode): """Creates or updates a saved mix from a blend_json (output of Style Blend). mode controls how the target record is resolved and what happens when it does or does not exist: - Create — inserts a new mix. Requires a non-empty name. The id input - must not be connected (ids are assigned automatically). + Create — inserts a new mix. The id input must not be connected (ids are + assigned automatically). If name is empty an auto-name of the + form 'Quick Mix (A, B, C)' is derived from the blend contents. Fails if a mix with the same name already exists. Update — modifies an existing mix (by id or by name); fails if not found. Connect either an id or enter a name — not both. Save — updates if found (by id or by name), creates if not found by - name. Fails if id is given but no match exists. Requires either - an id or a name, but not both. + name. If name is empty on create an auto-name is derived. + Fails if id is given but no match exists. The blend_json input must be wired from the blend_json output of a Style Blend node. Each entry in that JSON array encodes a {style_id, weight} pair that will be stored in the mix exactly as produced by Style Blend (including any deduplication / normalisation already applied there). + + Behaviour matrix (id = connected, name = non-empty, * = auto-derived): + + mode id name found? result + Create – – exists ✅ replace existing Quick Mix (auto-derived name upsert) + Create – – – ✅ insert new record (name auto-derived) + Create – ✓ exists error: name already taken; use Update or Save + Create – ✓ – ✅ insert new record + Create ✓ – – error: id must not be connected in Create mode + Create ✓ ✓ – error: id must not be connected in Create mode + Update – – – error: id or name required + Update – ✓ exists ✅ update by name + Update – ✓ – error: no mix named '{name}' + Update ✓ – exists ✅ update by id + Update ✓ – – error: no mix with id '{id}' + Update ✓ ✓ – error: provide id OR name, not both + Save – – – ✅ insert new record (name auto-derived) + Save – ✓ exists ✅ update by name + Save – ✓ – ✅ insert new record + Save ✓ – exists ✅ update by id + Save ✓ – – error: no mix with id '{id}'; provide a name to create + Save ✓ ✓ – error: provide id OR name, not both """ @classmethod @@ -105,10 +150,12 @@ class SaveMixNode(io.ComfyNode): io.String.Input("name", default="", multiline=False, optional=True), io.Combo.Input("mode", options=_MODE, default="Save"), io.String.Input("id", optional=True, default="", multiline=False), + io.Boolean.Input("favorite", optional=True, default=False), io.Image.Input("example_image", optional=True), ], outputs=[ io.String.Output(display_name="mix_id"), + io.String.Output(display_name="mix_name"), ], ) @@ -126,6 +173,7 @@ class SaveMixNode(io.ComfyNode): name: str = "", mode: str = "Save", id: str = "", + favorite: bool = False, example_image=None, ) -> io.NodeOutput: name = (name or "").strip() @@ -138,18 +186,14 @@ class SaveMixNode(io.ComfyNode): "— ids are assigned automatically." ) - # ── Guard: Create mode requires a name ─────────────────────────────── - if mode == "Create" and not name: - raise ValueError("[SaveMixNode] A name is required to create a new mix.") - # ── Guard: Update / Save must not have both id and name ────────────── if mode in ("Update", "Save") and mix_id_input and name: raise ValueError( "[SaveMixNode] Connect either an id or enter a name — not both." ) - # ── Guard: Update / Save require at least one identifier ───────────── - if mode in ("Update", "Save") and not mix_id_input and not name: + # ── Guard: Update requires at least one identifier ─────────────────── + if mode == "Update" and not mix_id_input and not name: raise ValueError( "[SaveMixNode] Connect an id or enter a name to identify the mix." ) @@ -179,23 +223,39 @@ class SaveMixNode(io.ComfyNode): if mix_id_input: existing = next((m for m in mixes if m.get("id") == mix_id_input), None) else: - existing = next((m for m in mixes if m.get("name") == name), None) + existing = next((m for m in mixes if m.get("name") == name), None) if name else None # ── Mode: Create ───────────────────────────────────────────────────── if mode == "Create": + # Auto-derive a name if none was given + name_was_derived = not name + if name_was_derived: + name = _derive_name(mix_styles, data) + if existing is None and name: + existing = next((m for m in mixes if m.get("name") == name), None) if existing is not None: + if name_was_derived: + # Quick Mix names are temporary — replace the existing record + _update_mix(existing, mix_styles, example_image, name, favorite) + save_data(data) + return io.NodeOutput(existing["id"], existing["name"]) raise RuntimeError( f"[SaveMixNode] A mix named '{name}' already exists " f"(id={existing['id']}). Switch to Update or Save mode to modify it." ) new_mix_id = str(uuid.uuid4()) - new_mix: dict = {"id": new_mix_id, "name": name, "styles": mix_styles} + new_mix: dict = { + "id": new_mix_id, + "name": name, + "favorite": favorite if favorite is not None else False, + "styles": mix_styles, + } if example_image is not None: new_mix["image_filename"] = _save_mix_image(example_image, name) new_mix["image_updated_at"] = int(time.time()) mixes.append(new_mix) save_data(data) - return io.NodeOutput(new_mix_id) + return io.NodeOutput(new_mix_id, name) # ── Mode: Update ───────────────────────────────────────────────────── if mode == "Update": @@ -208,9 +268,9 @@ class SaveMixNode(io.ComfyNode): raise RuntimeError( f"[SaveMixNode] No mix named '{name}' was found." ) - _update_mix(existing, mix_styles, example_image, name) + _update_mix(existing, mix_styles, example_image, name, favorite) save_data(data) - return io.NodeOutput(existing["id"]) + return io.NodeOutput(existing["id"], existing["name"]) # ── Mode: Save ─────────────────────────────────────────────────────── if mix_id_input and existing is None: @@ -220,16 +280,23 @@ class SaveMixNode(io.ComfyNode): ) if existing is not None: - _update_mix(existing, mix_styles, example_image, name) + _update_mix(existing, mix_styles, example_image, name, favorite) save_data(data) - return io.NodeOutput(existing["id"]) + return io.NodeOutput(existing["id"], existing["name"]) else: - # Save + name + not found → create + # Save + (name or auto-derive) + not found → create + if not name: + name = _derive_name(mix_styles, data) new_mix_id = str(uuid.uuid4()) - new_mix = {"id": new_mix_id, "name": name, "styles": mix_styles} + new_mix = { + "id": new_mix_id, + "name": name, + "favorite": favorite if favorite is not None else False, + "styles": mix_styles, + } if example_image is not None: new_mix["image_filename"] = _save_mix_image(example_image, name) new_mix["image_updated_at"] = int(time.time()) mixes.append(new_mix) save_data(data) - return io.NodeOutput(new_mix_id) + return io.NodeOutput(new_mix_id, name) diff --git a/src/immac_tools/style_blend_node.py b/src/immac_tools/style_blend_node.py index 5268721..df9b237 100644 --- a/src/immac_tools/style_blend_node.py +++ b/src/immac_tools/style_blend_node.py @@ -1,4 +1,4 @@ -"""StyleBlendNode — aggregates weighted style_entry slots into a blend JSON + prompt.""" +"""BlendStyleNode — aggregates weighted style_entry slots into a blend JSON + prompt.""" import json import os @@ -22,7 +22,7 @@ _WEIGHT_MODE = [ ] -class StyleBlendNode(io.ComfyNode): +class BlendStyleNode(io.ComfyNode): """Aggregates style_entry inputs (from Weight Style nodes) into a blend. Each slot accepts a style_entry JSON string produced by the Weight Style node. @@ -50,8 +50,8 @@ class StyleBlendNode(io.ComfyNode): max=16, ) return io.Schema( - node_id="StyleBlendImmacStyleMixer", - display_name="Style Blend", + node_id="BlendStyleImmacStyleMixer", + display_name="Blend Style", category="Immac/Style Mixer", description=( "Aggregates weighted style_entry inputs (from Weight Style nodes) " diff --git a/src/immac_tools/style_pick_node.py b/src/immac_tools/style_pick_node.py index 7fbff7c..c442eab 100644 --- a/src/immac_tools/style_pick_node.py +++ b/src/immac_tools/style_pick_node.py @@ -1,4 +1,4 @@ -"""StylePickNode — picks a saved style and outputs its name, id, and value.""" +"""PickStyleNode — picks a saved style and outputs its name, id, and value.""" import os @@ -13,14 +13,14 @@ def _style_names() -> list[str]: return names if names else ["(no styles saved)"] -class StylePickNode(io.ComfyNode): +class PickStyleNode(io.ComfyNode): """Picks a saved style and outputs its name, id, and prompt value.""" @classmethod def define_schema(cls) -> io.Schema: return io.Schema( - node_id="StylePickImmacStyleMixer", - display_name="Style Pick", + node_id="PickStyleImmacStyleMixer", + display_name="Pick Style", category="Immac/Style Mixer", description="Picks a saved style and outputs its name, id, and prompt value.", inputs=[ diff --git a/ui/src/components/StyleCard.tsx b/ui/src/components/StyleCard.tsx index cb3c018..94ac55d 100644 --- a/ui/src/components/StyleCard.tsx +++ b/ui/src/components/StyleCard.tsx @@ -76,7 +76,7 @@ export default function StyleCard({ style, onUpdate, onDelete, inCurrentMix, onA if (document.elementFromPoint(e.clientX, e.clientY) !== canvasEl) return const pos: [number, number] = lgCanvas.convertEventToCanvasOffset(e) const LG = (window as any).LiteGraph - const node = LG?.createNode('StylePickImmacStyleMixer') + const node = LG?.createNode('PickStyleImmacStyleMixer') if (!node) return node.pos = pos ;(window as any).app?.graph?.add(node) diff --git a/ui/src/main.tsx b/ui/src/main.tsx index 7feeb26..7f79362 100644 --- a/ui/src/main.tsx +++ b/ui/src/main.tsx @@ -168,11 +168,11 @@ async function init(): Promise { nodeCreated(node: any) { if ( node.comfyClass !== 'PickMixImmacStyleMixer' && - node.comfyClass !== 'StylePickImmacStyleMixer' + node.comfyClass !== 'PickStyleImmacStyleMixer' ) return - // ── shared DOM preview widget ──────────────────────────────────────── - const isPickNode = node.comfyClass === 'StylePickImmacStyleMixer' + // ── shared DOM preview widget ──────────────────────────────────────────────────────────────────── + const isPickNode = node.comfyClass === 'PickStyleImmacStyleMixer' const widgetName = isPickNode ? 'style' : 'mix' const comboWidget = node.widgets?.find((w: any) => w.name === widgetName) @@ -289,7 +289,7 @@ async function init(): Promise { node._immacPreviewScheduled = false - const isPickNode = node.comfyClass === 'StylePickImmacStyleMixer' + const isPickNode = node.comfyClass === 'PickStyleImmacStyleMixer' const widgetName = isPickNode ? 'style' : 'mix' const comboWidget = node.widgets?.find((w: any) => w.name === widgetName) const currentVal = String(comboWidget?.value ?? '') @@ -321,7 +321,7 @@ async function init(): Promise { // We listen on `execution_success` (fires once per completed queue item) // via the directly-imported `api` singleton. Any run could have updated an // image_filename via SaveMix / CreateStyle / ModifyStyle, so we refresh all - // PickMix and StylePick node previews. The image_updated_at cache-buster + // PickMix and Pick Style node previews. The image_updated_at cache-buster // ensures the browser fetches the new file only when it actually changed. function refreshAllPreviews() { const nodes: any[] = (app as any).graph?._nodes ?? []