Prefer HuggingFace direct download URLs and preserve them through selection

This commit is contained in:
Dariusz L
2026-09-16 20:14:43 +02:00
parent dca6bc93fb
commit 4054be2a04
4 changed files with 129 additions and 7 deletions
@@ -3830,6 +3830,39 @@ test('automatic Local Database download source stays hidden until that source is
assert.equal(shouldDisplayKnownDownloadSource.call(dialog, {}, downloadSource, state), true);
});
test('HuggingFace fallback download source prefers the direct file URL', () => {
const getBestDownloadSourceForMissing = eval(`(${extractMethod(resolveDownloadMethodsSource, 'getBestDownloadSourceForMissing')})`);
const pageUrl = 'https://huggingface.co/Comfy-Org/YuE2/tree/main/checkpoints';
const downloadUrl = 'https://huggingface.co/Comfy-Org/YuE2/resolve/main/checkpoints/yue2_3b_bf16.safetensors';
const missing = {
missing_search_key: 'missing-key',
original_path: 'yue2_convrot_int8.safetensors',
category: 'checkpoints',
};
const dialog = {
searchResultCache: new Map([[
'missing-key',
{ results: { huggingface: {
source: 'huggingface',
model_id: 'Comfy-Org/YuE2',
filename: 'yue2_3b_bf16.safetensors',
url: pageUrl,
download_url: downloadUrl,
} } }
]]),
getMissingSearchKey() {
return 'missing-key';
},
getFilenameFromPath(value = '') {
return String(value).split(/[\\/]/).at(-1) || '';
},
};
const source = getBestDownloadSourceForMissing.call(dialog, missing);
assert.equal(source.url, downloadUrl);
});
test('inactive missing models require every workflow reference to be inactive', () => {
const { isMissingModelInactive } = missingModelStateMethods;
@@ -8420,6 +8453,66 @@ test('applying source model details selection updates current download source ha
assert.equal(refreshCalls.length, 1);
});
test('applying a HuggingFace file selection keeps the direct URL in the search result', () => {
const applySourceModelDetailsSelection = eval(`(${extractMethod(modelInfoMethodsSource, 'applySourceModelDetailsSelection')})`);
const sha256 = 'c'.repeat(64);
const pageUrl = 'https://huggingface.co/Comfy-Org/YuE2/tree/main/checkpoints';
const downloadUrl = 'https://huggingface.co/Comfy-Org/YuE2/resolve/main/checkpoints/yue2_3b_bf16.safetensors';
const missing = {
original_path: 'yue2_convrot_int8.safetensors',
category: 'checkpoints',
civitai_info: {},
};
const state = { results: {} };
const dialog = {
getMissingByKey(key) {
return key === 'missing-key' ? missing : null;
},
getSearchState() {
return state;
},
getSourceModelFileHash(value = {}) {
const hashes = value.hashes && typeof value.hashes === 'object' ? value.hashes : {};
return String(value.sha256 || value.hash || hashes.SHA256 || hashes.sha256 || '').trim().toLowerCase();
},
refreshSearchUiForMissing() {},
refreshSearchBaseModelLabels() {},
updateBatchFooterButtons() {},
persistSearchStateForActiveWorkflow() {},
syncRemoteHashMatchesForResult() {},
showNotification() {},
};
applySourceModelDetailsSelection.call(dialog, {
source: 'huggingface',
model_id: 'Comfy-Org/YuE2',
version_id: 'main',
name: 'Comfy-Org/YuE2',
filename: 'yue2_3b_bf16.safetensors',
url: pageUrl,
download_url: downloadUrl,
path: 'checkpoints/yue2_3b_bf16.safetensors',
sha256,
hashes: { SHA256: sha256 },
file_info: {
name: 'yue2_3b_bf16.safetensors',
path: 'checkpoints/yue2_3b_bf16.safetensors',
sha256,
hashes: { SHA256: sha256 },
},
}, {
missing_key: 'missing-key',
details_source: 'huggingface',
});
assert.equal(state.results.huggingface.url, downloadUrl);
assert.equal(state.results.huggingface.download_url, downloadUrl);
assert.equal(state.results.huggingface.model_url, pageUrl);
assert.equal(missing.download_source.url, downloadUrl);
assert.equal(missing.download_source.model_url, pageUrl);
assert.equal(missing.download_source.sha256, sha256);
});
test('batch sources advance independently while each source stays sequential', async () => {
const runBatch = eval(`(${extractMethod(resolveDownloadMethodsSource, 'searchMissingBatch')})`);
const started = [];
+14 -1
View File
@@ -45,7 +45,7 @@ test('HuggingFace download rows expose the Show More action', () => {
);
assert.match(
resolveDownloadSource,
/if \(hfResult && hfResult\.url\)[\s\S]*?detailsContext:\s*\{[\s\S]*?\.\.\.hfResult,[\s\S]*?details_source:\s*'huggingface'/
/const hfDownloadUrl = hfResult\?\.download_url \|\| hfResult\?\.url \|\| ''[\s\S]*?if \(hfResult && hfDownloadUrl\)[\s\S]*?detailsContext:\s*\{[\s\S]*?\.\.\.hfResult,[\s\S]*?details_source:\s*'huggingface'/
);
});
@@ -55,6 +55,19 @@ test('HuggingFace details requests keep folder, branch, and token context', () =
assert.match(modelInfoSource, /hf_token: tokens\.hf_token \|\| ''/);
});
test('selected HuggingFace details reuse the row and download the direct file URL', () => {
assert.match(
resolveDownloadSource,
/const hfDownloadUrl = hfResult\?\.download_url \|\| hfResult\?\.url \|\| ''/
);
assert.match(resolveDownloadSource, /downloadUrl: hfDownloadUrl/);
assert.match(
modelInfoSource,
/const isHuggingFace = sourceKey === 'huggingface'[\s\S]*?url: isHuggingFace \? selection\.download_url : selection\.url/
);
assert.match(modelInfoSource, /model_url: sourceResult\.model_url \|\| sourceResult\.url/);
});
test('a selected HuggingFace variant keeps its repository path', () => {
assert.match(
modelInfoSource,
@@ -130,7 +130,8 @@ export const resolveDownloadMethods = {
{
source: 'huggingface',
result: first(results.huggingface),
urlKey: 'url',
urlKey: 'download_url',
fallbackUrlKey: 'url',
filenameKey: 'filename'
},
{
@@ -155,7 +156,8 @@ export const resolveDownloadMethods = {
for (const candidate of candidates) {
const result = candidate.result;
const url = result?.[candidate.urlKey];
const url = result?.[candidate.urlKey]
|| (candidate.fallbackUrlKey ? result?.[candidate.fallbackUrlKey] : '');
if (!url) continue;
const rawModelUrl = result.model_url || result.url || url;
@@ -3859,7 +3861,8 @@ export const resolveDownloadMethods = {
}));
}
if (hfResult && hfResult.url) {
const hfDownloadUrl = hfResult?.download_url || hfResult?.url || '';
if (hfResult && hfDownloadUrl) {
const hfRepo = hfResult.repo_id || hfResult.repo || '';
const hfModelUrl = hfRepo ? `https://huggingface.co/${hfRepo}` : getModelCardUrl(hfResult.url);
const missingCategory = this.getMissingDownloadCategory?.(missing, 'checkpoints') || missing.category || 'checkpoints';
@@ -3870,7 +3873,7 @@ export const resolveDownloadMethods = {
model: hfRepo || hfResult.filename,
filename: hfResult.filename,
secondary: hfResult.path && hfResult.path !== hfResult.filename ? hfResult.path : '',
downloadUrl: hfResult.url,
downloadUrl: hfDownloadUrl,
downloadFilename: hfResult.filename,
category: missingCategory,
openUrl: hfModelUrl,
+15 -2
View File
@@ -3549,6 +3549,13 @@ export const modelInfoMethods = {
}
const sourceKey = String(selection.source || contextModel.details_source || contextModel.source || '').toLowerCase();
const isHuggingFace = sourceKey === 'huggingface';
const sourcePageUrl = selection.page_url
|| selection.pageUrl
|| selection.version_url
|| selection.versionUrl
|| selection.url
|| '';
const selectedBaseModel = selection.base_model || contextModel.base_model || '';
const selectionHashes = selection.hashes && typeof selection.hashes === 'object' ? selection.hashes : {};
const selectionFile = selection.file_info || selection.selected_file || selection.file || null;
@@ -3565,7 +3572,7 @@ export const modelInfoMethods = {
version_name: selection.version_name,
type: selection.type,
filename: selection.filename,
url: selection.url,
url: isHuggingFace ? selection.download_url : selection.url,
download_url: selection.download_url,
size: selection.size,
base_model: selectedBaseModel,
@@ -3578,6 +3585,12 @@ export const modelInfoMethods = {
confidence: selection.confidence || 100,
searchedAt: new Date().toISOString()
};
if (isHuggingFace) {
sourceResult.repo_id = selection.repo_id || selection.repo || selection.model_id || '';
sourceResult.version_url = sourcePageUrl;
sourceResult.page_url = sourcePageUrl;
sourceResult.model_url = sourcePageUrl;
}
if (Object.keys(selectionHashes).length) sourceResult.hashes = selectionHashes;
if (selectionFile && typeof selectionFile === 'object') {
sourceResult.file_info = selectionFile;
@@ -3591,7 +3604,7 @@ export const modelInfoMethods = {
missing.download_source = {
...sourceResult,
url: sourceResult.download_url,
model_url: sourceResult.url,
model_url: sourceResult.model_url || sourceResult.url,
directory: missing.category || 'checkpoints'
};