From 8f3df243775d5dcbbcd3adf485311abdca328d38 Mon Sep 17 00:00:00 2001 From: AI Lab <129358391+1038lab@users.noreply.github.com> Date: Mon, 14 Sep 2026 00:32:36 -0700 Subject: [PATCH] Add files via upload --- web/js/downloader.js | 149 ++++++++++++++++++++++++++++++++++--------- 1 file changed, 118 insertions(+), 31 deletions(-) diff --git a/web/js/downloader.js b/web/js/downloader.js index cfd8281..fdb12b6 100644 --- a/web/js/downloader.js +++ b/web/js/downloader.js @@ -1,5 +1,8 @@ import { app } from "/scripts/app.js"; +const COPY_ICON_SVG = ``; +const CHECK_ICON_SVG = ``; + function escapeHtml(str) { if (!str) return ""; return String(str) @@ -20,20 +23,25 @@ function buildRichSummaryHTML(data, rawText) { if (data.registration && data.registration.entry) { const regSnippet = JSON.stringify({ [data.registration.key]: data.registration.entry }, null, 2); regHtml = ` -
- • Registered in custom_models.json [${escapeHtml(data.registration.section)}]: -
${escapeHtml(regSnippet)}
+
+
+ • Registered in custom_models.json [${escapeHtml(data.registration.section)}]: + +
+
${escapeHtml(regSnippet)}
`; } return ` -
+
📥 QwenVL Downloader Summary - + ✅ Completed
@@ -54,7 +62,7 @@ function buildRichSummaryHTML(data, rawText) { `; } else if (data && data.status === "error") { return ` -
+
❌ 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]); }