Refactor search result rendering into helpers

Add formatSearchResultMeta and renderOnlineSearchResultCard helper methods to web/linker.js and replace repeated HTML construction for online search results (Popular, Model Database, HuggingFace, CivitAI) with calls to these helpers. This centralizes formatting for filename/confidence/size/secondary text and download action rendering, simplifies per-source code (including a top-line model link for CivitAI), and improves readability/consistency without changing user-facing behavior.
This commit is contained in:
Dariusz L
2026-04-26 02:14:58 +02:00
parent c37cee540d
commit cf0cd8491a
+89 -66
View File
@@ -262,6 +262,56 @@ class LinkerManagerDialog extends ComfyDialog {
return html;
}
formatSearchResultMeta(result = {}, options = {}) {
const filename = options.filename || result.filename || '';
const secondaryText = options.secondaryText || '';
const confidenceValue = result.confidence;
const confidenceDisplay = typeof confidenceValue === 'number'
? `${Math.round(confidenceValue)}% match`
: '';
const sizeDisplay = result.size
? (typeof result.size === 'number' ? this.formatBytes(result.size) : result.size)
: '';
let html = `<div style="margin-top: 4px; font-size: 12px;">`;
if (filename) {
html += `<span class="ml-chip">${filename}</span>`;
}
if (confidenceDisplay) {
html += ` <span class="ml-chip">${confidenceDisplay}</span>`;
}
if (sizeDisplay) {
html += ` <span class="ml-download-size">[${sizeDisplay}]</span>`;
}
if (secondaryText) {
html += ` <span style="color: var(--ml-text-muted);">${secondaryText}</span>`;
}
html += `</div>`;
return html;
}
renderOnlineSearchResultCard({
statusClass,
title,
result,
filename,
secondaryText = '',
actionHtml = '',
topLineHtml = ''
}) {
let html = `<div class="ml-status ${statusClass}" style="flex-direction: column; align-items: flex-start;">`;
html += `<strong>${title}</strong>`;
if (topLineHtml) {
html += topLineHtml;
}
html += this.formatSearchResultMeta(result, { filename, secondaryText });
if (actionHtml) {
html += `<div style="margin-top: 8px; display: flex; gap: 8px;">${actionHtml}</div>`;
}
html += `</div>`;
return html;
}
buildContextMenuModelData(model = {}, fallbackName = '') {
const resolvedPath = model.path || model.resolved_path || '';
const filename = model.filename || fallbackName || resolvedPath.split(/[\/\\]/).pop() || '';
@@ -6011,53 +6061,45 @@ class LinkerManagerDialog extends ComfyDialog {
// Popular models result (highest priority)
if (popular) {
html += `<div class="ml-status ml-status-success" style="flex-direction: column; align-items: flex-start;">`;
html += `<strong>Found in Popular Models</strong>`;
html += `<div style="margin-top: 8px;">`;
html += `<button class="search-download-btn ml-btn ml-btn-primary ml-btn-sm" data-url="${popular.url}" data-filename="${popular.filename || missing.original_path?.split('/').pop()?.split('\\').pop()}" data-category="${popular.directory || missing.category}">`;
html += `<span class="ml-btn-icon">☁</span> Download`;
html += `</button>`;
html += `</div></div>`;
const popularFilename = popular.filename || missing.original_path?.split('/').pop()?.split('\\').pop() || '';
const actionHtml = `<button class="search-download-btn ml-btn ml-btn-primary ml-btn-sm" data-url="${popular.url}" data-filename="${popularFilename}" data-category="${popular.directory || missing.category}"><span class="ml-btn-icon">☁</span> Download</button>`;
html += this.renderOnlineSearchResultCard({
statusClass: 'ml-status-success',
title: 'Found in Popular Models',
result: popular,
filename: popularFilename,
secondaryText: popular.name || '',
actionHtml
});
}
// Model list result (ComfyUI Manager database with fuzzy matching)
if (modelListResult && modelListResult.url) {
const confidence = modelListResult.confidence ? ` (${modelListResult.confidence}%)` : '';
const matchType = modelListResult.match_type === 'exact' ? 'Exact match' : 'Similar model';
const statusClass = modelListResult.match_type === 'exact' ? 'ml-status-success' : 'ml-status-info';
html += `<div class="ml-status ${statusClass}" style="flex-direction: column; align-items: flex-start;">`;
html += `<strong>${matchType} in Model Database${confidence}</strong>`;
html += `<div style="margin-top: 4px; font-size: 12px;">`;
html += `<span class="ml-chip">${modelListResult.filename}</span>`;
if (modelListResult.name) {
html += ` <span style="color: var(--ml-text-muted);">${modelListResult.name}</span>`;
}
if (modelListResult.size) {
html += ` <span class="ml-download-size">[${modelListResult.size}]</span>`;
}
html += `</div>`;
html += `<div style="margin-top: 8px;">`;
html += `<button class="search-download-btn ml-btn ml-btn-primary ml-btn-sm" data-url="${modelListResult.url}" data-filename="${modelListResult.filename}" data-category="${modelListResult.directory || missing.category}">`;
html += `<span class="ml-btn-icon">☁</span> Download`;
html += `</button>`;
html += `</div></div>`;
const actionHtml = `<button class="search-download-btn ml-btn ml-btn-primary ml-btn-sm" data-url="${modelListResult.url}" data-filename="${modelListResult.filename}" data-category="${modelListResult.directory || missing.category}"><span class="ml-btn-icon">☁</span> Download</button>`;
html += this.renderOnlineSearchResultCard({
statusClass,
title: `${matchType} in Model Database`,
result: modelListResult,
filename: modelListResult.filename,
secondaryText: modelListResult.name || '',
actionHtml
});
}
// HuggingFace result
if (hfResult && hfResult.url) {
const hfRepo = hfResult.repo_id || hfResult.repo || '';
html += `<div class="ml-status ml-status-info" style="flex-direction: column; align-items: flex-start;">`;
html += `<strong>Found on HuggingFace</strong>`;
html += `<div style="margin-top: 4px; font-size: 12px;">`;
html += `<span class="ml-chip">${hfResult.filename}</span> `;
html += `<span style="color: var(--ml-text-muted);">${hfRepo}</span>`;
html += `</div>`;
html += `<div style="margin-top: 8px;">`;
html += `<button class="search-download-btn ml-btn ml-btn-link ml-btn-sm" data-url="${hfResult.url}" data-filename="${hfResult.filename}" data-category="${missing.category}">`;
html += `<span class="ml-btn-icon">☁</span> Download`;
html += `</button>`;
html += `</div></div>`;
const actionHtml = `<button class="search-download-btn ml-btn ml-btn-link ml-btn-sm" data-url="${hfResult.url}" data-filename="${hfResult.filename}" data-category="${missing.category}"><span class="ml-btn-icon">☁</span> Download</button>`;
html += this.renderOnlineSearchResultCard({
statusClass: 'ml-status-info',
title: 'Found on HuggingFace',
result: hfResult,
filename: hfResult.filename,
secondaryText: hfRepo,
actionHtml
});
}
// CivitAI result
@@ -6067,36 +6109,17 @@ class LinkerManagerDialog extends ComfyDialog {
const downloadFilename = missing.civitai_info?.expected_filename || civitaiResult.filename || civitaiResult.name;
// Build display name with version if available
const modelName = missing.civitai_info?.version_name ? `${missing.civitai_info.model_name} v${missing.civitai_info.version_name}` : (civitaiResult.name || 'Model');
const confidenceDisplay = typeof civitaiResult.confidence === 'number'
? `${Math.round(civitaiResult.confidence)}% match`
: '';
const sizeDisplay = civitaiResult.size
? (typeof civitaiResult.size === 'number'
? this.formatBytes(civitaiResult.size)
: civitaiResult.size)
: '';
html += `<div class="ml-status ml-status-warning" style="flex-direction: column; align-items: flex-start;">`;
html += `<strong>Found on CivitAI</strong>`;
html += `<div style="margin-top: 4px; font-size: 12px;">`;
// Model name as clickable button/link
html += `<button class="ml-btn ml-btn-sm" style="background: transparent; color: #FF9800; border: 1px solid #FF9800; font-weight: bold; cursor: pointer;" onclick="window.open('${modelUrl}', '_blank')">${modelName}</button> `;
html += `<span style="color: var(--ml-text-muted);">${civitaiResult.type || ''}</span>`;
html += `</div>`;
html += `<div style="margin-top: 4px; font-size: 12px;">`;
html += `<span class="ml-chip">${downloadFilename}</span>`;
if (confidenceDisplay) {
html += ` <span class="ml-chip">${confidenceDisplay}</span>`;
}
if (sizeDisplay) {
html += ` <span class="ml-download-size">[${sizeDisplay}]</span>`;
}
html += `</div>`;
html += `<div style="margin-top: 8px; display: flex; gap: 8px;">`;
// Download button - use expected_filename from URN resolution
html += `<button class="search-download-btn ml-btn ml-btn-sm" style="background: #FF9800;" data-url="${civitaiResult.download_url}" data-filename="${downloadFilename}" data-category="${missing.category}">`;
html += `<span class="ml-btn-icon">☁</span> Download`;
html += `</button>`;
html += `</div></div>`;
const topLineHtml = `<div style="margin-top: 4px; font-size: 12px;"><button class="ml-btn ml-btn-sm" style="background: transparent; color: #FF9800; border: 1px solid #FF9800; font-weight: bold; cursor: pointer;" onclick="window.open('${modelUrl}', '_blank')">${modelName}</button> <span style="color: var(--ml-text-muted);">${civitaiResult.type || ''}</span></div>`;
const actionHtml = `<button class="search-download-btn ml-btn ml-btn-sm" style="background: #FF9800;" data-url="${civitaiResult.download_url}" data-filename="${downloadFilename}" data-category="${missing.category}"><span class="ml-btn-icon">☁</span> Download</button>`;
html += this.renderOnlineSearchResultCard({
statusClass: 'ml-status-warning',
title: 'Found on CivitAI',
result: civitaiResult,
filename: downloadFilename,
secondaryText: '',
topLineHtml,
actionHtml
});
}
html += '</div>';