diff --git a/__init__.py b/__init__.py
index bb90e10..994fa25 100644
--- a/__init__.py
+++ b/__init__.py
@@ -210,54 +210,7 @@ class ModelLinkerExtension:
}
continue
- # 1. For URNs, we already have an exact CivitAI identity.
- # Expose it immediately as a direct download source.
- if missing.get("is_urn"):
- urn_model_id = missing.get("urn_model_id") or (
- missing.get("urn", {}) or {}
- ).get("model_id")
- urn_version_id = missing.get(
- "urn_version_id"
- ) or (missing.get("urn", {}) or {}).get(
- "version_id"
- )
-
- if urn_model_id and urn_version_id:
- civitai_info = resolve_urn(
- urn_model_id, urn_version_id
- )
- if civitai_info:
- missing["civitai_info"] = civitai_info
- expected_filename = (
- civitai_info.get("expected_filename")
- or filename
- )
- primary_file = (
- civitai_info.get("files") or [{}]
- )[0]
-
- missing["download_source"] = {
- "source": "civitai",
- "url": get_civitai_download_url(
- urn_version_id
- ),
- "filename": expected_filename,
- "name": civitai_info.get(
- "model_name"
- ),
- "type": missing.get("category"),
- "directory": missing.get(
- "category", "checkpoints"
- ),
- "match_type": "exact",
- "size": primary_file.get("size"),
- "model_id": urn_model_id,
- "version_id": urn_version_id,
- "model_url": f"https://civitai.com/models/{urn_model_id}?modelVersionId={urn_version_id}",
- }
- continue
-
- # 2. Check popular models (always exact match)
+ # 1. Check popular models (always exact match)
popular_info = get_popular_model_url(filename)
if popular_info:
missing["download_source"] = {
@@ -270,7 +223,7 @@ class ModelLinkerExtension:
}
continue
- # 3. Check model list (ComfyUI Manager database)
+ # 2. Check model list (ComfyUI Manager database)
# Use exact_only=True to avoid confusing fuzzy matches for downloads
model_list_result = search_model_list(
filename, exact_only=True
@@ -772,6 +725,19 @@ class ModelLinkerExtension:
# Use resolve_urn to get model info (cached)
model_info = resolve_urn(model_id, version_id)
if model_info:
+ primary_file = None
+ for file_info in model_info.get("files", []):
+ if (
+ file_info.get("name")
+ == model_info.get("expected_filename")
+ ):
+ primary_file = file_info
+ break
+ if primary_file is None:
+ primary_file = (
+ model_info.get("files") or [{}]
+ )[0]
+
download_url = get_civitai_download_url(
version_id
)
@@ -783,7 +749,10 @@ class ModelLinkerExtension:
),
"type": category,
"download_url": download_url,
- "url": f"https://civitai.com/models/{model_id}",
+ "url": f"https://civitai.com/models/{model_id}?modelVersionId={version_id}",
+ "model_id": model_id,
+ "version_id": version_id,
+ "size": primary_file.get("size"),
}
results["found"] = True
elif category:
diff --git a/web/linker.js b/web/linker.js
index c871f66..c7fb2c5 100644
--- a/web/linker.js
+++ b/web/linker.js
@@ -204,6 +204,52 @@ 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);
+
+ 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 += ``;
+ if (isCivitaiSource && modelCardUrl) {
+ html += ` Open on CivitAI`;
+ }
+ html += `
`;
+ html += `${isFromWorkflow ? 'URL from workflow' : sourceLabel}`;
+ if (modelCardUrl) {
+ html += ` ${formattedDownloadName.display}`;
+ } else {
+ html += ` ${formattedDownloadName.display}`;
+ }
+ if (isCivitaiSource) {
+ html += ` Direct CivitAI download can require an API key for some models.`;
+ }
+ html += `
`;
+ html += `
`;
+ return html;
+ }
+
/**
* Handle click outside context menu to hide it
*/
@@ -3073,6 +3119,7 @@ class LinkerManagerDialog extends ComfyDialog {
displayMissingModels(container, data) {
const missingModels = data.missing_models || [];
const totalMissing = data.total_missing || 0;
+ this.missingModels = missingModels;
// Check if there are active downloads
const activeCount = Object.keys(this.activeDownloads).length;
@@ -3504,48 +3551,16 @@ class LinkerManagerDialog extends ComfyDialog {
const filename = missing.original_path?.split('/').pop()?.split('\\').pop() || '';
const downloadSource = missing.download_source;
+ const urnDownloadId = `urn-download-${missing.node_id}-${missing.widget_index}`;
if (perfectMatches.length > 0) {
// Has perfect local match - download not needed
html += `