import { app } from "../../scripts/app.js";
import { api } from "../../scripts/api.js";
const LIME = "#4dff6e";
const C = {
lime:LIME, bg0:"#0b0b0b", bg1:"#111111", bg2:"#181818",
bg3:"#222222", border:"#2a2a2a", borderH:"#3c3c3c",
text:"#dedede", muted:"#565656", dim:"#2e2e2e",
warn:"#ffb347", err:"#ff6767",
};
const NODE_W = 980;
const NODE_H = Math.round(NODE_W * 9 / 16);
// ── Workflow node IDs ─────────────────────────────────────────────────────────
// Shared by both t2i_workflow.json and edit_workflow.json
const WF = {
model: "FK:165", // UNETLoader
kvCache: null, // No KV cache for Z-Image
textEnc: "FK:155", // CLIPLoader
vae: "FK:153", // VAELoader
promptPos: "FK:166", // CLIPTextEncode positive
promptNeg: "FK:156", // CLIPTextEncode negative
sampling: "FK:169", // ModelSamplingAuraFlow — receives model input
latent: "FK:170", // EmptySD3LatentImage (T2I) — width/height set here
sampler: "FK:171", // KSampler — receives seed
saveImage: "FK:86", // SaveImage
};
const LS_KEY = "one_node_z_image_state";
const DEFAULT_NEG_PROMPT = "low quality, deformed, blurry, watermark, ugly, bad anatomy, disfigured, mutated, extra limbs, poorly drawn face, bad proportions, gross proportions, jpeg artifacts, overexposed, underexposed";
// ── Resolution presets (Z-Image-friendly, divisible by 16) ──────────────────────
const RES_PRESETS = [
{ label:"1024 × 1024", w:1024, h:1024 },
{ label:"1920 × 1088", w:1920, h:1088 },
{ label:"1088 × 1920", w:1088, h:1920 },
{ label:"1280 × 720", w:1280, h:720 },
{ label:"720 × 1280", w:720, h:1280 },
{ label:"Custom…", w:0, h:0 },
];
function snapRes(v){ return Math.max(16, Math.round(v/16)*16); }
// ── DOM helpers ───────────────────────────────────────────────────────────────
const mk = (tag,css={},props={}) => { const e=document.createElement(tag); Object.assign(e.style,css); Object.assign(e,props); return e; };
const tx = (e,t) => { e.textContent=t; return e; };
const cap = (t) => tx(mk("div",{fontSize:"9px",fontWeight:"700",letterSpacing:".1em",
textTransform:"uppercase",color:C.muted,marginBottom:"5px"}),t);
// ── Notification sound ────────────────────────────────────────────────────────
function playDone(){
try{
const AC=window.AudioContext||/** @type {any} */(window).webkitAudioContext;
const ctx=new AC();
// Two soft sine tones: a gentle rising chime
[[660,0,0.09],[990,0.1,0.07]].forEach(([freq,delay,vol])=>{
const osc=ctx.createOscillator(),gain=ctx.createGain();
osc.connect(gain);gain.connect(ctx.destination);
osc.type="sine";osc.frequency.value=freq;
gain.gain.setValueAtTime(0,ctx.currentTime+delay);
gain.gain.linearRampToValueAtTime(vol,ctx.currentTime+delay+0.03);
gain.gain.exponentialRampToValueAtTime(0.001,ctx.currentTime+delay+0.55);
osc.start(ctx.currentTime+delay);osc.stop(ctx.currentTime+delay+0.6);
});
}catch(e){}
}
function fmtErr(v){
try{
if(!v) return "Unknown error.";
if(typeof v === "string") return v;
if(v.message) return String(v.message);
if(v.error){
if(typeof v.error === "string") return v.error;
if(v.error.message) return String(v.error.message);
}
return JSON.stringify(v);
}catch(e){ return String(v); }
}
// ── Dimmer ────────────────────────────────────────────────────────────────────
let _dim=null;
const showDimmer=()=>{ if(!_dim){_dim=mk("div",{position:"fixed",inset:"0",background:"rgba(0,0,0,.7)",zIndex:"999990",display:"none",pointerEvents:"none"});document.body.appendChild(_dim);} _dim.style.display="block"; };
const hideDimmer=()=>{ if(_dim)_dim.style.display="none"; };
// ── Toggle ────────────────────────────────────────────────────────────────────
function Toggle(labelTxt,checked,onChange,activeColor){
const onClr=activeColor||LIME;
const onThumb=activeColor?"#fff":"#111";
const wrap=mk("div",{display:"flex",alignItems:"center",justifyContent:"space-between",
padding:"9px 0",borderBottom:`1px solid ${C.border}`});
const lbl=mk("span",{fontSize:"12px",color:C.text});tx(lbl,labelTxt);
const track=mk("div",{width:"34px",height:"18px",borderRadius:"9px",
background:checked?onClr:C.dim,cursor:"pointer",position:"relative",transition:"background .2s",flexShrink:"0"});
const thumb=mk("div",{position:"absolute",top:"2px",left:checked?"16px":"2px",
width:"14px",height:"14px",borderRadius:"50%",
background:checked?onThumb:"#888",transition:"left .2s,background .2s"});
track.appendChild(thumb);
let val=checked;
track.onclick=()=>{
val=!val;track.style.background=val?onClr:C.dim;
thumb.style.left=val?"16px":"2px";thumb.style.background=val?onThumb:"#888";onChange(val);
};
wrap.append(lbl,track);
const _setChecked=(v)=>{
val=v;track.style.background=val?onClr:C.dim;
thumb.style.left=val?"16px":"2px";thumb.style.background=val?onThumb:"#888";
};
return{el:wrap,get value(){return val;},_setChecked};
}
// ── Dropdown ──────────────────────────────────────────────────────────────────
function DD(items,selected,onChange){
let val=selected;
const wrap=mk("div",{position:"relative",width:"100%",minWidth:"0",overflow:"hidden"});
const trig=mk("div",{background:C.bg3,border:`1px solid ${C.border}`,borderRadius:"7px",
padding:"0 8px",height:"28px",display:"flex",alignItems:"center",
justifyContent:"space-between",cursor:"pointer",boxSizing:"border-box",
transition:"border-color .15s",userSelect:"none",overflow:"hidden"});
const trigTxt=mk("span",{fontSize:"11px",color:C.text,overflow:"hidden",
textOverflow:"ellipsis",whiteSpace:"nowrap",flex:"1",minWidth:"0"});
tx(trigTxt,val); trigTxt.style.color=val?LIME:C.muted;
const arr=mk("span",{fontSize:"8px",color:C.muted,marginLeft:"5px",flexShrink:"0",transition:"transform .18s"});
tx(arr,"▾");
trig.append(trigTxt,arr);
const panel=mk("div",{display:"none",position:"fixed",background:C.bg1,
border:`1px solid ${C.borderH}`,borderRadius:"8px",zIndex:"999999",
flexDirection:"column",boxShadow:"0 8px 28px rgba(0,0,0,.9)",
overflow:"hidden",minWidth:"140px",maxWidth:"400px"});
const srch=mk("input",{background:C.bg2,border:"none",borderBottom:`1px solid ${C.border}`,
padding:"7px 10px",color:C.text,fontSize:"11px",outline:"none",
width:"100%",boxSizing:"border-box"},{type:"text",placeholder:"Type to filter…"});
const list=mk("div",{overflowY:"auto",maxHeight:"200px"});
const render=q=>{
const _norm=(s)=>(s||"").replace(/\\/g,"/").toLowerCase();
list.innerHTML="";
items.filter(i=>!q||i.toLowerCase().includes(q.toLowerCase())).forEach(item=>{
const isSel=_norm(item)===_norm(val);
const r=mk("div",{padding:"7px 12px",fontSize:"11px",cursor:"pointer",
color:isSel?LIME:C.text,background:isSel?C.bg2:"transparent",
whiteSpace:"nowrap",overflow:"hidden",textOverflow:"ellipsis",transition:"background .1s"});
tx(r,item);
r.onmouseenter=()=>r.style.background=C.bg3;
r.onmouseleave=()=>r.style.background=_norm(item)===_norm(val)?C.bg2:"transparent";
r.onclick=()=>{val=item;tx(trigTxt,item);trigTxt.style.color=item?LIME:C.muted;close();onChange(item);};
list.appendChild(r);
});
};
const reposition=()=>{
const rect=trig.getBoundingClientRect();
panel.style.left=rect.left+"px";
panel.style.width=Math.max(rect.width,140)+"px";
const ph=Math.min(items.length*28+44,220);
panel.style.top=(rect.top-ph-4>8?rect.top-ph-4:rect.bottom+4)+"px";
};
const open=()=>{
document.body.appendChild(panel);panel.style.display="flex";
reposition();arr.style.transform="rotate(180deg)";
trig.style.borderColor=LIME;showDimmer();
srch.value="";srch.focus();render("");
};
const close=()=>{
panel.style.display="none";
if(panel.parentNode)panel.parentNode.removeChild(panel);
arr.style.transform="";trig.style.borderColor=C.border;hideDimmer();
};
srch.oninput=()=>render(srch.value);
trig.onclick=e=>{e.stopPropagation();panel.style.display==="flex"?close():open();};
document.addEventListener("click",e=>{if(!wrap.contains(e.target)&&!panel.contains(e.target))close();});
trig.onmouseenter=()=>{if(panel.style.display!=="flex")trig.style.background=C.bg2;};
trig.onmouseleave=()=>{if(panel.style.display!=="flex")trig.style.background=C.bg3;};
panel.appendChild(srch);
panel.appendChild(list);
wrap.appendChild(trig);
render("");
return{
el:wrap,get value(){return val;},
set(v){val=v;tx(trigTxt,v);trigTxt.style.color=v?LIME:C.muted;render("");},
updateItems(ni){items=ni;if(!ni.some(i=>(i||"").replace(/\\/g,"/").toLowerCase()===(val||"").replace(/\\/g,"/").toLowerCase())){val=ni[0]||val;tx(trigTxt,val);trigTxt.style.color=val?LIME:C.muted;onChange(val);}render(srch.value||"");},
};
}
// ── Pill button ───────────────────────────────────────────────────────────────
function Pill(txt,active,onClick){
const b=mk("button",{
background:active?LIME:C.bg2,color:active?"#111":C.text,
border:`1px solid ${active?LIME:C.border}`,
borderRadius:"20px",padding:"3px 9px",fontSize:"9px",
fontWeight:active?"700":"400",cursor:"pointer",
transition:"all .14s",outline:"none",whiteSpace:"nowrap",
});
tx(b,txt);
b.onmousedown=()=>b.style.transform="scale(.95)";
b.onmouseup=()=>b.style.transform="";
b.onmouseleave=()=>b.style.transform="";
b.onclick=onClick;
return b;
}
// ── Number input ──────────────────────────────────────────────────────────────
function NI(_label,val,min,max,_step,onChange,width="72px"){
const wrap=mk("div",{
width,height:"28px",background:C.bg2,border:`1px solid ${C.border}`,
borderRadius:"6px",boxSizing:"border-box",display:"flex",alignItems:"center",
padding:"0 7px",transition:"border-color .15s",overflow:"hidden",
});
const inp=mk("input",{
flex:"1 1 0",minWidth:"0",background:"transparent",border:"none",outline:"none",
color:C.text,fontSize:"11px",padding:"0",textAlign:"left",
},{type:"number",min:String(min),max:String(max),value:String(val)});
inp.oninput=()=>{ const v=Math.max(min,Math.min(max,parseFloat(inp.value)||min)); onChange(v); };
inp.onfocus=()=>{ inp.select(); wrap.style.borderColor=LIME; };
inp.onblur=()=>{ inp.value=String(Math.max(min,Math.min(max,parseFloat(inp.value)||min))); wrap.style.borderColor=C.border; };
inp.addEventListener("keydown",e=>{
if(e.key==="Enter"){ inp.blur(); return; }
if(e.key==="ArrowUp"||e.key==="ArrowDown"){
e.preventDefault();
const step=wrap._arrowStep||8;
const cur=Math.max(min,Math.min(max,parseFloat(inp.value)||min));
const next=e.key==="ArrowUp"
? Math.min(max, Math.round((cur+step)/step)*step)
: Math.max(min, Math.round((cur-step)/step)*step);
inp.value=String(next); onChange(next);
}
});
inp.addEventListener("wheel",e=>e.stopPropagation(),{passive:true});
wrap.appendChild(inp);
wrap.onclick=()=>inp.focus();
wrap._inp=inp;
wrap.setVal=(v)=>{inp.value=String(v);};
Object.defineProperty(wrap,"numVal",{get(){return parseFloat(inp.value)||min;}});
return wrap;
}
// ── Remove button ─────────────────────────────────────────────────────────────
function mkRmBtn(){
const b=mk("button",{
position:"absolute",top:"4px",right:"4px",width:"18px",height:"18px",
borderRadius:"50%",background:"rgba(0,0,0,.85)",border:`1px solid ${C.border}`,
color:"rgba(255,255,255,.7)",fontSize:"9px",cursor:"pointer",display:"none",
alignItems:"center",justifyContent:"center",padding:"0",
transition:"background .15s, color .15s, border-color .15s",lineHeight:"1",zIndex:"2",
});
tx(b,"✕");
b.onmouseenter=()=>{ b.style.borderColor=LIME; b.style.color=LIME; };
b.onmouseleave=()=>{ b.style.borderColor=C.border; b.style.color="rgba(255,255,255,.7)"; };
return b;
}
// ── Global node-local fullscreen overlay factory (set per node instance) ─────
let _fkActiveFsFactory=null;
// ── Image upload slot ─────────────────────────────────────────────────────────
// Returns {el, name, hasFile(), setDimsLabel(w,h), _restorePreview(name)}
function ImgSlot(optional, onFile, onDims){
const wrap=mk("div",{
width:"88px",height:"88px",borderRadius:"12px",
border:`1.5px dashed ${C.border}`,background:C.bg2,
display:"flex",flexDirection:"column",alignItems:"center",justifyContent:"center",
cursor:"pointer",position:"relative",
transition:"border-color .18s, background .18s",
overflow:"hidden",flexShrink:"0",boxSizing:"border-box",
});
// Empty state
const icoWrap=mk("div",{
position:"absolute",inset:"0",
display:"flex",flexDirection:"column",alignItems:"center",justifyContent:"center",
gap:"5px",pointerEvents:"none",
});
const ico=document.createElementNS("http://www.w3.org/2000/svg","svg");
ico.setAttribute("viewBox","0 0 24 24");
ico.setAttribute("width","24");ico.setAttribute("height","24");
ico.setAttribute("fill","none");ico.setAttribute("stroke","currentColor");
ico.setAttribute("stroke-width","1.4");ico.setAttribute("stroke-linecap","round");
ico.setAttribute("stroke-linejoin","round");
ico.style.color=C.muted;ico.style.transition="color .18s";ico.style.pointerEvents="none";
ico.innerHTML=``;
const lbl=mk("div",{fontSize:"8px",color:C.muted,pointerEvents:"none",letterSpacing:".04em",fontWeight:"600",transition:"color .18s"});
tx(lbl,"Add image");
if(optional){
const optPill=mk("div",{fontSize:"6px",color:C.muted,letterSpacing:".06em",fontWeight:"700",
border:`1px solid ${C.border}`,borderRadius:"20px",padding:"1px 5px",pointerEvents:"none",
textTransform:"uppercase",background:"transparent",lineHeight:"1.7"});
tx(optPill,"Optional");icoWrap.append(ico,lbl,optPill);icoWrap._optPill=optPill;
} else { icoWrap.append(ico,lbl); }
// Preview image
const prevEl=mk("img",{
position:"absolute",inset:"0",width:"100%",height:"100%",
objectFit:"cover",display:"none",borderRadius:"11px",
});
// Remove button (top-right)
const rm=mkRmBtn();
// Fullscreen button (top-right next to rm, circular, hidden until file loaded)
const fsBtn=mk("button",{
position:"absolute",top:"4px",right:"26px",width:"18px",height:"18px",
borderRadius:"50%",background:"rgba(0,0,0,.85)",border:`1px solid ${C.border}`,
color:"rgba(255,255,255,.7)",cursor:"pointer",display:"none",
alignItems:"center",justifyContent:"center",padding:"0",
transition:"border-color .15s, color .15s",lineHeight:"1",zIndex:"2",
});
fsBtn.innerHTML=``;
fsBtn.onmouseenter=()=>{ fsBtn.style.borderColor=LIME; fsBtn.style.color=LIME; };
fsBtn.onmouseleave=()=>{ fsBtn.style.borderColor=C.border; fsBtn.style.color="rgba(255,255,255,.7)"; };
const inp=mk("input",{display:"none"},{type:"file",accept:"image/*"});
wrap.append(icoWrap,prevEl,fsBtn,rm,inp);
wrap.onmouseenter=()=>{
if(prevEl.style.display==="none"){ wrap.style.borderColor=LIME;wrap.style.background=C.bg1;ico.style.color=LIME;lbl.style.color=LIME;if(icoWrap._optPill){icoWrap._optPill.style.color=LIME;icoWrap._optPill.style.borderColor=LIME;} }
else { wrap.style.borderColor=LIME; }
};
wrap.onmouseleave=()=>{ wrap.style.borderColor=C.border;wrap.style.background=C.bg2;ico.style.color=C.muted;lbl.style.color=C.muted;if(icoWrap._optPill){icoWrap._optPill.style.color=C.muted;icoWrap._optPill.style.borderColor=C.border;} };
wrap.onclick=()=>inp.click();
let _dragDepth=0;
wrap.addEventListener("dragenter",e=>{e.preventDefault();e.stopPropagation();_dragDepth++;wrap.style.borderColor=LIME;wrap.style.background=C.bg1;});
wrap.addEventListener("dragover",e=>{e.preventDefault();e.stopPropagation();});
wrap.addEventListener("dragleave",()=>{ _dragDepth--;if(_dragDepth<=0){_dragDepth=0;wrap.style.borderColor=C.border;wrap.style.background=C.bg2;} });
wrap.addEventListener("drop",e=>{
e.preventDefault();e.stopPropagation();
_dragDepth=0;wrap.style.borderColor=C.border;wrap.style.background=C.bg2;
const f=e.dataTransfer.files[0];if(f&&f.type.startsWith("image/"))_load(f);
});
let _currentName=null;
let _fsSrc="", _fsFileName="";
const _showLoaded=(src,fname)=>{
prevEl.src=src;prevEl.style.display="block";
icoWrap.style.display="none";rm.style.display="flex";fsBtn.style.display="flex";
wrap.style.borderColor=LIME;
_fsSrc=src;_fsFileName=fname;
// Read natural dims once image loads
const _tmpImg=new Image();
_tmpImg.onload=()=>{ if(onDims) onDims(_tmpImg.naturalWidth,_tmpImg.naturalHeight); };
_tmpImg.src=src;
};
fsBtn.onclick=e=>{
e.stopPropagation();
const factory=_fkActiveFsFactory;
if(factory) factory()._open("image",_fsSrc,_fsFileName);
};
const _load=async(file)=>{
const objUrl=URL.createObjectURL(file);
_showLoaded(objUrl,file.name);
// Use ComfyUI image upload endpoint
const fd=new FormData();fd.append("image",file);fd.append("overwrite","true");
try{
const r=await api.fetchApi("/upload/image",{method:"POST",body:fd});
const d=await r.json();_currentName=d.name||file.name;
onFile(_currentName);
}catch(err){console.warn("[ZImageOneNode] upload:",err);_currentName=file.name;onFile(_currentName);}
};
inp.onchange=()=>{if(inp.files[0])_load(inp.files[0]);};
rm.onclick=e=>{
e.stopPropagation();
prevEl.src="";prevEl.style.display="none";
rm.style.display="none";fsBtn.style.display="none";icoWrap.style.display="flex";
wrap.style.borderColor=C.border;
inp.value="";_currentName=null;onFile(null);
if(onDims) onDims(0,0);
};
// Restore a previously-uploaded input image by name (state restore or programmatic set).
// Calls onFile so hasFile() returns true and size controls update correctly.
// Safe to call only after all dependents (resDD, updateSizeControls) are initialized.
const _restorePreview=(name)=>{
if(!name){ rm.onclick({stopPropagation:()=>{}}); return; }
const src=api.apiURL(`/view?filename=${encodeURIComponent(name)}&type=input&subfolder=`);
_currentName=name;
_showLoaded(src,name);
// onFile intentionally NOT called here — callers set S.imageXName directly and call
// updateSizeControls/persist themselves to avoid initialization-order issues.
};
// Restore from a fully-formed URL (e.g. output images from gallery)
// storedName is what gets written to S.image1Name — it must be usable with /upload/image later
const _restorePreviewUrl=(url,displayName,storedName)=>{
_currentName=storedName||displayName||url;
_showLoaded(url,displayName||url);
};
return {
el:wrap,
get name(){return _currentName;},
hasFile(){return !!_currentName;},
_restorePreview,
_restorePreviewUrl,
};
}
// ── State helpers ─────────────────────────────────────────────────────────────
function loadState(){
try{ return JSON.parse(localStorage.getItem(LS_KEY)||"{}"); }catch(e){return{};}
}
function saveState(s){
try{ localStorage.setItem(LS_KEY,JSON.stringify(s)); }catch(e){}
}
// ── Active refs for event handlers ────────────────────────────────────────────
let _activeS=null, _activeShowFinal=null, _activeResetBtn=null, _activeShowError=null, _activePromptIdRef=null, _activeShowPreview=null;
// ── API events ────────────────────────────────────────────────────────────────
(()=>{
api.addEventListener("progress",(evt)=>{
const {node,value,max}=evt.detail||{};
if(!_activeS?.generating||!node) return;
const pct=max>0?Math.round(value/max*100):0;
if(_activeSetStage) _activeSetStage("Generating…",`Step ${value}/${max}`,pct);
});
api.addEventListener("execution_success",async()=>{
if(!_activeS?.generating) return;
try{
const r=await api.fetchApi(`/z_image/gallery?offset=0&limit=20&subfolder=one-node-z-image`);
const d=await r.json();
const prev=_activeS?._preRunFiles||new Set();
const v=(d.images||d.videos||[]).find(v=>!prev.has(v.key||((v.subfolder?`${v.subfolder}/`:"")+v.filename)))||(d.images||d.videos||[])[0];
if(v){
const cb=Date.now();
const url=api.apiURL(`/view?filename=${encodeURIComponent(v.filename)}&type=output&subfolder=${encodeURIComponent(v.subfolder||"")}&t=${cb}`);
_activeShowFinal?.(url,v.filename,v.subfolder||"");
}else{
_activeResetBtn?.();
}
}catch(e){
console.error("[ZImageOneNode] execution_success:",e);
_activeResetBtn?.();
}
});
api.addEventListener("execution_error",evt=>{
const errorPromptId=evt.detail?.prompt_id;
if(errorPromptId && _activePromptIdRef && errorPromptId!==_activePromptIdRef()) return;
const msg=fmtErr(evt.detail?.exception_message||evt.detail?.error||evt.detail||"Execution failed.");
_activeShowError?.(msg);
_activeResetBtn?.();
});
api.addEventListener("b_preview",evt=>{
if(!_activeS?.generating) return;
const blob=evt.detail;
if(!blob) return;
const url=URL.createObjectURL(blob);
_activeShowPreview?.(url);
});
})();
let _activeSetStage=null;
// ─────────────────────────────────────────────────────────────────────────────
app.registerExtension({
name:"ZImagePlayground.v1",
async beforeRegisterNodeDef(nodeType,nodeData){
if(nodeData.name!=="ZImageOneNode") return;
nodeType.prototype.onNodeCreated=function(){
this.color=C.bg0;this.bgcolor=C.bg0;this.resizable=false;
this.outputs=[];
if(this.widgets)this.widgets=[];
if(!window.__zimage_nodes) window.__zimage_nodes={};
const nodeId=this.id;
const cached=window.__zimage_nodes[nodeId];
if(cached){
_activeS=cached.S;
_activeShowFinal=cached.fns.showFinal;
_activeShowPreview=cached.fns.showPreview;
_activeResetBtn=cached.fns.resetBtn;
_activeShowError=cached.fns.showError;
_activeSetStage=cached.fns.setStage;
_activePromptIdRef=cached.fns.getPromptId;
this.addDOMWidget("fk_ui","div",cached.root,{
getValue(){return null;},setValue(){},serialize:false,
computeSize(){const slotH=(LiteGraph.NODE_SLOT_HEIGHT||20);const n=(self.inputs||[]).length;return[NODE_W,NODE_H+n*slotH];},
});
this.setSize([NODE_W,NODE_H]);
requestAnimationFrame(()=>{
let el=cached.root;
for(let i=0;i<6;i++){ el=el?.parentElement; if(!el)break; el.querySelectorAll("[class*='bg-node-component-surface']").forEach(b=>b.style.display="none"); }
});
return;
}
this._buildUI();
};
nodeType.prototype.onResize=function(){
const slotH=(LiteGraph.NODE_SLOT_HEIGHT||20);
const n=(this.inputs||[]).length;
this.size=[NODE_W,NODE_H+n*slotH];
};
nodeType.prototype.onDrawConnections=function(){};
nodeType.prototype.getSlotMenuOptions=function(){return[];};
nodeType.prototype._buildUI=function(){
const self=this;
const saved=loadState();
if(!self._fk_S){
self._fk_S={
// Settings
modelVariant: saved.modelVariant||"9b", // "9b" | "9b-kv"
model: saved.model||"",
textEncoder: saved.textEncoder||"",
vae: saved.vae||"",
// Pill mode
pill: saved.pill||"t2i", // "t2i" | "i2i"
// Resolution
resLabel: saved.resLabel||RES_PRESETS[0].label,
resW: saved.resW||1024,
resH: saved.resH||1024,
isCustomRes: saved.isCustomRes||false,
customW: saved.customW||1024,
customH: saved.customH||1024,
useSizeFromImage1: saved.useSizeFromImage1||false,
// Seed
randomizeSeed: saved.randomizeSeed!==undefined?saved.randomizeSeed:true,
seed: saved.seed||0,
i2iImage: saved.i2iImage||null,
i2iDenoise: saved.i2iDenoise!==undefined?saved.i2iDenoise:0.75,
i2iResizeLonger: saved.i2iResizeLonger||0,
promptI2i: saved.promptI2i||"",
advancedUI: saved.advancedUI||false,
steps: saved.steps||4,
cfg: saved.cfg!==undefined?saved.cfg:1,
sampler: saved.sampler||"res_multistep",
scheduler: saved.scheduler||"simple",
denoise: saved.denoise!==undefined?saved.denoise:1,
// Images
image1Name: saved.image1Name||null,
image2Name: saved.image2Name||null,
bgRemovalModel: saved.bgRemovalModel||"", // birefnet model for remove bg
// Prompt — shared (active pill's value) + per-pill storage
prompt: saved.prompt||"",
promptT2i: saved.promptT2i!==undefined?saved.promptT2i:((!saved.pill||saved.pill==="t2i")?saved.prompt||"":""),
// LoRAs
userLoras: saved.userLoras||[{name:"",strength:1.0},{name:"",strength:1.0},{name:"",strength:1.0}],
// Generation state
generating: false,
_pendingMeta: null,
_preRunFiles: new Set(),
soundEnabled: saved.soundEnabled!==undefined?saved.soundEnabled:true,
extLoaders: saved.extLoaders||false,
previewUrl: null,
};
}
const S=self._fk_S;
// Sync S.prompt to the active pill's slot on init (covers first load before _pillPromptKey is available)
{
const _initKey=S.pill==="i2i"?"promptI2i":"promptT2i";
S.prompt=S[_initKey]||S.prompt||"";
S[_initKey]=S.prompt;
}
let soundEnabled=S.soundEnabled;
const persist=()=>{
S.soundEnabled=soundEnabled;
saveState({
modelVariant:S.modelVariant, model:S.model,
textEncoder:S.textEncoder, vae:S.vae,
pill:S.pill,
resLabel:S.resLabel, resW:S.resW, resH:S.resH,
isCustomRes:S.isCustomRes, customW:S.customW, customH:S.customH,
randomizeSeed:S.randomizeSeed, seed:S.seed,
advancedUI:S.advancedUI, steps:S.steps, cfg:S.cfg, sampler:S.sampler, scheduler:S.scheduler, denoise:S.denoise,
image1Name:S.image1Name, image2Name:S.image2Name,
bgRemovalModel:S.bgRemovalModel,
prompt:S.prompt, promptT2i:S.promptT2i, promptI2i:S.promptI2i,
i2iImage:S.i2iImage, i2iDenoise:S.i2iDenoise, i2iResizeLonger:S.i2iResizeLonger,
userLoras:S.userLoras, soundEnabled, extLoaders:S.extLoaders,
});
};
// getW/getH: 0 = "use image size via GetImageSize node", otherwise explicit pixels
const getW=()=>{
return S.isCustomRes?snapRes(S.customW):S.resW;
};
const getH=()=>{
return S.isCustomRes?snapRes(S.customH):S.resH;
};
// Returns actual pixel dims for metadata (reads from dims badge when using image size)
const getEffectiveW=()=>{
if(activePill==="i2i"){
try{
const d=_i2iDims._getDims();
if(d.w&&d.h&&!_i2iUseOrigSize&&S.i2iResizeLonger>0){
return Math.round(d.w*(S.i2iResizeLonger/Math.max(d.w,d.h))/16)*16;
}
return d.w||0;
}catch(ex){}
return S.resW;
}
return S.isCustomRes?snapRes(S.customW):S.resW;
};
const getEffectiveH=()=>{
if(activePill==="i2i"){
try{
const d=_i2iDims._getDims();
if(d.w&&d.h&&!_i2iUseOrigSize&&S.i2iResizeLonger>0){
return Math.round(d.h*(S.i2iResizeLonger/Math.max(d.w,d.h))/16)*16;
}
return d.h||0;
}catch(ex){}
return S.resH;
}
return S.isCustomRes?snapRes(S.customH):S.resH;
};
// ── ROOT ────────────────────────────────────────────────────────────────
if(!document.getElementById("fk-styles")){
const styleEl=document.createElement("style");
styleEl.id="fk-styles";
styleEl.textContent=`
@keyframes fk-gradient {
0% { background-position: 0% 50%; }
50% { background-position: 100% 50%; }
100% { background-position: 0% 50%; }
}
@keyframes fk-error-pulse {
0% { box-shadow: inset 0 0 0 1px rgba(255,103,103,.24), 0 0 0 0 rgba(255,103,103,.10); }
50% { box-shadow: inset 0 0 0 1px rgba(255,103,103,.46), 0 0 0 6px rgba(255,103,103,0); }
100% { box-shadow: inset 0 0 0 1px rgba(255,103,103,.24), 0 0 0 0 rgba(255,103,103,0); }
}
@keyframes fk-light-sweep {
0% { left: -80%; opacity: 0; }
15% { opacity: 1; }
85% { opacity: 1; }
100% { left: 120%; opacity: 0; }
}
@keyframes fk-heart-shake {
0% { transform: scale(1); }
30% { transform: scale(1.22); }
55% { transform: scale(0.95); }
75% { transform: scale(1.1); }
100%{ transform: scale(1); }
}
.fk-heart-anim { animation: fk-heart-shake .6s ease; }
@keyframes fk-manage-flash {
0% { background: linear-gradient(135deg,rgba(26,20,60,0) 0%,rgba(15,52,96,0) 50%,rgba(83,52,131,0) 100%); }
25% { background: linear-gradient(135deg,rgba(26,20,60,.7) 0%,rgba(15,52,96,.5) 50%,rgba(83,52,131,.6) 100%); }
60% { background: linear-gradient(135deg,rgba(26,20,60,.5) 0%,rgba(15,52,96,.35) 50%,rgba(83,52,131,.45) 100%); }
100% { background: linear-gradient(135deg,rgba(26,20,60,.35) 0%,rgba(15,52,96,.2) 50%,rgba(83,52,131,.3) 100%); }
}
.fk-manage-on { background: linear-gradient(135deg,rgba(26,20,60,.35) 0%,rgba(15,52,96,.2) 50%,rgba(83,52,131,.3) 100%) !important; }
.fk-manage-flash { animation: fk-manage-flash .5s ease forwards; }
input[type=number]::-webkit-inner-spin-button,
input[type=number]::-webkit-outer-spin-button { -webkit-appearance:none; margin:0; }
input[type=number] { -moz-appearance:textfield; }
/* Nodes 2.0: hide the auto-injected node-type label rendered below the DOM widget */
.fk-root ~ .node_title, .fk-root + .node_title { display:none !important; }
`;
document.head.appendChild(styleEl);
}
const root=mk("div",{width:"100%",background:C.bg0,boxSizing:"border-box",
fontFamily:"-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif",
color:C.text,overflow:"hidden",position:"relative"});
root.classList.add("fk-root");
// Nodes 2.0 compatibility: inherit border-radius from the DOM widget wrapper so overlays
// clip correctly. The wrapper gets its radius from the litegraph node shape.
const _syncNodeRadius=()=>{
const wrapper=root.parentElement;
if(!wrapper) return;
const r=getComputedStyle(wrapper).borderRadius;
// Only apply if non-zero and different from current
const effective=(r&&r!=="0px")?r:"0px";
root.style.borderRadius=effective;
};
// Sync once after mount and observe changes (theme switches, etc.)
requestAnimationFrame(()=>{
_syncNodeRadius();
if(typeof ResizeObserver!=="undefined"){
new ResizeObserver(_syncNodeRadius).observe(root.parentElement||root);
}
});
const titleH=(typeof LiteGraph!=="undefined"&&LiteGraph.NODE_TITLE_HEIGHT)||30;
const _uiH=NODE_H-titleH-4;
const scrollEl=mk("div",{
width:"100%",height:_uiH+"px",
overflowY:"hidden",overflowX:"hidden",boxSizing:"border-box",
});
const _fwdCv=document.querySelector("canvas.litegraph");
scrollEl.addEventListener("wheel",e=>{
// Always forward wheel to canvas for zoom — node content doesn't scroll
if(_fwdCv) _fwdCv.dispatchEvent(new WheelEvent("wheel",{deltaY:e.deltaY,deltaX:e.deltaX,
clientX:e.clientX,clientY:e.clientY,ctrlKey:e.ctrlKey,metaKey:e.metaKey,
bubbles:true,cancelable:true}));
e.preventDefault();
},{passive:false});
const pad=mk("div",{padding:"12px",display:"flex",flexDirection:"column",
gap:"10px",boxSizing:"border-box",width:"100%",
height:_uiH+"px"});
// ── SETTINGS OVERLAY ──────────────────────────────────────────────────
const settingsOverlay=mk("div",{
position:"absolute",inset:"0",background:"#0a0a0a",
display:"none",flexDirection:"column",padding:"16px",
boxSizing:"border-box",zIndex:"50",borderRadius:"8px",
overflowY:"auto",
opacity:"0",transition:"opacity .22s ease, transform .22s ease",
transform:"translateY(6px)",
});
const settHdr=mk("div",{display:"flex",alignItems:"center",justifyContent:"space-between",
marginBottom:"16px",flexShrink:"0"});
const settTitle=mk("div",{fontSize:"13px",fontWeight:"700",letterSpacing:".06em",
textTransform:"uppercase",color:C.text});tx(settTitle,"Settings");
const settClose=mk("button",{background:"transparent",border:`1px solid #e05555`,
borderRadius:"6px",padding:"4px 14px",fontSize:"11px",color:"#e05555",
cursor:"pointer",outline:"none",transition:"opacity .15s"});
tx(settClose,"✕ Close");
settClose.onmouseenter=()=>settClose.style.opacity=".7";
settClose.onmouseleave=()=>settClose.style.opacity="1";
const settRefresh=mk("button",{background:"transparent",border:`1px solid ${C.border}`,
borderRadius:"6px",padding:"4px 14px",fontSize:"11px",color:C.muted,
cursor:"pointer",outline:"none",transition:"opacity .15s",marginRight:"8px"});
tx(settRefresh,"↻ Refresh models");
settRefresh.onmouseenter=()=>settRefresh.style.opacity=".7";
settRefresh.onmouseleave=()=>settRefresh.style.opacity="1";
settRefresh.onclick=()=>{ tx(settRefresh,"↻ Refreshing…"); _loadModels().then(()=>tx(settRefresh,"↻ Refresh models")); };
const settBtnRow=mk("div",{display:"flex",alignItems:"center",gap:"0"});
settBtnRow.append(settRefresh,settClose);
settHdr.append(settTitle,settBtnRow);
// ── Model dropdowns ───────────────────────────────────────────────────
const mkPathLabel=(txt)=>mk("div",{fontSize:"10px",color:C.muted,marginTop:"-2px",marginBottom:"5px",lineHeight:"1.3",whiteSpace:"nowrap",overflow:"hidden",textOverflow:"ellipsis"},{textContent:txt});
const mkModDD=(capTxt,pathTxt,defVal,onCh,autoKeyword=null)=>{
const wrap=mk("div",{minWidth:"0",overflow:"hidden"});
wrap.appendChild(cap(capTxt));
wrap.appendChild(mkPathLabel(pathTxt));
const row=mk("div",{display:"flex",gap:"4px",alignItems:"center",minWidth:"0",overflow:"hidden"});
const dd=DD([defVal||"—"],[defVal||"—"][0],v=>{onCh(v);persist();});
dd.el.style.flex="1";dd.el.style.minWidth="0";
dd._items=[defVal||""];
const origUpdate=dd.updateItems.bind(dd);
dd.updateItems=ni=>{
dd._items=ni;
const _norm=(s)=>(s||"").replace(/\\/g,"/").toLowerCase();
const currentVal=dd.value||"";
// If the current value exists in the new list, keep it — don't auto-select by keyword
const existsInList=ni.some(i=>_norm(i)===_norm(currentVal));
if(existsInList){
origUpdate(ni); // keeps current selection
} else {
// Current value not in list — try keyword auto-select, else first item
origUpdate(ni);
if(autoKeyword&&ni.length){
const kws=autoKeyword.split(',').map(k=>k.trim().toLowerCase());
const best=ni.find(f=>kws.every(k=>f.toLowerCase().includes(k)));
if(best){dd.set(best);onCh(best);persist();}
}
}
};
row.append(dd.el);
wrap.appendChild(row);
return{wrap,dd};
};
// Row 1: Model / Text Encoder / VAE
const modGrid=mk("div",{display:"grid",gridTemplateColumns:"1fr 1fr 1fr",gap:"8px",marginBottom:"4px"});
const modelF =mkModDD("Model", "/models/diffusion_models", S.model, v=>{S.model=v;if(typeof _kvUpdateNote==="function")_kvUpdateNote();},"z_image");
const teF =mkModDD("Text Encoder", "/models/text_encoders", S.textEncoder, v=>S.textEncoder=v, "qwen");
const vaeF =mkModDD("VAE", "/models/vae", S.vae, v=>S.vae=v, "");
modGrid.append(modelF.wrap,teF.wrap,vaeF.wrap);
// KV info note — shown below Model dropdown when selected model name contains "kv"
const _isBaseModel=()=>(S.model||"").toLowerCase().includes("base");
const _kvNote=mk("div",{fontSize:"9px",color:"#f0a040",marginTop:"0px",marginBottom:"4px",display:"none"});
tx(_kvNote,"⚙ KV model version detected. Settings adjusted for KV model.");
const _baseNote=mk("div",{fontSize:"9px",color:"#f0a040",marginTop:"0px",marginBottom:"8px",display:"none"});
tx(_baseNote,"⚙ Base model detected. Settings adjusted for base model.");
let _advControlsReady=false;
const _kvUpdateNote=()=>{
const name=(S.model||"").toLowerCase();
_kvNote.style.display=name.includes("kv")?"block":"none";
const isBase=name.includes("base");
_baseNote.style.display=isBase?"block":"none";
// Sync advanced control defaults when base model selected
if(!_advControlsReady) return;
if(isBase){
if(S.steps===4||S.steps===20){ S.steps=20; stepsInp.setVal(20); }
if(S.cfg===1||S.cfg===5){ S.cfg=5; cfgInp.setVal(5); }
} else {
if(S.steps===20){ S.steps=4; stepsInp.setVal(4); }
if(S.cfg===5){ S.cfg=1; cfgInp.setVal(1); }
}
};
_kvUpdateNote();
// ── Trigger words system ───────────────────────────────────────────────
// Custom trigger words stored in config.json under key "lora_triggers_custom"
// key = lora basename (no path), value = user-saved trigger string
if(!window.__fkCustomTriggers) window.__fkCustomTriggers=null; // null = not loaded yet
const _loadCustomTriggers=async()=>{
if(window.__fkCustomTriggers!==null) return window.__fkCustomTriggers;
try{
const r=await api.fetchApi("/z_image/config");
const d=await r.json();
window.__fkCustomTriggers=d.lora_triggers_custom||{};
}catch(e){ window.__fkCustomTriggers={}; }
return window.__fkCustomTriggers;
};
const _saveCustomTrigger=async(loraName,triggerText)=>{
const base=loraName.split(/[\\/]/).pop();
if(!window.__fkCustomTriggers) window.__fkCustomTriggers={};
if(triggerText.trim()) window.__fkCustomTriggers[base]=triggerText.trim();
else delete window.__fkCustomTriggers[base];
try{
await api.fetchApi("/z_image/config",{
method:"POST",headers:{"Content-Type":"application/json"},
body:JSON.stringify({lora_triggers_custom:window.__fkCustomTriggers}),
});
}catch(e){ console.warn("[ZImageOneNode] save custom trigger:",e); }
};
const _getCustomTrigger=(loraName)=>{
if(!loraName||loraName==="none"||!window.__fkCustomTriggers) return "";
const base=loraName.split(/[\\/]/).pop();
return window.__fkCustomTriggers[base]||"";
};
// ── Preferences ───────────────────────────────────────────────────────
const prefTitle=mk("div",{fontSize:"10px",fontWeight:"700",letterSpacing:".1em",
textTransform:"uppercase",color:C.muted,padding:"10px 0 2px",
borderBottom:`1px solid ${C.border}`,marginBottom:"4px"});
tx(prefTitle,"Preferences");
const soundToggle=Toggle("Notification sound on complete",soundEnabled,v=>{soundEnabled=v;persist();});
const advUIToggle=Toggle("Advanced control (steps, CFG, sampler…)",S.advancedUI,v=>{S.advancedUI=v;persist();_advRefresh();},"#6450b4");
const _slotH=LiteGraph.NODE_SLOT_HEIGHT||20;
const _extInputNames=["model","clip","vae"];
const _extInputColors=["#b39ddb","#80cbc4","#ef9a9a"];
const _applyExtLoaders=(enabled)=>{
const node=app.graph.getNodeById(self.id)||self;
if(!node) return;
if(enabled){
const existing=(node.inputs||[]).filter(i=>_extInputNames.includes(i.name));
if(existing.length===0){
_extInputNames.forEach((name,i)=>{
const type=i===0?"MODEL":i===1?"CLIP":"VAE";
node.addInput(name,type);
const slot=node.inputs[node.inputs.length-1];
if(slot) slot.color_on=_extInputColors[i];
});
}
const n=(node.inputs||[]).length;
node.size=[NODE_W, NODE_H+n*_slotH];
node.setDirtyCanvas(true,true);
} else {
if(node.inputs&&node.inputs.length>0){
for(let i=node.inputs.length-1;i>=0;i--){
if(_extInputNames.includes(node.inputs[i].name)) node.removeInput(i);
}
}
node.size=[NODE_W, NODE_H];
node.setDirtyCanvas(true,true);
}
};
const extLoadersToggle=Toggle("External model/clip/vae inputs (for GGUF etc.)",S.extLoaders||false,v=>{
S.extLoaders=v;persist();
_applyExtLoaders(v);
_refreshExtInputUI();
});
settingsOverlay.append(settHdr,modGrid,_kvNote,_baseNote,prefTitle,soundToggle.el,advUIToggle.el,extLoadersToggle.el);
// ── Overlay helpers ───────────────────────────────────────────────────
const openOverlay=(el)=>{
el.style.display="flex";
el.offsetHeight;
el.style.opacity="1";
el.style.transform="translateY(0)";
};
const closeOverlayFade=(el,cb)=>{
el.style.opacity="0";
el.style.transform="translateY(6px)";
setTimeout(()=>{el.style.display="none";if(cb)cb();},220);
};
// ── TOP BAR ──────────────────────────────────────────────────────────
const topBar=mk("div",{display:"flex",justifyContent:"space-between",alignItems:"center",gap:"6px",marginBottom:"-2px"});
// Gallery button (placeholder — will be wired when gallery is implemented)
const galleryBtn=mk("button",{
background:"linear-gradient(90deg,#1a1a2e,#0f3460,#533483)",
border:"1.5px solid rgba(255,255,255,.15)",
borderRadius:"6px",padding:"4px 11px",cursor:"pointer",color:"#e0e0ff",
fontSize:"11px",fontWeight:"700",display:"flex",alignItems:"center",gap:"5px",
transition:"opacity .15s, filter .15s",outline:"none",
});
const galleryIcon=mk("span",{fontSize:"12px"});tx(galleryIcon,"▦");
const galleryLbl=mk("span");tx(galleryLbl,"Gallery");
galleryBtn.append(galleryIcon,galleryLbl);
galleryBtn.onmouseenter=()=>galleryBtn.style.filter="brightness(1.3)";
galleryBtn.onmouseleave=()=>galleryBtn.style.filter="";
galleryBtn.onclick=()=>{}; // TODO: gallery overlay
// Simple credit
const creditText=mk("div",{fontSize:"9px",color:C.muted,fontStyle:"italic",position:"absolute",bottom:"8px",left:"16px",right:"16px",textAlign:"center",pointerEvents:"none"});
tx(creditText,"node by Adeliox");
// Settings button
const settingsBtn=mk("button",{
background:"transparent",border:`1.5px solid ${C.borderH}`,
borderRadius:"6px",padding:"4px 11px",
cursor:"pointer",color:C.muted,fontSize:"11px",fontWeight:"700",
display:"flex",alignItems:"center",gap:"5px",
transition:"opacity .15s",outline:"none",
});
const settGear=mk("span",{fontSize:"12px",transition:"transform .3s",lineHeight:"1"});tx(settGear,"⚙");
const settLblEl=mk("span");tx(settLblEl,"Settings");
settingsBtn.append(settGear,settLblEl);
settingsBtn.onmouseenter=()=>{settingsBtn.style.borderColor=C.text;settingsBtn.style.color=C.text;settGear.style.transform="rotate(30deg)";};
settingsBtn.onmouseleave=()=>{settingsBtn.style.borderColor=C.borderH;settingsBtn.style.color=C.muted;settGear.style.transform="";};
const _refreshExtInputUI=()=>{
const n=app.graph.getNodeById(self.id);
const isConn=(name)=>{
if(!n||!n.inputs) return false;
const slot=n.inputs.find(i=>i.name===name);
return slot&&slot.link!=null;
};
const dim=(wrap,connected)=>{
wrap.style.opacity=connected?"0.4":"1";
wrap.style.pointerEvents=connected?"none":"";
wrap.title=connected?"Connected externally — disconnect to use dropdown":"";
};
dim(modelF.wrap,isConn("model"));
dim(teF.wrap, isConn("clip"));
dim(vaeF.wrap, isConn("vae"));
};
settingsBtn.onclick=e=>{e.stopPropagation();_refreshExtInputUI();openOverlay(settingsOverlay);};
settClose.onclick=()=>closeOverlayFade(settingsOverlay);
// Fullscreen node button
const fsNodeBtn=mk("button",{
background:"transparent",border:`1.5px solid ${C.borderH}`,
borderRadius:"6px",padding:"4px 8px",
cursor:"pointer",color:C.muted,
display:"flex",alignItems:"center",gap:"4px",
transition:"opacity .15s, border-color .15s, color .15s",outline:"none",
});
fsNodeBtn.title="Fullscreen";
fsNodeBtn.innerHTML=``;
fsNodeBtn.onmouseenter=()=>{fsNodeBtn.style.borderColor=LIME;fsNodeBtn.style.color=LIME;};
fsNodeBtn.onmouseleave=()=>{fsNodeBtn.style.borderColor=C.borderH;fsNodeBtn.style.color=C.muted;};
let _inFullscreen=false;
let _fsNodeOverlay=null;
let _rootOrigParent=null,_rootOrigNextSibling=null;
const _enterFullscreen=()=>{
if(_inFullscreen) return;
if(!_fsNodeOverlay){
_fsNodeOverlay=mk("div",{
position:"fixed",inset:"0",zIndex:"99990",
background:"rgba(6,6,8,.97)",
display:"none",flexDirection:"column",
alignItems:"center",justifyContent:"center",
boxSizing:"border-box",overflow:"hidden",
});
// No keydown handler — Esc is blocked globally via capture handler below
document.body.appendChild(_fsNodeOverlay);
}
_rootOrigParent=root.parentNode;
_rootOrigNextSibling=root.nextSibling;
root.style.width=NODE_W+"px";
root.style.height=NODE_H+"px";
root.style.overflow="hidden";
root.style.borderRadius="0";
root.style.position="absolute";
root.style.top="0";root.style.left="0";root.style.margin="0";
const _vw=window.innerWidth,_vh=window.innerHeight;
const _scale=Math.min(_vw/NODE_W,_vh/NODE_H)*0.97;
root.style.transformOrigin="top left";
root.style.transform=`scale(${_scale})`;
const _scW=Math.round(NODE_W*_scale),_scH=Math.round(NODE_H*_scale);
const _scWrap=mk("div",{width:_scW+"px",height:_scH+"px",position:"relative",flexShrink:"0",overflow:"hidden"});
_scWrap.appendChild(root);
_fsNodeOverlay.appendChild(_scWrap);
_fsNodeOverlay._scWrap=_scWrap;
_fsNodeOverlay.style.display="flex";
_fsNodeOverlay.setAttribute("tabindex","-1");
_fsNodeOverlay.focus();
fsNodeBtn.innerHTML=``;
_inFullscreen=true;
};
const _exitFullscreen=()=>{
if(!_inFullscreen) return;
if(_rootOrigParent){
if(_rootOrigNextSibling) _rootOrigParent.insertBefore(root,_rootOrigNextSibling);
else _rootOrigParent.appendChild(root);
}
root.style.position="";root.style.inset="";root.style.width="100%";root.style.height="";
root.style.borderRadius="";root.style.overflow="hidden";
root.style.transform="";root.style.transformOrigin="";root.style.margin="";
root.style.top="";root.style.left="";
scrollEl.style.height=_uiH+"px";
if(_fsNodeOverlay._scWrap) _fsNodeOverlay._scWrap.remove();
_fsNodeOverlay._scWrap=null;
_fsNodeOverlay.style.display="none";
fsNodeBtn.innerHTML=``;
_inFullscreen=false;
};
fsNodeBtn.onclick=()=>{ if(_inFullscreen) _exitFullscreen(); else _enterFullscreen(); };
const topBarLeft=mk("div",{display:"flex",gap:"3px",alignItems:"center",flexWrap:"nowrap"});
const topBarRight=mk("div",{display:"flex",gap:"6px",alignItems:"center",flexShrink:"0"});
topBarRight.append(galleryBtn,settingsBtn,fsNodeBtn);
topBar.append(topBarLeft,topBarRight);
// ── PILLS ─────────────────────────────────────────────────────────────
let activePill=S.pill||"t2i";
const pillT2I =Pill("ZIMG", activePill==="t2i", ()=>setPill("t2i"));
const pillI2I =Pill("Z-I2I", activePill==="i2i", ()=>setPill("i2i"));
topBarLeft.append(pillT2I,pillI2I);
let _promptTARef=null; // set after promptTA is created
const _pillPromptKey=(p)=>p==="i2i"?"promptI2i":"promptT2i";
function setPill(p){
if(_promptTARef&&activePill){
S[_pillPromptKey(activePill)]=_promptTARef.value;
}
activePill=p;S.pill=p;
S.prompt=S[_pillPromptKey(p)];
if(_promptTARef){ _promptTARef.value=S.prompt; if(typeof _promptOvTA!=="undefined"&&_promptOvTA) _promptOvTA.value=S.prompt; }
persist();
[pillT2I,pillI2I].forEach(b=>{
const isActive=
(b===pillT2I&&p==="t2i")||
(b===pillI2I&&p==="i2i");
b.style.background=isActive?LIME:C.bg2;
b.style.color=isActive?"#111":C.text;
b.style.borderColor=isActive?LIME:C.border;
b.style.fontWeight=isActive?"700":"400";
});
updatePillVisibility();
updateSizeControls();
}
// ── MAIN ROW ─────────────────────────────────────────────────────────
const mainRow=mk("div",{display:"flex",gap:"12px",alignItems:"stretch",flex:"1",minHeight:"0"});
const leftPanel=mk("div",{display:"flex",flexDirection:"column",gap:"7px",
width:"300px",flexShrink:"0"});
// ── Node-local fullscreen overlay ────────────────────────────────────
let _nodeFsOv=null;
const _initNodeFsOverlay=()=>{
if(_nodeFsOv) return _nodeFsOv;
const ov=mk("div",{position:"absolute",inset:"0",zIndex:"9999",
background:"rgba(28,28,32,.97)",display:"none",flexDirection:"column",
alignItems:"stretch",borderRadius:"inherit",overflow:"hidden"});
const _nfTopBar=mk("div",{display:"flex",alignItems:"center",
padding:"10px 12px",gap:"10px",flexShrink:"0",
background:"linear-gradient(to bottom,rgba(0,0,0,.7),rgba(0,0,0,0))",
position:"absolute",top:"0",left:"0",right:"0",zIndex:"3"});
const _nfName=mk("div",{fontSize:"11px",fontWeight:"700",color:"#fff",
overflow:"hidden",textOverflow:"ellipsis",whiteSpace:"nowrap",
letterSpacing:".01em",textAlign:"center",width:"100%",padding:"0 36px",boxSizing:"border-box"});
const _nfCloseBtn=mk("button",{width:"26px",height:"26px",borderRadius:"50%",
position:"absolute",right:"12px",top:"10px",
background:"rgba(255,255,255,.08)",border:"1px solid rgba(255,255,255,.15)",
color:"rgba(255,255,255,.85)",fontSize:"10px",cursor:"pointer",
display:"flex",alignItems:"center",justifyContent:"center",padding:"0",outline:"none"});
_nfCloseBtn.innerHTML=``;
_nfCloseBtn.onmouseenter=()=>{_nfCloseBtn.style.background="rgba(255,255,255,.2)";};
_nfCloseBtn.onmouseleave=()=>{_nfCloseBtn.style.background="rgba(255,255,255,.08)";};
_nfTopBar.append(_nfName,_nfCloseBtn);
const _nfMediaWrap=mk("div",{width:"100%",height:"100%",display:"flex",
alignItems:"center",justifyContent:"center",padding:"48px 16px 16px",boxSizing:"border-box"});
const _nfPillRow=mk("div",{display:"flex",gap:"6px",justifyContent:"center",
flexWrap:"wrap",marginTop:"8px"});
const _nfPill=(t)=>{
const p=mk("div",{fontSize:"9px",color:"rgba(255,255,255,.55)",fontWeight:"600",
border:"1px solid rgba(255,255,255,.15)",borderRadius:"20px",
padding:"2px 9px",letterSpacing:".04em",whiteSpace:"nowrap",background:"rgba(255,255,255,.05)"});
tx(p,t);return p;
};
const _nfClose=()=>{
if(ov._cleanupCmp){ov._cleanupCmp();ov._cleanupCmp=null;}
ov.style.display="none";
const img=_nfMediaWrap.querySelector("img");
if(img) img.src="";
_nfMediaWrap.innerHTML="";_nfPillRow.innerHTML="";
// Restore preview action buttons
if(typeof previewUseWrap!=="undefined") previewUseWrap.style.visibility="";
if(typeof previewDelBtn!=="undefined") previewDelBtn.style.visibility="";
};
_nfCloseBtn.onclick=_nfClose;
ov.addEventListener("keydown",e=>{if(e.key==="Escape")_nfClose();});
ov.setAttribute("tabindex","-1");
ov._close=_nfClose;
ov.append(_nfTopBar,_nfMediaWrap);
ov._open=(type,src,name,opts)=>{
_nfMediaWrap.innerHTML="";_nfPillRow.innerHTML="";
tx(_nfName,name||"");
// Hide preview action buttons while fullscreen overlay is open (image-only mode)
if(type==="image"){
if(typeof previewUseWrap!=="undefined"&&previewUseWrap) previewUseWrap.style.visibility="hidden";
if(typeof previewDelBtn!=="undefined"&&previewDelBtn) previewDelBtn.style.visibility="hidden";
}
if(type==="image"){
const outer=mk("div",{display:"flex",flexDirection:"column",alignItems:"center",
justifyContent:"center",gap:"8px",width:"100%",height:"100%"});
const img=mk("img",{maxWidth:"100%",maxHeight:"calc(100% - 56px)",objectFit:"contain",
borderRadius:"8px",boxShadow:"0 4px 24px rgba(0,0,0,.5)",display:"block"});
img.src=src;
img.onload=()=>{
_nfPillRow.innerHTML="";
_nfPillRow.appendChild(_nfPill(`${img.naturalWidth}×${img.naturalHeight} px`));
};
outer.append(img,_nfPillRow);
_nfMediaWrap.appendChild(outer);
} else if(type==="comparer"){
// Full-screen before/after comparer with "Use as input" in top-right
const {genSrc,baseSrc,onUse}=opts||{};
const cWrap=mk("div",{position:"relative",width:"100%",height:"100%",
overflow:"hidden",borderRadius:"8px",cursor:"col-resize",userSelect:"none",
minHeight:"0",flex:"1"});
const cBase=mk("img",{position:"absolute",inset:"0",width:"100%",height:"100%",objectFit:"contain"});
cBase.src=baseSrc||"";
const cGen=mk("div",{position:"absolute",top:"0",left:"0",bottom:"0",overflow:"hidden",width:"100%"});
const cGenImg=mk("img",{position:"absolute",top:"0",left:"0",height:"100%",objectFit:"contain"});
cGen.appendChild(cGenImg);
cGenImg.src=genSrc||"";
const cLine=mk("div",{position:"absolute",top:"0",bottom:"0",width:"2px",
background:LIME,left:"calc(100% - 1px)",boxShadow:"0 0 8px rgba(240,255,65,.5)",
display:"flex",flexDirection:"column",alignItems:"center",justifyContent:"center",zIndex:"4"});
const cHandle=mk("div",{width:"30px",height:"30px",borderRadius:"50%",background:LIME,
border:"2px solid #111",flexShrink:"0",display:"flex",alignItems:"center",justifyContent:"center",
boxShadow:"0 2px 10px rgba(0,0,0,.7)",pointerEvents:"none"});
cHandle.innerHTML=``;
cLine.appendChild(cHandle);
const _fsSetPct=(pct)=>{
pct=Math.max(0,Math.min(100,pct));
cGen.style.width=pct+"%";
cLine.style.left=`calc(${pct}% - 1px)`;
cGenImg.style.width=(cWrap.offsetWidth||900)+"px";
};
let _fsDrag=false;
cWrap.addEventListener("mousedown",e=>{_fsDrag=true;e.preventDefault();});
const _fsMM=(e)=>{if(!_fsDrag)return;const r=cWrap.getBoundingClientRect();_fsSetPct((e.clientX-r.left)/r.width*100);};
const _fsMU=()=>{_fsDrag=false;};
document.addEventListener("mousemove",_fsMM);
document.addEventListener("mouseup",_fsMU);
cWrap.addEventListener("touchstart",()=>{_fsDrag=true;},{passive:true});
cWrap.addEventListener("touchmove",e=>{if(!_fsDrag)return;const r=cWrap.getBoundingClientRect();_fsSetPct((e.touches[0].clientX-r.left)/r.width*100);},{passive:true});
cWrap.addEventListener("touchend",()=>{_fsDrag=false;});
ov._cleanupCmp=()=>{document.removeEventListener("mousemove",_fsMM);document.removeEventListener("mouseup",_fsMU);};
cWrap.append(cBase,cGen,cLine);
_nfMediaWrap.style.position="relative";
_nfMediaWrap.style.padding="0"; // comparer fills full area
_nfMediaWrap.appendChild(cWrap);
cGenImg.onload=()=>{ _fsSetPct(100); };
// "Use as input" button — top-right corner of the overlay
if(onUse){
const useBtn=mk("button",{
position:"absolute",top:"54px",right:"14px",zIndex:"10",
background:"rgba(20,20,20,.82)",color:"rgba(255,255,255,.82)",
border:"1px solid rgba(255,255,255,.18)",
borderRadius:"6px",padding:"5px 12px",fontSize:"10px",fontWeight:"600",
cursor:"pointer",outline:"none",whiteSpace:"nowrap",
backdropFilter:"blur(4px)",letterSpacing:".04em",
boxShadow:"0 2px 8px rgba(0,0,0,.5)",
transition:"background .15s, color .15s, border-color .15s",
});
tx(useBtn,"Use as input");
useBtn.onmouseenter=()=>{useBtn.style.background="rgba(40,40,40,.95)";useBtn.style.color="#fff";useBtn.style.borderColor="rgba(255,255,255,.35)";};
useBtn.onmouseleave=()=>{useBtn.style.background="rgba(20,20,20,.82)";useBtn.style.color="rgba(255,255,255,.82)";useBtn.style.borderColor="rgba(255,255,255,.18)";};
useBtn.onclick=(e)=>{e.stopPropagation();onUse();_nfClose();};
ov.appendChild(useBtn);
}
}
ov.style.display="flex";ov.focus();
};
root.appendChild(ov);
_nodeFsOv=ov;
return ov;
};
_fkActiveFsFactory=_initNodeFsOverlay;
// ── I2I PANEL ─────────────────────────────────────────────────────────
const i2iPanel=mk("div",{display:"none",flexDirection:"column",gap:"5px"});
// _i2iDims: same helper pattern as _fsTargetDims
const _i2iDims=(()=>{
let _w=0,_h=0;
const el=mk("div",{
fontSize:"9px",fontWeight:"700",letterSpacing:".04em",
textAlign:"center",cursor:"pointer",display:"none",
borderRadius:"5px",padding:"2px 6px",boxSizing:"border-box",
background:C.bg3,border:`1px solid ${C.borderH}`,color:LIME,
background:"rgba(240,255,65,.13)",borderColor:"rgba(240,255,65,.5)",
});
el._getDims=()=>({w:_w,h:_h});
el._set=(w,h)=>{ _w=w;_h=h; if(w&&h){ tx(el,`${w}×${h}`);el.style.display="block"; } else el.style.display="none"; };
return el;
})();
let _i2iUseOrigSize=S.i2iResizeLonger<=0; // true = lime badge, locked; false = unlocked
const _i2iResizePreview=mk("span",{fontSize:"9px",fontWeight:"700",color:LIME,letterSpacing:".03em",whiteSpace:"nowrap"});
const _i2iResizeLongerInp=NI("px",S.i2iResizeLonger||1024,64,8192,8,v=>{
S.i2iResizeLonger=Math.round(v)||1024;
_i2iResizeUpdatePreview();
persist();
},52);
const _i2iUseOrigNote=mk("div",{fontSize:"8px",color:LIME,display:"none",marginTop:"0"});
tx(_i2iUseOrigNote,"Using size from Input image.");
const _i2iResizeRow=mk("div",{display:"none",alignItems:"center",gap:"6px",marginTop:"2px"});
const _i2iResizeRowLbl=mk("span",{fontSize:"8px",color:C.muted,whiteSpace:"nowrap",flexShrink:"0"});
tx(_i2iResizeRowLbl,"Scale by longer side");
_i2iResizeRow.append(_i2iResizeRowLbl,_i2iResizeLongerInp,_i2iResizePreview);
const _i2iResizeUpdatePreview=()=>{
const dims=_i2iDims._getDims();
if(!_i2iUseOrigSize&&dims.w&&dims.h&&S.i2iResizeLonger>0){
const scale=S.i2iResizeLonger/Math.max(dims.w,dims.h);
const nw=Math.round(dims.w*scale/16)*16;
const nh=Math.round(dims.h*scale/16)*16;
tx(_i2iResizePreview,`→ ${nw}×${nh}`);
} else {
tx(_i2iResizePreview,"");
}
};
const _i2iApplyState=()=>{
const dims=_i2iDims._getDims();
if(!dims.w||!dims.h) return;
if(_i2iUseOrigSize){
_i2iDims.style.color=LIME;
_i2iDims.style.background="rgba(240,255,65,.13)";
_i2iDims.style.borderColor="rgba(240,255,65,.5)";
_i2iUseOrigNote.style.display="block";
_i2iResizeRow.style.opacity="0.35";
_i2iResizeRow.style.pointerEvents="none";
_i2iResizeLongerInp._inp.disabled=true;
} else {
_i2iDims.style.color=C.text;
_i2iDims.style.background=C.bg3;
_i2iDims.style.borderColor=C.borderH;
_i2iUseOrigNote.style.display="none";
_i2iResizeRow.style.opacity="1";
_i2iResizeRow.style.pointerEvents="auto";
_i2iResizeLongerInp._inp.disabled=false;
if(S.i2iResizeLonger<=0){ S.i2iResizeLonger=_i2iResizeLongerInp.numVal||1024; persist(); }
}
_i2iResizeUpdatePreview();
};
_i2iDims.onclick=()=>{
const dims=_i2iDims._getDims();
if(!dims.w||!dims.h) return;
_i2iUseOrigSize=!_i2iUseOrigSize;
if(_i2iUseOrigSize){ S.i2iResizeLonger=0; persist(); }
_i2iApplyState();
};
const i2iSlotRow=mk("div",{display:"flex",gap:"10px",alignItems:"flex-start"});
const i2iSlotCard=mk("div",{display:"flex",flexDirection:"column",gap:"3px",alignItems:"center"});
const i2iSlot=ImgSlot(false,(name)=>{
S.i2iImage=name||null;
if(!name){
_i2iDims._set(0,0);
_i2iResizeRow.style.display="none";
_i2iUseOrigNote.style.display="none";
_i2iUseOrigSize=true;
S.i2iResizeLonger=0;
}
if(name){ i2iSlot.el.style.borderColor=""; tx(i2iSlotLbl,"Input Image"); i2iSlotLbl.style.color=C.muted; }
persist();
},(w,h)=>{
if(w&&h){
_i2iDims._set(w,h);
_i2iResizeRow.style.display="flex";
_i2iApplyState();
} else {
_i2iDims._set(0,0);
_i2iResizeRow.style.display="none";
_i2iUseOrigNote.style.display="none";
}
});
const i2iSlotLbl=mk("div",{fontSize:"8px",fontWeight:"700",color:C.muted,textTransform:"uppercase",letterSpacing:".07em",textAlign:"center"});
tx(i2iSlotLbl,"Input Image");
i2iSlotCard.append(i2iSlot.el,i2iSlotLbl,_i2iDims);
i2iSlotRow.append(i2iSlotCard);
// Denoise slider — 0 = no change, 100 = full generation
const i2iSliderWrap=mk("div",{display:"flex",flexDirection:"column",gap:"2px"});
const i2iSliderHdr=mk("div",{display:"flex",alignItems:"center",justifyContent:"space-between"});
const i2iSliderLbl=mk("div",{fontSize:"8px",fontWeight:"700",color:C.muted,letterSpacing:".05em",textTransform:"uppercase"});
tx(i2iSliderLbl,"Change strength");
const i2iSliderVal=mk("div",{fontSize:"9px",fontWeight:"700",color:LIME});
tx(i2iSliderVal,`${Math.round((S.i2iDenoise||0.75)*100)}%`);
i2iSliderHdr.append(i2iSliderLbl,i2iSliderVal);
const i2iSlider=mk("input",{
width:"100%",cursor:"pointer",accentColor:LIME,height:"18px",display:"block",
},{type:"range",min:"0",max:"100",step:"1",value:String(Math.round((S.i2iDenoise||0.75)*100))});
const _i2iSliderSet=(pct)=>{
pct=Math.max(0,Math.min(100,pct));
i2iSlider.value=String(pct);
S.i2iDenoise=pct/100;
tx(i2iSliderVal,`${pct}%`);
persist();
};
i2iSlider.oninput=()=>_i2iSliderSet(parseInt(i2iSlider.value));
i2iSlider.addEventListener("mouseup",()=>i2iSlider.blur());
i2iSlider.addEventListener("touchend",()=>i2iSlider.blur());
i2iSlider.addEventListener("wheel",(e)=>{
e.preventDefault();e.stopPropagation();
_i2iSliderSet(parseInt(i2iSlider.value)+(e.deltaY<0?1:-1)*(e.shiftKey?10:1));
},{passive:false});
i2iSliderWrap.append(i2iSliderHdr,i2iSlider);
i2iPanel.append(i2iSlotRow,_i2iUseOrigNote,_i2iResizeRow,i2iSliderWrap);
if(S.i2iImage) i2iSlot._restorePreview(S.i2iImage);
// ── (paint/inpaint/sketch features removed) ──────────────────────────
// ── RESOLUTION ───────────────────────────────────────────────────────
const resSect=mk("div",{display:"flex",flexDirection:"column",gap:"4px"});
const _sizeLabelRow=mk("div",{display:"flex",alignItems:"center",gap:"6px"});
_sizeLabelRow.appendChild(cap("Size"));
resSect.appendChild(_sizeLabelRow);
const resDD=DD(RES_PRESETS.map(p=>p.label),S.resLabel,val=>{
const p=RES_PRESETS.find(r=>r.label===val);
if(p&&p.w>0){
S.resLabel=val;S.resW=p.w;S.resH=p.h;S.isCustomRes=false;
customResRow.style.display="none";
}else{
S.resLabel=val;S.isCustomRes=true;
customResRow.style.display="flex";
}
persist();_arRefreshDimsPreview();
});
resSect.appendChild(resDD.el);
const customResRow=mk("div",{display:S.isCustomRes?"flex":"none",gap:"5px",alignItems:"center",marginTop:"5px"});
// Swap W↔H button
const _arSwapBtn=mk("button",{
width:"18px",height:"22px",borderRadius:"4px",flexShrink:"0",
background:"transparent",border:`1px solid ${C.border}`,
color:C.muted,cursor:"pointer",outline:"none",padding:"0",
display:"flex",alignItems:"center",justifyContent:"center",
transition:"border-color .15s,color .15s",
});
_arSwapBtn.innerHTML=``;
_arSwapBtn.title="Swap W and H";
_arSwapBtn.onmouseenter=()=>{_arSwapBtn.style.borderColor=LIME;_arSwapBtn.style.color=LIME;};
_arSwapBtn.onmouseleave=()=>{_arSwapBtn.style.borderColor=C.border;_arSwapBtn.style.color=C.muted;};
_arSwapBtn.onclick=()=>{
const oldW=wInp.numVal||S.customW;
const oldH=hInp.numVal||S.customH;
S.customW=Math.max(1,Math.round(oldH));
S.customH=Math.max(1,Math.round(oldW));
wInp.setVal(S.customW);hInp.setVal(S.customH);
if(_arLocked&&_arRatio) _arRatio=1/_arRatio;
persist();_arRefreshDimsPreview();
};
// Aspect ratio lock
let _arLocked=false;
let _arRatio=null;
const _arLockBtn=mk("button",{
width:"22px",height:"22px",borderRadius:"5px",flexShrink:"0",
background:"transparent",border:`1px solid ${C.border}`,
color:C.muted,cursor:"pointer",outline:"none",padding:"0",
display:"flex",alignItems:"center",justifyContent:"center",
transition:"border-color .15s,color .15s,background .15s",
});
// Flat SVG lock icons
const _lockIconOpen=``;
const _lockIconClosed=``;
_arLockBtn.innerHTML=_lockIconOpen;
const _arSetLocked=(locked)=>{
_arLocked=locked;
_arLockBtn.style.borderColor=locked?LIME:C.border;
_arLockBtn.style.color=locked?LIME:C.muted;
_arLockBtn.style.background=locked?"rgba(240,255,65,.08)":"transparent";
_arLockBtn.innerHTML=locked?_lockIconClosed:_lockIconOpen;
};
// Get best available aspect ratio: current W/H
const _arGetRatio=()=>{
const cw=wInp.numVal||S.customW||1024;
const ch=hInp.numVal||S.customH||1024;
return cw/ch;
};
_arLockBtn.title="Lock aspect ratio";
const snap8=(v)=>Math.max(16,Math.round(v/16)*16);
// W/H inputs store raw values — no snapping in the field itself.
// snap8 is applied only in getEffectiveW/H (workflow) and in the preview label.
const _deactivateImgSize=()=>{};
const wInp=NI("w",S.customW,1,8192,1,v=>{
S.customW=Math.max(1,Math.round(v));
if(_arLocked&&_arRatio){
S.customH=Math.max(1,Math.round(S.customW/_arRatio));
hInp.setVal(S.customH);
}
_deactivateImgSize();
persist(); _arRefreshDimsPreview();
},"80px");
const xLbl=mk("span",{fontSize:"10px",color:C.muted,flexShrink:"0"});tx(xLbl,"×");
const hInp=NI("h",S.customH,1,8192,1,v=>{
S.customH=Math.max(1,Math.round(v));
if(_arLocked&&_arRatio){
S.customW=Math.max(1,Math.round(S.customH*_arRatio));
wInp.setVal(S.customW);
}
_deactivateImgSize();
persist(); _arRefreshDimsPreview();
},"80px");
wInp._inp.addEventListener("keydown",e=>{ if(e.key==="Tab"&&!e.shiftKey){ e.preventDefault(); hInp._inp.focus(); hInp._inp.select(); } });
hInp._inp.addEventListener("keydown",e=>{ if(e.key==="Tab"&&e.shiftKey){ e.preventDefault(); wInp._inp.focus(); wInp._inp.select(); } });
_arLockBtn.onclick=()=>{
const nowLocked=!_arLocked;
if(nowLocked) _arRatio=_arGetRatio();
_arSetLocked(nowLocked);
_arRefreshDimsPreview();
};
// Live dims preview — shows actual W×H after snap, lime colored
const _arDimsPreview=mk("div",{
fontSize:"9px",color:LIME,fontWeight:"600",whiteSpace:"nowrap",
letterSpacing:".02em",opacity:"0.85",alignSelf:"center",
});
const _arRefreshDimsPreview=()=>{
const rawW=S.isCustomRes?(wInp.numVal||S.customW||1024):S.resW;
const rawH=S.isCustomRes?(hInp.numVal||S.customH||1024):S.resH;
const sw=snap8(rawW), sh=snap8(rawH);
// Show snapped dims (what will actually generate); dim if same as raw
const changed=(sw!==rawW||sh!==rawH);
tx(_arDimsPreview,`→ ${sw}×${sh}`);
_arDimsPreview.style.opacity=changed?"1":"0.5";
_arDimsPreview.title=changed?`Input: ${rawW}×${rawH} → snapped to ${sw}×${sh}`:"";
};
_arRefreshDimsPreview();
customResRow.append(wInp,_arSwapBtn,hInp,_arLockBtn,_arDimsPreview);
resSect.appendChild(customResRow);
function updateSizeControls(){
customResRow.style.display=S.isCustomRes?"flex":"none";
}
const seedInp=NI("seed",S.seed||0,0,999999999999,1,v=>{ S.seed=Math.round(v)||0; persist(); },"90px");
// ── Advanced control panel ────────────────────────────────────────────
let SAMPLERS=["euler","euler_cfg_pp","euler_ancestral","euler_ancestral_cfg_pp","heun","heunpp2","exp_heun_2_x0","exp_heun_2_x0_sde","dpm_2","dpm_2_ancestral","lms","dpm_fast","dpm_adaptive","dpmpp_2s_ancestral","dpmpp_2s_ancestral_cfg_pp","dpmpp_sde","dpmpp_sde_gpu","dpmpp_2m","dpmpp_2m_cfg_pp","dpmpp_2m_sde","dpmpp_2m_sde_gpu","dpmpp_2m_sde_heun","dpmpp_2m_sde_heun_gpu","dpmpp_3m_sde","dpmpp_3m_sde_gpu","ddpm","lcm","ipndm","ipndm_v","deis","res_multistep","res_multistep_cfg_pp","res_multistep_ancestral","res_multistep_ancestral_cfg_pp","gradient_estimation","gradient_estimation_cfg_pp","er_sde","seeds_2","seeds_3","sa_solver","sa_solver_pece","ddim","uni_pc","uni_pc_bh2"];
let SCHEDULERS=["simple","sgm_uniform","karras","exponential","ddim_uniform","beta","normal","linear_quadratic","kl_optimal"];
const ADV_BORDER="rgba(100,80,180,.5)";
const ADV_BG="rgba(26,20,60,.55)";
const ADV_LABEL="rgba(160,140,220,.7)";
const advPanel=mk("div",{
display:"none",flexDirection:"column",gap:"4px",
border:`1px solid ${ADV_BORDER}`,borderRadius:"6px",
padding:"5px",background:ADV_BG,
});
const _advNIStyle=(ni)=>{ ni._inp.style.fontSize="9px"; ni._inp.style.padding="1px 2px"; ni._inp.style.height="22px"; return ni; };
const stepsInp=_advNIStyle(NI("steps",S.steps,1,150,1,v=>{S.steps=Math.round(v)||4;persist();},"100%"));
const cfgInp=_advNIStyle(NI("cfg",S.cfg,0,30,0.1,v=>{S.cfg=parseFloat(v.toFixed(2));persist();},"100%"));
_advControlsReady=true;
const _advDDStyle=(dd)=>{
const trig=dd.el.querySelector("div");
if(trig){ trig.style.height="22px"; trig.style.fontSize="9px"; trig.style.padding="0 6px"; }
return dd;
};
const samplerDD=_advDDStyle(DD(SAMPLERS,S.sampler,v=>{S.sampler=v;persist();}));
const schedulerDD=_advDDStyle(DD(SCHEDULERS,S.scheduler,v=>{S.scheduler=v;persist();}));
// Fetch available samplers/schedulers from ComfyUI (includes custom node samplers)
api.fetchApi("/object_info/KSampler").then(r=>r.json()).then(d=>{
const info=d?.KSampler?.input?.required;
const slist=info?.sampler_name?.[0];
const schlist=info?.scheduler?.[0];
if(Array.isArray(slist)&&slist.length){
SAMPLERS=slist;
samplerDD.updateItems(slist);
if(slist.includes(S.sampler)) samplerDD.set(S.sampler);
else{ S.sampler=slist[0]; samplerDD.set(slist[0]); persist(); }
}
if(Array.isArray(schlist)&&schlist.length){
SCHEDULERS=schlist;
schedulerDD.updateItems(schlist);
if(schlist.includes(S.scheduler)) schedulerDD.set(S.scheduler);
else{ S.scheduler=schlist[0]; schedulerDD.set(schlist[0]); persist(); }
}
}).catch(()=>{});
// Seed controls
const _advSeedInp=_advNIStyle(NI("seed",S.seed||0,0,999999999999,1,v=>{
S.seed=Math.round(v)||0; seedInp.setVal(S.seed); persist();
},"60px"));
_advSeedInp._inp.disabled=S.randomizeSeed;
_advSeedInp.style.opacity=S.randomizeSeed?"0.4":"1";
const _advSeedIconLock=``;
const _advSeedIconDice=``;
const _advSeedLockBtn=mk("button",{
background:"transparent",border:"none",padding:"0 1px",cursor:"pointer",outline:"none",
display:"flex",alignItems:"center",gap:"3px",flexShrink:"0",color:ADV_LABEL,transition:"color .15s",
});
const _advSeedStateLbl=mk("span",{fontSize:"7px",fontWeight:"700",letterSpacing:".06em",textTransform:"uppercase",whiteSpace:"nowrap",transition:"color .15s"});
const _advSeedRefresh=()=>{
_advSeedLockBtn.innerHTML="";
const ico=mk("span"); ico.innerHTML=S.randomizeSeed?_advSeedIconDice:_advSeedIconLock;
tx(_advSeedStateLbl,S.randomizeSeed?"Random":"Locked");
const col=S.randomizeSeed?ADV_LABEL:"#c0a0ff";
_advSeedLockBtn.style.color=col;
_advSeedStateLbl.style.color=col;
_advSeedLockBtn.append(ico,_advSeedStateLbl);
_advSeedInp._inp.disabled=S.randomizeSeed;
_advSeedInp.style.opacity=S.randomizeSeed?"0.4":"1";
};
_advSeedRefresh();
_advSeedLockBtn.onclick=()=>{ S.randomizeSeed=!S.randomizeSeed; persist(); _advSeedRefresh(); _advRefresh(); };
_advSeedLockBtn.onmouseenter=()=>{ _advSeedLockBtn.style.color="#d0b0ff"; _advSeedStateLbl.style.color="#d0b0ff"; };
_advSeedLockBtn.onmouseleave=()=>_advSeedRefresh();
// Helper: inline label+control pair
const _mkInline=(lbl,el)=>{
const w=mk("div",{display:"flex",alignItems:"center",gap:"3px",flexShrink:"0"});
const l=mk("span",{fontSize:"7px",fontWeight:"700",color:ADV_LABEL,letterSpacing:".06em",textTransform:"uppercase",whiteSpace:"nowrap"});
tx(l,lbl); w.append(l,el); return w;
};
// Single flat row: Steps · CFG · 🎲seed · Sampler · Scheduler
const advRow1=mk("div",{display:"flex",gap:"5px",alignItems:"center",flexWrap:"wrap"});
stepsInp.style.width="34px"; stepsInp.style.minWidth="0";
cfgInp.style.width="30px"; cfgInp.style.minWidth="0";
samplerDD.el.style.width="80px";
schedulerDD.el.style.width="70px";
const _advSeedGroup=mk("div",{display:"flex",alignItems:"center",gap:"2px",flexShrink:"0"});
_advSeedGroup.append(_mkInline("Seed",_advSeedInp),_advSeedLockBtn);
advRow1.append(
_mkInline("Steps",stepsInp),
_mkInline("CFG",cfgInp),
_advSeedGroup,
_mkInline("Sampler",samplerDD.el),
_mkInline("Scheduler",schedulerDD.el),
);
advPanel.append(advRow1);
// Seed locked warning — shown when advanced UI is off but seed is fixed
const _seedLockedWarn=mk("div",{
display:"none",alignItems:"center",gap:"8px",
background:"rgba(160,120,255,.10)",border:"1px solid rgba(160,120,255,.35)",
borderRadius:"6px",padding:"6px 10px",
});
const _seedLockedIcon=mk("span",{fontSize:"12px",flexShrink:"0"});tx(_seedLockedIcon,"🔒");
const _seedLockedText=mk("div",{fontSize:"8px",color:"rgba(200,180,255,.9)",lineHeight:"1.5",flex:"1"});
tx(_seedLockedText,"Seed is locked — you left it fixed.");
const _seedLockedBtn=mk("button",{
background:"rgba(160,120,255,.25)",border:"1px solid rgba(160,120,255,.6)",
borderRadius:"4px",padding:"3px 8px",fontSize:"8px",fontWeight:"700",
color:"#d0b8ff",cursor:"pointer",outline:"none",flexShrink:"0",
transition:"background .15s,border-color .15s",whiteSpace:"nowrap",
});
tx(_seedLockedBtn,"Set to random");
_seedLockedBtn.onmouseenter=()=>{_seedLockedBtn.style.background="rgba(160,120,255,.4)";};
_seedLockedBtn.onmouseleave=()=>{_seedLockedBtn.style.background="rgba(160,120,255,.25)";};
_seedLockedBtn.onclick=()=>{
S.randomizeSeed=true; persist();
_advSeedRefresh();
_advRefresh();
};
_seedLockedWarn.append(_seedLockedIcon,_seedLockedText,_seedLockedBtn);
const _advRefresh=()=>{
advPanel.style.display=S.advancedUI?"flex":"none";
_seedLockedWarn.style.display=(!S.advancedUI&&!S.randomizeSeed)?"flex":"none";
};
_advRefresh();
const genRow=mk("div",{display:"flex",gap:"0",alignItems:"stretch",marginTop:"auto",width:"100%",boxSizing:"border-box"});
const genBtn=mk("button",{
background:LIME,color:"#111",border:"2px solid transparent",borderRadius:"8px",
padding:"0",height:"38px",fontSize:"13px",fontWeight:"700",
cursor:"pointer",flex:"1",letterSpacing:".02em",
transition:"background .3s,color .3s,border-color .3s,box-shadow .15s,transform .1s",
outline:"none",position:"relative",overflow:"hidden",
});
tx(genBtn,"Generate");
const _genSweep=mk("div",{
position:"absolute",top:"0",left:"-80%",width:"50%",height:"100%",
background:"linear-gradient(90deg,transparent 0%,rgba(255,255,255,.75) 50%,transparent 100%)",
transform:"skewX(-20deg)",pointerEvents:"none",opacity:"0",transition:"none",
});
genBtn.appendChild(_genSweep);
genBtn.onmouseenter=()=>{
if(!S.generating){
_genSweep.style.animation="none";void _genSweep.offsetWidth;
_genSweep.style.animation="fk-light-sweep 1s ease forwards";
}
};
const stopBtn=mk("button",{
background:"transparent",border:`1px solid ${C.border}`,borderRadius:"8px",
color:C.muted,fontSize:"12px",cursor:"pointer",
maxWidth:"0",minWidth:"0",width:"0",opacity:"0",padding:"0",height:"38px",
transition:"max-width .25s ease, opacity .25s ease, padding .25s ease",outline:"none",
overflow:"hidden",flexShrink:"0",whiteSpace:"nowrap",
});
tx(stopBtn,"■ Stop");
stopBtn.onmouseenter=()=>{stopBtn.style.borderColor=C.err;stopBtn.style.color=C.err;};
stopBtn.onmouseleave=()=>{stopBtn.style.borderColor=C.border;stopBtn.style.color=C.muted;};
let _activePromptId=null;
stopBtn.onclick=async()=>{
// 1. Interrupt the currently running execution immediately
try{ await api.fetchApi("/interrupt",{method:"POST"}); }catch(e){}
// 2. Delete our prompt from the queue if it's still pending
if(_activePromptId){
try{
await api.fetchApi("/queue",{
method:"POST",
headers:{"Content-Type":"application/json"},
body:JSON.stringify({delete:[_activePromptId]}),
});
}catch(e){}
_activePromptId=null;
}
resetBtn();
};
genRow.append(genBtn,stopBtn);
// ── LEFT PANEL ASSEMBLY ──────────────────────────────────────────────
leftPanel.append(i2iPanel,resSect,advPanel,_seedLockedWarn,genRow);
// ── RIGHT PANEL — Preview area fills available height ──
const rightPanel=mk("div",{flex:"1",minWidth:"0",display:"flex",flexDirection:"column",overflow:"hidden"});
const previewBox=mk("div",{
width:"100%",flex:"1",minHeight:"0",background:"#000",
borderRadius:"10px",border:`1px solid ${C.border}`,
position:"relative",overflow:"hidden",
});
// Placeholder
const placeholder=mk("div",{
position:"absolute",inset:"0",display:"flex",flexDirection:"column",
alignItems:"center",justifyContent:"center",gap:"10px",
});
const placeholderSvg=document.createElementNS("http://www.w3.org/2000/svg","svg");
placeholderSvg.setAttribute("viewBox","0 0 24 24");placeholderSvg.setAttribute("width","40");placeholderSvg.setAttribute("height","40");
placeholderSvg.setAttribute("fill","none");placeholderSvg.setAttribute("stroke","currentColor");
placeholderSvg.setAttribute("stroke-width","1");placeholderSvg.setAttribute("stroke-linecap","round");
placeholderSvg.style.color=C.border;
placeholderSvg.innerHTML=``;
const placeholderLbl=mk("div",{fontSize:"11px",color:C.muted});
tx(placeholderLbl,"Generated image will appear here");
placeholder.append(placeholderSvg,placeholderLbl);
// Final image
const finalImg=mk("img",{
position:"absolute",inset:"0",width:"100%",height:"100%",
objectFit:"contain",display:"none",borderRadius:"10px",
});
// Progress bar — overlaid at the bottom of the preview box
const progWrap=mk("div",{
position:"absolute",bottom:"0",left:"0",right:"0",
background:"linear-gradient(transparent,rgba(0,0,0,.88))",
padding:"16px 14px 12px",display:"none",
flexDirection:"column",gap:"4px",boxSizing:"border-box",pointerEvents:"none",
});
const progTop=mk("div",{display:"flex",justifyContent:"space-between",alignItems:"center"});
const progStageL=mk("div",{fontSize:"11px",fontWeight:"600",color:C.text,textAlign:"center",flex:"1"});
tx(progStageL,"Generating…");
const progPct=mk("div",{fontSize:"10px",color:C.muted,flexShrink:"0"});tx(progPct,"0%");
progTop.append(progStageL,progPct);
const progBar=mk("div",{height:"3px",borderRadius:"2px",background:"rgba(255,255,255,.15)",overflow:"hidden",marginTop:"4px"});
const progFill=mk("div",{height:"100%",background:LIME,width:"0%",transition:"width .3s ease",borderRadius:"2px"});
progBar.appendChild(progFill);
const progDetailL=mk("div",{fontSize:"9px",color:"rgba(255,255,255,.5)",textAlign:"center",marginTop:"2px"});
progWrap.append(progTop,progBar,progDetailL);
const setStage=(l,d,p)=>{
tx(progStageL,l);tx(progDetailL,d);
progFill.style.width=p+"%";tx(progPct,Math.round(p)+"%");
};
// ── Image Comparer — always active after I2I generation ──────────────
// Generated image fills the box; Image 1 revealed from the right by dragging divider left.
// Divider starts at 100% (full generated shown), user drags left to reveal Image 1.
const comparerWrap=mk("div",{
position:"absolute",inset:"0",display:"none",cursor:"col-resize",
userSelect:"none",borderRadius:"10px",overflow:"hidden",
});
// Image 1 (reference) — full-size background, visible on the right of divider
const comparerBase=mk("img",{
position:"absolute",inset:"0",width:"100%",height:"100%",objectFit:"contain",
});
// Generated image — clipped to left portion
const comparerGen=mk("div",{
position:"absolute",top:"0",left:"0",bottom:"0",overflow:"hidden",
width:"100%", // starts at 100% so only generated is visible
});
const comparerGenImg=mk("img",{
position:"absolute",top:"0",left:"0",height:"100%",objectFit:"contain",
});
comparerGen.appendChild(comparerGenImg);
// Divider line
const comparerLine=mk("div",{
position:"absolute",top:"0",bottom:"0",width:"2px",
background:LIME,left:"calc(100% - 1px)",
boxShadow:"0 0 8px rgba(240,255,65,.5)",
display:"flex",flexDirection:"column",alignItems:"center",justifyContent:"center",
gap:"6px",zIndex:"4",
});
// Handle circle on divider
const comparerHandle=mk("div",{
width:"30px",height:"30px",borderRadius:"50%",background:LIME,
border:"2px solid #111",flexShrink:"0",
display:"flex",alignItems:"center",justifyContent:"center",
boxShadow:"0 2px 10px rgba(0,0,0,.7)",pointerEvents:"none",
});
comparerHandle.innerHTML=``;
comparerLine.append(comparerHandle);
comparerWrap.append(comparerBase,comparerGen,comparerLine);
// Drag logic
let _cmpDragging=false;
const _cmpSetPct=(pct)=>{
pct=Math.max(0,Math.min(100,pct));
comparerGen.style.width=pct+"%";
comparerLine.style.left=`calc(${pct}% - 1px)`;
comparerGenImg.style.width=(comparerWrap.offsetWidth||620)+"px";
};
comparerWrap.addEventListener("mousedown",e=>{
_cmpDragging=true;e.preventDefault();
});
document.addEventListener("mousemove",e=>{
if(!_cmpDragging) return;
const r=comparerWrap.getBoundingClientRect();
_cmpSetPct((e.clientX-r.left)/r.width*100);
});
document.addEventListener("mouseup",()=>{ _cmpDragging=false; });
comparerWrap.addEventListener("touchstart",()=>{_cmpDragging=true;},{passive:true});
comparerWrap.addEventListener("touchmove",e=>{
if(!_cmpDragging) return;
const r=comparerWrap.getBoundingClientRect();
_cmpSetPct((e.touches[0].clientX-r.left)/r.width*100);
},{passive:true});
comparerWrap.addEventListener("touchend",()=>{ _cmpDragging=false; });
// "Use as…" dropdown — top-right of previewBox, visible after generation
const previewUseWrap=mk("div",{
position:"absolute",top:"10px",right:"10px",zIndex:"5",display:"none",
});
const previewUseBtn=mk("button",{
background:"rgba(20,20,20,.88)",color:"rgba(255,255,255,.88)",
border:"1px solid rgba(255,255,255,.22)",
borderRadius:"6px",padding:"4px 11px",fontSize:"9px",fontWeight:"600",
cursor:"pointer",outline:"none",whiteSpace:"nowrap",
backdropFilter:"blur(4px)",letterSpacing:".04em",
boxShadow:"0 2px 8px rgba(0,0,0,.5)",
display:"flex",alignItems:"center",gap:"5px",
transition:"background .15s,color .15s,border-color .15s",
});
previewUseBtn.innerHTML=`Use as… `;
previewUseBtn.onmouseenter=()=>{previewUseBtn.style.background="rgba(40,40,40,.97)";previewUseBtn.style.color="#fff";previewUseBtn.style.borderColor="rgba(255,255,255,.4)";};
previewUseBtn.onmouseleave=()=>{previewUseBtn.style.background="rgba(20,20,20,.88)";previewUseBtn.style.color="rgba(255,255,255,.88)";previewUseBtn.style.borderColor="rgba(255,255,255,.22)";};
const previewUseDrop=mk("div",{
position:"absolute",top:"calc(100% + 4px)",right:"0",
background:C.bg1,border:`1px solid ${C.borderH}`,borderRadius:"8px",
minWidth:"160px",overflow:"hidden",display:"none",zIndex:"200",
boxShadow:"0 4px 20px rgba(0,0,0,.7)",flexDirection:"column",
});
const _mkPUSection=(label)=>{ const h=mk("div",{padding:"6px 12px 3px",fontSize:"8px",fontWeight:"700",letterSpacing:".08em",textTransform:"uppercase",color:C.muted,userSelect:"none"});tx(h,label);return h; };
const _mkPUItem=(label,icon,fn)=>{ const row=mk("div",{padding:"7px 12px",fontSize:"10px",fontWeight:"500",color:C.text,cursor:"pointer",display:"flex",alignItems:"center",gap:"7px",transition:"background .1s,color .1s",userSelect:"none"});const ico=mk("span",{fontSize:"11px",width:"14px",textAlign:"center",flexShrink:"0",color:C.muted});tx(ico,icon);const lbl=mk("span");tx(lbl,label);row.append(ico,lbl);row.onmouseenter=()=>{row.style.background="rgba(240,255,65,.10)";row.style.color=LIME;ico.style.color=LIME;};row.onmouseleave=()=>{row.style.background="";row.style.color=C.text;ico.style.color=C.muted;};row.onclick=()=>{previewUseDrop.style.display="none";_puDropOpen=false;fn();};return row; };
const _mkPUDivider=()=>mk("div",{height:"1px",background:C.border,margin:"2px 0"});
const _getLastSrc=()=>_lastGenObj||(_galImages&&_galImages[0]);
const _puUpload=async(fn)=>{ const v=_getLastSrc();if(!v)return;try{const n=await _uploadOutputToInput(v);fn(n);}catch(e){console.warn("[ZImageOneNode] use-as:",e);} };
previewUseDrop.append(
_mkPUSection("I2I"),
_mkPUItem("I2I slot","⟳",()=>_puUpload(n=>{setPill("i2i");S.i2iImage=n;i2iSlot._restorePreview(n);persist();})),
);
let _puDropOpen=false;
previewUseBtn.onclick=e=>{ e.stopPropagation();_puDropOpen=!_puDropOpen;previewUseDrop.style.display=_puDropOpen?"flex":"none"; };
document.addEventListener("click",()=>{ if(_puDropOpen){previewUseDrop.style.display="none";_puDropOpen=false;} });
previewUseDrop.addEventListener("click",e=>e.stopPropagation());
previewUseWrap.append(previewUseBtn,previewUseDrop);
// ── Delete helper ─────────────────────────────────────────────────────
// Confirm popover for delete — shows "Sure? Yes / Keep" near the trigger button
const _confirmPop=mk("div",{
position:"fixed",zIndex:"99999",
background:"#18181c",border:`1px solid rgba(255,80,80,.45)`,
borderRadius:"10px",padding:"10px 14px",
display:"none",flexDirection:"column",alignItems:"center",gap:"8px",
boxShadow:"0 6px 24px rgba(0,0,0,.7)",
minWidth:"110px",
});
const _confirmTxt=mk("div",{fontSize:"11px",fontWeight:"600",color:"rgba(255,200,200,.9)",
letterSpacing:".02em",whiteSpace:"nowrap"});
tx(_confirmTxt,"Sure?");
const _confirmBtns=mk("div",{display:"flex",gap:"6px"});
const _mkConfBtn=(label,bg,hoverBg,color,border)=>{
const b=mk("button",{
background:bg,border:`1px solid ${border}`,borderRadius:"6px",
padding:"4px 13px",fontSize:"10px",fontWeight:"700",
color,cursor:"pointer",outline:"none",letterSpacing:".04em",
transition:"background .12s,border-color .12s",
});
tx(b,label);
b.onmouseenter=()=>{b.style.background=hoverBg;};
b.onmouseleave=()=>{b.style.background=bg;};
return b;
};
const _confirmYes=_mkConfBtn("Yes","rgba(180,30,30,.8)","rgba(220,40,40,.95)","#ffb0b0","rgba(255,80,80,.5)");
const _confirmKeep=_mkConfBtn("Keep","rgba(255,255,255,.06)","rgba(255,255,255,.13)","rgba(255,255,255,.7)","rgba(255,255,255,.18)");
_confirmBtns.append(_confirmYes,_confirmKeep);
_confirmPop.append(_confirmTxt,_confirmBtns);
document.body.appendChild(_confirmPop);
let _confirmResolve=null;
const _showConfirm=(anchorEl)=>new Promise(res=>{
_confirmResolve=res;
const r=anchorEl.getBoundingClientRect();
// Position above the button, centered
_confirmPop.style.display="flex";
const pw=_confirmPop.offsetWidth||120;
let left=r.left+r.width/2-pw/2;
let top=r.top-_confirmPop.offsetHeight-8;
if(top<8) top=r.bottom+8;
left=Math.max(8,Math.min(left,window.innerWidth-pw-8));
_confirmPop.style.left=left+"px";
_confirmPop.style.top=top+"px";
});
const _hideConfirm=()=>{ _confirmPop.style.display="none"; _confirmResolve=null; };
_confirmYes.onclick=()=>{ const r=_confirmResolve; _hideConfirm(); r?.(true); };
_confirmKeep.onclick=()=>{ const r=_confirmResolve; _hideConfirm(); r?.(false); };
document.addEventListener("keydown",e=>{ if(e.key==="Escape"&&_confirmPop.style.display!=="none"){ _hideConfirm(); _confirmResolve?.(false); } });
document.addEventListener("click",e=>{ if(_confirmPop.style.display!=="none"&&!_confirmPop.contains(e.target)) { _hideConfirm(); _confirmResolve?.(false); } },{capture:true});
const _deleteImage=async(imgObj,anchorEl,onSuccess)=>{
if(!imgObj||!imgObj.filename) return;
const ok=await _showConfirm(anchorEl);
if(!ok) return;
try{
const r=await api.fetchApi("/z_image/delete",{
method:"POST",headers:{"Content-Type":"application/json"},
body:JSON.stringify({filename:imgObj.filename,subfolder:imgObj.subfolder||""}),
});
const d=await r.json();
if(d.ok){ onSuccess?.(); }
else{ console.warn("[ZImageOneNode] delete failed:",(d.error||"unknown")); }
}catch(e){ console.warn("[ZImageOneNode] delete error:",fmtErr(e)); }
};
// Preview delete button — bottom-right of previewBox
const previewDelBtn=mk("button",{
position:"absolute",bottom:"10px",right:"10px",zIndex:"5",
width:"28px",height:"28px",borderRadius:"8px",
background:"rgba(180,30,30,.75)",border:"1px solid rgba(255,80,80,.35)",
color:"rgba(255,200,200,.9)",cursor:"pointer",outline:"none",
display:"none",alignItems:"center",justifyContent:"center",padding:"0",
backdropFilter:"blur(4px)",transition:"background .15s,border-color .15s",
});
previewDelBtn.title="Delete image";
previewDelBtn.innerHTML=``;
previewDelBtn.onmouseenter=()=>{previewDelBtn.style.background="rgba(220,40,40,.95)";previewDelBtn.style.borderColor="rgba(255,80,80,.7)";};
previewDelBtn.onmouseleave=()=>{previewDelBtn.style.background="rgba(180,30,30,.75)";previewDelBtn.style.borderColor="rgba(255,80,80,.35)";};
previewDelBtn.onclick=()=>{
const src=_getLastSrc();
if(!src) return;
_deleteImage(src,previewDelBtn,()=>{
// Hide preview, show placeholder, clear _lastGenObj
finalImg.src="";finalImg.style.display="none";
comparerWrap.style.display="none";
previewUseWrap.style.display="none";
previewDelBtn.style.display="none";
placeholder.style.display="flex";
_lastGenObj=null;
_galNeedsRefresh=true;
});
};
// Comparer activates automatically in EDIT mode after generation
previewBox.append(placeholder,finalImg,comparerWrap,previewUseWrap,previewDelBtn,progWrap);
rightPanel.appendChild(previewBox);
mainRow.append(leftPanel,rightPanel);
// ── PROMPT ───────────────────────────────────────────────────────────
const promptWrap=mk("div",{display:"flex",flexDirection:"column",gap:"5px"});
const promptHdr=mk("div",{display:"flex",alignItems:"center",gap:"5px"});
const promptCap=cap("Prompt");
// ── LoRA overlay ──────────────────────────────────────────────────────
const _ulOverlay=mk("div",{
position:"fixed",inset:"0",zIndex:"99998",display:"none",
alignItems:"center",justifyContent:"center",
});
const _ulBg=mk("div",{position:"absolute",inset:"0",background:"rgba(0,0,0,.7)"});
const _ulPanel=mk("div",{
position:"relative",
background:"linear-gradient(145deg,#111 0%,#0d0d0d 100%)",
border:`1px solid rgba(240,255,65,.18)`,
borderRadius:"16px",padding:"20px 22px 22px",width:"520px",
boxShadow:"0 20px 60px rgba(0,0,0,.95),inset 0 1px 0 rgba(255,255,255,.04)",
display:"flex",flexDirection:"column",gap:"16px",
});
const _ulPHdr=mk("div",{display:"flex",alignItems:"center",gap:"8px"});
const _ulPTitle=mk("div",{fontSize:"12px",fontWeight:"700",color:"#fff",flex:"1",
letterSpacing:".06em",textTransform:"uppercase"});
tx(_ulPTitle,"LoRA");
const _ulPClose=mk("button",{background:"none",border:"none",cursor:"pointer",
color:C.muted,fontSize:"16px",lineHeight:"1",padding:"0",outline:"none",flexShrink:"0"});
tx(_ulPClose,"×");
_ulPClose.onmouseenter=()=>_ulPClose.style.color="#fff";
_ulPClose.onmouseleave=()=>_ulPClose.style.color=C.muted;
_ulPClose.onclick=()=>{ _ulOverlay.style.display="none"; hideDimmer(); };
_ulBg.onclick=()=>{ _ulOverlay.style.display="none"; hideDimmer(); };
const _ulRefreshBtn=mk("button",{background:"none",border:"none",cursor:"pointer",
color:C.muted,fontSize:"13px",lineHeight:"1",padding:"0 6px 0 0",outline:"none",flexShrink:"0"});
tx(_ulRefreshBtn,"↻");
_ulRefreshBtn.title="Refresh model list";
_ulRefreshBtn.onmouseenter=()=>_ulRefreshBtn.style.color="#fff";
_ulRefreshBtn.onmouseleave=()=>_ulRefreshBtn.style.color=C.muted;
_ulRefreshBtn.onclick=()=>{ tx(_ulRefreshBtn,"↻"); _loadModels(); };
_ulPHdr.append(_ulPTitle,_ulRefreshBtn,_ulPClose);
const _ulPSub=mk("div",{width:"100%",height:"1px",background:"rgba(240,255,65,.10)",marginTop:"-6px"});
const _ulRows=mk("div",{display:"flex",flexDirection:"column",gap:"10px"});
const _mkULRow=(idx)=>{
const row=mk("div",{display:"flex",flexDirection:"column",gap:"4px"});
const rowLbl=mk("div",{fontSize:"7px",color:"rgba(240,255,65,.5)",fontWeight:"700",
letterSpacing:".1em",textTransform:"uppercase"});
tx(rowLbl,`SLOT ${idx+1}`);
const rowCtrl=mk("div",{display:"flex",alignItems:"center",gap:"6px"});
// Trigger words area — shown below the control row
const trigRow=mk("div",{display:"none",flexDirection:"column",gap:"5px",
marginTop:"2px",padding:"8px 10px",background:"rgba(240,255,65,.04)",
border:`1px solid rgba(240,255,65,.12)`,borderRadius:"8px",
});
const trigTopRow=mk("div",{display:"flex",alignItems:"center",gap:"6px"});
const trigLbl=mk("div",{fontSize:"9px",fontWeight:"600",color:C.muted,whiteSpace:"nowrap",flexShrink:"0"});
tx(trigLbl,"Trigger words:");
const trigVal=mk("div",{fontSize:"10px",color:LIME,flex:"1",minWidth:"0",
wordBreak:"break-word",lineHeight:"1.4"});
tx(trigVal,"—");
trigTopRow.append(trigLbl,trigVal);
// Custom trigger input row
const trigCustomRow=mk("div",{display:"flex",alignItems:"center",gap:"5px",marginTop:"2px"});
const trigCustomLbl=mk("div",{fontSize:"8px",color:C.muted,whiteSpace:"nowrap",flexShrink:"0"});
tx(trigCustomLbl,"Custom:");
const trigCustomInp=mk("input",{
flex:"1",background:"rgba(255,255,255,.06)",border:`1px solid rgba(255,255,255,.12)`,
borderRadius:"5px",color:C.text,fontSize:"10px",padding:"3px 7px",
outline:"none",transition:"border-color .15s",
},{type:"text",placeholder:"Add custom trigger words…"});
trigCustomInp.onfocus=()=>trigCustomInp.style.borderColor=LIME;
trigCustomInp.onblur=()=>trigCustomInp.style.borderColor="rgba(255,255,255,.12)";
const trigSaveBtn=mk("button",{
background:"rgba(255,255,255,.07)",border:`1px solid rgba(255,255,255,.15)`,
borderRadius:"5px",cursor:"pointer",color:C.muted,fontSize:"9px",fontWeight:"700",
padding:"3px 9px",outline:"none",transition:"all .15s",flexShrink:"0",whiteSpace:"nowrap",
});
tx(trigSaveBtn,"Save");
trigSaveBtn.onmouseenter=()=>{trigSaveBtn.style.background="rgba(240,255,65,.12)";trigSaveBtn.style.borderColor=LIME;trigSaveBtn.style.color=LIME;};
trigSaveBtn.onmouseleave=()=>{trigSaveBtn.style.background="rgba(255,255,255,.07)";trigSaveBtn.style.borderColor="rgba(255,255,255,.15)";trigSaveBtn.style.color=C.muted;};
trigSaveBtn.onclick=async()=>{
const name=ulDD.value;
if(!name||name==="none") return;
const txt=trigCustomInp.value.trim();
await _saveCustomTrigger(name,txt);
// Update displayed value and clear input field
tx(trigVal,txt||"—");
trigCustomInp.value="";
trigSaveBtn.style.color=LIME; tx(trigSaveBtn,"Saved ✓");
setTimeout(()=>{ trigSaveBtn.style.color=C.muted; tx(trigSaveBtn,"Save"); },1500);
};
trigCustomRow.append(trigCustomLbl,trigCustomInp,trigSaveBtn);
trigRow.append(trigTopRow,trigCustomRow);
// Load and display trigger words when lora changes
const _refreshTrigWords=async(loraName)=>{
if(!loraName||loraName==="none"){ trigRow.style.display="none"; return; }
trigRow.style.display="flex";
tx(trigVal,"…");
trigCustomInp.value="";
await _loadCustomTriggers();
const custom=_getCustomTrigger(loraName);
if(custom){
// Custom always wins over metadata
tx(trigVal,custom);
} else {
try{
const r=await api.fetchApi(`/z_image/lora_triggers?name=${encodeURIComponent(loraName)}`);
const d=await r.json();
tx(trigVal,(d.ok&&d.triggers?.length)?d.triggers.join(", "):"—");
}catch(e){ tx(trigVal,"—"); }
}
};
const ulDD=DD(["none"],"none",v=>{
const has=v&&v!=="none";
S.userLoras[idx].name=has?v:"";
if(!has){ S.userLoras[idx].strength=0; ulStr.value="0"; }
else if(S.userLoras[idx].strength===0){ S.userLoras[idx].strength=1; ulStr.value="1"; }
_ulUpdateBtn();persist();
_refreshTrigWords(v);
});
ulDD.el.style.flex="1";ulDD.el.style.minWidth="0";
const ulStr=mk("input",{
width:"44px",textAlign:"center",background:"rgba(255,255,255,.06)",
border:`1px solid rgba(255,255,255,.1)`,borderRadius:"6px",
color:LIME,fontSize:"10px",fontWeight:"700",
padding:"5px 0",outline:"none",transition:"border-color .15s",flexShrink:"0",
},{type:"number",step:"0.05",value:String(S.userLoras[idx].name&&S.userLoras[idx].name!=="none"?S.userLoras[idx].strength||1:0)});
ulStr.onfocus=()=>ulStr.style.borderColor=LIME;
ulStr.onblur=()=>{ S.userLoras[idx].strength=isNaN(+ulStr.value)?1:+ulStr.value;
ulStr.value=String(S.userLoras[idx].strength);persist(); };
ulStr.oninput=()=>{ S.userLoras[idx].strength=+ulStr.value||0;persist(); };
const ulClr=mk("button",{
background:"rgba(255,80,80,.08)",border:"1px solid rgba(255,80,80,.25)",borderRadius:"6px",
cursor:"pointer",color:"rgba(255,100,100,.7)",fontSize:"9px",fontWeight:"700",
padding:"5px 8px",outline:"none",transition:"all .15s",flexShrink:"0",
});
tx(ulClr,"CLR");
ulClr.onmouseenter=()=>{ ulClr.style.background="rgba(255,80,80,.18)";ulClr.style.color="#ff6666"; };
ulClr.onmouseleave=()=>{ ulClr.style.background="rgba(255,80,80,.08)";ulClr.style.color="rgba(255,100,100,.7)"; };
ulClr.onclick=()=>{
S.userLoras[idx]={name:"",strength:0};ulDD.set("none");ulStr.value="0";
_ulUpdateBtn();persist();
trigRow.style.display="none";
};
rowCtrl.append(ulDD.el,ulStr,ulClr);
row.append(rowLbl,rowCtrl,trigRow);
row._dd=ulDD;row._str=ulStr;
// Restore trigger words display if lora already selected
if(S.userLoras[idx].name&&S.userLoras[idx].name!=="none"){
_refreshTrigWords(S.userLoras[idx].name);
}
return row;
};
const _ulRowEls=[_mkULRow(0),_mkULRow(1),_mkULRow(2)];
_ulRowEls.forEach(r=>_ulRows.appendChild(r));
// Info note at bottom of panel
const _ulInfoNote=mk("div",{
fontSize:"9px",color:C.muted,lineHeight:"1.5",
padding:"8px 10px",background:"rgba(255,255,255,.03)",
borderRadius:"6px",border:`1px solid ${C.border}`,
});
tx(_ulInfoNote,"✦ Trigger words are applied automatically if saved and set for the selected LoRA.");
_ulPanel.append(_ulPHdr,_ulPSub,_ulRows,_ulInfoNote);
_ulOverlay.append(_ulBg,_ulPanel);
root.appendChild(_ulOverlay);
// ── Collect trigger words for all active LoRAs at generate time ────────
const _buildPromptWithTriggers=async(basePrompt)=>{
await _loadCustomTriggers();
const trigParts=[];
for(const ul of S.userLoras){
if(!ul.name||ul.name==="none"||!(+(ul.strength||0)>0)) continue;
// Custom trigger words override metadata
const custom=_getCustomTrigger(ul.name);
if(custom){ trigParts.push(custom); continue; }
// Try metadata
try{
const r=await api.fetchApi(`/z_image/lora_triggers?name=${encodeURIComponent(ul.name)}`);
const d=await r.json();
if(d.ok&&d.triggers?.length) trigParts.push(d.triggers.join(", "));
}catch(e){}
}
if(!trigParts.length) return basePrompt;
const prefix=trigParts.join(", ");
return basePrompt.trim()?`${prefix}, ${basePrompt.trim()}`:prefix;
};
// ── Add LoRA button — identical style/layout to LTX node ─────────────
const _ulBtn=mk("button",{
background:"linear-gradient(135deg,rgba(240,255,65,.10),rgba(240,255,65,.04))",
border:"1.5px solid rgba(240,255,65,.35)",cursor:"pointer",
padding:"2px 8px 2px 6px",color:LIME,outline:"none",
display:"flex",alignItems:"center",gap:"5px",borderRadius:"5px",
transition:"all .15s",flexShrink:"0",marginLeft:"auto",
boxShadow:"0 0 0 0 rgba(240,255,65,0)",
});
const _ulBtnIco=document.createElementNS("http://www.w3.org/2000/svg","svg");
_ulBtnIco.setAttribute("viewBox","0 0 24 24");_ulBtnIco.setAttribute("width","9");_ulBtnIco.setAttribute("height","9");
_ulBtnIco.setAttribute("fill","none");_ulBtnIco.setAttribute("stroke","currentColor");
_ulBtnIco.setAttribute("stroke-width","2");_ulBtnIco.setAttribute("stroke-linecap","round");
_ulBtnIco.innerHTML=``;
const _ulBtnTxt=mk("span",{fontSize:"9px",fontWeight:"700",letterSpacing:".04em"});
tx(_ulBtnTxt,"Add LoRA");
const _ulBtnBadge=mk("span",{fontSize:"7px",fontWeight:"700",background:LIME,color:"#111",
borderRadius:"20px",padding:"0 4px",lineHeight:"1.6",display:"none",flexShrink:"0"});
_ulBtn.append(_ulBtnIco,_ulBtnTxt,_ulBtnBadge);
_ulBtn.onmouseenter=()=>{ _ulBtn.style.background="linear-gradient(135deg,rgba(240,255,65,.18),rgba(240,255,65,.08))";_ulBtn.style.borderColor=LIME;_ulBtn.style.boxShadow="0 0 8px rgba(240,255,65,.12)"; };
_ulBtn.onmouseleave=()=>{ _ulBtn.style.background="linear-gradient(135deg,rgba(240,255,65,.10),rgba(240,255,65,.04))";_ulBtn.style.borderColor="rgba(240,255,65,.35)";_ulBtn.style.boxShadow="0 0 0 0 rgba(240,255,65,0)"; };
_ulBtn.onclick=()=>{ _ulOverlay.style.display="flex";showDimmer(); };
const _ulUpdateBtn=()=>{
const n=S.userLoras.filter(l=>l.name&&l.name!=="none").length;
tx(_ulBtnBadge,String(n));
_ulBtnBadge.style.display=n>0?"":"none";
_ulBtn.style.borderColor=n>0?LIME:"rgba(240,255,65,.35)";
_ulBtn.style.color=n>0?LIME:LIME;
};
_ulUpdateBtn();
// ── Expand prompt button — identical style to LTX node ────────────────
const _promptExpandBtn=mk("button",{
background:"none",border:`1px solid ${C.border}`,cursor:"pointer",
padding:"2px 7px 2px 5px",color:C.muted,outline:"none",
display:"flex",alignItems:"center",gap:"5px",borderRadius:"5px",
transition:"color .15s,border-color .15s",flexShrink:"0",
});
_promptExpandBtn.innerHTML=`Expand prompt`;
_promptExpandBtn.onmouseenter=()=>{ _promptExpandBtn.style.color="#fff";_promptExpandBtn.style.borderColor="#555"; };
_promptExpandBtn.onmouseleave=()=>{ _promptExpandBtn.style.color=C.muted;_promptExpandBtn.style.borderColor=C.border; };
// Error chip (shown when error is minimized)
const errMinChip=mk("button",{
display:"none",alignItems:"center",gap:"4px",
background:"none",border:`1px solid rgba(255,103,103,.35)`,
borderRadius:"5px",padding:"2px 7px",cursor:"pointer",outline:"none",
color:"rgba(255,103,103,.8)",fontSize:"9px",fontWeight:"700",
letterSpacing:".03em",transition:"border-color .15s,color .15s",flexShrink:"0",
});
tx(errMinChip,"⚠ Error");
// ── Get Inspired overlay ──────────────────────────────────────────────
// Per-pill prompt suggestions
// INSPIRE_BY_PILL is loaded from config.json on first Discover open.
// This object serves as the default — written to config.json if discover_prompts is empty.
let INSPIRE_BY_PILL={
t2i:{
categories:[],
},
i2i:{categories:[{cat:"I2I",items:[]}]},
};
// ── Get Inspired overlay — inside root (position:absolute like _promptOverlay) ──
const _inspireOverlay=mk("div",{
position:"absolute",inset:"0",zIndex:"260",background:C.bg0,
display:"none",flexDirection:"column",
padding:"12px",boxSizing:"border-box",gap:"0",
opacity:"0",transition:"opacity 0.15s ease",overflow:"hidden",
});
// Manage mode gradient flash layer — sits behind content, above bg0
const _inspireManageFlash=mk("div",{
position:"absolute",inset:"0",zIndex:"0",pointerEvents:"none",
background:"transparent",transition:"background .4s ease",
});
_inspireOverlay.appendChild(_inspireManageFlash);
const _closeInspire=()=>{
_inspireOverlay.style.opacity="0";
setTimeout(()=>_inspireOverlay.style.display="none",160);
};
// Header (static)
const _inspireHdr=mk("div",{display:"flex",alignItems:"center",justifyContent:"space-between",
marginBottom:"10px",flexShrink:"0"});
const _inspireTitleEl=mk("div",{fontSize:"10px",fontWeight:"700",color:C.muted,
letterSpacing:".07em",textTransform:"uppercase"});
tx(_inspireTitleEl,"✦ Discover");
let _inspireShowFull=false;
let _t2iTemplates=[]; // persistent across _buildInspireBody rebuilds
let _t2iTemplatesLoaded=false;
const _inspireShowFullBtn=mk("button",{
background:"rgba(83,52,131,.15)",border:"1px solid rgba(83,52,131,.4)",borderRadius:"5px",
padding:"3px 8px",fontSize:"8px",fontWeight:"700",letterSpacing:".05em",textTransform:"uppercase",
cursor:"pointer",outline:"none",color:"rgba(180,160,220,.7)",transition:"all .15s",marginRight:"6px",flexShrink:"0",
});
tx(_inspireShowFullBtn,"Edit mode (E)");
_inspireShowFullBtn.style.display="none";
const _inspireShowFullUpdate=()=>{
tx(_inspireShowFullBtn,_inspireShowFull?"Exit edit mode":"Edit mode (E)");
_inspireShowFullBtn.style.background=_inspireShowFull?"rgba(180,140,255,.15)":"rgba(83,52,131,.15)";
_inspireShowFullBtn.style.borderColor=_inspireShowFull?"rgba(220,180,255,.8)":"rgba(83,52,131,.4)";
_inspireShowFullBtn.style.color=_inspireShowFull?"#f0e8ff":"rgba(180,160,220,.7)";
};
_inspireShowFullBtn.onmouseenter=()=>{
_inspireShowFullBtn.style.background="linear-gradient(90deg,rgba(26,26,46,.8),rgba(15,52,96,.6),rgba(83,52,131,.6))";
_inspireShowFullBtn.style.borderColor="rgba(180,140,255,.8)";
_inspireShowFullBtn.style.color="#e0e0ff";
};
_inspireShowFullBtn.onmouseleave=()=>_inspireShowFullUpdate();
_inspireShowFullBtn.onclick=async()=>{
_inspireShowFull=!_inspireShowFull; _inspireShowFullUpdate();
await _loadDiscoverPrompts(); _buildInspireBody();
if(_inspireShowFull){
_inspireManageFlash.style.transition="none";
_inspireManageFlash.style.background="linear-gradient(135deg,rgba(26,20,60,.9) 0%,rgba(15,52,96,.7) 50%,rgba(83,52,131,.8) 100%)";
void _inspireManageFlash.offsetWidth;
_inspireManageFlash.style.transition="background .6s ease";
_inspireManageFlash.style.background="linear-gradient(135deg,rgba(26,20,60,.45) 0%,rgba(15,52,96,.25) 50%,rgba(83,52,131,.35) 100%)";
} else {
_inspireManageFlash.style.transition="background .3s ease";
_inspireManageFlash.style.background="transparent";
}
};
const _inspireCloseBtn=mk("button",{background:"none",border:"none",cursor:"pointer",
color:C.muted,fontSize:"14px",lineHeight:"1",padding:"2px 4px",outline:"none",
display:"flex",alignItems:"center",borderRadius:"4px",transition:"color .15s"});
tx(_inspireCloseBtn,"×");
_inspireCloseBtn.onmouseenter=()=>_inspireCloseBtn.style.color="#fff";
_inspireCloseBtn.onmouseleave=()=>_inspireCloseBtn.style.color=C.muted;
_inspireCloseBtn.onclick=_closeInspire;
const _inspireHdrRight=mk("div",{display:"flex",alignItems:"center"});
_inspireHdrRight.append(_inspireShowFullBtn,_inspireCloseBtn);
_inspireHdr.append(_inspireTitleEl,_inspireHdrRight);
// Dynamic content area — rebuilt each time overlay opens
const _inspireBody=mk("div",{display:"flex",flexDirection:"column",flex:"1",minHeight:"0",gap:"0"});
const _usePrompt=(p)=>{
S.prompt=p;
if(_promptTARef) _promptTARef.value=p;
if(typeof _promptOvTA!=="undefined") _promptOvTA.value=p;
persist();_closeInspire();
};
const _buildInspireBody=()=>{
_inspireBody.innerHTML="";
const def=INSPIRE_BY_PILL[activePill]||INSPIRE_BY_PILL.t2i;
// ── Categories mode (new format) ────────────────────────────────────
if(def.categories){
const hasShortLabels=def.categories.some(({items})=>items.some(it=>typeof it==="object"&&it.label!==it.prompt));
// T2I: show toggle for templates (name ≠ prompt). EDIT also has short labels.
_inspireShowFullBtn.style.display=(hasShortLabels||activePill==="t2i"||activePill==="i2i")?"":"none";
const scroll=mk("div",{
flex:"1",overflowY:"auto",display:"flex",flexDirection:"column",gap:"12px",
scrollbarWidth:"thin",scrollbarColor:`${C.border} transparent`,
});
scroll.addEventListener("wheel",e=>e.stopPropagation(),{passive:true});
// ── T2I Templates ────────────────────────────────────────────────
if(activePill==="t2i"){
// Use persistent vars so rebuilds don't lose state
const _templates=_t2iTemplates;
let _editIdx=-1;
let _tmplPillsRow=null;
const _saveTmpl=async()=>{
try{ await api.fetchApi("/z_image/config",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({t2i_templates:_templates})}); }
catch(e){ console.warn("[ZImageOneNode] save templates:",e); }
};
// Form (create/edit) — framed, compact
const tmplForm=mk("div",{
display:"none",flexDirection:"column",gap:"6px",marginBottom:"6px",
border:`1px solid rgba(240,255,65,.3)`,borderRadius:"16px",
padding:"10px 12px",background:"rgba(240,255,65,.04)",boxSizing:"border-box",
});
const tmplFormTitle=mk("div",{fontSize:"8px",fontWeight:"700",color:LIME,letterSpacing:".08em",textTransform:"uppercase",marginBottom:"2px"});
tx(tmplFormTitle,"New Template");
const tmplNameInp=mk("input",{
background:C.bg2,border:`1px solid ${C.border}`,borderRadius:"999px",
color:C.text,fontSize:"10px",padding:"6px 14px",outline:"none",
transition:"border-color .15s",width:"100%",boxSizing:"border-box",
},{placeholder:"Title…"});
tmplNameInp.onfocus=()=>tmplNameInp.style.borderColor=LIME;
tmplNameInp.onblur=()=>tmplNameInp.style.borderColor=C.border;
const tmplPromptTA=mk("textarea",{
background:C.bg2,border:`1px solid ${C.border}`,borderRadius:"16px",
color:C.text,fontSize:"10px",padding:"8px 14px",outline:"none",lineHeight:"1.5",
resize:"vertical",minHeight:"60px",fontFamily:"inherit",
transition:"border-color .15s",width:"100%",boxSizing:"border-box",
},{placeholder:"Prompt…"});
tmplPromptTA.addEventListener("wheel",e=>e.stopPropagation(),{passive:true});
tmplPromptTA.onfocus=()=>tmplPromptTA.style.borderColor=LIME;
tmplPromptTA.onblur=()=>tmplPromptTA.style.borderColor=C.border;
const tmplFormBtns=mk("div",{display:"flex",gap:"6px",justifyContent:"flex-end"});
const tmplCancelBtn=mk("button",{
background:"transparent",border:`1px solid ${C.borderH}`,borderRadius:"999px",
padding:"5px 16px",fontSize:"9px",fontWeight:"700",color:C.muted,
cursor:"pointer",outline:"none",transition:"border-color .15s,color .15s",
});
tx(tmplCancelBtn,"Cancel");
tmplCancelBtn.onmouseenter=()=>{tmplCancelBtn.style.borderColor=C.text;tmplCancelBtn.style.color=C.text;};
tmplCancelBtn.onmouseleave=()=>{tmplCancelBtn.style.borderColor=C.borderH;tmplCancelBtn.style.color=C.muted;};
const tmplSaveBtn=mk("button",{
background:LIME,color:"#111",border:"none",borderRadius:"999px",
padding:"5px 16px",fontSize:"9px",fontWeight:"700",cursor:"pointer",outline:"none",
transition:"opacity .15s",
});
tx(tmplSaveBtn,"Save");
tmplSaveBtn.onmouseenter=()=>tmplSaveBtn.style.opacity=".85";
tmplSaveBtn.onmouseleave=()=>tmplSaveBtn.style.opacity="1";
tmplFormBtns.append(tmplCancelBtn,tmplSaveBtn);
tmplFormBtns.style.justifyContent="flex-end";
tmplForm.append(tmplFormTitle,tmplNameInp,tmplPromptTA,tmplFormBtns);
// Header: "MY TEMPLATES" only
const tmplHdr=mk("div",{display:"flex",alignItems:"center",marginBottom:"5px"});
const tmplHdrLbl=mk("div",{fontSize:"9px",fontWeight:"700",letterSpacing:".1em",textTransform:"uppercase",color:C.muted});
tx(tmplHdrLbl,"My Templates");
tmplHdr.append(tmplHdrLbl);
// Pills container + details area (same pattern as categories)
_tmplPillsRow=mk("div",{display:"flex",flexWrap:"wrap",gap:"6px",minHeight:"16px"});
const _tmplDetailsArea=mk("div",{display:"flex",flexDirection:"column",gap:"4px",marginTop:"2px"});
let _tmplOpenIdx=-1;
const _renderTmplPills=()=>{
_tmplPillsRow.innerHTML=""; _tmplDetailsArea.innerHTML=""; _tmplOpenIdx=-1;
_templates.forEach((t,idx)=>{
// Pill
const pill=mk("button",{padding:"7px 14px",borderRadius:"999px",cursor:"pointer",fontSize:"10px",fontWeight:"500",lineHeight:"1.5",border:`1px solid ${C.border}`,background:C.bg1,color:C.text,outline:"none",transition:"background .12s,border-color .12s,color .12s"});
tx(pill,t.name);
pill.onmouseenter=()=>{ if(_tmplOpenIdx!==idx){pill.style.background="rgba(240,255,65,.10)";pill.style.borderColor=LIME;pill.style.color=LIME;} };
pill.onmouseleave=()=>{ if(_tmplOpenIdx!==idx){pill.style.background=C.bg1;pill.style.borderColor=C.border;pill.style.color=C.text;} };
// Detail panel
const detail=mk("div",{display:"none",flexDirection:"column",gap:"6px",border:`1px solid rgba(180,140,255,.4)`,borderRadius:"12px",padding:"10px 14px",background:"rgba(26,20,60,.55)",boxSizing:"border-box"});
const detailTop=mk("div",{display:"flex",alignItems:"center",gap:"6px"});
const detailLbl=mk("span",{fontSize:"9px",fontWeight:"700",color:"rgba(255,255,255,.85)",flex:"1"});tx(detailLbl,t.name);
// Copy
const dCopyBtn=mk("button",{background:"transparent",border:`1px solid rgba(100,220,120,.4)`,borderRadius:"999px",padding:"2px 10px",fontSize:"8px",fontWeight:"700",color:"rgba(100,220,120,.8)",cursor:"pointer",outline:"none",transition:"all .15s",flexShrink:"0"});
tx(dCopyBtn,"Copy");
dCopyBtn.onmouseenter=()=>{dCopyBtn.style.background="rgba(100,220,120,.12)";dCopyBtn.style.borderColor="rgba(100,220,120,.8)";};
dCopyBtn.onmouseleave=()=>{dCopyBtn.style.background="transparent";dCopyBtn.style.borderColor="rgba(100,220,120,.4)";};
dCopyBtn.onclick=(e)=>{ e.stopPropagation(); navigator.clipboard.writeText(t.prompt).then(()=>{ tx(dCopyBtn,"✓ Copied"); setTimeout(()=>tx(dCopyBtn,"Copy"),1500); }).catch(()=>{}); };
// Edit
const dEditBtn=mk("button",{background:"transparent",border:`1px solid rgba(160,140,220,.4)`,borderRadius:"999px",padding:"2px 10px",fontSize:"8px",fontWeight:"700",color:"rgba(160,140,220,.8)",cursor:"pointer",outline:"none",transition:"all .15s",flexShrink:"0"});
tx(dEditBtn,"Edit");
dEditBtn.onmouseenter=()=>{dEditBtn.style.background="rgba(160,140,220,.1)";dEditBtn.style.borderColor="rgba(160,140,220,.8)";dEditBtn.style.color="#e0d0ff";};
dEditBtn.onmouseleave=()=>{dEditBtn.style.background="transparent";dEditBtn.style.borderColor="rgba(160,140,220,.4)";dEditBtn.style.color="rgba(160,140,220,.8)";};
// Delete
const dDelWrap=mk("div",{position:"relative",display:"inline-flex",flexShrink:"0"});
const dDelBtn=mk("button",{background:"transparent",border:"1px solid rgba(220,80,80,.4)",borderRadius:"999px",padding:"2px 10px",fontSize:"8px",fontWeight:"700",color:"rgba(220,80,80,.8)",cursor:"pointer",outline:"none",transition:"all .15s"});
tx(dDelBtn,"Delete");
dDelBtn.onmouseenter=()=>{dDelBtn.style.background="rgba(220,80,80,.1)";dDelBtn.style.borderColor="rgba(220,80,80,.8)";};
dDelBtn.onmouseleave=()=>{dDelBtn.style.background="transparent";dDelBtn.style.borderColor="rgba(220,80,80,.4)";};
const dDelPop=mk("div",{display:"none",position:"absolute",right:"0",top:"calc(100% + 4px)",background:C.bg1,border:`1px solid ${C.borderH}`,borderRadius:"7px",padding:"6px 8px",zIndex:"10",whiteSpace:"nowrap",boxShadow:"0 4px 12px rgba(0,0,0,.5)",flexDirection:"column",gap:"5px",alignItems:"center"});
const dDelQ=mk("div",{fontSize:"9px",color:C.text,fontWeight:"600"});tx(dDelQ,"Sure?");
const dDelBtns=mk("div",{display:"flex",gap:"4px"});
const dDelYes=mk("button",{background:"rgba(160,25,25,.7)",border:"1px solid rgba(255,80,80,.3)",borderRadius:"999px",padding:"2px 10px",fontSize:"8px",fontWeight:"700",color:"rgba(255,180,180,.9)",cursor:"pointer",outline:"none"});tx(dDelYes,"Yes");
const dDelNo=mk("button",{background:C.bg3,border:`1px solid ${C.borderH}`,borderRadius:"999px",padding:"2px 10px",fontSize:"8px",fontWeight:"700",color:C.muted,cursor:"pointer",outline:"none"});tx(dDelNo,"Keep");
dDelBtns.append(dDelYes,dDelNo); dDelPop.append(dDelQ,dDelBtns); dDelWrap.append(dDelBtn,dDelPop);
dDelBtn.onclick=(e)=>{ e.stopPropagation(); dDelPop.style.display=dDelPop.style.display==="flex"?"none":"flex"; };
dDelYes.onclick=async(e)=>{ e.stopPropagation(); dDelPop.style.display="none"; _templates.splice(idx,1); await _saveTmpl(); _renderTmplPills(); };
dDelNo.onclick=(e)=>{ e.stopPropagation(); dDelPop.style.display="none"; };
const dUseBtn=mk("button",{background:LIME,color:"#111",border:"none",borderRadius:"999px",padding:"2px 10px",fontSize:"8px",fontWeight:"700",cursor:"pointer",outline:"none",transition:"opacity .15s",flexShrink:"0"});
tx(dUseBtn,"Use"); dUseBtn.onmouseenter=()=>dUseBtn.style.opacity=".85"; dUseBtn.onmouseleave=()=>dUseBtn.style.opacity="1";
dUseBtn.onclick=(e)=>{ e.stopPropagation(); _usePrompt(t.prompt); };
detailTop.append(detailLbl,dUseBtn,dCopyBtn,dEditBtn,dDelWrap);
const detailPromptTxt=mk("div",{fontSize:"9px",color:"rgba(200,185,230,.7)",lineHeight:"1.55",fontStyle:"italic"});tx(detailPromptTxt,t.prompt);
// Inline edit form
const dEditForm=mk("div",{display:"none",flexDirection:"column",gap:"6px",borderTop:`1px solid rgba(180,140,255,.2)`,paddingTop:"8px"});
const dETitleInp=mk("input",{background:C.bg2,border:`1px solid ${C.border}`,borderRadius:"999px",color:C.text,fontSize:"10px",padding:"6px 14px",outline:"none",transition:"border-color .15s",width:"100%",boxSizing:"border-box"},{placeholder:"Title…"});
dETitleInp.onfocus=()=>dETitleInp.style.borderColor=LIME; dETitleInp.onblur=()=>dETitleInp.style.borderColor=C.border;
const dEPromptTA=mk("textarea",{background:C.bg2,border:`1px solid ${C.border}`,borderRadius:"16px",color:C.text,fontSize:"10px",padding:"8px 14px",outline:"none",lineHeight:"1.5",resize:"vertical",minHeight:"60px",fontFamily:"inherit",transition:"border-color .15s",width:"100%",boxSizing:"border-box"},{placeholder:"Prompt…"});
dEPromptTA.addEventListener("wheel",e=>e.stopPropagation(),{passive:true});
dEPromptTA.onfocus=()=>dEPromptTA.style.borderColor=LIME; dEPromptTA.onblur=()=>dEPromptTA.style.borderColor=C.border;
const dECancelBtn=mk("button",{background:"transparent",border:`1px solid ${C.borderH}`,borderRadius:"999px",padding:"5px 16px",fontSize:"9px",fontWeight:"700",color:C.muted,cursor:"pointer",outline:"none",transition:"all .15s"});
tx(dECancelBtn,"Cancel"); dECancelBtn.onmouseenter=()=>{dECancelBtn.style.borderColor=C.text;dECancelBtn.style.color=C.text;}; dECancelBtn.onmouseleave=()=>{dECancelBtn.style.borderColor=C.borderH;dECancelBtn.style.color=C.muted;};
const dEUpdateBtn=mk("button",{background:LIME,color:"#111",border:"none",borderRadius:"999px",padding:"5px 16px",fontSize:"9px",fontWeight:"700",cursor:"pointer",outline:"none",transition:"opacity .15s"});
tx(dEUpdateBtn,"Update"); dEUpdateBtn.onmouseenter=()=>dEUpdateBtn.style.opacity=".85"; dEUpdateBtn.onmouseleave=()=>dEUpdateBtn.style.opacity="1";
const dEBtmRow=mk("div",{display:"flex",alignItems:"center",gap:"6px"});
dEBtmRow.append(mk("div",{flex:"1"}),dECancelBtn,dEUpdateBtn);
dEditForm.append(dETitleInp,dEPromptTA,dEBtmRow);
const _tmplSetEditMode=(on)=>{ dUseBtn.style.display=on?"none":""; dCopyBtn.style.display=on?"none":""; dEditBtn.style.display=on?"none":""; dDelWrap.style.display=on?"none":"inline-flex"; };
dEditBtn.onclick=(e)=>{ e.stopPropagation(); const opening=dEditForm.style.display!=="flex"; dETitleInp.value=t.name; dEPromptTA.value=t.prompt; dEditForm.style.display=opening?"flex":"none"; _tmplSetEditMode(opening); if(opening) setTimeout(()=>dETitleInp.focus(),30); };
dECancelBtn.onclick=()=>{ dEditForm.style.display="none"; _tmplSetEditMode(false); };
dEUpdateBtn.onclick=async()=>{ const n=dETitleInp.value.trim(),p=dEPromptTA.value.trim(); if(!n||!p) return; _templates[idx]={name:n,prompt:p}; await _saveTmpl(); _renderTmplPills(); _tmplOpenIdx=idx; const det=_tmplDetailsArea.children[idx]; if(det){det.style.display="flex";det.dataset.open="1";} const pil=_tmplPillsRow.querySelectorAll("button:not([data-addpill])")[idx]; if(pil){pil.style.background="rgba(180,140,255,.15)";pil.style.borderColor="rgba(180,140,255,.6)";pil.style.color="#e0d0ff";} };
detail.append(detailTop,detailPromptTxt,dEditForm);
_tmplDetailsArea.appendChild(detail);
const _closeTmplDetail=()=>{ detail.style.display="none"; _tmplOpenIdx=-1; pill.style.background=C.bg1;pill.style.borderColor=C.border;pill.style.color=C.text; };
pill.onclick=()=>{
if(!_inspireShowFull){ _usePrompt(t.prompt); return; }
if(_tmplOpenIdx===idx){ _closeTmplDetail(); return; }
_tmplDetailsArea.querySelectorAll("div[data-open]").forEach(d=>{d.style.display="none";delete d.dataset.open;});
_tmplPillsRow.querySelectorAll("button").forEach(p2=>{p2.style.background=C.bg1;p2.style.borderColor=C.border;p2.style.color=C.text;});
_tmplOpenIdx=idx; detail.style.display="flex"; detail.dataset.open="1";
pill.style.background="rgba(180,140,255,.15)";pill.style.borderColor="rgba(180,140,255,.6)";pill.style.color="#e0d0ff";
};
_tmplPillsRow.appendChild(pill);
});
// "+ Add" pill always at the end
const tmplAddPill=mk("button",{
padding:"5px 12px",borderRadius:"999px",cursor:"pointer",
fontSize:"10px",fontWeight:"600",lineHeight:"1.5",
border:`1px dashed rgba(240,255,65,.4)`,
background:"rgba(240,255,65,.05)",color:"rgba(240,255,65,.6)",
outline:"none",transition:"all .15s",flexShrink:"0",
});
tx(tmplAddPill,"+ Add"); tmplAddPill.dataset.addpill="1";
tmplAddPill.onmouseenter=()=>{ tmplAddPill.style.borderColor=LIME;tmplAddPill.style.background="rgba(240,255,65,.12)";tmplAddPill.style.color=LIME; };
tmplAddPill.onmouseleave=()=>{ tmplAddPill.style.borderColor="rgba(240,255,65,.4)";tmplAddPill.style.background="rgba(240,255,65,.05)";tmplAddPill.style.color="rgba(240,255,65,.6)"; };
tmplAddPill.onclick=()=>{
_editIdx=-1; tmplNameInp.value=""; tmplPromptTA.value=S.prompt||"";
tx(tmplFormTitle,"New Template"); tx(tmplSaveBtn,"Save");
tmplForm.style.display=tmplForm.style.display==="flex"?"none":"flex";
if(tmplForm.style.display==="flex") setTimeout(()=>tmplNameInp.focus(),30);
};
_tmplPillsRow.appendChild(tmplAddPill);
};
tmplCancelBtn.onclick=()=>{ tmplForm.style.display="none"; };
tmplSaveBtn.onclick=async()=>{
const name=tmplNameInp.value.trim(), prompt=tmplPromptTA.value.trim();
if(!name||!prompt) return;
if(_editIdx>=0) _templates[_editIdx]={name,prompt};
else _templates.push({name,prompt});
await _saveTmpl();
tmplForm.style.display="none";
_editIdx=-1; _renderTmplPills();
};
// Load & render — only fetch from server once
if(!_t2iTemplatesLoaded){
_t2iTemplatesLoaded=true;
(async()=>{
try{ const r=await api.fetchApi("/z_image/config"); const d=await r.json(); _t2iTemplates.length=0; (d.t2i_templates||[]).forEach(t=>_t2iTemplates.push(t)); }
catch(e){}
_renderTmplPills();
})();
} else {
_renderTmplPills();
}
const tmplSection=mk("div",{});
tmplSection.append(tmplHdr,tmplForm,_tmplPillsRow,_tmplDetailsArea);
scroll.appendChild(tmplSection);
}
let _hasDual=false;
def.categories.forEach(({cat,items})=>{
// Category label — if cat contains "(note)", render note part smaller without uppercase
const catLbl=mk("div",{
display:"flex",alignItems:"baseline",gap:"5px",flexWrap:"wrap",
marginBottom:"5px",
});
const parenIdx=cat.indexOf("(");
if(parenIdx>0){
const mainPart=mk("span",{fontSize:"9px",fontWeight:"700",letterSpacing:".1em",textTransform:"uppercase",color:C.muted});
tx(mainPart,cat.slice(0,parenIdx).trim());
const notePart=mk("span",{fontSize:"8px",fontWeight:"400",color:C.muted,opacity:".7",textTransform:"none",letterSpacing:".01em"});
tx(notePart,cat.slice(parenIdx));
catLbl.append(mainPart,notePart);
} else {
catLbl.style.fontSize="9px";catLbl.style.fontWeight="700";
catLbl.style.letterSpacing=".1em";catLbl.style.textTransform="uppercase";
catLbl.style.color=C.muted;
tx(catLbl,cat);
}
// Pills always render as pills; in preview mode click shows inline detail instead of inserting
const pillsRow=mk("div",{display:"flex",flexWrap:"wrap",gap:"6px"});
// Container for detail panels (below pills row)
const detailsArea=mk("div",{display:"flex",flexDirection:"column",gap:"4px",marginTop:"2px"});
let _openDetailIdx=-1; // which pill's detail is currently open
items.forEach(({label,prompt,dual},itemIdx)=>{
if(dual) _hasDual=true;
const pill=mk("button",{
padding:dual?"5px 10px 5px 8px":"7px 14px",
borderRadius:"999px",cursor:"pointer",
fontSize:"10px",fontWeight:"500",lineHeight:"1.5",
border:`1px solid ${dual?"rgba(100,160,255,.35)":C.border}`,
background:dual?"rgba(80,120,220,.08)":C.bg1,
color:dual?"rgba(140,190,255,.9)":C.text,
outline:"none",textAlign:"left",
display:"flex",alignItems:"center",gap:"6px",
transition:"background .12s,border-color .12s,color .12s",
});
if(dual){
const badge=mk("span",{fontSize:"7px",fontWeight:"800",letterSpacing:".06em",background:"rgba(100,160,255,.2)",color:"rgba(140,190,255,.8)",border:"1px solid rgba(100,160,255,.3)",borderRadius:"4px",padding:"1px 4px",flexShrink:"0",lineHeight:"1.6"});
tx(badge,"2 imgs"); const lbl=mk("span");tx(lbl,label); pill.append(badge,lbl);
pill.onmouseenter=()=>{ pill.style.background="rgba(80,140,255,.18)";pill.style.borderColor="rgba(100,160,255,.7)";pill.style.color="#9bbfff"; };
pill.onmouseleave=()=>{ if(_openDetailIdx!==itemIdx){pill.style.background="rgba(80,120,220,.08)";pill.style.borderColor="rgba(100,160,255,.35)";pill.style.color="rgba(140,190,255,.9)";} };
} else {
tx(pill,label);
pill.onmouseenter=()=>{ if(_openDetailIdx!==itemIdx){pill.style.background="rgba(240,255,65,.10)";pill.style.borderColor=LIME;pill.style.color=LIME;} };
pill.onmouseleave=()=>{ if(_openDetailIdx!==itemIdx){pill.style.background=C.bg1;pill.style.borderColor=C.border;pill.style.color=C.text;} };
}
// Detail panel (shown in preview mode)
const detail=mk("div",{
display:"none",flexDirection:"column",gap:"6px",
border:`1px solid rgba(180,140,255,.4)`,borderRadius:"12px",
padding:"10px 14px",background:"rgba(26,20,60,.55)",boxSizing:"border-box",
});
const detailTop=mk("div",{display:"flex",alignItems:"center",gap:"6px"});
const detailLbl=mk("span",{fontSize:"9px",fontWeight:"700",color:"rgba(255,255,255,.85)",flex:"1"});
tx(detailLbl,label);
// Copy button
const detailCopyBtn=mk("button",{background:"transparent",border:`1px solid rgba(100,220,120,.4)`,borderRadius:"999px",padding:"2px 10px",fontSize:"8px",fontWeight:"700",color:"rgba(100,220,120,.8)",cursor:"pointer",outline:"none",transition:"all .15s",flexShrink:"0"});
tx(detailCopyBtn,"Copy");
detailCopyBtn.onmouseenter=()=>{detailCopyBtn.style.background="rgba(100,220,120,.12)";detailCopyBtn.style.borderColor="rgba(100,220,120,.8)";};
detailCopyBtn.onmouseleave=()=>{detailCopyBtn.style.background="transparent";detailCopyBtn.style.borderColor="rgba(100,220,120,.4)";};
detailCopyBtn.onclick=(e)=>{ e.stopPropagation(); navigator.clipboard.writeText(prompt).then(()=>{ tx(detailCopyBtn,"✓ Copied"); setTimeout(()=>tx(detailCopyBtn,"Copy"),1500); }).catch(()=>{}); };
// Edit button
const detailEditBtn=mk("button",{background:"transparent",border:`1px solid rgba(160,140,220,.4)`,borderRadius:"999px",padding:"2px 10px",fontSize:"8px",fontWeight:"700",color:"rgba(160,140,220,.8)",cursor:"pointer",outline:"none",transition:"all .15s",flexShrink:"0"});
tx(detailEditBtn,"Edit");
detailEditBtn.onmouseenter=()=>{detailEditBtn.style.background="rgba(160,140,220,.1)";detailEditBtn.style.borderColor="rgba(160,140,220,.8)";detailEditBtn.style.color="#e0d0ff";};
detailEditBtn.onmouseleave=()=>{detailEditBtn.style.background="transparent";detailEditBtn.style.borderColor="rgba(160,140,220,.4)";detailEditBtn.style.color="rgba(160,140,220,.8)";};
// Delete button with confirm popover
const detailDelWrap=mk("div",{position:"relative",display:"inline-flex",flexShrink:"0"});
const detailDelBtn=mk("button",{background:"transparent",border:"1px solid rgba(220,80,80,.4)",borderRadius:"999px",padding:"2px 10px",fontSize:"8px",fontWeight:"700",color:"rgba(220,80,80,.8)",cursor:"pointer",outline:"none",transition:"all .15s"});
tx(detailDelBtn,"Delete");
detailDelBtn.onmouseenter=()=>{detailDelBtn.style.background="rgba(220,80,80,.1)";detailDelBtn.style.borderColor="rgba(220,80,80,.8)";};
detailDelBtn.onmouseleave=()=>{detailDelBtn.style.background="transparent";detailDelBtn.style.borderColor="rgba(220,80,80,.4)";};
const detailDelPop=mk("div",{display:"none",position:"absolute",right:"0",top:"calc(100% + 4px)",background:C.bg1,border:`1px solid ${C.borderH}`,borderRadius:"7px",padding:"6px 8px",zIndex:"10",whiteSpace:"nowrap",boxShadow:"0 4px 12px rgba(0,0,0,.5)",flexDirection:"column",gap:"5px",alignItems:"center"});
const detailDelQ=mk("div",{fontSize:"9px",color:C.text,fontWeight:"600"});tx(detailDelQ,"Sure?");
const detailDelBtns=mk("div",{display:"flex",gap:"4px"});
const detailDelYes=mk("button",{background:"rgba(160,25,25,.7)",border:"1px solid rgba(255,80,80,.3)",borderRadius:"999px",padding:"2px 10px",fontSize:"8px",fontWeight:"700",color:"rgba(255,180,180,.9)",cursor:"pointer",outline:"none"});tx(detailDelYes,"Yes");
const detailDelNo=mk("button",{background:C.bg3,border:`1px solid ${C.borderH}`,borderRadius:"999px",padding:"2px 10px",fontSize:"8px",fontWeight:"700",color:C.muted,cursor:"pointer",outline:"none"});tx(detailDelNo,"Keep");
detailDelBtns.append(detailDelYes,detailDelNo); detailDelPop.append(detailDelQ,detailDelBtns); detailDelWrap.append(detailDelBtn,detailDelPop);
detailDelBtn.onclick=(e)=>{ e.stopPropagation(); detailDelPop.style.display=detailDelPop.style.display==="flex"?"none":"flex"; };
detailDelYes.onclick=async(e)=>{ e.stopPropagation(); detailDelPop.style.display="none"; items.splice(itemIdx,1); await _saveDiscoverPrompts(); _inspirePromptsLoaded=false; _buildInspireBody(); };
detailDelNo.onclick=(e)=>{ e.stopPropagation(); detailDelPop.style.display="none"; };
const detailUseBtn=mk("button",{background:LIME,color:"#111",border:"none",borderRadius:"999px",padding:"2px 10px",fontSize:"8px",fontWeight:"700",cursor:"pointer",outline:"none",transition:"opacity .15s",flexShrink:"0"});
tx(detailUseBtn,"Use"); detailUseBtn.onmouseenter=()=>detailUseBtn.style.opacity=".85"; detailUseBtn.onmouseleave=()=>detailUseBtn.style.opacity="1";
detailUseBtn.onclick=(e)=>{ e.stopPropagation(); _usePrompt(prompt); };
detailTop.append(detailLbl,detailUseBtn,detailCopyBtn,detailEditBtn,detailDelWrap);
const detailPromptTxt=mk("div",{fontSize:"9px",color:"rgba(200,185,230,.7)",lineHeight:"1.55",fontStyle:"italic"});
tx(detailPromptTxt,prompt);
// Edit form inside detail
const editForm=mk("div",{display:"none",flexDirection:"column",gap:"6px",borderTop:`1px solid rgba(180,140,255,.2)`,paddingTop:"8px"});
const editTitleInp=mk("input",{background:C.bg2,border:`1px solid ${C.border}`,borderRadius:"999px",color:C.text,fontSize:"10px",padding:"6px 14px",outline:"none",transition:"border-color .15s",width:"100%",boxSizing:"border-box"},{placeholder:"Title…"});
editTitleInp.onfocus=()=>editTitleInp.style.borderColor=LIME; editTitleInp.onblur=()=>editTitleInp.style.borderColor=C.border;
const editPromptTA=mk("textarea",{background:C.bg2,border:`1px solid ${C.border}`,borderRadius:"16px",color:C.text,fontSize:"10px",padding:"8px 14px",outline:"none",lineHeight:"1.5",resize:"vertical",minHeight:"60px",fontFamily:"inherit",transition:"border-color .15s",width:"100%",boxSizing:"border-box"},{placeholder:"Prompt…"});
editPromptTA.addEventListener("wheel",e=>e.stopPropagation(),{passive:true});
editPromptTA.onfocus=()=>editPromptTA.style.borderColor=LIME; editPromptTA.onblur=()=>editPromptTA.style.borderColor=C.border;
const editBtns=mk("div",{display:"flex",gap:"6px",justifyContent:"flex-end"});
const editCancelBtn=mk("button",{background:"transparent",border:`1px solid ${C.borderH}`,borderRadius:"999px",padding:"5px 16px",fontSize:"9px",fontWeight:"700",color:C.muted,cursor:"pointer",outline:"none",transition:"all .15s"});
tx(editCancelBtn,"Cancel"); editCancelBtn.onmouseenter=()=>{editCancelBtn.style.borderColor=C.text;editCancelBtn.style.color=C.text;}; editCancelBtn.onmouseleave=()=>{editCancelBtn.style.borderColor=C.borderH;editCancelBtn.style.color=C.muted;};
const editUpdateBtn=mk("button",{background:LIME,color:"#111",border:"none",borderRadius:"999px",padding:"5px 16px",fontSize:"9px",fontWeight:"700",cursor:"pointer",outline:"none",transition:"opacity .15s"});
tx(editUpdateBtn,"Update"); editUpdateBtn.onmouseenter=()=>editUpdateBtn.style.opacity=".85"; editUpdateBtn.onmouseleave=()=>editUpdateBtn.style.opacity="1";
let _editDualVal=false;
const editDualChk={get checked(){return _editDualVal;},set checked(v){_editDualVal=v;_editDualRefresh();}};
const editDualToggle=mk("button",{background:"transparent",border:`1px solid rgba(160,140,220,.35)`,borderRadius:"999px",padding:"3px 10px",fontSize:"8px",fontWeight:"600",color:"rgba(160,140,220,.6)",cursor:"pointer",outline:"none",transition:"all .15s",textAlign:"left"});
tx(editDualToggle,"Requires 2 images");
const _editDualRefresh=()=>{
if(_editDualVal){editDualToggle.style.background="rgba(100,160,255,.15)";editDualToggle.style.borderColor="rgba(100,160,255,.7)";editDualToggle.style.color="#9bbfff";}
else{editDualToggle.style.background="transparent";editDualToggle.style.borderColor="rgba(160,140,220,.35)";editDualToggle.style.color="rgba(160,140,220,.6)";}
};
editDualToggle.onclick=(e)=>{e.stopPropagation();_editDualVal=!_editDualVal;_editDualRefresh();};
editBtns.append(editCancelBtn,editUpdateBtn);
const editBottomRow=mk("div",{display:"flex",alignItems:"center",gap:"6px"});
if(activePill==="i2i") editDualToggle.style.display="none";
editBottomRow.append(editDualToggle,mk("div",{flex:"1"}),editBtns);
editForm.append(editTitleInp,editPromptTA,editBottomRow);
const _setDetailEditMode=(on)=>{ detailUseBtn.style.display=on?"none":""; detailCopyBtn.style.display=on?"none":""; detailEditBtn.style.display=on?"none":""; detailDelWrap.style.display=on?"none":"inline-flex"; };
detailEditBtn.onclick=(e)=>{ e.stopPropagation(); const opening=editForm.style.display!=="flex"; editTitleInp.value=label; editPromptTA.value=prompt; editDualChk.checked=!!dual; editForm.style.display=opening?"flex":"none"; _setDetailEditMode(opening); if(opening) setTimeout(()=>editTitleInp.focus(),30); };
editCancelBtn.onclick=()=>{ editForm.style.display="none"; _setDetailEditMode(false); };
editUpdateBtn.onclick=async()=>{
const t=editTitleInp.value.trim(),p=editPromptTA.value.trim(); if(!t||!p) return;
const entry={label:t,prompt:p}; if(editDualChk.checked) entry.dual=true;
items[itemIdx]=entry; await _saveDiscoverPrompts(); _inspirePromptsLoaded=false; _buildInspireBody();
};
detail.append(detailTop,detailPromptTxt,editForm);
detailsArea.appendChild(detail);
const _closeDetail=()=>{
detail.style.display="none"; _openDetailIdx=-1;
pill.style.background=dual?"rgba(80,120,220,.08)":C.bg1;
pill.style.borderColor=dual?"rgba(100,160,255,.35)":C.border;
pill.style.color=dual?"rgba(140,190,255,.9)":C.text;
};
pill.onclick=()=>{
if(!_inspireShowFull){ _usePrompt(prompt); return; }
if(_openDetailIdx===itemIdx){ _closeDetail(); return; }
// Close previously open detail
detailsArea.querySelectorAll("div[data-detail-open]").forEach(d=>{ d.style.display="none"; delete d.dataset.detailOpen; });
// Reset all pills in this category
pillsRow.querySelectorAll("button").forEach(p2=>{ p2.style.background=C.bg1;p2.style.borderColor=C.border;p2.style.color=C.text; });
_openDetailIdx=itemIdx;
detail.style.display="flex"; detail.dataset.detailOpen="1";
pill.style.background="rgba(180,140,255,.15)"; pill.style.borderColor="rgba(180,140,255,.6)"; pill.style.color="#e0d0ff";
};
pillsRow.appendChild(pill);
});
// ── + Add pill at end of each category ──────────────────────
const addPill=mk("button",{
padding:"5px 12px",borderRadius:"999px",cursor:"pointer",
fontSize:"10px",fontWeight:"600",lineHeight:"1.5",
border:`1px dashed rgba(240,255,65,.4)`,
background:"rgba(240,255,65,.05)",color:"rgba(240,255,65,.6)",
outline:"none",transition:"all .15s",flexShrink:"0",
});
tx(addPill,"+ Add");
addPill.onmouseenter=()=>{ addPill.style.borderColor=LIME;addPill.style.background="rgba(240,255,65,.12)";addPill.style.color=LIME; };
addPill.onmouseleave=()=>{ addPill.style.borderColor="rgba(240,255,65,.4)";addPill.style.background="rgba(240,255,65,.05)";addPill.style.color="rgba(240,255,65,.6)"; };
// Inline form (hidden, appears below pills row)
const addForm=mk("div",{
display:"none",flexDirection:"column",gap:"6px",marginTop:"4px",
border:`1px solid rgba(240,255,65,.3)`,borderRadius:"16px",
padding:"10px 12px",background:"rgba(240,255,65,.04)",boxSizing:"border-box",
});
const addFormTitle=mk("div",{fontSize:"8px",fontWeight:"700",color:LIME,letterSpacing:".08em",textTransform:"uppercase",marginBottom:"2px"});
tx(addFormTitle,"Add to "+cat.split("(")[0].trim());
const addTitleInp=mk("input",{
background:C.bg2,border:`1px solid ${C.border}`,borderRadius:"999px",
color:C.text,fontSize:"10px",padding:"6px 14px",outline:"none",
transition:"border-color .15s",width:"100%",boxSizing:"border-box",
},{placeholder:"Title…"});
addTitleInp.onfocus=()=>addTitleInp.style.borderColor=LIME;
addTitleInp.onblur=()=>addTitleInp.style.borderColor=C.border;
const addPromptTA=mk("textarea",{
background:C.bg2,border:`1px solid ${C.border}`,borderRadius:"16px",
color:C.text,fontSize:"10px",padding:"8px 14px",outline:"none",lineHeight:"1.5",
resize:"vertical",minHeight:"60px",fontFamily:"inherit",
transition:"border-color .15s",width:"100%",boxSizing:"border-box",
},{placeholder:"Prompt…"});
addPromptTA.addEventListener("wheel",e=>e.stopPropagation(),{passive:true});
addPromptTA.onfocus=()=>addPromptTA.style.borderColor=LIME;
addPromptTA.onblur=()=>addPromptTA.style.borderColor=C.border;
const addFormBtns=mk("div",{display:"flex",gap:"6px",justifyContent:"flex-end"});
const addCancelBtn=mk("button",{background:"transparent",border:`1px solid ${C.borderH}`,borderRadius:"999px",padding:"5px 16px",fontSize:"9px",fontWeight:"700",color:C.muted,cursor:"pointer",outline:"none",transition:"all .15s"});
tx(addCancelBtn,"Cancel");
addCancelBtn.onmouseenter=()=>{addCancelBtn.style.borderColor=C.text;addCancelBtn.style.color=C.text;};
addCancelBtn.onmouseleave=()=>{addCancelBtn.style.borderColor=C.borderH;addCancelBtn.style.color=C.muted;};
const addSaveBtn=mk("button",{background:LIME,color:"#111",border:"none",borderRadius:"999px",padding:"5px 16px",fontSize:"9px",fontWeight:"700",cursor:"pointer",outline:"none",transition:"opacity .15s"});
tx(addSaveBtn,"Save");
addSaveBtn.onmouseenter=()=>addSaveBtn.style.opacity=".85";
addSaveBtn.onmouseleave=()=>addSaveBtn.style.opacity="1";
// Dual checkbox
let _addDualVal=false;
const addDualChk={get checked(){return _addDualVal;},set checked(v){_addDualVal=v;_addDualRefresh();}};
const addDualToggle=mk("button",{background:"transparent",border:`1px solid rgba(160,140,220,.35)`,borderRadius:"999px",padding:"3px 10px",fontSize:"8px",fontWeight:"600",color:"rgba(160,140,220,.6)",cursor:"pointer",outline:"none",transition:"all .15s"});
tx(addDualToggle,"Requires 2 images");
const _addDualRefresh=()=>{
if(_addDualVal){addDualToggle.style.background="rgba(100,160,255,.15)";addDualToggle.style.borderColor="rgba(100,160,255,.7)";addDualToggle.style.color="#9bbfff";}
else{addDualToggle.style.background="transparent";addDualToggle.style.borderColor="rgba(160,140,220,.35)";addDualToggle.style.color="rgba(160,140,220,.6)";}
};
addDualToggle.onclick=(e)=>{e.stopPropagation();_addDualVal=!_addDualVal;_addDualRefresh();};
addFormBtns.append(addCancelBtn,addSaveBtn);
const addBottomRow=mk("div",{display:"flex",alignItems:"center",gap:"6px"});
if(activePill==="i2i") addDualToggle.style.display="none";
addBottomRow.append(addDualToggle,mk("div",{flex:"1"}),addFormBtns);
addForm.append(addFormTitle,addTitleInp,addPromptTA,addBottomRow);
addPill.onclick=()=>{
addTitleInp.value=""; addPromptTA.value=""; _addDualVal=false; _addDualRefresh();
addForm.style.display=addForm.style.display==="flex"?"none":"flex";
if(addForm.style.display==="flex") setTimeout(()=>addTitleInp.focus(),30);
};
addCancelBtn.onclick=()=>{ addForm.style.display="none"; };
addSaveBtn.onclick=async()=>{
const title=addTitleInp.value.trim(), prompt=addPromptTA.value.trim();
if(!title||!prompt) return;
const entry={label:title,prompt};
if(addDualChk.checked) entry.dual=true;
items.push(entry);
await _saveDiscoverPrompts();
_inspirePromptsLoaded=false;
addForm.style.display="none";
_buildInspireBody();
};
pillsRow.append(addPill);
const section=mk("div",{});
section.append(catLbl,pillsRow,detailsArea,addForm);
scroll.appendChild(section);
});
_inspireBody.appendChild(scroll);
// Legend for dual-input pills
if(_hasDual){
const legend=mk("div",{
display:"flex",alignItems:"center",gap:"5px",
marginTop:"8px",flexShrink:"0",
});
const badge=mk("span",{
fontSize:"7px",fontWeight:"800",letterSpacing:".06em",
background:"rgba(100,160,255,.2)",color:"rgba(140,190,255,.8)",
border:"1px solid rgba(100,160,255,.3)",borderRadius:"4px",
padding:"1px 4px",lineHeight:"1.6",flexShrink:"0",
});
tx(badge,"2 imgs");
const legendTxt=mk("span",{fontSize:"9px",color:C.muted,lineHeight:"1.5"});
tx(legendTxt,"requires both Image 1 and Image 2 to be loaded");
legend.append(badge,legendTxt);
_inspireBody.appendChild(legend);
}
// Optional note — shown below the prompt pills
if(def.note){
const noteLbl=mk("div",{
fontSize:"9px",color:"#f0a040",lineHeight:"1.5",marginTop:"8px",flexShrink:"0",
});
tx(noteLbl,"⚠ "+def.note);
_inspireBody.appendChild(noteLbl);
}
return;
}
// ── Tabs mode (original format) ─────────────────────────────────────
_inspireShowFullBtn.style.display="";
const tabs=def.tabs||[];
const tabRow=mk("div",{display:"flex",gap:"4px",marginBottom:"8px",flexShrink:"0"});
const showTabs=tabs.length>1;
tabRow.style.display=showTabs?"flex":"none";
const pages=tabs.map(({items},i)=>{
const page=mk("div",{
flex:"1",overflowY:"auto",display:i===0?"flex":"none",
flexDirection:"column",gap:"4px",
scrollbarWidth:"thin",scrollbarColor:`${C.border} transparent`,
});
page.addEventListener("wheel",e=>e.stopPropagation(),{passive:true});
if(!items||items.length===0){
const empty=mk("div",{fontSize:"11px",color:C.muted,padding:"20px 0",textAlign:"center"});
tx(empty,"No suggestions for this mode yet.");
page.appendChild(empty);
} else {
items.forEach(prompt=>{
const row=mk("div",{
padding:"8px 10px",borderRadius:"7px",cursor:"pointer",
fontSize:"10px",lineHeight:"1.55",
border:`1px solid ${C.border}`,background:C.bg1,
transition:"background .12s,border-color .12s,color .12s",
boxSizing:"border-box",
});
if(_inspireShowFull){
row.style.color=C.text;
tx(row,prompt);
let _copied=false;
row.onmouseenter=()=>{ row.style.background=C.bg3;row.style.borderColor="rgba(100,160,255,.6)";row.style.color="#9bbfff"; };
row.onmouseleave=()=>{ row.style.background=C.bg1;row.style.borderColor=C.border;row.style.color=C.text; if(_copied){_copied=false;} };
row.onclick=()=>{
navigator.clipboard.writeText(prompt).then(()=>{
row.style.borderColor="rgba(100,220,120,.7)";row.style.color="#7ddd9a";
setTimeout(()=>{ row.style.borderColor=C.border;row.style.color=C.text; },1200);
}).catch(()=>{});
};
} else {
row.style.color=C.text;
// In normal mode items are plain strings — show truncated label
const short=prompt.length>72?prompt.slice(0,70).trimEnd()+"…":prompt;
tx(row,short);
row.title=prompt;
row.onmouseenter=()=>{ row.style.background=C.bg3;row.style.borderColor=LIME;row.style.color="#fff"; };
row.onmouseleave=()=>{ row.style.background=C.bg1;row.style.borderColor=C.border;row.style.color=C.text; };
row.onclick=()=>_usePrompt(prompt);
}
page.appendChild(row);
});
}
return page;
});
if(showTabs){
tabs.forEach((_tab,i)=>{
const cat=_tab.cat;
const tab=mk("button",{
border:`1px solid ${i===0?LIME:C.border}`,cursor:"pointer",
padding:"3px 10px",fontSize:"9px",fontWeight:"700",letterSpacing:".04em",
borderRadius:"5px",outline:"none",transition:"all .15s",
color:i===0?LIME:C.muted,
background:i===0?"rgba(240,255,65,.08)":"transparent",
});
tx(tab,cat);
tab.onclick=()=>{
tabRow.querySelectorAll("button").forEach((t,j)=>{
t.style.color=j===i?LIME:C.muted;
t.style.borderColor=j===i?LIME:C.border;
t.style.background=j===i?"rgba(240,255,65,.08)":"transparent";
});
pages.forEach((p,j)=>p.style.display=j===i?"flex":"none");
};
tabRow.appendChild(tab);
});
}
_inspireBody.append(tabRow,...pages);
};
let _inspirePromptsLoaded=false;
const _saveDiscoverPrompts=async()=>{
try{
const toSave={};
["i2i"].forEach(pill=>{
const d=INSPIRE_BY_PILL[pill];
if(d&&d.categories) toSave[pill]={categories:d.categories};
else if(d&&d.tabs) toSave[pill]={tabs:d.tabs};
});
await api.fetchApi("/z_image/config",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({discover_prompts:toSave})});
}catch(e){ console.warn("[ZImageOneNode] save discover_prompts:",e); }
};
// Autofill prompts loaded from config.json — fallbacks used if config missing
let _autofillPrompts={
i2i:"Enhance, transform, or re-imagine this image based on your prompt while preserving the core composition.",
};
const _loadDiscoverPrompts=async()=>{
if(_inspirePromptsLoaded) return;
_inspirePromptsLoaded=true;
try{
const r=await api.fetchApi("/z_image/config");
const d=await r.json();
if(d.autofill_prompts&&Object.keys(d.autofill_prompts).length){
_autofillPrompts={..._autofillPrompts,...d.autofill_prompts};
}
if(d.discover_prompts){
["i2i"].forEach(pill=>{
if(d.discover_prompts[pill]) INSPIRE_BY_PILL[pill]=d.discover_prompts[pill];
});
}
}catch(e){ console.warn("[ZImageOneNode] load discover_prompts:",e); }
};
const _openInspire=async()=>{
await _loadDiscoverPrompts();
_buildInspireBody();
_inspireOverlay.style.display="flex";
_inspireOverlay.getBoundingClientRect();
_inspireOverlay.style.opacity="1";
};
_inspireHdr.style.position="relative"; _inspireHdr.style.zIndex="1";
_inspireBody.style.position="relative"; _inspireBody.style.zIndex="1";
_inspireOverlay.append(_inspireHdr,_inspireBody);
// ── Get Inspired button ───────────────────────────────────────────────
const _inspireBtn=mk("button",{
background:"none",border:`1px solid ${C.border}`,cursor:"pointer",
padding:"2px 7px",color:C.muted,outline:"none",
display:"flex",alignItems:"center",gap:"4px",borderRadius:"5px",
transition:"color .15s,border-color .15s",flexShrink:"0",
});
_inspireBtn.innerHTML=`✦ Discover`;
_inspireBtn.onmouseenter=()=>{_inspireBtn.style.color="#fff";_inspireBtn.style.borderColor="#555";};
_inspireBtn.onmouseleave=()=>{_inspireBtn.style.color=C.muted;_inspireBtn.style.borderColor=C.border;};
_inspireBtn.onclick=_openInspire;
// Order: cap | Get Inspired | errChip | [spacer via marginLeft:auto on _ulBtn] | Add LoRA | Expand prompt
promptCap.style.marginBottom="0";
promptHdr.append(promptCap,_inspireBtn,errMinChip,_ulBtn,_promptExpandBtn);
promptWrap.appendChild(promptHdr);
const promptTA=mk("textarea",{
width:"100%",height:"80px",resize:"none",
background:C.bg2,border:`1px solid ${C.border}`,borderRadius:"8px",
color:C.text,fontSize:"12px",padding:"9px 12px",
boxSizing:"border-box",outline:"none",lineHeight:"1.55",
fontFamily:"inherit",transition:"border-color .15s",display:"block",
},{placeholder:"Describe what you want to generate…"});
promptTA.value=S.prompt;
_promptTARef=promptTA;
promptTA.onfocus=()=>promptTA.style.borderColor=LIME;
promptTA.onblur=()=>promptTA.style.borderColor=C.border;
promptTA.oninput=()=>{S.prompt=promptTA.value;S[_pillPromptKey(activePill)]=promptTA.value;persist();};
promptTA.addEventListener("keydown",e=>{if(e.key==="Escape"){e.preventDefault();promptTA.blur();}});
promptTA.addEventListener("wheel",e=>e.stopPropagation(),{passive:true});
// ── Error panel ───────────────────────────────────────────────────────
const errPanel=mk("div",{display:"none",borderRadius:"8px",overflow:"hidden"});
const errMain=mk("div",{background:"linear-gradient(180deg,rgba(255,103,103,.12),rgba(255,103,103,.05))",
border:`1px solid rgba(255,103,103,.34)`,borderRadius:"8px",
padding:"10px 12px",boxSizing:"border-box",
animation:"fk-error-pulse 1.8s ease-in-out infinite"});
const errTopRow=mk("div",{display:"flex",alignItems:"center",justifyContent:"space-between",marginBottom:"6px"});
const errTitle=mk("div",{fontSize:"11px",fontWeight:"700",color:C.err});tx(errTitle,"Generation error");
const _errMinBtn=mk("button",{
background:"none",border:"none",cursor:"pointer",padding:"2px 6px",
color:C.muted,fontSize:"9px",fontWeight:"700",letterSpacing:".04em",
outline:"none",borderRadius:"4px",textTransform:"uppercase",
border:`1px solid rgba(255,103,103,.25)`,transition:"color .15s,border-color .15s",
});
tx(_errMinBtn,"Hide");
errTopRow.append(errTitle,_errMinBtn);
const errMsg=mk("div",{fontSize:"11px",lineHeight:"1.55",color:C.text,whiteSpace:"pre-wrap",wordBreak:"break-word"});
const errHint=mk("div",{fontSize:"10px",lineHeight:"1.5",color:C.warn,marginTop:"6px"});
tx(errHint,"Check the console log for details and make sure the correct models are selected in Settings.");
errMain.append(errTopRow,errMsg,errHint);
errPanel.appendChild(errMain);
let _errMinimized=false;
const _toggleErrMin=()=>{
_errMinimized=!_errMinimized;
errPanel.style.display=_errMinimized?"none":"block";
errMain.style.display=_errMinimized?"none":"block";
errMinChip.style.display=_errMinimized?"flex":"none";
promptTA.style.display=_errMinimized?"block":"none";
tx(_errMinBtn,_errMinimized?"Show":"Hide");
};
_errMinBtn.onclick=_toggleErrMin;
errMinChip.onclick=_toggleErrMin;
function showError(msg){
_errMinimized=false;tx(_errMinBtn,"Hide");
errMsg.textContent=msg||"Unknown error.";
errMain.style.display="block";errMinChip.style.display="none";
promptTA.style.display="none";errPanel.style.display="block";
}
function clearError(){
_errMinimized=false;errPanel.style.display="none";promptTA.style.display="block";
errMinChip.style.display="none";
}
promptWrap.append(promptTA,errPanel);
// ── Prompt expand overlay ─────────────────────────────────────────────
const _promptOverlay=mk("div",{
position:"absolute",inset:"0",zIndex:"250",background:C.bg0,
display:"none",flexDirection:"column",
padding:"14px",boxSizing:"border-box",gap:"8px",
opacity:"0",transition:"opacity 0.15s ease",
});
const _promptOvHdr=mk("div",{display:"flex",alignItems:"center",justifyContent:"space-between",flexShrink:"0"});
const _promptOvCap=mk("div",{fontSize:"10px",fontWeight:"700",color:C.muted,letterSpacing:".07em",textTransform:"uppercase"});
tx(_promptOvCap,"Prompt");
const _promptCollapseBtn=mk("button",{
background:"none",border:"none",cursor:"pointer",padding:"2px 4px",
color:C.muted,lineHeight:"1",outline:"none",
display:"flex",alignItems:"center",borderRadius:"4px",transition:"color .15s",
});
_promptCollapseBtn.innerHTML=``;
_promptCollapseBtn.onmouseenter=()=>_promptCollapseBtn.style.color=LIME;
_promptCollapseBtn.onmouseleave=()=>_promptCollapseBtn.style.color=C.muted;
_promptOvHdr.append(_promptOvCap,_promptCollapseBtn);
const _promptOvTA=mk("textarea",{
flex:"1",width:"100%",resize:"none",minHeight:"0",
background:C.bg2,border:`1px solid ${C.border}`,borderRadius:"8px",
color:C.text,fontSize:"12px",padding:"10px 12px",
boxSizing:"border-box",outline:"none",lineHeight:"1.6",
fontFamily:"inherit",transition:"border-color .15s",
},{placeholder:"Describe what you want to generate…"});
_promptOvTA.addEventListener("wheel",e=>e.stopPropagation(),{passive:true});
_promptOvTA.onfocus=()=>_promptOvTA.style.borderColor=LIME;
_promptOvTA.onblur=()=>_promptOvTA.style.borderColor=C.border;
_promptOvTA.oninput=()=>{S.prompt=_promptOvTA.value;S[_pillPromptKey(activePill)]=_promptOvTA.value;promptTA.value=_promptOvTA.value;persist();};
const _openPromptOverlay=()=>{
_promptOvTA.value=S.prompt;_promptOverlay.style.display="flex";
_promptOverlay.getBoundingClientRect();_promptOverlay.style.opacity="1";
setTimeout(()=>{_promptOvTA.focus();_promptOvTA.setSelectionRange(_promptOvTA.value.length,_promptOvTA.value.length);},50);
};
const _closePromptOverlay=()=>{
S.prompt=_promptOvTA.value;promptTA.value=_promptOvTA.value;persist();
_promptOverlay.style.opacity="0";
setTimeout(()=>_promptOverlay.style.display="none",160);
};
_promptExpandBtn.onclick=_openPromptOverlay;
_promptCollapseBtn.onclick=_closePromptOverlay;
_promptOverlay.append(_promptOvHdr,_promptOvTA);
_promptOverlay.addEventListener("keydown",e=>{if(e.key==="Escape")_closePromptOverlay();});
// ── GENERATION ────────────────────────────────────────────────────────
const _resetGenBtn=()=>{
genBtn.disabled=false;tx(genBtn,"Generate");
genBtn.style.background=LIME;genBtn.style.backgroundSize="";
genBtn.style.animation="none";genBtn.style.color="#111";
genBtn.style.border="2px solid transparent";
stopBtn.style.maxWidth="0";stopBtn.style.minWidth="0";stopBtn.style.width="0";stopBtn.style.opacity="0";stopBtn.style.padding="0";stopBtn.style.marginLeft="0";
progWrap.style.display="none";
if(_lastGenObj) previewDelBtn.style.display="flex";
};
const resetBtn=()=>{
S.generating=false;S._pendingMeta=null;_activePromptId=null;
S._preRunFiles=new Set();persist();_resetGenBtn();
};
let _lastGenObj=null; // {filename, subfolder} of the most recently generated image
let _previewBlobUrl=null;
const showPreview=(url)=>{
if(_previewBlobUrl) URL.revokeObjectURL(_previewBlobUrl);
_previewBlobUrl=url;
placeholder.style.display="none";
finalImg.src=url;
finalImg.style.display="block";
};
const showFinal=(url,filename,subfolder)=>{
if(_previewBlobUrl){ URL.revokeObjectURL(_previewBlobUrl); _previewBlobUrl=null; }
clearError();S.generating=false;S.previewUrl=null;_activePromptId=null;persist();
_resetGenBtn();
if(soundEnabled)playDone();
_galNeedsRefresh=true;
if(filename) _lastGenObj={filename,subfolder:subfolder||""};
placeholder.style.display="none";
// Save metadata — use snapshot captured at Generate click time
const meta=S._pendingMeta?{v:1,...S._pendingMeta}:{v:1,prompt:S.prompt,w:getEffectiveW(),h:getEffectiveH(),mode:activePill};
if(filename){
api.fetchApi("/z_image/save_meta",{
method:"POST",headers:{"Content-Type":"application/json"},
body:JSON.stringify({filename,subfolder:subfolder||"",meta}),
}).catch(e=>console.warn("[ZImageOneNode] save_meta:",e));
}
const _showComparer=(img1InputName)=>{
finalImg.style.display="none";
comparerGenImg.src=url;
comparerGenImg.style.width=(previewBox.offsetWidth||620)+"px";
comparerBase.src=api.apiURL(`/view?filename=${encodeURIComponent(img1InputName)}&type=input&subfolder=`);
comparerWrap.style.display="block";
previewUseWrap.style.display="block";
_cmpSetPct(100);
};
// Use snapshot mode/image so switching pills mid-generation doesn't corrupt comparer
const _snapMode=S._pendingMeta?.mode||activePill;
const _snapImg1=S._pendingMeta?.image1||null;
if(_snapMode==="i2i"&&_snapImg1){
_showComparer(_snapImg1);
} else {
comparerWrap.style.display="none";
finalImg.src=url;finalImg.style.display="block";
}
previewUseWrap.style.display="block";
if(filename) previewDelBtn.style.display="flex";
};
const _slotErr=(slot,lbl)=>{ slot.el.style.borderColor="#e05555"; tx(lbl,"Required!"); lbl.style.color="#e05555"; };
genBtn.onclick=async()=>{
if(!S.prompt.trim()){showError("Please enter a prompt.");return;}
if(activePill==="i2i"&&!i2iSlot.hasFile()){_slotErr(i2iSlot,i2iSlotLbl);return;}
const _hasExtModel=(()=>{ const n=app.graph.getNodeById(self.id); const inputs=n?.inputs||[]; const slot=inputs.find(i=>i.name==="model"); return slot?.link!=null; })();
if(!S.model&&!_hasExtModel){showError("No model selected. Open Settings and choose a model.");return;}
clearError();S.generating=true;
let _snapMode=activePill;
S._pendingMeta={
prompt:S.prompt,
w:getEffectiveW(), h:getEffectiveH(),
mode:_snapMode,
image1:activePill==="i2i"?(S.i2iImage||null):null,
i2iDenoise:activePill==="i2i"?S.i2iDenoise:undefined,
userLoras:S.userLoras.filter(l=>l.name&&l.name!=="none"&&+(l.strength||0)>0).map(l=>({n:l.name.split(/[\\/]/).pop(),s:l.strength})),
...(S.advancedUI?{steps:S.steps||4, cfg:S.cfg!==undefined?S.cfg:1,
sampler:S.sampler||"res_multistep", scheduler:S.scheduler||"simple",
advancedUI:true}:{}),
seed:S.seed||0, randomizeSeed:S.randomizeSeed,
};
S._preRunFiles=new Set();persist();
// Keep existing image visible until the new one arrives
const _hadImage=finalImg.style.display!=="none"||comparerWrap.style.display!=="none";
if(!_hadImage) placeholder.style.display="none";
progWrap.style.display="flex";
setStage("Generating…","Preparing workflow…",0);
genBtn.disabled=true;tx(genBtn,"Generating…");
genBtn.style.background="linear-gradient(270deg,#4dff6e,#00e5ff,#a259ff,#4dff6e)";
genBtn.style.backgroundSize="300% 300%";
genBtn.style.animation="fk-gradient 2.4s ease infinite";
genBtn.style.color=LIME;genBtn.style.border="2px solid transparent";
previewDelBtn.style.display="none";
requestAnimationFrame(()=>{
stopBtn.style.maxWidth="120px";stopBtn.style.minWidth="";stopBtn.style.width="";stopBtn.style.opacity="1";stopBtn.style.padding="0 14px";stopBtn.style.marginLeft="6px";
});
// Snapshot pre-run gallery files so we can identify the new output
try{
const prevR=await api.fetchApi("/z_image/gallery?offset=0&limit=200&subfolder=one-node-z-image");
const prevD=await prevR.json();
S._preRunFiles=new Set((prevD.images||[]).map(v=>v.key||((v.subfolder?`${v.subfolder}/`:"")+v.filename)));
}catch(e){ S._preRunFiles=new Set(); }
// Load correct workflow
const isI2IMode=activePill==="i2i";
let wfUrl;
if(isI2IMode) wfUrl="/z_image/workflow_i2i";
else wfUrl="/z_image/workflow_t2i";
let wfData;
try{
const r=await api.fetchApi(wfUrl);
if(!r.ok) throw new Error("HTTP "+r.status);
wfData=await r.json();
}catch(e){
showError("Could not load workflow (HTTP 404 = restart ComfyUI): "+fmtErr(e));resetBtn();return;
}
const prompt=JSON.parse(JSON.stringify(wfData));
const set=(id,key,val)=>{ if(prompt[id]) prompt[id].inputs[key]=val; };
const _isBase=_isBaseModel();
const _setAdv=(samplerNodeId,skipDenoise)=>{
const steps=S.advancedUI?(S.steps||4):(_isBase?20:4);
const cfg=S.advancedUI?(S.cfg!==undefined?S.cfg:1):(_isBase?5:1);
set(samplerNodeId,"steps",steps);
set(samplerNodeId,"cfg",cfg);
if(S.advancedUI){
set(samplerNodeId,"sampler_name",S.sampler||"res_multistep");
set(samplerNodeId,"scheduler",S.scheduler||"simple");
if(!skipDenoise) set(samplerNodeId,"denoise",S.denoise!==undefined?S.denoise:1);
}
};
// Build effective prompt — trigger words from all active LoRAs prepended
const _effectivePrompt=await _buildPromptWithTriggers(S.prompt||"");
const useKV=false; // KV cache not available for Z-Image
// ── External model/clip/vae input detection ─────────────────────────
// If the node has optional inputs wired from outside (e.g. a GGUF loader),
// skip internal loaders and use the external node's output instead.
// The external node is serialized and added to the prompt so ComfyUI can find it.
const _selfNode=app.graph.getNodeById(self.id);
const _extSlot=(name)=>{
if(!_selfNode) return null;
const inputs=_selfNode.inputs||[];
const slot=inputs.find(i=>i.name===name);
if(!slot||slot.link==null) return null;
const link=app.graph.links[slot.link];
if(!link) return null;
// Serialize the external node and all its upstream dependencies into prompt
const _addNodeToPrompt=(nodeId)=>{
if(prompt[String(nodeId)]) return; // already added
const extNode=app.graph.getNodeById(nodeId);
if(!extNode) return;
const serialized={class_type:extNode.comfyClass||extNode.type,inputs:{},_meta:{title:extNode.title||extNode.type}};
// Add widget values as inputs
(extNode.widgets||[]).forEach(w=>{ if(w.name) serialized.inputs[w.name]=w.value; });
// Add connected inputs
(extNode.inputs||[]).forEach((inp,i)=>{
if(inp.link!=null){
const l=app.graph.links[inp.link];
if(l){ _addNodeToPrompt(l.origin_id); serialized.inputs[inp.name]=[String(l.origin_id),l.origin_slot||0]; }
}
});
prompt[String(nodeId)]=serialized;
};
_addNodeToPrompt(link.origin_id);
return [String(link.origin_id),link.origin_slot||0];
};
const extModel=_extSlot("model");
const extClip =_extSlot("clip");
const extVae =_extSlot("vae");
// ── LoRA chain helper ───────────────────────────────────────────────
const _applyLoRAs=(chainSrc,idPrefix)=>{
const toPrev=(p)=>typeof p==="string"?[p,0]:p;
let prev=chainSrc;
(S.userLoras||[]).forEach((ul,i)=>{
if(!ul.name||ul.name==="none"||!(+(ul.strength||0)>0)) return;
const id=`${idPrefix}UL${i+1}`;
prompt[id]={
inputs:{lora_name:ul.name,strength_model:+(ul.strength??1.0),model:toPrev(prev)},
class_type:"LoraLoaderModelOnly",
_meta:{title:`User LoRA ${i+1}`},
};
prev=[id,0];
});
return prev;
};
if(isI2IMode){
// ── I2I workflow patching ──────────────────────────────────────────
if(extModel){ delete prompt["FK:165"]; }
else set("FK:165","unet_name",S.model||"z_image_turbo_bf16.safetensors");
if(extClip){ delete prompt["FK:155"]; prompt["FK:166"].inputs.clip=extClip; prompt["FK:156"].inputs.clip=extClip; }
else set("FK:155","clip_name",S.textEncoder||"qwen_3_4b.safetensors");
if(extVae){ delete prompt["FK:153"]; prompt["FKI2I:vae"].inputs.vae=extVae; prompt["FK:152"].inputs.vae=extVae; }
else set("FK:153","vae_name",S.vae||"ae.safetensors");
set("FK:166","text", _effectivePrompt);
set("FKI2I:img","image", S.i2iImage||"placeholder.png");
set("FK:86","filename_prefix","one-node-z-image/FK");
// Resize input image by longer side if enabled
if(S.i2iResizeLonger>0){
const dims=_i2iDims._getDims();
if(dims.w&&dims.h){
const scale=S.i2iResizeLonger/Math.max(dims.w,dims.h);
const newW=Math.round(dims.w*scale/16)*16;
const newH=Math.round(dims.h*scale/16)*16;
prompt["FKI2I:scale"]={
class_type:"ImageScale",
inputs:{image:["FKI2I:img",0],upscale_method:"lanczos",width:newW,height:newH,crop:"center"},
_meta:{title:"Scale I2I Input"},
};
prompt["FKI2I:vae"].inputs.pixels=["FKI2I:scale",0];
}
}
// Model chain → ModelSamplingAuraFlow → KSampler
let i2iModelSrc=extModel||"FK:165";
const i2iLoraRef=_applyLoRAs(i2iModelSrc,"FK:");
set("FK:169","model",typeof i2iLoraRef==="string"?[i2iLoraRef,0]:i2iLoraRef);
const seed=S.randomizeSeed?Math.floor(Math.random()*999999999999):S.seed;
if(S.randomizeSeed){S.seed=seed;seedInp.setVal(seed);_advSeedInp.setVal(seed);_advSeedRefresh();persist();}
set("FK:171","seed",seed);
set("FK:171","denoise",S.i2iDenoise!==undefined?S.i2iDenoise:0.75);
_setAdv("FK:171",true); // skipDenoise=true — denoise controlled by slider, not advanced
} else {
// ── T2I: original model chain ─────────────────────────────────────
if(extModel){ delete prompt[WF.model]; }
else set(WF.model,"unet_name",S.model||"z_image_turbo_bf16.safetensors");
if(extClip){ delete prompt[WF.textEnc]; prompt[WF.promptPos].inputs.clip=extClip; prompt[WF.promptNeg].inputs.clip=extClip; }
else set(WF.textEnc,"clip_name",S.textEncoder||"qwen_3_4b.safetensors");
if(extVae){ delete prompt[WF.vae]; if(prompt["FK:132"]) prompt["FK:132"].inputs.vae=extVae; if(prompt["FK:232"]) prompt["FK:232"].inputs.vae=extVae; prompt["FK:152"].inputs.vae=extVae; }
else set(WF.vae,"vae_name",S.vae||"ae.safetensors");
// Build chain: UNETLoader → (LoRAs?) → ModelSamplingAuraFlow
let modelSrc=extModel||WF.model;
const finalModelRef=_applyLoRAs(modelSrc,"FK:");
set(WF.sampling,"model",typeof finalModelRef==="string"?[finalModelRef,0]:finalModelRef);
const seed=S.randomizeSeed?Math.floor(Math.random()*999999999999):S.seed;
if(S.randomizeSeed){S.seed=seed;seedInp.setVal(seed);_advSeedInp.setVal(seed);_advSeedRefresh();persist();}
set(WF.sampler,"seed",seed); _setAdv(WF.sampler);
set(WF.promptPos,"text",_effectivePrompt);
set(WF.promptNeg,"text",DEFAULT_NEG_PROMPT);
set(WF.saveImage,"filename_prefix","one-node-z-image/FK");
// ── T2I ──────────────────────────────────────────────────────────
if(activePill==="t2i"){
const w=getW(), h=getH();
set(WF.latent,"width", w||1024);
set(WF.latent,"height", h||1024);
}
}
try{
const resp=await api.fetchApi("/prompt",{
method:"POST",headers:{"Content-Type":"application/json"},
body:JSON.stringify({prompt,client_id:api.clientId,extra_data:{enable_previews:true}}),
});
const result=await resp.json();
const wfErrs=Object.entries(result.node_errors||{}).filter(([k])=>k!==String(self.id));
if(result.error){
showError(fmtErr(result.error));resetBtn();
}else if(wfErrs.length){
showError(fmtErr(wfErrs[0][1]));resetBtn();
}else{
_activePromptId=result.prompt_id||null;
console.log("[ZImageOneNode] queued:",result.prompt_id);
}
}catch(err){
showError(fmtErr(err));resetBtn();
}
};
// ── PILL VISIBILITY ───────────────────────────────────────────────────
function updatePillVisibility(){
i2iPanel.style.display=activePill==="i2i"?"flex":"none";
resSect.style.display=activePill==="i2i"?"none":"flex";
updateSizeControls();
}
// ── mkHeart helper (used by gallery) ─────────────────────────────────
const _mkHeart=(size)=>{
const svg=document.createElementNS("http://www.w3.org/2000/svg","svg");
svg.setAttribute("viewBox","0 0 24 24");svg.setAttribute("width",size||"12px");svg.setAttribute("height",size||"12px");
svg.style.fill="currentColor";svg.style.display="block";svg.style.flexShrink="0";
const p=document.createElementNS("http://www.w3.org/2000/svg","path");
p.setAttribute("d","M12 21.35l-1.45-1.32C5.4 15.36 2 12.28 2 8.5 2 5.42 4.42 3 7.5 3c1.74 0 3.41.81 4.5 2.09C13.09 3.81 14.76 3 16.5 3 19.58 3 22 5.42 22 8.5c0 3.78-3.4 6.86-8.55 11.54L12 21.35z");
svg.appendChild(p);return svg;
};
// ── GALLERY OVERLAY ───────────────────────────────────────────────────
const galleryOverlay=mk("div",{
position:"absolute",inset:"0",background:"#0a0a0a",
display:"none",flexDirection:"column",padding:"16px",
boxSizing:"border-box",zIndex:"50",borderRadius:"8px",
opacity:"0",transition:"opacity .22s ease, transform .22s ease",
transform:"translateY(6px)",
});
// Gallery header
const galHdr=mk("div",{display:"flex",alignItems:"center",justifyContent:"space-between",
marginBottom:"12px",flexShrink:"0",gap:"8px"});
const galTitle=mk("div",{fontSize:"13px",fontWeight:"700",letterSpacing:".06em",
textTransform:"uppercase",color:C.text});tx(galTitle,"Gallery");
const galHdrRight=mk("div",{display:"flex",gap:"6px",alignItems:"center"});
// Favorites toggle
let _galFavOnly=false;
const galFavBtn=mk("button",{background:"transparent",border:`1px solid ${C.border}`,
borderRadius:"6px",padding:"4px 10px",fontSize:"11px",color:C.muted,
cursor:"pointer",outline:"none",transition:"border-color .15s,color .15s,background .15s",
display:"flex",alignItems:"center",gap:"5px"});
galFavBtn.appendChild(_mkHeart("11px"));
const _galFavLbl=mk("span");tx(_galFavLbl,"Favorites");galFavBtn.appendChild(_galFavLbl);
const _setGalFavBtn=(active)=>{
_galFavOnly=active;
galFavBtn.style.background=active?`rgba(240,255,65,.18)`:"transparent";
galFavBtn.style.borderColor=active?LIME:C.border;
galFavBtn.style.color=active?LIME:C.muted;
galleryOverlay.style.background=active?
"linear-gradient(180deg,rgba(240,255,65,.06) 0%,rgba(240,255,65,.02) 40%,rgba(0,0,0,0) 100%), #0a0a0a":
"#0a0a0a";
};
galFavBtn.onmouseenter=()=>{if(!_galFavOnly){galFavBtn.style.borderColor=LIME;galFavBtn.style.color=LIME;}};
galFavBtn.onmouseleave=()=>{if(!_galFavOnly){galFavBtn.style.borderColor=C.border;galFavBtn.style.color=C.muted;}};
galFavBtn.onclick=()=>{ _setGalFavBtn(!_galFavOnly); galLoad(true); };
const galRefreshBtn=mk("button",{background:C.bg3,border:`1px solid ${C.border}`,
borderRadius:"6px",padding:"4px 10px",fontSize:"11px",color:C.muted,
cursor:"pointer",outline:"none",transition:"border-color .15s,color .15s"});
tx(galRefreshBtn,"↺ Refresh");
galRefreshBtn.onmouseenter=()=>{galRefreshBtn.style.borderColor=C.text;galRefreshBtn.style.color=C.text;};
galRefreshBtn.onmouseleave=()=>{galRefreshBtn.style.borderColor=C.border;galRefreshBtn.style.color=C.muted;};
const galClose=mk("button",{background:"transparent",border:`1px solid #e05555`,
borderRadius:"6px",padding:"4px 14px",fontSize:"11px",color:"#e05555",
cursor:"pointer",outline:"none",transition:"opacity .15s"});
tx(galClose,"✕ Close");
galClose.onmouseenter=()=>galClose.style.opacity=".7";
galClose.onmouseleave=()=>galClose.style.opacity="1";
galHdrRight.append(galFavBtn,galRefreshBtn,galClose);
galHdr.append(galTitle,galHdrRight);
// Grid + scroll area
const galScroll=mk("div",{flex:"1",overflowY:"auto",scrollbarWidth:"thin",
scrollbarColor:`${C.border} transparent`});
const galGrid=mk("div",{
display:"grid",gridTemplateColumns:"repeat(auto-fill,minmax(100px,1fr))",
gap:"6px",paddingBottom:"12px",
});
const galEmpty=mk("div",{fontSize:"12px",color:C.muted,textAlign:"center",
padding:"40px 0",display:"none"});
tx(galEmpty,"No images found.");
// Infinite scroll sentinel inside galScroll — must be a child of the scroll container
const galSentinel=mk("div",{height:"2px",flexShrink:"0",marginTop:"4px"});
// Loading spinner shown below grid while fetching next page
const galMoreWrap=mk("div",{display:"none",justifyContent:"center",alignItems:"center",
padding:"14px 0",gap:"8px",flexShrink:"0"});
const galSpinner=mk("div",{
width:"16px",height:"16px",borderRadius:"50%",flexShrink:"0",
border:"2px solid rgba(240,255,65,.2)",borderTopColor:LIME,
animation:"fk-galSpin .7s linear infinite",
});
if(!document.getElementById("fk-galspin-style")){
const ss=document.createElement("style");ss.id="fk-galspin-style";
ss.textContent="@keyframes fk-galSpin{to{transform:rotate(360deg)}}";
document.head.appendChild(ss);
}
const galMoreBtn=mk("div"); // kept for compatibility
galMoreWrap.appendChild(galSpinner);
// Sentinel and spinner are inside galScroll so IntersectionObserver root works correctly
galScroll.append(galGrid,galEmpty,galMoreWrap,galSentinel);
let _galLoading=false;
const _galIo=new IntersectionObserver(entries=>{
if(!entries[0].isIntersecting) return;
if(_galLoading||_galFavOnly||_galOffset>=_galTotal) return;
galLoad(false,GAL_MORE);
},{root:galScroll,threshold:0});
_galIo.observe(galSentinel);
galleryOverlay.append(galHdr,galScroll);
// ── LIGHTBOX ──────────────────────────────────────────────────────────
const lightbox=mk("div",{
position:"absolute",inset:"0",background:"#0a0a0a",
display:"none",flexDirection:"column",zIndex:"60",borderRadius:"8px",
boxSizing:"border-box",
});
// Top bar: filename (left) + close (right)
const lbTop=mk("div",{display:"flex",alignItems:"center",justifyContent:"space-between",
padding:"8px 10px 4px",flexShrink:"0",gap:"8px"});
const lbFilename=mk("div",{fontSize:"11px",color:C.muted,flex:"1",
overflow:"hidden",textOverflow:"ellipsis",whiteSpace:"nowrap"});
const lbClose=mk("button",{background:"transparent",border:`1px solid #e05555`,
borderRadius:"6px",padding:"3px 12px",fontSize:"11px",color:"#e05555",
cursor:"pointer",outline:"none",transition:"opacity .15s"});
tx(lbClose,"✕");
lbClose.onmouseenter=()=>lbClose.style.opacity=".7";
lbClose.onmouseleave=()=>lbClose.style.opacity="1";
lbTop.append(lbFilename,lbClose);
// Body: arrows + image wrap as flex siblings (full-height arrows like LTX node)
const lbBody=mk("div",{display:"flex",alignItems:"center",flex:"1",
minHeight:"0",position:"relative",gap:"8px",padding:"0 8px"});
const lbImgWrap=mk("div",{flex:"1",minWidth:"0",display:"flex",alignItems:"center",
justifyContent:"center",overflow:"hidden",height:"100%",minHeight:"0"});
const lbImg=mk("img",{maxWidth:"100%",maxHeight:"100%",objectFit:"contain",
borderRadius:"8px",display:"block"});
lbImgWrap.appendChild(lbImg);
// Prev / Next arrows — flex siblings, full height
const lbArrowL=mk("button",{background:"rgba(255,255,255,.08)",border:"none",
borderRadius:"6px",width:"36px",flexShrink:"0",alignSelf:"stretch",
cursor:"pointer",fontSize:"18px",color:C.text,outline:"none",
transition:"background .15s",display:"flex",alignItems:"center",justifyContent:"center"});
tx(lbArrowL,"‹");
lbArrowL.onmouseenter=()=>lbArrowL.style.background="rgba(255,255,255,.15)";
lbArrowL.onmouseleave=()=>lbArrowL.style.background="rgba(255,255,255,.08)";
const lbArrowR=mk("button",{background:"rgba(255,255,255,.08)",border:"none",
borderRadius:"6px",width:"36px",flexShrink:"0",alignSelf:"stretch",
cursor:"pointer",fontSize:"18px",color:C.text,outline:"none",
transition:"background .15s",display:"flex",alignItems:"center",justifyContent:"center"});
tx(lbArrowR,"›");
lbArrowR.onmouseenter=()=>lbArrowR.style.background="rgba(255,255,255,.15)";
lbArrowR.onmouseleave=()=>lbArrowR.style.background="rgba(255,255,255,.08)";
lbBody.append(lbArrowL,lbImgWrap,lbArrowR);
// Metadata panel — shown below image
const lbMeta=mk("div",{
flexShrink:"0",margin:"0 8px 4px",
background:"linear-gradient(180deg,rgba(240,255,65,.07),rgba(240,255,65,.02))",
border:"1px solid rgba(240,255,65,.2)",borderRadius:"10px",
padding:"9px 12px",display:"none",flexDirection:"column",gap:"6px",
});
// Prompt row
const lbPromptRow=mk("div",{display:"flex",flexDirection:"column",gap:"3px"});
const lbPromptLbl=mk("div",{fontSize:"9px",color:C.muted,fontWeight:"700",
letterSpacing:".08em",textTransform:"uppercase"});
tx(lbPromptLbl,"Prompt");
const lbPromptText=mk("div",{fontSize:"10px",color:C.text,lineHeight:"1.5",
maxHeight:"42px",overflowY:"auto",scrollbarWidth:"thin"});
lbPromptRow.append(lbPromptLbl,lbPromptText);
// Info chips row: resolution chip + input thumb + restore btn + fav + open folder
const lbInfoRow=mk("div",{display:"flex",gap:"6px",alignItems:"stretch",flexWrap:"wrap"});
const _lbChip=(label,val)=>{
const chip=mk("div",{display:"flex",flexDirection:"column",gap:"1px",
background:C.bg3,borderRadius:"5px",padding:"4px 8px",minWidth:"50px"});
const cl=mk("div",{fontSize:"8px",color:C.muted,fontWeight:"700",
letterSpacing:".07em",textTransform:"uppercase"});
tx(cl,label);
const cv=mk("div",{fontSize:"10px",color:C.text,fontWeight:"600"});
tx(cv,val||"—");
chip.append(cl,cv);
return chip;
};
const lbChipRes=_lbChip("Size","—");
const lbChipMode=_lbChip("Mode","—");
const lbChipAdv=mk("div",{
display:"none",alignItems:"center",gap:"4px",
background:C.bg3,borderRadius:"5px",padding:"4px 8px",
fontSize:"9px",color:C.muted,fontWeight:"700",letterSpacing:".05em",
cursor:"default",title:"Advanced settings saved",
});
lbChipAdv.innerHTML=`ADV`;
// Input image thumbnails (shown when meta has image1 / image2)
const lbImgThumb=mk("img",{height:"40px",borderRadius:"4px",objectFit:"cover",
display:"none",alignSelf:"center",border:`1px solid ${C.border}`});
const lbImgThumb2=mk("img",{height:"40px",borderRadius:"4px",objectFit:"cover",
display:"none",alignSelf:"center",border:`1px solid ${C.border}`});
// Restore button
const lbRestoreBtn=mk("button",{
background:LIME,color:"#111",border:"none",borderRadius:"5px",
padding:"0 14px",fontSize:"11px",fontWeight:"700",
cursor:"pointer",outline:"none",transition:"opacity .15s",
display:"none",alignSelf:"stretch",whiteSpace:"nowrap",
alignItems:"center",justifyContent:"center"});
tx(lbRestoreBtn,"Load settings into UI");
lbRestoreBtn.onmouseenter=()=>lbRestoreBtn.style.opacity=".85";
lbRestoreBtn.onmouseleave=()=>lbRestoreBtn.style.opacity="1";
// Fav button
let _lbFavActive=false;
const lbFavBtn=mk("button",{
background:"rgba(20,20,30,.85)",border:"1px solid rgba(240,255,65,.2)",
borderRadius:"6px",width:"40px",height:"40px",flexShrink:"0",
cursor:"pointer",outline:"none",
transition:"background .2s,border-color .2s,color .2s",
display:"flex",alignItems:"center",justifyContent:"center",
color:"rgba(240,255,65,.35)",alignSelf:"stretch"});
const _lbFavApplyStyle=(hover)=>{
if(_lbFavActive){
lbFavBtn.style.background=hover?"rgba(240,255,65,.22)":"rgba(240,255,65,.15)";
lbFavBtn.style.borderColor=hover?LIME:LIME;
lbFavBtn.style.color=hover?"#fff":LIME;
lbFavBtn.style.boxShadow=hover?"0 0 10px rgba(240,255,65,.3)":"0 0 6px rgba(240,255,65,.15)";
} else {
lbFavBtn.style.background=hover?"rgba(240,255,65,.08)":"rgba(20,20,30,.85)";
lbFavBtn.style.borderColor=hover?"rgba(240,255,65,.5)":"rgba(240,255,65,.2)";
lbFavBtn.style.color=hover?"rgba(240,255,65,.85)":"rgba(240,255,65,.35)";
lbFavBtn.style.boxShadow="none";
}
};
lbFavBtn.onmouseenter=()=>_lbFavApplyStyle(true);
lbFavBtn.onmouseleave=()=>_lbFavApplyStyle(false);
lbFavBtn.appendChild(_mkHeart("14px"));
// Open folder button
const lbOpenBtn=mk("button",{
background:"transparent",border:`1px solid ${C.border}`,
borderRadius:"6px",padding:"0 10px",fontSize:"10px",color:C.muted,
cursor:"pointer",outline:"none",transition:"border-color .15s,color .15s",
alignSelf:"stretch",display:"flex",alignItems:"center",gap:"4px",
marginLeft:"auto",whiteSpace:"nowrap"});
// Folder SVG icon
const _lbFolderSvg=(()=>{
const s=document.createElementNS("http://www.w3.org/2000/svg","svg");
s.setAttribute("viewBox","0 0 24 24");s.setAttribute("width","12");s.setAttribute("height","12");
s.style.fill="currentColor";s.style.flexShrink="0";
const p=document.createElementNS("http://www.w3.org/2000/svg","path");
p.setAttribute("d","M20 6h-8l-2-2H4c-1.1 0-2 .9-2 2v12c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V8c0-1.1-.9-2-2-2z");
s.appendChild(p);return s;
})();
lbOpenBtn.appendChild(_lbFolderSvg);
const _lbOpenLbl=mk("span");tx(_lbOpenLbl,"Show in folder");lbOpenBtn.appendChild(_lbOpenLbl);
lbOpenBtn.onmouseenter=()=>{lbOpenBtn.style.borderColor=C.text;lbOpenBtn.style.color=C.text;};
lbOpenBtn.onmouseleave=()=>{lbOpenBtn.style.borderColor=C.border;lbOpenBtn.style.color=C.muted;};
lbOpenBtn.onclick=async()=>{
const v=_lbActiveImg; if(!v) return;
try{
await api.fetchApi("/z_image/open_folder",{
method:"POST",headers:{"Content-Type":"application/json"},
body:JSON.stringify({filename:v.filename,subfolder:v.subfolder||""}),
});
}catch(e){ console.warn("[ZImageOneNode] open_folder:",e); }
};
// ── "Use as…" dropdown button ─────────────────────────────────────────
const _lbUseWrap=mk("div",{position:"relative",alignSelf:"stretch",display:"flex"});
const _lbUseBtn=mk("button",{
background:C.bg3,color:C.text,border:`1px solid ${C.borderH}`,
borderRadius:"5px",padding:"0 11px",fontSize:"10px",fontWeight:"600",
cursor:"pointer",outline:"none",whiteSpace:"nowrap",
display:"flex",alignItems:"center",gap:"5px",
transition:"border-color .15s,color .15s,background .15s",
});
_lbUseBtn.innerHTML=`Use as… `;
// Dropdown panel — appears above the button (bottom-anchored)
const _lbUseDrop=mk("div",{
position:"absolute",bottom:"calc(100% + 5px)",left:"0",
background:C.bg1,border:`1px solid ${C.borderH}`,borderRadius:"8px",
minWidth:"170px",overflow:"hidden",display:"none",zIndex:"200",
boxShadow:"0 4px 20px rgba(0,0,0,.7)",flexDirection:"column",
});
// Section header inside dropdown
const _mkDropSection=(label)=>{
const h=mk("div",{
padding:"6px 12px 3px",fontSize:"8px",fontWeight:"700",letterSpacing:".08em",
textTransform:"uppercase",color:C.muted,userSelect:"none",
});
tx(h,label);return h;
};
// Clickable slot item inside dropdown
const _mkDropItem=(label,icon,onClick)=>{
const row=mk("div",{
padding:"7px 12px",fontSize:"10px",fontWeight:"500",color:C.text,
cursor:"pointer",display:"flex",alignItems:"center",gap:"7px",
transition:"background .1s,color .1s",userSelect:"none",
});
const ico=mk("span",{fontSize:"11px",width:"14px",textAlign:"center",flexShrink:"0",color:C.muted});
tx(ico,icon);
const lbl=mk("span",{}); tx(lbl,label);
row.append(ico,lbl);
row.onmouseenter=()=>{row.style.background="rgba(240,255,65,.10)";row.style.color=LIME;ico.style.color=LIME;};
row.onmouseleave=()=>{row.style.background="";row.style.color=C.text;ico.style.color=C.muted;};
row.onclick=()=>{ _lbCloseDrop(); onClick(); };
return row;
};
// Thin divider between sections
const _mkDropDivider=()=>mk("div",{height:"1px",background:C.border,margin:"2px 0"});
// Build dropdown items
_lbUseDrop.append(
_mkDropSection("I2I"),
_mkDropItem("I2I slot","⟳",()=>{ const v=_lbActiveImg;if(v)_loadIntoI2ISlot(v); }),
);
let _lbDropOpen=false;
const _lbCloseDrop=()=>{
_lbDropOpen=false;
_lbUseDrop.style.display="none";
};
const _lbToggleDrop=()=>{
_lbDropOpen=!_lbDropOpen;
_lbUseDrop.style.display=_lbDropOpen?"flex":"none";
};
_lbUseBtn.onmouseenter=()=>{_lbUseBtn.style.borderColor=LIME;_lbUseBtn.style.color=LIME;_lbUseBtn.style.background=C.bg2;};
_lbUseBtn.onmouseleave=()=>{_lbUseBtn.style.borderColor=C.borderH;_lbUseBtn.style.color=C.text;_lbUseBtn.style.background=C.bg3;};
_lbUseBtn.onclick=(e)=>{ e.stopPropagation(); _lbToggleDrop(); };
// Close dropdown when clicking outside
document.addEventListener("click",()=>{ if(_lbDropOpen) _lbCloseDrop(); });
_lbUseDrop.addEventListener("click",e=>e.stopPropagation());
_lbUseWrap.append(_lbUseBtn,_lbUseDrop);
// Delete button in lightbox
const lbDelBtn=mk("button",{
background:"rgba(160,25,25,.7)",border:"1px solid rgba(255,80,80,.3)",
borderRadius:"6px",width:"42px",height:"40px",flexShrink:"0",
cursor:"pointer",outline:"none",transition:"background .15s,border-color .15s",
display:"flex",alignItems:"center",justifyContent:"center",color:"rgba(255,180,180,.9)",
alignSelf:"stretch"});
lbDelBtn.title="Delete image";
lbDelBtn.innerHTML=``;
lbDelBtn.onmouseenter=()=>{lbDelBtn.style.background="rgba(210,35,35,.9)";lbDelBtn.style.borderColor="rgba(255,80,80,.6)";};
lbDelBtn.onmouseleave=()=>{lbDelBtn.style.background="rgba(160,25,25,.7)";lbDelBtn.style.borderColor="rgba(255,80,80,.3)";};
lbDelBtn.onclick=()=>{
const v=_lbActiveImg; if(!v) return;
const delIdx=_lbIdx;
_deleteImage(v,lbDelBtn,()=>{
// Remove from _galImages and _lbNavList
const galIdx=_galImages.indexOf(v);
if(galIdx!==-1){ _galImages.splice(galIdx,1); }
_galTotal=Math.max(0,_galTotal-1);
delete _galMetas[v.filename];
// Remove from navList too (may differ from galImages in fav-only mode)
const navIdx=_lbNavList.indexOf(v);
if(navIdx!==-1){ _lbNavList.splice(navIdx,1); }
// Rebuild grid
galGrid.innerHTML="";
if(_galFavOnly){
const favs=_galImages.filter(img=>_galMetas[img.filename]?.favorite===true);
_galAppend(favs,0);
tx(galTitle,`Gallery (${favs.length} fav)`);
galEmpty.style.display=favs.length?"none":"block";
} else {
_galAppend(_galImages,0);
tx(galTitle,`Gallery (${_galTotal})`);
galEmpty.style.display=_galImages.length?"none":"block";
}
// Navigate to next image or close
if(_lbNavList.length===0){ _lbClose(); }
else { _lbNav(Math.min(delIdx,_lbNavList.length-1)); }
});
};
lbInfoRow.append(lbChipRes,lbChipMode,lbChipAdv,lbImgThumb,lbImgThumb2,lbRestoreBtn,_lbUseWrap,lbFavBtn,lbDelBtn,lbOpenBtn);
// LoRA row — subtle, hidden when no loras
const lbLoraRow=mk("div",{display:"none",gap:"4px",flexWrap:"wrap",alignItems:"center"});
const lbLoraLbl=mk("span",{fontSize:"8px",color:C.muted,fontWeight:"700",
letterSpacing:".07em",textTransform:"uppercase",marginRight:"2px"});
tx(lbLoraLbl,"LoRA");
lbLoraRow.appendChild(lbLoraLbl);
lbMeta.append(lbPromptRow,lbLoraRow,lbInfoRow);
// Bottom counter
const lbBottom=mk("div",{display:"flex",justifyContent:"center",
alignItems:"center",padding:"4px 10px 8px",flexShrink:"0",position:"relative"});
const lbCounter=mk("div",{fontSize:"10px",color:C.muted});
const lbFShortcut=mk("div",{
position:"absolute",right:"12px",display:"flex",alignItems:"center",gap:"4px",
});
const _lbFKbd=mk("span",{fontSize:"7px",fontWeight:"700",color:"#111",background:C.muted,borderRadius:"3px",padding:"0px 3px",letterSpacing:".02em",lineHeight:"1.7"});
tx(_lbFKbd,"F");
const _lbFLbl=mk("span",{fontSize:"7px",color:C.muted,whiteSpace:"nowrap"});
tx(_lbFLbl,"Fullscreen");
lbFShortcut.append(_lbFKbd,_lbFLbl);
lbBottom.append(lbCounter,lbFShortcut);
lightbox.append(lbTop,lbBody,lbMeta,lbBottom);
galleryOverlay.appendChild(lightbox);
// ── Gallery state ────────────────────────────────────────────────────
let _galImages=[];
let _galTotal=0;
let _galOffset=0;
let _galNeedsRefresh=false;
let _galMetas={};
let _lbActiveImg=null;
let _loraList=[];
let _lbIdx=0;
const GAL_LIMIT=50;
const GAL_MORE=50;
const _galImgUrl=(v)=>`/view?filename=${encodeURIComponent(v.filename)}&type=output&subfolder=${encodeURIComponent(v.subfolder||"")}`;
const _fetchMeta=async(v,force=false)=>{
const key=v.filename;
if(!force&&_galMetas[key]!==undefined) return _galMetas[key];
try{
const r=await api.fetchApi(`/z_image/meta?filename=${encodeURIComponent(v.filename)}&subfolder=${encodeURIComponent(v.subfolder||"")}`);
const d=await r.json();
_galMetas[key]=d.ok?d.meta:null;
}catch(e){ _galMetas[key]=null; }
return _galMetas[key];
};
const _setLbFav=(isFav)=>{
_lbFavActive=isFav;
_lbFavApplyStyle(false);
lightbox.style.background=isFav?
"linear-gradient(180deg,rgba(240,255,65,.18) 0%,rgba(240,255,65,.06) 50%,rgba(0,0,0,0) 100%), #0a0a0a":
"#0a0a0a";
};
const _lbClose=()=>{ lightbox.style.display="none"; _lbActiveImg=null; };
// Active navigation list — all images or favorites-only depending on current filter
let _lbNavList=[];
const _lbNav=async(i)=>{
if(!_lbNavList.length) return;
// Load more when reaching end in non-fav mode
if(i>=_lbNavList.length&&_galOffset<_galTotal&&!_galFavOnly){
await galLoad(false,GAL_MORE);
_lbNavList=_galImages; // refresh after load
}
i=Math.max(0,Math.min(_lbNavList.length-1,i));
lbShow(_lbNavList[i],i);
};
lbArrowL.onclick=()=>_lbNav(_lbIdx-1);
lbArrowR.onclick=()=>_lbNav(_lbIdx+1);
// Keyboard handler for lightbox — capture phase so we beat ComfyUI canvas handlers
document.addEventListener("keydown",(e)=>{
if(lightbox.style.display!=="flex") return;
if(e.key==="Escape"){ e.preventDefault(); e._lbHandled=true; _lbClose(); return; }
if(e.key==="ArrowLeft"){ e.preventDefault(); e.stopPropagation(); _lbNav(_lbIdx-1); return; }
if(e.key==="ArrowRight"){ e.preventDefault(); e.stopPropagation(); _lbNav(_lbIdx+1); return; }
if(e.key==="f"||e.key==="F"){
e.preventDefault();
if(!document.fullscreenElement) lbImg.requestFullscreen().catch(()=>{});
else document.exitFullscreen().catch(()=>{});
}
},{capture:true});
// Open lightbox
const lbShow=async(v,idx)=>{
_lbActiveImg=v;
_lbIdx=idx??0;
tx(lbFilename,v.filename);
lbImg.src=_galImgUrl(v)+"&t="+v.mtime;
lbMeta.style.display="none";lbRestoreBtn.style.display="none";
lbPromptText.textContent="";
lightbox.style.display="flex";
const total=_lbNavList.length||_galImages.length;
tx(lbCounter,`${_lbIdx+1} / ${total}`);
lbArrowL.style.opacity=_lbIdx>0?"1":".25";
lbArrowR.style.opacity=_lbIdx1) lbLoraRow.removeChild(lbLoraRow.lastChild);
lbLoraRow.style.display="none";
if(meta){
lbPromptText.textContent=meta.prompt||"(no prompt saved)";
const w=meta.w,h=meta.h;
tx(lbChipRes.querySelector("div:last-child"),w&&h?`${w}×${h}`:"—");
tx(lbChipMode.querySelector("div:last-child"),(meta.mode||"").toUpperCase()||"—");
const _hasAdv=meta.advancedUI===true;
lbChipAdv.style.display=_hasAdv?"flex":"none";
if(_hasAdv) lbChipAdv.title=`Steps:${meta.steps||8} CFG:${meta.cfg??1} Sampler:${meta.sampler||"res_multistep"}`;
if(meta.image1){
lbImgThumb.src=api.apiURL(`/view?filename=${encodeURIComponent(meta.image1)}&type=input&subfolder=`);
lbImgThumb.style.display="block";
} else { lbImgThumb.style.display="none"; }
if(meta.image2){
lbImgThumb2.src=api.apiURL(`/view?filename=${encodeURIComponent(meta.image2)}&type=input&subfolder=`);
lbImgThumb2.style.display="block";
} else { lbImgThumb2.style.display="none"; }
// LoRA chips
if(Array.isArray(meta.userLoras)&&meta.userLoras.length){
meta.userLoras.forEach(ul=>{
const chip=mk("span",{
fontSize:"9px",color:C.muted,background:C.bg3,
borderRadius:"4px",padding:"2px 6px",lineHeight:"1.6",
border:`1px solid ${C.border}`,whiteSpace:"nowrap",
});
const name=(ul.n||"").replace(/\.safetensors$/i,"");
chip.textContent=`${name} ×${+(ul.s??1).toFixed(2)}`;
lbLoraRow.appendChild(chip);
});
lbLoraRow.style.display="flex";
}
lbMeta.style.display="flex";
lbRestoreBtn.style.display="flex";
lbRestoreBtn.onclick=()=>_lbApplyMeta(meta);
_setLbFav(meta.favorite===true);
} else {
lbPromptText.textContent="⚠ No metadata.";
tx(lbChipRes.querySelector("div:last-child"),"—");
tx(lbChipMode.querySelector("div:last-child"),"—");
lbChipAdv.style.display="none";
lbImgThumb.style.display="none";
lbImgThumb2.style.display="none";
lbMeta.style.display="flex";
_setLbFav(false);
}
};
// Resolve an image name from metadata: if it's an output path (contains "/"), upload to input first.
const _resolveMetaImage=async(name)=>{
if(!name) return null;
if(!name.includes("/")) return name;
const parts=name.split("/");
const filename=parts[parts.length-1];
const subfolder=parts.slice(0,-1).join("/");
try{
return await _uploadOutputToInput({filename,subfolder});
}catch(e){
console.warn("[ZImageOneNode] resolveMetaImage:",e);
return filename;
}
};
// Apply meta — restores prompt, mode, resolution AND image slots (the source images stored in metadata).
// "Use as Image 1/2" buttons handle loading the currently-viewed gallery image into a slot instead.
const _lbApplyMeta=async(meta)=>{
// Close gallery immediately — don't wait for async image uploads
lightbox.style.display="none"; _lbActiveImg=null;
closeOverlayFade(galleryOverlay);
try{
const mode=meta.mode||"t2i";
// Map meta.mode → pill name
const pillMap={"i2i":"i2i","t2i":"t2i"};
const pill=pillMap[mode]||"t2i";
// Prompt
if(meta.prompt){
S.prompt=meta.prompt; S[_pillPromptKey(pill)]=meta.prompt;
promptTA.value=meta.prompt; _promptOvTA.value=meta.prompt;
}
// Pill
setPill(pill);
// Resolution (T2I / Edit only)
if(meta.w&&meta.h&&pill==="t2i"){
const preset=RES_PRESETS.find(r=>r.w===meta.w&&r.h===meta.h);
if(preset){ S.resLabel=preset.label;S.resW=preset.w;S.resH=preset.h;S.isCustomRes=false;
resDD.set(preset.label);customResRow.style.display="none"; }
else { S.isCustomRes=true;S.customW=meta.w;S.customH=meta.h;S.resLabel="Custom…";
resDD.set("Custom…");customResRow.style.display="flex";
wInp.setVal(meta.w);hInp.setVal(meta.h); }
}
// Image slots — by mode
const _ri=async(name)=>{ try{ return await _resolveMetaImage(name); }catch(e){ return null; } };
if(mode==="i2i"){
if(meta.image1){ const n=await _ri(meta.image1); if(n){S.i2iImage=n;i2iSlot._restorePreview(n);} }
if(meta.i2iDenoise!==undefined){ S.i2iDenoise=meta.i2iDenoise; _i2iSliderSet(Math.round(meta.i2iDenoise*100)); }
}
// Advanced params — only restore if explicitly saved with advancedUI:true
if(meta.advancedUI===true){
if(meta.steps){ S.steps=meta.steps; stepsInp.setVal(meta.steps); }
if(meta.cfg!==undefined){ S.cfg=meta.cfg; cfgInp.setVal(meta.cfg); }
if(meta.sampler){ S.sampler=meta.sampler; samplerDD.set(meta.sampler); }
if(meta.scheduler){ S.scheduler=meta.scheduler; schedulerDD.set(meta.scheduler); }
if(!S.advancedUI){ S.advancedUI=true; _advRefresh(); advUIToggle._setChecked(true); }
}
if(meta.seed!==undefined&&meta.randomizeSeed===false){
S.randomizeSeed=false; S.seed=meta.seed;
seedInp.setVal(meta.seed); _advSeedInp.setVal(meta.seed); _advSeedRefresh();
}
// LoRAs — always reset all slots first, then apply what meta has
_ulRowEls.forEach((r,i)=>{
S.userLoras[i]={name:"",strength:0};
r._dd.set("none"); r._str.value="0";
});
if(Array.isArray(meta.userLoras)&&meta.userLoras.length&&_loraList.length){
const nd=(s)=>(s||"").replace(/\\/g,"/").toLowerCase();
const loraOpts=["none",..._loraList];
meta.userLoras.forEach((ul,i)=>{
if(i>=_ulRowEls.length) return;
const basename=nd(ul.n||"").split("/").pop();
const match=loraOpts.find(o=>nd(o)===nd(ul.n||""))||
loraOpts.find(o=>nd(o).split("/").pop()===basename);
if(match&&match!=="none"){
S.userLoras[i].name=match; S.userLoras[i].strength=+(ul.s??1);
_ulRowEls[i]._dd.set(match); _ulRowEls[i]._str.value=String(S.userLoras[i].strength);
}
});
}
_ulUpdateBtn();
updateSizeControls(); persist();
}catch(err){
console.warn("[ZImageOneNode] _lbApplyMeta error:",err);
}
};
lbFavBtn.onclick=async()=>{
const v=_lbActiveImg; if(!v) return;
const newFav=!(_galMetas[v.filename]?.favorite===true);
try{
const r=await api.fetchApi("/z_image/update_meta",{
method:"POST",headers:{"Content-Type":"application/json"},
body:JSON.stringify({filename:v.filename,subfolder:v.subfolder||"",patch:{favorite:newFav}}),
});
const d=await r.json();
if(d.ok){
const cached=_galMetas[v.filename]||{};
cached.favorite=newFav;_galMetas[v.filename]=cached;
_setLbFav(newFav);
lbFavBtn.classList.remove("fk-heart-anim");
void lbFavBtn.offsetWidth;
lbFavBtn.classList.add("fk-heart-anim");
galGrid.querySelectorAll("[data-filename]").forEach(cell=>{
if(cell.dataset.filename===v.filename){
const ico=cell.querySelector("._favico");
if(ico){ ico.style.opacity=newFav?"1":"0"; }
}
});
if(_galFavOnly&&!newFav) galGrid.querySelector(`[data-filename="${CSS.escape(v.filename)}"]`)?.remove();
}
}catch(e){ console.warn("[ZImageOneNode] fav:",e); }
};
lbClose.onclick=_lbClose;
// Upload an output image into ComfyUI's input folder so LoadImage can reference it.
// Returns the uploaded input filename, or null on failure.
const _uploadOutputToInput=async(v)=>{
const outputUrl=api.apiURL(`/view?filename=${encodeURIComponent(v.filename)}&type=output&subfolder=${encodeURIComponent(v.subfolder||"")}`);
const resp=await fetch(outputUrl);
if(!resp.ok) throw new Error("fetch "+resp.status);
const blob=await resp.blob();
const fd=new FormData();
fd.append("image",new File([blob],v.filename,{type:blob.type||"image/png"}));
fd.append("overwrite","true");
const up=await api.fetchApi("/upload/image",{method:"POST",body:fd});
const upd=await up.json();
return upd.name||v.filename;
};
const _loadIntoI2ISlot=async(v)=>{
if(activePill!=="i2i") setPill("i2i");
let inputName;
try{ inputName=await _uploadOutputToInput(v); }
catch(err){ console.warn("[ZImageOneNode] load-into-i2i:",err); inputName=v.filename; }
S.i2iImage=inputName; i2iSlot._restorePreview(inputName);
persist();
lightbox.style.display="none";_lbActiveImg=null;
closeOverlayFade(galleryOverlay);
};
// mkApplyIcon for Load button (same as LTX node)
const _mkApplyIcon=(size)=>{
size=size||"13px";
const svg=document.createElementNS("http://www.w3.org/2000/svg","svg");
svg.setAttribute("viewBox","0 0 24 24");svg.setAttribute("width",size);svg.setAttribute("height",size);
svg.style.fill="currentColor";svg.style.display="block";svg.style.flexShrink="0";
const r1=document.createElementNS("http://www.w3.org/2000/svg","rect");
r1.setAttribute("x","3");r1.setAttribute("y","3");r1.setAttribute("width","13");r1.setAttribute("height","13");
r1.setAttribute("rx","2");r1.setAttribute("fill","none");r1.setAttribute("stroke","currentColor");r1.setAttribute("stroke-width","2");
const r2=document.createElementNS("http://www.w3.org/2000/svg","rect");
r2.setAttribute("x","8");r2.setAttribute("y","8");r2.setAttribute("width","13");r2.setAttribute("height","13");r2.setAttribute("rx","2");
svg.appendChild(r1);svg.appendChild(r2);return svg;
};
// Build / append grid cells
const _galAppend=(images,startIdx)=>{
images.forEach((v,i)=>{
const idx=startIdx+i;
const cell=mk("div",{
position:"relative",borderRadius:"8px",overflow:"hidden",
background:C.bg2,border:`1px solid ${C.border}`,
cursor:"pointer",transition:"border-color .15s",
aspectRatio:"1/1",
});
cell.dataset.filename=v.filename;
cell.dataset.idx=String(idx);
// Thumbnail
const thumb=mk("img",{
width:"100%",height:"100%",objectFit:"cover",
display:"block",background:C.bg3,position:"absolute",inset:"0",
});
thumb.loading="lazy";
thumb.src=_galImgUrl(v)+"&t="+v.mtime;
// Hover overlay
const ov=mk("div",{position:"absolute",inset:"0",background:"rgba(0,0,0,.35)",
opacity:"0",transition:"opacity .15s",pointerEvents:"none"});
// Filename strip at bottom
const strip=mk("div",{
position:"absolute",bottom:"0",left:"0",right:"0",
padding:"3px 6px",fontSize:"8px",color:"#ccc",
background:"rgba(0,0,0,.65)",
overflow:"hidden",textOverflow:"ellipsis",whiteSpace:"nowrap",
});
tx(strip,v.filename);
// Fav indicator
const favIco=mk("div",{
position:"absolute",bottom:"22px",right:"5px",
opacity:"0",transition:"opacity .15s",
color:LIME,pointerEvents:"none",
textShadow:"0 1px 2px rgba(0,0,0,.9)",
});
favIco.className="_favico";
favIco.appendChild(_mkHeart("11px"));
cell.append(thumb,ov,strip,favIco);
cell.onmouseenter=()=>{ cell.style.borderColor=LIME;ov.style.opacity="1"; };
cell.onmouseleave=()=>{ cell.style.borderColor=C.border;ov.style.opacity="0"; };
cell.onclick=()=>{ _lbNavList=images; lbShow(v,idx); };
if(v.favorite===true){
favIco.style.opacity="1";
cell.style.background="linear-gradient(180deg,rgba(240,255,65,.12) 0%,rgba(240,255,65,.04) 100%)";
cell.style.borderColor="rgba(240,255,65,.4)";
}
galGrid.appendChild(cell);
});
};
const galLoad=async(reset=true,limit=GAL_LIMIT)=>{
if(_galLoading&&!reset) return;
_galLoading=true;
galMoreWrap.style.display="flex";
if(reset){ _galImages=[];_galMetas={};_galOffset=0;galGrid.innerHTML=""; }
tx(galTitle,`Gallery (loading…)`);
try{
if(_galFavOnly){
// Fast path: single request, server resolves favorites index
const r=await api.fetchApi(`/z_image/gallery?offset=${_galOffset}&limit=${limit}&subfolder=one-node-z-image&favonly=1`);
const d=await r.json();
const newImgs=d.images||[];
_galTotal=d.total||0;
_galImages.push(...newImgs);
_galOffset=_galImages.length;
_galAppend(newImgs,_galImages.length-newImgs.length);
galEmpty.style.display=_galImages.length?"none":"block";
tx(galTitle,`Gallery (${_galTotal} fav)`);
galMoreWrap.style.display=_galOffset<_galTotal?"flex":"none";
} else {
const r=await api.fetchApi(`/z_image/gallery?offset=${_galOffset}&limit=${limit}&subfolder=one-node-z-image`);
const d=await r.json();
const newImgs=d.images||[];
_galTotal=d.total||0;
const startIdx=_galImages.length;
_galImages.push(...newImgs);
_galOffset=_galImages.length;
galEmpty.style.display=_galImages.length?"none":"block";
_galAppend(newImgs,startIdx);
tx(galTitle,`Gallery (${_galTotal})`);
galMoreWrap.style.display=_galOffset<_galTotal?"flex":"none";
}
}catch(e){
tx(galTitle,"Gallery (error)");
console.warn("[ZImageOneNode] gallery:",e);
}finally{
_galLoading=false;
if(_galOffset>=_galTotal) galMoreWrap.style.display="none";
}
};
galMoreBtn.onclick=()=>galLoad(false,GAL_MORE); // kept for compatibility
galRefreshBtn.onclick=()=>galLoad(true);
galClose.onclick=()=>closeOverlayFade(galleryOverlay,()=>{ lightbox.style.display="none"; _lbActiveImg=null; });
galleryBtn.onclick=e=>{
e.stopPropagation();
openOverlay(galleryOverlay);
if(_galNeedsRefresh||_galImages.length===0){ galLoad(true); _galNeedsRefresh=false; }
};
// Mark gallery as needing refresh after a successful generation
// _galNeedsRefresh is set to true in showFinal so gallery auto-refreshes on next open
// ── ASSEMBLE ─────────────────────────────────────────────────────────
pad.append(topBar,mainRow,promptWrap,creditText);
root.appendChild(settingsOverlay);
root.appendChild(galleryOverlay);
root.appendChild(_promptOverlay);
root.appendChild(_inspireOverlay);
scrollEl.appendChild(pad);
root.appendChild(scrollEl);
// ── Esc in any number/text input → blur (dismiss focus) ──────────────
root.addEventListener("keydown",(e)=>{
if(e.key!=="Escape") return;
const t=e.target;
if(t&&(t.tagName==="INPUT")&&t.type!=="range"){ e.stopPropagation(); t.blur(); }
},true);
const _creditEl=mk("div",{
position:"absolute",bottom:"5px",left:"12px",right:"12px",
fontSize:"8px",color:"#555",pointerEvents:"none",
letterSpacing:".04em",userSelect:"none",zIndex:"1",
display:"flex",alignItems:"center",justifyContent:"space-between",
});
const _shortcutsEl=mk("span",{color:"#444",letterSpacing:".03em"});
tx(_shortcutsEl,"D · discover G · gallery Space · generate F · fullscreen");
const _creditTxt=mk("div",{fontSize:"8px",color:"#555",letterSpacing:".04em",whiteSpace:"nowrap"});
tx(_creditTxt,"created by Adeliox");
_creditEl.style.justifyContent="";
_creditEl.innerHTML="";
_creditEl.style.pointerEvents="auto";
// Left: shortcuts toggle button + expanded bar
const _scLeft=mk("div",{flex:"1",display:"flex",alignItems:"center",gap:"6px"});
const _scToggleBtn=mk("button",{
background:"transparent",border:`1px solid ${C.border}`,borderRadius:"4px",
padding:"1px 6px",fontSize:"7px",fontWeight:"600",color:C.muted,
cursor:"pointer",outline:"none",letterSpacing:".05em",textTransform:"uppercase",
transition:"border-color .15s,color .15s",whiteSpace:"nowrap",flexShrink:"0",
});
tx(_scToggleBtn,"Shortcuts");
_scToggleBtn.onmouseenter=()=>{_scToggleBtn.style.borderColor=C.text;_scToggleBtn.style.color=C.text;};
_scToggleBtn.onmouseleave=()=>{_scToggleBtn.style.borderColor=C.border;_scToggleBtn.style.color=C.muted;};
const _scBar=mk("div",{display:"none",alignItems:"center",flexShrink:"0",gap:"0",cursor:"pointer"});
[["D","Discover"],["F","Fullscreen preview"],["G","Gallery"],["Esc","Exit prompt"],["Space","Generate"]].forEach(([key,desc],idx)=>{
if(idx>0){ const sep=mk("div",{width:"1px",height:"8px",background:C.border,margin:"0 6px",flexShrink:"0"});_scBar.appendChild(sep); }
const item=mk("div",{display:"flex",alignItems:"center",gap:"3px"});
const kbd=mk("span",{fontSize:"7px",fontWeight:"700",color:"#111",background:C.muted,borderRadius:"3px",padding:"0px 3px",letterSpacing:".02em",flexShrink:"0",lineHeight:"1.7"});
tx(kbd,key);
const lbl=mk("span",{fontSize:"7px",color:C.muted,whiteSpace:"nowrap"});
tx(lbl,desc);
item.append(kbd,lbl);
_scBar.appendChild(item);
});
_scBar.onclick=()=>{ _scBar.style.display="none"; _scToggleBtn.style.display=""; };
_scToggleBtn.onclick=()=>{ _scToggleBtn.style.display="none"; _scBar.style.display="flex"; };
_scLeft.append(_scToggleBtn,_scBar);
// Right: credit
const _creditRight=mk("div",{flex:"1",display:"flex",justifyContent:"flex-end",pointerEvents:"none"});
_creditRight.appendChild(_creditTxt);
_creditEl.append(_scLeft,_creditRight);
root.appendChild(_creditEl);
// Initialize visibility
updatePillVisibility();
updateSizeControls();
// ── F key → fullscreen current preview image inside the node overlay ──
// Listen on document; only fire when mouse is over this node's root element.
let _mouseOverRoot=false;
root.addEventListener("mouseenter",()=>{ _mouseOverRoot=true; });
root.addEventListener("mouseleave",()=>{ _mouseOverRoot=false; });
const _fKeyHandler=(e)=>{
if(e.key!=="f"&&e.key!=="F") return;
if(!_mouseOverRoot) return;
// Don't fire when typing in a text field
const tag=(document.activeElement||{}).tagName||"";
if(tag==="INPUT"||tag==="TEXTAREA") return;
// Don't fire when any overlay is open inside the node
if(settingsOverlay.style.display!=="none") return;
if(galleryOverlay.style.display!=="none") return;
if(_promptOverlay.style.display!=="none") return;
if(_inspireOverlay.style.display!=="none") return;
if(_nodeFsOv&&_nodeFsOv.style.display!=="none"){_nodeFsOv._close();return;}
// Get currently visible image
let src="", name="", fsType="image", fsOpts=null;
if(comparerWrap.style.display!=="none"&&comparerGenImg.src){
src=comparerGenImg.src;
name="Before / After";
fsType="comparer";
fsOpts={
genSrc:comparerGenImg.src,
baseSrc:comparerBase.src,
};
} else if(finalImg.style.display!=="none"&&finalImg.src){
src=finalImg.src;
name=finalImg.src.split("/").pop().split("?")[0]||"Image";
}
if(!src) return;
e.preventDefault();
e.stopPropagation();
_initNodeFsOverlay()._open(fsType,src,name,fsOpts);
};
document.addEventListener("keydown",_fKeyHandler);
// ── D key → open/close Discover (Get Inspired) ───────────────────────
document.addEventListener("keydown",(e)=>{
if(e.key!=="d"&&e.key!=="D") return;
if(!_mouseOverRoot) return;
const tag=(document.activeElement||{}).tagName||"";
if(tag==="INPUT"||tag==="TEXTAREA") return;
if(settingsOverlay.style.display!=="none") return;
if(galleryOverlay.style.display!=="none") return;
if(_nodeFsOv&&_nodeFsOv.style.display!=="none") return;
e.preventDefault();e.stopPropagation();
if(_inspireOverlay.style.display!=="none"){ _closeInspire(); }
else { _openInspire(); }
});
// ── G key → open Gallery (grid view) ─────────────────────────────────
document.addEventListener("keydown",(e)=>{
if(e.key!=="g"&&e.key!=="G") return;
if(!_mouseOverRoot) return;
const tag=(document.activeElement||{}).tagName||"";
if(tag==="INPUT"||tag==="TEXTAREA") return;
if(settingsOverlay.style.display!=="none") return;
if(_promptOverlay.style.display!=="none") return;
if(_inspireOverlay.style.display!=="none") return;
if(_nodeFsOv&&_nodeFsOv.style.display!=="none") return;
// If gallery is open (grid view, no lightbox) → close it
if(galleryOverlay.style.display!=="none"&&lightbox.style.display==="none"){
closeOverlayFade(galleryOverlay,()=>{ lightbox.style.display="none"; _lbActiveImg=null; });
e.preventDefault();e.stopPropagation();return;
}
if(galleryOverlay.style.display!=="none") return;
e.preventDefault();e.stopPropagation();
galleryBtn.click();
});
// ── Space → trigger Generate (main UI only) ───────────────────────────
document.addEventListener("keydown",(e)=>{
if(e.code!=="Space") return;
if(!_mouseOverRoot) return;
const tag=(document.activeElement||{}).tagName||"";
if(tag==="INPUT"||tag==="TEXTAREA") return;
if(settingsOverlay.style.display!=="none") return;
if(galleryOverlay.style.display!=="none") return;
if(_promptOverlay.style.display!=="none") return;
if(_inspireOverlay.style.display!=="none") return;
if(_nodeFsOv&&_nodeFsOv.style.display!=="none") return;
e.preventDefault();e.stopPropagation();
genBtn.click();
});
// (fullscreen Esc guard is applied per-handler below)
// ── Escape → close Get Inspired overlay ──────────────────────────────
document.addEventListener("keydown",(e)=>{
if(e.key!=="Escape") return;
if(_inspireOverlay.style.display==="none") return;
e.preventDefault();e.stopPropagation();
_closeInspire();
});
// ── E → toggle Edit mode in Discover overlay ──────────────────────────
document.addEventListener("keydown",(e)=>{
if(e.key!=="e"&&e.key!=="E") return;
if(_inspireOverlay.style.display==="none") return;
const tag=(document.activeElement||{}).tagName||"";
if(tag==="INPUT"||tag==="TEXTAREA") return;
e.preventDefault();e.stopPropagation();
_inspireShowFullBtn.click();
});
// ── Escape in gallery grid → close gallery ────────────────────────────
document.addEventListener("keydown",(e)=>{
if(e.key!=="Escape") return;
if(!_mouseOverRoot) return;
if(galleryOverlay.style.display==="none") return;
if(e._lbHandled) return; // lightbox already handled this Escape — stay in grid
e.preventDefault();e.stopPropagation();
closeOverlayFade(galleryOverlay,()=>{ lightbox.style.display="none"; _lbActiveImg=null; });
});
// ── Escape → close Settings / Help overlays ──────────────────────────
document.addEventListener("keydown",(e)=>{
if(e.key!=="Escape") return;
if(settingsOverlay.style.display!=="none"){ e.preventDefault();e.stopPropagation();closeOverlayFade(settingsOverlay);return; }
});
// Fetch models
const _loadModels=()=>api.fetchApi("/z_image/models")
.then(r=>r.json())
.then(d=>{
const _norm=(s)=>(s||"").replace(/\\/g,"/").toLowerCase();
// pick: saved value takes priority; keyword auto-select only when no saved value matches
const pick=(list,saved,kw)=>{
if(!list?.length) return saved;
const ns=_norm(saved||"");
// 1. exact match
let r=list.find(i=>_norm(i)===ns);
if(r) return r;
// 2. basename match
if(ns){
const base=ns.split("/").pop();
r=list.find(i=>_norm(i).split("/").pop()===base);
if(r) return r;
}
// 3. no saved value — use keyword auto-select
if(kw){
const kws=kw.split(',').map(k=>k.trim().toLowerCase());
r=list.find(f=>kws.every(k=>_norm(f).includes(k)));
if(r) return r;
}
// 4. fallback: first item
return list[0]||saved;
};
const modelList=(d.diffusion_models||[]).filter(f=>f!=="none");
if(modelList.length){const v=pick(modelList,S.model,"z_image");S.model=v;modelF.dd.updateItems(modelList);modelF.dd.set(v);}
else{S.model="";modelF.dd.updateItems(["none"]);modelF.dd.set("none");}
const teList=(d.text_encoders||[]).filter(f=>f!=="none");
if(teList.length){const v=pick(teList,S.textEncoder,"qwen");S.textEncoder=v;teF.dd.updateItems(teList);teF.dd.set(v);}
else{S.textEncoder="";teF.dd.updateItems(["none"]);teF.dd.set("none");}
const vaeList=(d.vaes||[]).filter(f=>f!=="none");
if(vaeList.length){const v=pick(vaeList,S.vae,"");S.vae=v;vaeF.dd.updateItems(vaeList);vaeF.dd.set(v);}
else{S.vae="";vaeF.dd.updateItems(["none"]);vaeF.dd.set("none");}
persist();
// Populate LoRA dropdowns
const loraList=(d.loras||[]).filter(f=>f!=="none");
_loraList=loraList;
const loraOpts=["none",...loraList];
_ulRowEls.forEach((r,i)=>{
r._dd.updateItems(loraOpts);
const saved=S.userLoras[i].name;
if(saved&&saved!=="none"&&loraList.length){
const nd=(s)=>s.replace(/\\/g,"/").toLowerCase();
const match=loraOpts.find(o=>nd(o)===nd(saved))||
loraOpts.find(o=>nd(o).split("/").pop()===nd(saved).split("/").pop());
if(match){r._dd.set(match);S.userLoras[i].name=match;}
else{r._dd.set("none");S.userLoras[i].name="";}
} else{r._dd.set("none");S.userLoras[i].name="";}
});
_ulUpdateBtn();
persist();
})
.catch(e=>console.warn("[ZImageOneNode] models:",e));
_loadModels();
if(S.extLoaders) _applyExtLoaders(true);
// Auto-refresh Settings dropdowns when connections change
self.onConnectionsChange=function(){ _refreshExtInputUI(); };
const _slotHInit=(self.inputs||[]).length*(LiteGraph.NODE_SLOT_HEIGHT||20);
this.addDOMWidget("fk_ui","div",root,{
getValue(){return null;},setValue(){},serialize:false,
computeSize(){const slotH=(LiteGraph.NODE_SLOT_HEIGHT||20);const n=(self.inputs||[]).length;return[NODE_W,NODE_H+n*slotH];},
});
this.setSize([NODE_W,NODE_H+_slotHInit]);
// Nodes 2.0: hide the auto-injected node-type name badge rendered in the node footer.
// The badge has class "bg-node-component-surface" (Tailwind) and contains the node type string.
const _hideNodes2Badge=()=>{
// Walk up from root to find the node-level container (up to 6 levels)
let el=root;
for(let i=0;i<6;i++){
el=el?.parentElement;
if(!el) break;
el.querySelectorAll("[class*='bg-node-component-surface']").forEach(badge=>{
badge.style.display="none";
});
}
};
requestAnimationFrame(()=>{
_hideNodes2Badge();
if(typeof MutationObserver!=="undefined"){
let obs=root;
for(let i=0;i<4;i++) obs=obs?.parentElement||obs;
new MutationObserver(_hideNodes2Badge).observe(obs,{childList:true,subtree:true});
}
});
if(!window.__zimage_nodes) window.__zimage_nodes={};
window.__zimage_nodes[this.id]={
root,S,
fns:{showFinal,showPreview,resetBtn,setStage,showError,clearError,getPromptId:()=>_activePromptId},
};
_activeS=S;
_activeShowFinal=showFinal;
_activeShowPreview=showPreview;
_activeResetBtn=resetBtn;
_activeSetStage=setStage;
_activeShowError=showError;
_activePromptIdRef=()=>_activePromptId;
};
},
});