❌ Download Failed
Error
@@ -63,7 +71,7 @@ function buildRichSummaryHTML(data, rawText) {
• Repository:
${escapeHtml(data.repo_id || "")}
-
+
${escapeHtml(data.error || "Unknown error occurred")}
@@ -78,7 +86,7 @@ function buildRichSummaryHTML(data, rawText) {
if (line.includes("Download Summary")) {
resultHtml += `
${escapeHtml(line)}
`;
} else if (line.includes("Status:") && line.includes("Completed")) {
- resultHtml += `
• Status: ✅ Completed Successfully
`;
+ resultHtml += `
• Status: ✅ Completed Successfully
`;
} else if (line.startsWith("• Repository:")) {
resultHtml += `
• Repository: ${escapeHtml(line.replace("• Repository:", "").trim())}
`;
} else if (line.startsWith("• Save Location:")) {
@@ -88,33 +96,36 @@ function buildRichSummaryHTML(data, rawText) {
} else if (line.trim().startsWith("- ")) {
resultHtml += `
${escapeHtml(line.trim())}
`;
} else if (line.includes("Registered to custom_models.json")) {
- resultHtml += `
${escapeHtml(line)}
`;
+ resultHtml += `
${escapeHtml(line)}
`;
} else {
resultHtml += `
${escapeHtml(line)}
`;
}
}
- return `
${resultHtml}
`;
+ return `
${resultHtml}
`;
}
-const BASE_INPUTS_HEIGHT = 184;
-const INITIAL_NODE_HEIGHT = 188;
-
-function syncContentSize(node, size) {
- const domWidget = node.widgets?.find((w) => w.name === "download_display");
- if (domWidget && domWidget.element) {
- const nodeH = size ? size[1] : (node.size ? node.size[1] : INITIAL_NODE_HEIGHT);
- const availableHeight = Math.max(120, Math.round(nodeH - BASE_INPUTS_HEIGHT));
- domWidget.element.style.height = `${availableHeight}px`;
+function getInputsBottom(node) {
+ if (!node.widgets || node.widgets.length === 0) return 210;
+ let maxBottom = 0;
+ for (const w of node.widgets) {
+ if (w.name === "download_display") continue;
+ if (typeof w.last_y === "number") {
+ const h = (typeof w.computeSize === "function" ? w.computeSize(node.size[0])[1] : w.height) || 26;
+ maxBottom = Math.max(maxBottom, w.last_y + h);
+ }
}
+ return maxBottom > 50 ? maxBottom + 10 : 210;
}
+const INITIAL_NODE_HEIGHT = 214;
+
app.registerExtension({
name: "AILab.HuggingFaceDownloader",
nodeCreated(node) {
if (node.comfyClass === "AILab_HuggingFaceDownloader") {
requestAnimationFrame(() => {
- node.size = [Math.max(node.size?.[0] || 420, 420), INITIAL_NODE_HEIGHT];
+ node.size = [Math.max(node.size?.[0] || 440, 440), INITIAL_NODE_HEIGHT];
if (typeof node.setSize === "function") {
node.setSize(node.size);
}
@@ -128,9 +139,18 @@ app.registerExtension({
const origOnResize = nodeType.prototype.onResize;
nodeType.prototype.onResize = function (size) {
origOnResize?.apply(this, arguments);
- size[0] = Math.max(size[0], 360);
- size[1] = Math.max(size[1], INITIAL_NODE_HEIGHT);
- syncContentSize(this, size);
+ size[0] = Math.max(size[0], 380);
+ const inputsBottom = getInputsBottom(this);
+ const domWidget = this.widgets?.find((w) => w.name === "download_display");
+
+ if (domWidget && domWidget.element) {
+ const minH = inputsBottom + 160;
+ size[1] = Math.max(size[1], minH);
+ const availH = Math.max(size[1] - inputsBottom - 16, 120);
+ domWidget.element.style.height = `${availH}px`;
+ } else {
+ size[1] = Math.max(size[1], INITIAL_NODE_HEIGHT);
+ }
};
const onExecuted = nodeType.prototype.onExecuted;
@@ -150,13 +170,10 @@ app.registerExtension({
container.className = "ailab-download-display-container";
container.style.boxSizing = "border-box";
container.style.width = "100%";
- container.style.height = "100%";
container.style.margin = "0";
container.style.padding = "0 2px 4px 2px";
container.style.userSelect = "text";
container.style.cursor = "auto";
- container.style.display = "flex";
- container.style.flexDirection = "column";
container.style.overflow = "hidden";
domWidget = this.addDOMWidget("download_display", "display_element", container, {
@@ -172,19 +189,89 @@ app.registerExtension({
if (domWidget.options) {
domWidget.options.serialize = false;
}
+
+ domWidget.computeSize = function (width) {
+ return [width, 160];
+ };
}
if (domWidget && domWidget.element) {
domWidget.element.innerHTML = richHtml;
+ // Wire up the muted green copy JSON button (symmetrical with Completed badge)
+ const copyBtn = domWidget.element.querySelector(".ailab-copy-json-btn");
+ if (copyBtn && summaryData?.registration?.entry) {
+ const regSnippet = JSON.stringify({ [summaryData.registration.key]: summaryData.registration.entry }, null, 2);
+ copyBtn.onclick = async (e) => {
+ e.stopPropagation();
+ e.preventDefault();
+ try {
+ if (navigator.clipboard && navigator.clipboard.writeText) {
+ await navigator.clipboard.writeText(regSnippet);
+ } else {
+ const ta = document.createElement("textarea");
+ ta.value = regSnippet;
+ ta.style.position = "fixed";
+ ta.style.opacity = "0";
+ document.body.appendChild(ta);
+ ta.select();
+ document.execCommand("copy");
+ document.body.removeChild(ta);
+ }
+
+ // Smooth icon and color transition: green checkmark, zero layout jitter
+ copyBtn.innerHTML = CHECK_ICON_SVG;
+ copyBtn.style.color = "#38b26e";
+ copyBtn.style.borderColor = "rgba(56, 178, 110, 0.6)";
+ copyBtn.style.background = "rgba(46, 125, 50, 0.25)";
+
+ setTimeout(() => {
+ copyBtn.innerHTML = COPY_ICON_SVG;
+ copyBtn.style.color = "#38b26e";
+ copyBtn.style.borderColor = "rgba(56, 178, 110, 0.35)";
+ copyBtn.style.background = "rgba(46, 125, 50, 0.15)";
+ }, 1200);
+ } catch (err) {
+ console.error("[QwenVL Downloader] Copy failed:", err);
+ }
+ };
+ copyBtn.onmouseenter = () => {
+ if (!copyBtn.innerHTML.includes("polyline")) {
+ copyBtn.style.color = "#4cd98a";
+ copyBtn.style.background = "rgba(46, 125, 50, 0.28)";
+ copyBtn.style.borderColor = "rgba(76, 217, 138, 0.5)";
+ }
+ };
+ copyBtn.onmouseleave = () => {
+ if (!copyBtn.innerHTML.includes("polyline")) {
+ copyBtn.style.color = "#38b26e";
+ copyBtn.style.background = "rgba(46, 125, 50, 0.15)";
+ copyBtn.style.borderColor = "rgba(56, 178, 110, 0.35)";
+ }
+ };
+ }
+
requestAnimationFrame(() => {
- const card = domWidget.element.querySelector(".ailab-download-card") || domWidget.element;
- const cardH = card ? (card.scrollHeight || card.offsetHeight) : 260;
- const targetHeight = Math.ceil(BASE_INPUTS_HEIGHT + cardH + 10);
- const targetWidth = Math.max(this.size ? this.size[0] : 420, 420);
+ const inputsBottom = getInputsBottom(this);
+
+ // Calculate deterministic height directly from content to avoid DOM scrollHeight feedback loop
+ let neededCardHeight = 365;
+ if (summaryData?.registration?.entry) {
+ const jsonSnippet = JSON.stringify({ [summaryData.registration.key]: summaryData.registration.entry }, null, 2);
+ const lines = jsonSnippet.split("\n").length;
+ const fileCount = (summaryData.files || []).length || 1;
+ neededCardHeight = Math.max(155 + (fileCount * 18) + (lines * 16.5) + 30, 385);
+ } else {
+ const card = domWidget.element.querySelector(".ailab-download-card");
+ neededCardHeight = card ? Math.max(card.scrollHeight, 240) : 260;
+ }
+
+ const targetHeight = Math.ceil(inputsBottom + neededCardHeight + 16);
+ const targetWidth = Math.max(this.size ? this.size[0] : 440, 440);
+
+ domWidget.element.style.height = `${neededCardHeight}px`;
this.size = [targetWidth, targetHeight];
- syncContentSize(this, this.size);
if (typeof this.setSize === "function") {
this.setSize([targetWidth, targetHeight]);
}