diff --git a/web/css/linker-main.css b/web/css/linker-main.css
index cd41eeb..e94822a 100644
--- a/web/css/linker-main.css
+++ b/web/css/linker-main.css
@@ -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 {
diff --git a/web/linker.js b/web/linker.js
index b09037b..a270a9b 100644
--- a/web/linker.js
+++ b/web/linker.js
@@ -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
+ ? `${this.escapeHtml(parts.version)}`
+ : '';
+ 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 {
| ${sourceLabel} |
-
- ${model}
+
+ ${modelHtml}
${secondary || filename ? `${secondary || filename}` : ''}
|
@@ -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 ? `${civitaiLabel}` : `${civitaiLabel}`;
+ const civitaiLabelHtml = this.renderVersionedModelNameHtml(civitaiInfo.model_name, civitaiInfo.version_name);
+ if (civitaiLabelHtml) {
+ const linkHtml = modelUrl ? `${civitaiLabelHtml}` : `${civitaiLabelHtml}`;
titlePrimaryHtml = `${linkHtml}`;
}
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 = `${label}`;
+ loadingEl.innerHTML = `${labelHtml}`;
} 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),