Files
2026-11-08 20:24:09 +05:00

677 lines
31 KiB
JavaScript

import { app } from "/scripts/app.js";
import { PromptStatus } from "../core/constants.js";
import { t } from "../core/i18n.js";
import { store } from "../core/store.js";
import { renderCardImages } from "./mediaPreview.js";
import { copyImageToClipboard, openFileOrFolder, deleteFileOnServer, findNodeIdForImage, removeImageFromNodeOutputs } from "./mediaActions.js";
import { centerAndSelectCanvasNode } from "../comfy/adapter.js";
import { getRunOutputs, extractWorkflowFromPng, isImageFormat, animateCardRemoval, downloadJson, getScrollContainer, attachDeleteConfirmation } from "../utils/utils.js";
import { showFullscreenPreview } from "../utils/comparison.js";
import { stopAllAudioPlayback } from "./audioController.js";
import { deleteHistoryItem, cancelPendingTask, interruptActive } from "../queue/queueService.js";
import { showNotification } from "../utils/nodeRenumber.js";
export const cardPool = new Map();
export function resetAllCardHoverStates() {
for (const cardObj of cardPool.values()) {
if (cardObj.hoverPanel) cardObj.hoverPanel.style.display = "none";
if (cardObj.leftHoverPanel) cardObj.leftHoverPanel.style.display = "none";
}
}
export function syncCardButtonVisibility(cardObj, state) {
if (!cardObj) return;
const isPendingOrActive = state.status === PromptStatus.PENDING || state.status === PromptStatus.ACTIVE;
if (isPendingOrActive) {
if (cardObj.hoverPanel) cardObj.hoverPanel.style.setProperty("display", "none", "important");
if (cardObj.leftHoverPanel) cardObj.leftHoverPanel.style.setProperty("display", "none", "important");
return;
} else {
if (cardObj.hoverPanel) cardObj.hoverPanel.style.removeProperty("display");
if (cardObj.leftHoverPanel) cardObj.leftHoverPanel.style.removeProperty("display");
}
const randParam = state?.pid ? `&rand=${encodeURIComponent(state.pid)}` : "";
// Check if the card has an actual verified file saved on disk
const hasRealDiskFiles = state.images && state.images.length > 0 &&
state.images.some(img => img.filename && !img.isFallback && (!img.url || !img.url.startsWith("blob:")));
const hasRealImages = hasRealDiskFiles && !state.images.some(img => img.url && img.url.startsWith("blob:"));
const currentImg = hasRealImages ? state.images[cardObj.currentImageIndex || 0] : null;
const isPng = currentImg && (currentImg.filename || "").toLowerCase().endsWith(".png");
if (cardObj.btnImg) {
if (hasRealImages) {
cardObj.btnImg.style.removeProperty("display");
cardObj.btnImg.style.display = "inline-flex";
cardObj.btnImg.onclick = (ev) => {
ev.stopPropagation();
state.images.forEach(img => {
const a = document.createElement("a");
a.href = img.url ? img.url : `/view?filename=${encodeURIComponent(img.filename)}&type=${img.type || 'output'}&subfolder=${encodeURIComponent(img.subfolder || '')}${randParam}`;
a.download = img.filename || "output";
a.click();
});
};
} else {
cardObj.btnImg.style.setProperty("display", "none", "important");
}
}
if (cardObj.btnCopy) {
const isCopyableImage = hasRealImages && currentImg && isImageFormat(currentImg.filename || currentImg.url);
if (isCopyableImage) {
cardObj.btnCopy.style.removeProperty("display");
cardObj.btnCopy.style.display = "inline-flex";
cardObj.btnCopy.onclick = async (ev) => {
ev.stopPropagation();
const src = currentImg.url ? currentImg.url : `/view?filename=${encodeURIComponent(currentImg.filename)}&type=${currentImg.type || 'output'}&subfolder=${encodeURIComponent(currentImg.subfolder || '')}${randParam}`;
const success = await copyImageToClipboard(src);
if (success) {
cardObj.btnCopy.className = "pi pi-check comfy-sidebar-card-action-btn";
cardObj.btnCopy.style.color = "#4ade80";
setTimeout(() => {
cardObj.btnCopy.className = "pi pi-copy comfy-sidebar-card-action-btn";
cardObj.btnCopy.style.color = "";
}, 1500);
}
};
} else {
cardObj.btnCopy.style.setProperty("display", "none", "important");
}
}
if (cardObj.btnJson) {
if (state.workflow || isPng) {
cardObj.btnJson.style.removeProperty("display");
cardObj.btnJson.style.display = "inline-flex";
cardObj.btnJson.onclick = async (ev) => {
ev.stopPropagation();
let wf = state.workflow;
if (!wf && currentImg) {
const src = currentImg.url ? currentImg.url : `/view?filename=${encodeURIComponent(currentImg.filename)}&type=${currentImg.type || 'output'}&subfolder=${encodeURIComponent(currentImg.subfolder || '')}${randParam}`;
wf = await extractWorkflowFromPng(src);
if (wf) state.workflow = wf;
}
if (!wf) {
alert(t("No workflow metadata found in this file."));
return;
}
downloadJson(wf, `workflow_${state.pid || "comfy"}.json`);
};
} else {
cardObj.btnJson.style.setProperty("display", "none", "important");
}
}
if (cardObj.btnDel) {
cardObj.btnDel.style.removeProperty("display");
cardObj.btnDel.style.display = "inline-flex";
cardObj.canDeleteFromDisk = hasRealDiskFiles;
if (cardObj.btnDelLabel) {
cardObj.btnDelLabel.style.display = hasRealDiskFiles ? "" : "none";
}
cardObj.btnDel.title = hasRealDiskFiles
? t("Delete Card (Hold Ctrl to delete file from disk)")
: t("Delete Card");
}
if (cardObj.btnFocus) {
const nodeId = (currentImg ? findNodeIdForImage(state, currentImg) : null) || state.activeNodeId;
if (nodeId) {
cardObj.btnFocus.style.removeProperty("display");
cardObj.btnFocus.style.display = "inline-flex";
cardObj.btnFocus.title = (!currentImg && state.activeNodeId) ? t("Show Stopped/Failed Node") : t("Show Node");
cardObj.btnFocus.onclick = (ev) => {
ev.stopPropagation();
centerAndSelectCanvasNode(nodeId);
};
} else {
cardObj.btnFocus.style.setProperty("display", "none", "important");
}
}
if (cardObj.leftHoverBtn) {
const outputs = getRunOutputs(state.nodeOutputs, state.workflow);
const validOutputs = outputs
.map(o => ({
...o,
images: (o.images || []).filter(img => img && img.filename && !img.isFallback)
}))
.filter(o => o.images.length > 0);
const primarySignatures = new Set((state.images || []).map(i => `${i.subfolder || ""}/${i.filename}`));
const distinctOutputs = [];
for (const out of validOutputs) {
const distinctImages = out.images.filter(img => !primarySignatures.has(`${img.subfolder || ""}/${img.filename}`));
if (distinctImages.length > 0) {
distinctOutputs.push({ ...out, images: distinctImages });
}
}
const hasIntermediates = (validOutputs.length > 1 && distinctOutputs.length > 0) ||
(state.status !== PromptStatus.COMPLETED && validOutputs.length > 0);
if (!hasIntermediates) {
cardObj.leftHoverBtn.style.setProperty("display", "none", "important");
} else {
// If already verified (or freshly generated), display button immediately
if (state._hasValidatedIntermediates) {
cardObj.leftHoverBtn.style.removeProperty("display");
cardObj.leftHoverBtn.style.display = "inline-flex";
} else {
// Old restored card: keep hidden by default while stealthily testing files
cardObj.leftHoverBtn.style.setProperty("display", "none", "important");
if (!cardObj._isCheckingIntermediates) {
cardObj._isCheckingIntermediates = true;
const checkPromises = [];
const missingImages = [];
for (const out of distinctOutputs) {
for (const img of out.images) {
if (!img.filename || (img.url && img.url.startsWith("blob:"))) continue;
const src = img.url ? img.url : `/view?filename=${encodeURIComponent(img.filename)}&type=${img.type || 'output'}&subfolder=${encodeURIComponent(img.subfolder || '')}${randParam}`;
checkPromises.push(
fetch(src, { method: "HEAD" })
.then(res => {
if (res.status === 404) {
missingImages.push(img);
}
})
.catch(() => {
missingImages.push(img);
})
);
}
}
const finalizeCheck = () => {
cardObj._isCheckingIntermediates = false;
const liveState = store.getPrompt(state.pid) || state;
liveState._hasValidatedIntermediates = true;
if (missingImages.length > 0 && liveState && liveState.nodeOutputs) {
for (const img of missingImages) {
removeImageFromNodeOutputs(liveState.nodeOutputs, img);
}
for (const nodeId in liveState.nodeOutputs) {
const nodeVal = liveState.nodeOutputs[nodeId];
if (nodeVal && typeof nodeVal === "object") {
const hasAny = Object.values(nodeVal).some(v => Array.isArray(v) ? v.length > 0 : Boolean(v));
if (!hasAny) {
delete liveState.nodeOutputs[nodeId];
}
}
}
store.updatePrompt(liveState.pid, liveState);
}
const curOutputs = getRunOutputs(liveState.nodeOutputs, liveState.workflow);
const curValid = curOutputs
.map(o => ({
...o,
images: (o.images || []).filter(i => i && i.filename && !i.isFallback)
}))
.filter(o => o.images.length > 0);
const curDistinct = curValid.filter(o => o.images.some(i => !primarySignatures.has(`${i.subfolder || ""}/${i.filename}`)));
const stillHas = (curValid.length > 1 && curDistinct.length > 0) ||
(liveState.status !== PromptStatus.COMPLETED && curValid.length > 0);
if (stillHas) {
cardObj.leftHoverBtn.style.removeProperty("display");
cardObj.leftHoverBtn.style.display = "inline-flex";
} else {
cardObj.leftHoverBtn.style.setProperty("display", "none", "important");
}
};
if (checkPromises.length > 0) {
Promise.all(checkPromises).then(finalizeCheck).catch(finalizeCheck);
} else {
finalizeCheck();
}
}
}
cardObj.leftHoverBtn.onclick = async (ev) => {
ev.stopPropagation();
const liveState = store.getPrompt(state.pid) || state;
const curOutputs = getRunOutputs(liveState.nodeOutputs, liveState.workflow);
const curValid = curOutputs
.map(o => ({
...o,
images: (o.images || []).filter(i => i && i.filename && !i.isFallback)
}))
.filter(o => o.images.length > 0);
const primSigs = new Set((liveState.images || []).map(i => `${i.subfolder || ""}/${i.filename}`));
const curDistinct = curValid.filter(o => o.images.some(img => !primSigs.has(`${img.subfolder || ""}/${img.filename}`)));
if (curDistinct.length === 0) {
cardObj.leftHoverBtn.style.setProperty("display", "none", "important");
return;
}
let anyMissing = false;
for (const out of curDistinct) {
for (const img of out.images) {
if (primSigs.has(`${img.subfolder || ""}/${img.filename}`)) continue;
const src = img.url ? img.url : `/view?filename=${encodeURIComponent(img.filename)}&type=${img.type || 'output'}&subfolder=${encodeURIComponent(img.subfolder || '')}${randParam}`;
if (!src.startsWith("blob:")) {
try {
const res = await fetch(src, { method: "HEAD" });
if (res.status === 404) {
removeImageFromNodeOutputs(liveState.nodeOutputs, img);
anyMissing = true;
}
} catch (e) {}
}
}
}
if (anyMissing) {
for (const nodeId in liveState.nodeOutputs) {
const nodeVal = liveState.nodeOutputs[nodeId];
if (nodeVal && typeof nodeVal === "object") {
const hasAny = Object.values(nodeVal).some(v => Array.isArray(v) ? v.length > 0 : Boolean(v));
if (!hasAny) {
delete liveState.nodeOutputs[nodeId];
}
}
}
store.updatePrompt(liveState.pid, liveState);
syncCardButtonVisibility(cardObj, liveState);
const remainingOutputs = getRunOutputs(liveState.nodeOutputs, liveState.workflow);
const remaining = remainingOutputs
.map(o => ({ ...o, images: (o.images || []).filter(i => i && i.filename && !i.isFallback) }))
.filter(o => o.images.some(img => !primSigs.has(`${img.subfolder || ""}/${img.filename}`)));
if (remaining.length === 0) {
return;
}
}
const scrollEl = getScrollContainer(store.ui.cardStack);
if (scrollEl) {
store.ui.mainQueueScrollTop = scrollEl.scrollTop;
}
store.openOutputsSubmenu(liveState.pid);
};
}
}
}
export function updateCardProgressTargeted(cardObj, progress, activeNodeName, showWorkingNode = true, state = null) {
if (!cardObj) return;
if (cardObj.progressBar) {
cardObj.progressBar.style.width = `${progress}%`;
}
if (cardObj.statusText && showWorkingNode) {
cardObj.statusText.style.display = "block";
const nodeText = activeNodeName ? (activeNodeName === "Finishing..." ? t("Finishing...") : `[${activeNodeName}]`) : t("Processing...");
cardObj.statusText.textContent = `${nodeText} ${progress}%`;
}
if (state && state.images && state.images.length > 0) {
const firstImg = state.images[0];
const randParam = state?.pid ? `&rand=${encodeURIComponent(state.pid)}` : "";
const previewUrl = firstImg.url || (firstImg.filename ? `/view?filename=${encodeURIComponent(firstImg.filename)}&type=${firstImg.type || 'output'}&subfolder=${encodeURIComponent(firstImg.subfolder || '')}${randParam}` : null);
if (previewUrl && cardObj.firstImgElement && cardObj.firstImgElement.tagName === "IMG") {
const currentSrc = cardObj.firstImgElement.getAttribute("src") || cardObj.firstImgElement.src;
if (currentSrc !== previewUrl && !cardObj.firstImgElement.src.endsWith(previewUrl)) {
cardObj.firstImgElement.src = previewUrl;
}
} else {
updateCardDOM(cardObj, state, true, showWorkingNode);
}
}
}
export function getOrCreateCard(state, callbacks = {}) {
let cardObj = cardPool.get(state.pid);
if (cardObj) return cardObj;
const card = document.createElement("div");
card.id = `card-${state.pid}`;
card.className = `comfy-sidebar-card ${state.status || "completed"}`;
card.style.position = "relative";
const timerEl = document.createElement("div");
timerEl.className = "comfy-sidebar-card-timer";
const cancelX = document.createElement("span");
cancelX.className = "pi pi-times comfy-sidebar-queue-cancel-btn";
Object.assign(cancelX.style, {
position: "absolute", top: "4px", right: "4px", display: "none", zIndex: "25"
});
const sBadge = document.createElement("div");
sBadge.className = "comfy-sidebar-status-badge";
Object.assign(sBadge.style, {
position: "absolute", top: "6px", right: "8px", fontSize: "10px", fontWeight: "bold",
padding: "3px 7px", borderRadius: "3px", textTransform: "uppercase", display: "none",
pointerEvents: "none", zIndex: "30", letterSpacing: "0.5px", boxShadow: "0 2px 6px rgba(0,0,0,0.6)"
});
const dimEl = document.createElement("div");
Object.assign(dimEl.style, {
position: "absolute", top: "6px", right: "8px", fontSize: "10px",
fontFamily: "monospace", opacity: "0.7", background: "rgba(0, 0, 0, 0.6)",
padding: "2px 4px", borderRadius: "3px", pointerEvents: "none", zIndex: "5", color: "#fff",
display: "none", transform: "translateZ(0)"
});
const grid = document.createElement("div");
grid.style.display = "flex";
grid.style.flexDirection = "column";
grid.style.gap = "6px";
const p = document.createElement("div");
p.className = "comfy-sidebar-text-clamp";
const pt = document.createElement("div");
Object.assign(pt.style, {
width: "100%", height: "4px", background: "var(--border, #333)", borderRadius: "2px", marginTop: "8px",
overflow: "hidden", display: "none"
});
const pb = document.createElement("div");
Object.assign(pb.style, {
width: "0%", height: "100%", background: "var(--primary, var(--p-primary-color, #ea580c))", transition: "width 0.1s linear"
});
pt.appendChild(pb);
const statusText = document.createElement("div");
Object.assign(statusText.style, {
fontSize: "11px", opacity: "0.9", color: "var(--primary, var(--p-primary-color, #ea580c))", textAlign: "center", marginTop: "6px",
display: "none", fontWeight: "bold"
});
const hoverPanel = document.createElement("div");
hoverPanel.className = "comfy-sidebar-hover-panel";
Object.assign(hoverPanel.style, {
position: "absolute", bottom: "4px", right: "4px", flexDirection: "column", gap: "4px", zIndex: "20"
});
const btnImg = document.createElement("span");
btnImg.className = "pi pi-image comfy-sidebar-card-action-btn";
btnImg.title = t("Download Object");
const btnCopy = document.createElement("span");
btnCopy.className = "pi pi-copy comfy-sidebar-card-action-btn";
btnCopy.title = t("Copy to Clipboard");
btnCopy.style.display = "none";
const btnJson = document.createElement("span");
btnJson.className = "pi pi-file comfy-sidebar-card-action-btn";
btnJson.title = t("Download JSON");
const btnDel = document.createElement("span");
btnDel.className = "pi pi-trash comfy-sidebar-card-action-btn comfy-sidebar-btn-del";
btnDel.title = t("Delete Card (Hold Ctrl to delete file from disk)");
const btnDelLabel = document.createElement("span");
btnDelLabel.className = "comfy-sidebar-del-label";
btnDelLabel.textContent = t("Delete File");
btnDel.appendChild(btnDelLabel);
const leftHoverPanel = document.createElement("div");
leftHoverPanel.className = "comfy-sidebar-left-hover-panel";
Object.assign(leftHoverPanel.style, {
position: "absolute", bottom: "4px", left: "4px", flexDirection: "column", gap: "4px", zIndex: "20"
});
const btnFocus = document.createElement("span");
btnFocus.className = "pi pi-eye comfy-sidebar-card-action-btn";
btnFocus.title = t("Show Node");
const leftHoverBtn = document.createElement("span");
leftHoverBtn.className = "pi pi-images comfy-sidebar-card-action-btn";
leftHoverBtn.title = t("View all intermediate outputs");
hoverPanel.append(btnImg, btnJson, btnDel);
leftHoverPanel.append(btnCopy, btnFocus, leftHoverBtn);
// Appending grid FIRST ensures overlays (badges, cancel button) naturally render on top
card.append(grid, p, pt, statusText, timerEl, cancelX, dimEl, sBadge, hoverPanel, leftHoverPanel);
cardObj = {
element: card,
timerEl,
statusBadge: sBadge,
dimEl,
grid,
placeholder: p,
progressContainer: pt,
progressBar: pb,
cancelBtn: cancelX,
hoverPanel,
leftHoverPanel,
btnFocus,
leftHoverBtn,
btnImg,
btnCopy,
btnJson,
btnDel,
btnDelLabel,
canDeleteFromDisk: true,
statusText,
firstImgElement: null,
lastImagesSignature: "",
currentImageIndex: 0,
_isCheckingIntermediates: false
};
card.addEventListener("mouseenter", () => {
if (store.hasPrompt(state.pid)) {
const liveState = store.getPrompt(state.pid);
syncCardButtonVisibility(cardObj, liveState);
}
});
card.addEventListener("dragstart", (e) => {
const liveState = store.getPrompt(state.pid) || state;
const isUnfinished = liveState.status && liveState.status !== PromptStatus.COMPLETED;
const hasNoImages = !liveState.images || liveState.images.length === 0;
if (liveState.workflow && (hasNoImages || isUnfinished)) {
if (cardObj.firstImgElement) {
try { e.dataTransfer.setDragImage(cardObj.firstImgElement, 15, 15); } catch(err){}
}
const jsonStr = JSON.stringify(liveState.workflow, null, 2);
const filename = `workflow_${liveState.pid}.json`;
const base64Data = btoa(unescape(encodeURIComponent(jsonStr)));
try {
e.dataTransfer.setData("DownloadURL", `application/json:${filename}:data:application/json;base64,${base64Data}`);
e.dataTransfer.setData("application/json", jsonStr);
} catch (err) {}
e.dataTransfer.effectAllowed = "copy";
}
});
attachDeleteConfirmation(btnDel, {
canDiskDelete: () => cardObj.canDeleteFromDisk !== false,
idleTitle: () => cardObj.canDeleteFromDisk ? t("Delete Card (Hold Ctrl to delete file from disk)") : t("Delete Card"),
confirmTitle: t("Click again to confirm removing card"),
confirmDiskTitle: t("Ctrl+Click again to delete file from DISK / TRASH"),
onConfirm: async (shouldDeleteFromDisk) => {
animateCardRemoval(card, async () => {
const liveState = store.getPrompt(state.pid) || state;
if (shouldDeleteFromDisk) {
const filesToDelete = [];
const seen = new Set();
const addFile = (item) => {
if (item && item.filename) {
const key = `${item.type || ""}/${item.subfolder || ""}/${item.filename}`;
if (!seen.has(key)) {
seen.add(key);
filesToDelete.push(item);
}
}
};
(liveState.images || []).forEach(addFile);
const allOutputs = getRunOutputs(liveState.nodeOutputs, liveState.workflow);
allOutputs.forEach(out => (out.images || []).forEach(addFile));
let deletedCount = 0;
let lastAction = "trashed";
for (const fileItem of filesToDelete) {
const action = await deleteFileOnServer(fileItem);
if (action) {
deletedCount++;
lastAction = action;
}
}
if (deletedCount > 0) {
const msg = lastAction === "trashed"
? `Moved ${deletedCount} file${deletedCount > 1 ? "s" : ""} to Recycle Bin.`
: `Permanently deleted ${deletedCount} file${deletedCount > 1 ? "s" : ""}.`;
showNotification(msg, "success", "Delete Files");
}
}
deleteHistoryItem(state.pid);
});
}
});
cardPool.set(state.pid, cardObj);
return cardObj;
}
export function updateCardDOM(cardObj, state, showPendingSummary = true, showWorkingNode = true) {
cardObj.element.className = `comfy-sidebar-card ${state.status}`;
cardObj.element.style.position = "relative";
const isUnfinished = state.status && state.status !== PromptStatus.COMPLETED;
if (state.images && state.images.length > 0 && !isUnfinished) {
cardObj.element.removeAttribute("draggable");
} else {
cardObj.element.setAttribute("draggable", "true");
}
if (state.status === PromptStatus.ACTIVE) {
cardObj.timerEl.textContent = state.startTime ? ((Date.now() - state.startTime) / 1000).toFixed(2) + "s" : "...";
cardObj.timerEl.style.display = "block";
} else if (state.duration !== undefined && state.duration !== null) {
cardObj.timerEl.textContent = state.duration.toFixed(2) + "s";
cardObj.timerEl.style.display = "block";
} else {
cardObj.timerEl.style.display = "none";
}
if (state.status === PromptStatus.CANCELLED) {
Object.assign(cardObj.statusBadge.style, {
display: "block",
background: "#eab308",
color: "#000",
zIndex: "30"
});
cardObj.statusBadge.textContent = t("Cancelled");
if (cardObj.dimEl) cardObj.dimEl.style.display = "none";
} else if (state.status === PromptStatus.ERROR) {
Object.assign(cardObj.statusBadge.style, {
display: "block",
background: "#dc3545",
color: "#fff",
zIndex: "30"
});
cardObj.statusBadge.textContent = t("Error");
if (cardObj.dimEl) cardObj.dimEl.style.display = "none";
} else {
cardObj.statusBadge.style.display = "none";
}
if (state.status === PromptStatus.PENDING && !showPendingSummary) {
cardObj.cancelBtn.style.display = "flex";
cardObj.cancelBtn.title = t("Cancel Queued Task");
cardObj.cancelBtn.onclick = async (ev) => {
ev.stopPropagation();
cancelPendingTask(state.pid);
};
} else if (state.status === PromptStatus.ACTIVE) {
cardObj.cancelBtn.style.display = "flex";
cardObj.cancelBtn.title = t("Interrupt Execution");
cardObj.cancelBtn.onclick = async (ev) => {
ev.stopPropagation();
interruptActive();
};
} else {
cardObj.cancelBtn.style.display = "none";
}
const currentImagesSignature = `${state.status || ""}:${state.images ? state.images.map(img => img.url || img.filename).join("|") : ""}`;
if (cardObj.lastImagesSignature !== currentImagesSignature) {
if (!state.images || state.images.length === 0) {
cardObj.grid.innerHTML = "";
cardObj.firstImgElement = null;
cardObj.placeholder.style.display = "block";
} else {
cardObj.placeholder.style.display = "none";
renderCardImages(cardObj, state, (st) => {
const scrollEl = getScrollContainer(store.ui.cardStack);
if (scrollEl) {
store.ui.mainQueueScrollTop = scrollEl.scrollTop;
}
store.openBatchSubmenu({
pid: st.pid,
images: st.images,
workflow: st.workflow,
nodeOutputs: st.nodeOutputs
});
});
}
cardObj.lastImagesSignature = currentImagesSignature;
}
if (state.images.length === 0) {
if (state.texts && state.texts.length > 0) {
const fullText = state.texts.join("\n\n");
cardObj.placeholder.textContent = fullText;
cardObj.placeholder.title = t("Click to read full text");
cardObj.placeholder.className = "comfy-sidebar-text-clamp";
cardObj.placeholder.style.cssText = "";
cardObj.placeholder.style.display = "-webkit-box";
cardObj.placeholder.onclick = (e) => {
e.stopPropagation();
stopAllAudioPlayback();
showFullscreenPreview([{ text: fullText, pid: state.pid }], false, state.pid);
};
} else {
cardObj.placeholder.textContent = state.progressText || t("No Outputs");
cardObj.placeholder.className = "";
Object.assign(cardObj.placeholder.style, {
fontSize: "11px", opacity: "0.5", textAlign: "center", padding: "12px", marginTop: "12px",
userSelect: "none", whiteSpace: "normal", maxHeight: "none", display: "block", cursor: "default"
});
cardObj.placeholder.onclick = null;
}
}
if (state.status === PromptStatus.ACTIVE) {
if (showWorkingNode) {
cardObj.statusText.style.display = "block";
const nodeText = state.activeNodeName ? (state.activeNodeName === "Finishing..." ? t("Finishing...") : `[${state.activeNodeName}]`) : t("Processing...");
cardObj.statusText.textContent = `${nodeText}${state.progress ? ` ${state.progress}%` : ""}`;
} else {
cardObj.statusText.style.display = "none";
}
cardObj.progressContainer.style.display = "block";
cardObj.progressBar.style.width = `${state.progress || 0}%`;
} else {
cardObj.statusText.style.display = "none";
cardObj.progressContainer.style.display = "none";
}
syncCardButtonVisibility(cardObj, state);
return cardObj.element;
}