Render and normalize model version names in Linker UI

Add helper methods to normalize and split model and version names, render versioned model names in search results, cards, loading states and CivitAI links, include version metadata on rows, and add CSS styling for model version text. Improves display clarity when models include version names and avoids duplicated filenames.
This commit is contained in:
Dariusz L
2026-05-26 17:02:36 +02:00
parent ec4d6fe8f9
commit 497818d1ee
2 changed files with 67 additions and 20 deletions
+5
View File
@@ -784,6 +784,11 @@
color: var(--ml-text);
font-weight: 650;
}
.ml-model-version {
color: var(--ml-text-secondary);
font-style: italic;
font-weight: 500;
}
.ml-search-result-model small,
.ml-search-size,
.ml-search-result-empty {
+62 -20
View File
@@ -402,6 +402,37 @@ class LinkerManagerDialog extends ComfyDialog {
this.syncSearchSourceUi(missing, container);
}
normalizeVersionName(versionName) {
return String(versionName || '').trim().replace(/^v{2,}(?=\d)/i, 'v');
}
getModelVersionParts(modelName, versionName) {
const name = String(modelName || '').trim();
const version = this.normalizeVersionName(versionName);
if (!version || version === name) {
return { name: name || version, version: '' };
}
if (name && name.toLowerCase().includes(version.toLowerCase())) {
return { name, version: '' };
}
return { name, version };
}
getVersionedModelName(modelName, versionName) {
const parts = this.getModelVersionParts(modelName, versionName);
if (!parts.version) return parts.name;
return parts.name ? `${parts.name} ${parts.version}` : parts.version;
}
renderVersionedModelNameHtml(modelName, versionName) {
const parts = this.getModelVersionParts(modelName, versionName);
const nameHtml = parts.name ? this.escapeHtml(parts.name) : '';
const versionHtml = parts.version
? `<em class="ml-model-version">${this.escapeHtml(parts.version)}</em>`
: '';
return [nameHtml, versionHtml].filter(Boolean).join(' ');
}
formatSearchResultSize(result = {}) {
if (result.size === 0) return '0 B';
if (!result.size) return '';
@@ -455,13 +486,21 @@ class LinkerManagerDialog extends ComfyDialog {
const modelUrl = downloadSource.model_url
|| downloadSource.workflow_model_url
|| this.getModelCardUrl(downloadSource.url);
const versionName = downloadSource.version_name || missing.civitai_info?.version_name || '';
const modelParts = this.getModelVersionParts(
downloadSource.name || missing.civitai_info?.model_name || '',
versionName
);
const modelName = modelParts.name || downloadFilename;
const fullModelName = this.getVersionedModelName(modelName, modelParts.version);
return {
sourceKey,
sourceLabel,
model: downloadSource.name || downloadFilename,
model: modelName,
version: modelParts.version,
filename: downloadFilename,
secondary: sourceSecondary || (downloadSource.name && downloadSource.name !== downloadFilename ? downloadFilename : ''),
secondary: sourceSecondary || (fullModelName && fullModelName !== downloadFilename ? downloadFilename : ''),
match: isFromWorkflow
? { label: 'Provided', className: 'strong' }
: this.getSearchResultMatchDisplay(downloadSource, 'Known', 'strong'),
@@ -517,7 +556,11 @@ class LinkerManagerDialog extends ComfyDialog {
for (const row of rows) {
const sourceKey = String(row.sourceKey || '').replace(/[^a-z0-9_-]/gi, '');
const sourceLabel = this.escapeHtml(row.sourceLabel || row.sourceKey || 'Source');
const model = this.escapeHtml(row.model || row.filename || 'Model');
const rawModel = row.model || row.filename || 'Model';
const rawVersion = row.version || '';
const model = this.escapeHtml(rawModel);
const modelTitle = this.escapeHtml(this.getVersionedModelName(rawModel, rawVersion) || rawModel);
const modelHtml = this.renderVersionedModelNameHtml(rawModel, rawVersion) || model;
const secondary = row.secondary ? this.escapeHtml(row.secondary) : '';
const filename = row.filename && row.filename !== row.model ? this.escapeHtml(row.filename) : '';
const match = row.match || { label: 'Match', className: 'neutral' };
@@ -557,8 +600,8 @@ class LinkerManagerDialog extends ComfyDialog {
<tr>
<td><span class="ml-search-source-pill ml-search-source-${sourceKey}">${sourceLabel}</span></td>
<td>
<div class="ml-search-result-model" title="${model}">
<span>${model}</span>
<div class="ml-search-result-model" title="${modelTitle}">
<span>${modelHtml}</span>
${secondary || filename ? `<small>${secondary || filename}</small>` : ''}
</div>
</td>
@@ -1358,7 +1401,7 @@ class LinkerManagerDialog extends ComfyDialog {
if (titleEl) {
const modelName = data.model_name || data.modelName || 'Unknown Model';
const versionName = data.version_name || data.versionName || '';
titleEl.textContent = versionName ? `${modelName} - ${versionName}` : modelName;
titleEl.innerHTML = this.renderVersionedModelNameHtml(modelName, versionName) || this.escapeHtml(modelName);
}
// Update type tag
@@ -3990,15 +4033,9 @@ class LinkerManagerDialog extends ComfyDialog {
} else if (missing.is_urn && missing.civitai_info) {
// URN with resolved info - show model name/version
const civitaiInfo = missing.civitai_info;
let civitaiLabel = '';
if (civitaiInfo.model_name) {
civitaiLabel += civitaiInfo.model_name;
}
if (civitaiInfo.version_name && civitaiInfo.version_name !== civitaiInfo.model_name) {
civitaiLabel += ` v${civitaiInfo.version_name}`;
}
if (civitaiLabel) {
const linkHtml = modelUrl ? `<a href="${modelUrl}" target="_blank" class="ml-inline-civitai-link">${civitaiLabel}</a>` : `<span class="ml-inline-civitai-link">${civitaiLabel}</span>`;
const civitaiLabelHtml = this.renderVersionedModelNameHtml(civitaiInfo.model_name, civitaiInfo.version_name);
if (civitaiLabelHtml) {
const linkHtml = modelUrl ? `<a href="${modelUrl}" target="_blank" class="ml-inline-civitai-link">${civitaiLabelHtml}</a>` : `<span class="ml-inline-civitai-link">${civitaiLabelHtml}</span>`;
titlePrimaryHtml = `<span class="ml-card-title-primary">${linkHtml}</span>`;
}
if (civitaiInfo.expected_filename) {
@@ -4978,9 +5015,10 @@ class LinkerManagerDialog extends ComfyDialog {
const loadingEl = document.getElementById(loadingElementId);
if (loadingEl && data.civitai) {
const civitai = data.civitai;
const label = civitai.name || civitai.filename || 'Model';
const labelHtml = this.renderVersionedModelNameHtml(civitai.name, civitai.version_name)
|| this.escapeHtml(civitai.filename || 'Model');
const url = modelUrl || `https://civitai.com/models/${modelId}?modelVersionId=${versionId}`;
loadingEl.innerHTML = `<a href="${url}" target="_blank" class="ml-inline-civitai-link">${label}</a>`;
loadingEl.innerHTML = `<a href="${url}" target="_blank" class="ml-inline-civitai-link">${labelHtml}</a>`;
} else if (loadingEl) {
loadingEl.textContent = 'Not found';
loadingEl.style.color = 'var(--ml-text-muted)';
@@ -5005,6 +5043,7 @@ class LinkerManagerDialog extends ComfyDialog {
url: data.civitai.download_url,
filename: data.civitai.filename,
name: data.civitai.name,
version_name: data.civitai.version_name,
type: data.civitai.type,
directory: missing.category || 'checkpoints',
match_type: 'exact',
@@ -5202,12 +5241,14 @@ class LinkerManagerDialog extends ComfyDialog {
if (loraManagerArchiveResult && loraManagerArchiveResult.url) {
const archiveFilename = loraManagerArchiveResult.filename || missing.original_path?.split('/').pop()?.split('\\').pop() || '';
const archiveName = loraManagerArchiveResult.name || archiveFilename;
addRow({
sourceKey: 'lora-archive',
sourceLabel: 'LoRA Archive',
model: loraManagerArchiveResult.name || loraManagerArchiveResult.version_name || archiveFilename,
model: archiveName,
version: loraManagerArchiveResult.version_name || '',
filename: archiveFilename,
secondary: loraManagerArchiveResult.version_name || '',
secondary: archiveName && archiveName !== archiveFilename ? archiveFilename : '',
match: this.getSearchResultMatchDisplay(loraManagerArchiveResult),
size: this.formatSearchResultSize(loraManagerArchiveResult),
downloadUrl: loraManagerArchiveResult.download_url || '',
@@ -5220,11 +5261,12 @@ class LinkerManagerDialog extends ComfyDialog {
if (civitaiResult && civitaiResult.download_url) {
const modelUrl = civitaiResult.url || (civitaiResult.model_id ? `https://civitai.com/models/${civitaiResult.model_id}${civitaiResult.version_id ? `?modelVersionId=${civitaiResult.version_id}` : ''}` : '');
const downloadFilename = missing.civitai_info?.expected_filename || civitaiResult.filename || civitaiResult.name;
const modelName = missing.civitai_info?.version_name ? `${missing.civitai_info.model_name} v${missing.civitai_info.version_name}` : (civitaiResult.name || downloadFilename || 'Model');
const modelName = missing.civitai_info?.model_name || civitaiResult.name || downloadFilename || 'Model';
addRow({
sourceKey: 'civitai',
sourceLabel: 'CivitAI',
model: modelName,
version: missing.civitai_info?.version_name || civitaiResult.version_name || '',
filename: downloadFilename,
secondary: civitaiResult.type || civitaiResult.base_model || '',
match: this.getSearchResultMatchDisplay(civitaiResult),