From ec4d6fe8f9247d49ede362b09767662bbe5f9ee7 Mon Sep 17 00:00:00 2001 From: Dariusz L Date: Tue, 26 May 2026 16:52:38 +0200 Subject: [PATCH] Refactor download/search UI and URN handling - Replace renderDownloadSourceSection with renderKnownDownloadPanel and wireDownloadSearchPanel to centralize download/search UI wiring - Improve URN filename resolution and search flags - Remove obsolete .ml-download-actions CSS rules - Minor __init__.py fix: check filename before enabling lora_manager_archive search --- __init__.py | 2 +- web/css/linker-main.css | 36 --------- web/linker.js | 174 ++++++++++++++-------------------------- 3 files changed, 62 insertions(+), 150 deletions(-) diff --git a/__init__.py b/__init__.py index 822ec27..92aa4ee 100644 --- a/__init__.py +++ b/__init__.py @@ -1127,7 +1127,7 @@ class ModelLinkerExtension: search_tasks.append( asyncio.to_thread(search_civitai_source_task) ) - if search_lora_manager_archive_source and not is_urn: + if search_lora_manager_archive_source and filename: search_tasks.append( asyncio.to_thread( search_lora_manager_archive_source_task diff --git a/web/css/linker-main.css b/web/css/linker-main.css index 66b4731..cd41eeb 100644 --- a/web/css/linker-main.css +++ b/web/css/linker-main.css @@ -656,38 +656,6 @@ border: 1px solid transparent; box-shadow: inset 0 0 0 1px rgba(255,255,255,0.05); } -.ml-download-actions { - display: grid; - grid-template-columns: minmax(0, 1fr) auto; - gap: var(--ml-spacing-8); - align-items: center; -} -.ml-download-actions .ml-btn-download, -.ml-download-actions .ml-btn-download:hover:not(:disabled), -.ml-btn.ml-btn-download.ml-footer-btn, -.ml-btn.ml-btn-download.ml-footer-btn:hover:not(:disabled) { - background: rgba(255,255,255,0.03); - color: var(--ml-text); - border-color: rgba(255,255,255,0.1); - box-shadow: inset 0 1px 0 rgba(255,255,255,0.02); -} -.ml-download-actions .ml-btn-download:hover:not(:disabled), -.ml-btn.ml-btn-download.ml-footer-btn:hover:not(:disabled) { - background: rgba(255,255,255,0.05); - color: var(--ml-text); - border-color: rgba(255,255,255,0.16); - box-shadow: inset 0 1px 0 rgba(255,255,255,0.02); - transform: translateY(-1px); -} -.ml-btn-download-main { - justify-content: flex-start; - padding-inline: 10px; - font-size: 8.9px; -} -.ml-btn-download-side { - min-width: 132px; - font-size: 8.6px; -} .ml-status-inline { display: flex; align-items: center; @@ -700,10 +668,6 @@ margin-top: 0; line-height: 1.55; } -.ml-download-source { - color: var(--ml-accent); - font-weight: 700; -} .ml-download-size { color: var(--ml-text-dim); margin-left: var(--ml-spacing-8); diff --git a/web/linker.js b/web/linker.js index 347a513..b09037b 100644 --- a/web/linker.js +++ b/web/linker.js @@ -233,7 +233,7 @@ class LinkerManagerDialog extends ComfyDialog { } const sources = ['local', 'huggingface', 'civitai']; - if (!missing.is_urn && this.isSourceAvailable('lora_manager_archive')) { + if (this.isSourceAvailable('lora_manager_archive')) { sources.push('lora_manager_archive'); } return sources; @@ -402,58 +402,6 @@ class LinkerManagerDialog extends ComfyDialog { this.syncSearchSourceUi(missing, container); } - renderDownloadSourceSection(missing, downloadSource) { - const filename = missing.original_path?.split('/').pop()?.split('\\').pop() || ''; - const isFromWorkflow = downloadSource.url_source === 'workflow'; - const isCivitaiSource = downloadSource.source === 'civitai'; - const sourceLabels = { - popular: 'Popular Models', - model_list: 'Model Database', - huggingface: 'HuggingFace', - civitai: 'CivitAI', - workflow: 'Workflow' - }; - const sourceLabel = isFromWorkflow ? 'Workflow' : (sourceLabels[downloadSource.source] || 'Online'); - const downloadFilename = downloadSource.filename || filename; - const formattedDownloadName = this.formatFilename(downloadFilename, 45); - const confidenceDisplay = typeof downloadSource.confidence === 'number' - ? `${Math.round(downloadSource.confidence)}% match` - : ''; - - let sizeDisplay = ''; - if (downloadSource.size) { - sizeDisplay = typeof downloadSource.size === 'number' - ? this.formatBytes(downloadSource.size) - : downloadSource.size; - } - - const modelCardUrl = downloadSource.model_url || this.getModelCardUrl(downloadSource.url); - - let html = `
`; - html += `
`; - html += ``; - if (isCivitaiSource && modelCardUrl) { - html += `Open on CivitAI`; - } - html += `
`; - html += `
`; - html += `${isFromWorkflow ? 'URL from workflow' : sourceLabel}`; - if (confidenceDisplay) { - html += ` ${confidenceDisplay}`; - } - if (modelCardUrl) { - html += `
${formattedDownloadName.display}${sizeDisplay ? ` (${sizeDisplay})` : ''}`; - } else { - html += `
${formattedDownloadName.display}${sizeDisplay ? ` (${sizeDisplay})` : ''}`; - } - html += `
`; - html += this.renderDownloadTargetControls(missing, downloadSource.directory || downloadSource.category || 'checkpoints'); - html += `
`; - return html; - } - formatSearchResultSize(result = {}) { if (result.size === 0) return '0 B'; if (!result.size) return ''; @@ -525,6 +473,22 @@ class LinkerManagerDialog extends ComfyDialog { }; } + renderKnownDownloadPanel(missing, downloadSource) { + let html = `
`; + html += this.renderSearchControls(missing, { buttonText: 'Search Again' }); + html += this.renderDownloadTargetControls( + missing, + downloadSource.directory || downloadSource.category || missing.category || 'checkpoints' + ); + html += `
`; + + const downloadSourceRow = this.getDownloadSourceTableRow(missing, downloadSource); + html += `
`; + html += this.renderSearchResultsTable(downloadSourceRow ? [downloadSourceRow] : []); + html += `
`; + return html; + } + renderSearchResultsTable(rows = []) { if (!rows.length) return ''; @@ -3864,39 +3828,7 @@ class LinkerManagerDialog extends ComfyDialog { sortedMissingModels.forEach((missing, missingIndex) => { this.wireLocalMatchButtons(container, missing, missingIndex); - // Attach download button listener - const downloadBtnId = `download-${missing.node_id}-${missing.widget_index}`; - const downloadBtn = container.querySelector(`#${downloadBtnId}`); - if (downloadBtn && missing.download_source) { - downloadBtn.addEventListener('click', () => { - this.downloadModel(missing); - }); - } - - const searchResultsId = `search-results-${missing.node_id}-${missing.widget_index}`; - const searchResultsDiv = container.querySelector(`#${searchResultsId}`); - if (searchResultsDiv) { - this.wireSearchDownloadButtons(searchResultsDiv, missing); - } - - // Attach search button listener - const searchBtnId = `search-${missing.node_id}-${missing.widget_index}`; - const searchBtn = container.querySelector(`#${searchBtnId}`); - if (searchBtn) { - searchBtn.addEventListener('click', () => { - this.searchOnline(missing); - }); - } - - const sourceSelect = container.querySelector(`#search-source-select-${missing.node_id}-${missing.widget_index}`); - if (sourceSelect) { - sourceSelect.addEventListener('change', () => { - this.setSearchSource(missing, sourceSelect.value, container); - }); - this.syncSearchSourceUi(missing, container); - } - - this.wireDownloadTargetAutocomplete(container, missing); + this.wireDownloadSearchPanel(container, missing); // Wire locate chip (only for top-level nodes) const locateId = `locate-${missing.node_id}-${missing.widget_index}`; @@ -4142,17 +4074,7 @@ class LinkerManagerDialog extends ComfyDialog { html += ``; html += ``; } else if (downloadSource && downloadSource.url) { - html += `
`; - html += this.renderSearchControls(missing, { buttonText: 'Search Again' }); - html += this.renderDownloadTargetControls( - missing, - downloadSource.directory || downloadSource.category || missing.category || 'checkpoints' - ); - html += `
`; - const downloadSourceRow = this.getDownloadSourceTableRow(missing, downloadSource); - html += `
`; - html += this.renderSearchResultsTable(downloadSourceRow ? [downloadSourceRow] : []); - html += `
`; + html += this.renderKnownDownloadPanel(missing, downloadSource); } else if (missing.is_urn) { html += `
`; html += `
Resolving CivitAI download for this URN...
`; @@ -4809,11 +4731,14 @@ class LinkerManagerDialog extends ComfyDialog { const selectedSourceLabel = this.getSearchSourceLabel(selectedSource); const sourceIds = this.getSearchSourcesForSelection(selectedSource, missing); - // For URNs, use the CivitAI model name for searching instead of the URN itself + // For URNs, use the resolved file/model name for searching instead of the URN itself // and pass the URN type as category (CivitAI expects specific type names) if (missing.is_urn) { - if (missing.civitai_info?.model_name) { - filename = missing.civitai_info.model_name; + const urnSearchName = missing.civitai_info?.expected_filename + || missing.download_source?.filename + || missing.civitai_info?.model_name; + if (urnSearchName) { + filename = urnSearchName; } // Pass URN type directly - CivitAPI expects types like 'Upscaler', 'Checkpoint' const urnType = missing.urn_type || ''; @@ -4906,8 +4831,10 @@ class LinkerManagerDialog extends ComfyDialog { let hadError = false; const searchPromises = sourceIds.map(async (source) => { + const sourceIsUrn = isUrn && source !== 'lora_manager_archive'; const searchData = { ...baseSearchData, + is_urn: sourceIsUrn, sources: [source] }; @@ -5061,7 +4988,7 @@ class LinkerManagerDialog extends ComfyDialog { const downloadContainerId = loadingElementId.replace('urn-loading-', 'urn-download-'); const downloadEl = document.getElementById(downloadContainerId); - if (downloadEl && data.civitai) { + if (downloadEl && data.civitai?.download_url) { const missing = this.missingModels.find(m => `urn-download-${m.node_id}-${m.widget_index}` === downloadContainerId ); @@ -5088,18 +5015,10 @@ class LinkerManagerDialog extends ComfyDialog { base_model: data.civitai.base_model, tags: data.civitai.tags || [] }; - downloadEl.outerHTML = this.renderDownloadSourceSection(missing, missing.download_source); - - const refreshedBtn = this.contentElement?.querySelector(`#download-${missing.node_id}-${missing.widget_index}`); - if (refreshedBtn) { - refreshedBtn.addEventListener('click', () => { - this.downloadModel(missing); - }); - } - if (this.contentElement) { - this.wireDownloadTargetAutocomplete(this.contentElement, missing); - } - this.refreshUrnLocalMatches(missing); + const downloadParent = downloadEl.parentElement; + downloadEl.outerHTML = this.renderKnownDownloadPanel(missing, missing.download_source); + this.wireDownloadSearchPanel(downloadParent || this.contentElement, missing); + this.refreshUrnLocalMatches(missing); } } else if (downloadEl) { downloadEl.innerHTML = `
Unable to resolve direct download for this URN.
`; @@ -5145,6 +5064,35 @@ class LinkerManagerDialog extends ComfyDialog { }); } + wireDownloadSearchPanel(container, missing) { + if (!container) return; + + const searchResultsId = `search-results-${missing.node_id}-${missing.widget_index}`; + const searchResultsDiv = container.querySelector(`#${searchResultsId}`); + if (searchResultsDiv) { + this.wireSearchDownloadButtons(searchResultsDiv, missing); + } + + const searchBtn = container.querySelector(`#search-${missing.node_id}-${missing.widget_index}`); + if (searchBtn && searchBtn.dataset.mlSearchBound !== 'true') { + searchBtn.dataset.mlSearchBound = 'true'; + searchBtn.addEventListener('click', () => { + this.searchOnline(missing); + }); + } + + const sourceSelect = container.querySelector(`#search-source-select-${missing.node_id}-${missing.widget_index}`); + if (sourceSelect && sourceSelect.dataset.mlSearchSourceBound !== 'true') { + sourceSelect.dataset.mlSearchSourceBound = 'true'; + sourceSelect.addEventListener('change', () => { + this.setSearchSource(missing, sourceSelect.value, container); + }); + this.syncSearchSourceUi(missing, container); + } + + this.wireDownloadTargetAutocomplete(container, missing); + } + /** * Display search results */