refactor: centralize source API requests, polling, and path parsing

- Add request_source_response and request_source_json helpers with 429 retry logic
- Introduce shared split_path_segments and parse_provider_model_url utilities
- Migrate CivitAI, CivArchive, HuggingFace, and ModelList sources to centralized helpers
- Add generic pollBackgroundTask utility for frontend progress polling
- Add tests for request_source_json, split_path_segments, parse_provider_model_url
- Add huggingface-hub to requirements
This commit is contained in:
Dariusz L
2026-07-14 12:03:35 +02:00
parent a5b54bb7de
commit 8cf688eba6
14 changed files with 376 additions and 204 deletions
+36 -51
View File
@@ -2,7 +2,7 @@ import { app } from "../../../../../scripts/app.js";
import { api } from "../../../../../scripts/api.js";
import { $el } from "../../../../../scripts/ui.js";
import { getSvgIcon } from "../../utils/icon_utils.js";
import { escapeHtml, escapeJsString } from "./html_utils.js";
import { escapeHtml, escapeJsString, pollBackgroundTask } from "./html_utils.js";
export const renderFormatMethods = {
encodeContextMenuModel(context = null) {
@@ -248,69 +248,54 @@ export const renderFormatMethods = {
},
async pollAnalysisProgress(analysisId, token) {
while (this._analysisProgressToken === token) {
try {
const progress = await this.fetchJson(`/model_resolver/analyze-progress/${analysisId}`, {
silent: true
}, 'Poll analysis progress');
if (
progress &&
this.contentElement &&
this._analysisProgressToken === token &&
this.activeTab === 'missing'
) {
await pollBackgroundTask({
endpoint: `/model_resolver/analyze-progress/${analysisId}`,
tokenCheck: () => this._analysisProgressToken === token,
onProgress: (progress) => {
if (this.contentElement && this.activeTab === 'missing') {
this.contentElement.innerHTML = this.renderAnalysisProgress(progress);
if (progress.status === 'completed' || progress.status === 'error') {
if (this._analysisProgressToken === token) {
this._analysisProgressToken = null;
}
return;
}
}
} catch (error) {
},
isTerminal: (progress) => progress.status === 'completed' || progress.status === 'error',
onTerminal: () => {
if (this._analysisProgressToken === token) {
this._analysisProgressToken = null;
}
},
onError: (error) => {
console.warn('Model Resolver: analysis progress polling failed', error);
}
await new Promise(resolve => setTimeout(resolve, 250));
}
},
intervalMs: 250,
fetchJson: this.fetchJson.bind(this)
});
},
async pollLoadedModelsProgress(loadedId, token) {
while (this._loadedModelsProgressToken === token) {
try {
const progress = await this.fetchJson(`/model_resolver/loaded-progress/${encodeURIComponent(loadedId)}`, {
silent: true
}, 'Poll loaded models progress');
if (progress?.status === 'unknown') {
await new Promise(resolve => setTimeout(resolve, 250));
continue;
}
if (
progress &&
this.contentElement &&
this._loadedModelsProgressToken === token &&
this.activeTab === 'loaded'
) {
await pollBackgroundTask({
endpoint: `/model_resolver/loaded-progress/${encodeURIComponent(loadedId)}`,
tokenCheck: () => this._loadedModelsProgressToken === token,
filterIgnoredStatus: (progress) => progress?.status === 'unknown',
onProgress: (progress) => {
if (this.contentElement && this.activeTab === 'loaded') {
this.contentElement.innerHTML = this.renderLoadedModelsProgress(progress);
if (progress.status === 'completed' || progress.status === 'error') {
if (this._loadedModelsProgressToken === token) {
this._loadedModelsProgressToken = null;
}
return;
}
}
} catch (error) {
},
isTerminal: (progress) => progress.status === 'completed' || progress.status === 'error',
onTerminal: () => {
if (this._loadedModelsProgressToken === token) {
this._loadedModelsProgressToken = null;
}
},
onError: (error) => {
console.warn('Model Resolver: loaded models progress polling failed', error);
}
await new Promise(resolve => setTimeout(resolve, 250));
}
},
intervalMs: 250,
fetchJson: this.fetchJson.bind(this)
});
},
/**
* Get display information for the best local match of a missing model
* @param {object} missing - The missing model object
* @returns {object} Match display metadata
*/
getLocalMatchDisplayInfo(missing) {
const bestMatch = this.getBestLocalMatch?.(missing, 70) || null;