diff --git a/web/resolver/actions/resolve_download_methods.js b/web/resolver/actions/resolve_download_methods.js index b71341c..b155aa0 100644 --- a/web/resolver/actions/resolve_download_methods.js +++ b/web/resolver/actions/resolve_download_methods.js @@ -1735,6 +1735,12 @@ export const resolveDownloadMethods = { if (currentJob?.runId === searchRunId) { this.backgroundSearchJobs.delete(backgroundJobKey); } + this.settleInactiveSearchProgress?.(missing, state, { + workflowKey, + message: state.lastAttemptError || 'Search interrupted', + persist: false, + refresh: false + }); if (searchBtn) { searchBtn.disabled = false; searchBtn.innerHTML = this.renderSearchButtonContent('Search Again'); diff --git a/web/resolver/resolver_dialog.js b/web/resolver/resolver_dialog.js index 6ab231f..aac7916 100644 --- a/web/resolver/resolver_dialog.js +++ b/web/resolver/resolver_dialog.js @@ -188,6 +188,12 @@ export class ResolverManagerDialog extends ComfyDialog { this.boundHandleFooterMenuClick = (e) => this.handleFooterMenuOutsideClick(e); document.addEventListener('click', this.boundHandleFooterMenuClick); window.addEventListener('resize', this._boundHandleViewportResize); + this._boundSyncSearchProgressAfterResume = () => { + if (document.visibilityState === 'hidden') return; + window.setTimeout(() => this.syncSearchProgressAfterResume?.(), 0); + }; + window.addEventListener('focus', this._boundSyncSearchProgressAfterResume); + document.addEventListener('visibilitychange', this._boundSyncSearchProgressAfterResume); } } diff --git a/web/resolver/search/search_panel.js b/web/resolver/search/search_panel.js index 6f3a6da..ace5d37 100644 --- a/web/resolver/search/search_panel.js +++ b/web/resolver/search/search_panel.js @@ -968,23 +968,96 @@ export const searchPanelMethods = { ); }, - reconnectActiveSearchProgress(missingModels = this.missingModels || []) { + settleInactiveSearchProgress(missing, state = null, { + workflowKey = this.getWorkflowScopedQueueKey(), + message = 'Search interrupted', + persist = true, + refresh = true + } = {}) { + if (!missing || !state) return false; + + const runId = state.activeSearchRunId; + const missingSearchKey = this.getMissingSearchKey(missing); + const hasActiveJob = Boolean( + runId && this.hasBackgroundSearchJob(workflowKey, missingSearchKey, runId) + ); + if (hasActiveJob) return false; + + let changed = false; + let markedError = false; + let markedFound = false; + + if (runId) { + this.clearSearchProgressTimers(runId); + state.activeSearchRunId = null; + changed = true; + } + + for (const [source, progress] of Object.entries(state.sourceProgress || {})) { + if (!progress || (progress.status !== 'pending' && progress.status !== 'running')) continue; + + const resultStatus = this.getMissingSourceResultStatus?.(missing, source, state) || ''; + const hasResult = resultStatus === 'exact' + || resultStatus === 'partial' + || resultStatus === 'found'; + + state.sourceProgress[source] = { + ...progress, + status: hasResult ? 'found' : 'error', + percent: 100, + message: hasResult ? 'Found' : message, + error: hasResult ? null : (progress.error || message) + }; + markedFound = markedFound || hasResult; + markedError = markedError || !hasResult; + changed = true; + } + + if (!changed) return false; + + if (markedFound) { + state.lastAttemptFound = true; + } + if (markedError && !state.lastAttemptError) { + state.lastAttemptError = message; + } + + if (persist) { + this.persistSearchStateForWorkflow(workflowKey, missing, state); + } + if (refresh) { + this.refreshSearchUiForMissing(missing, state, { workflowKey }); + } + return true; + }, + + syncSearchProgressAfterResume(missingModels = this.missingModels || [], { workflowKey = this.getWorkflowScopedQueueKey() } = {}) { for (const missing of missingModels || []) { - const state = this.searchResultCache.get(this.getMissingSearchKey(missing)); - const runId = state?.activeSearchRunId; - if (!runId) continue; + const missingSearchKey = this.getMissingSearchKey(missing); + const state = workflowKey === this.getWorkflowScopedQueueKey() + ? this.searchResultCache.get(missingSearchKey) + : this.getWorkflowSearchCache(workflowKey)?.get(missingSearchKey); + if (!state) continue; + + if (this.settleInactiveSearchProgress(missing, state, { workflowKey })) { + continue; + } + + const runId = state.activeSearchRunId; + if (!runId || !this.hasBackgroundSearchJob(workflowKey, missingSearchKey, runId)) continue; for (const [source, progress] of Object.entries(state.sourceProgress || {})) { if (progress?.status !== 'running') continue; - const timerKey = this.getSearchProgressTimerKey(runId, source); - if (!this.searchProgressTimers.has(timerKey)) { - this.startEstimatedSearchProgress(state, missing, null, source, runId); - } + this.startEstimatedSearchProgress(state, missing, null, source, runId, { workflowKey }); } - this.refreshSearchUiForMissing(missing, state); + this.refreshSearchUiForMissing(missing, state, { workflowKey }); } }, + reconnectActiveSearchProgress(missingModels = this.missingModels || []) { + this.syncSearchProgressAfterResume(missingModels); + }, + hasActiveSearchProgress(state = {}) { return Object.values(state.sourceProgress || {}).some(progress => ( progress?.status === 'pending' || progress?.status === 'running' @@ -1164,28 +1237,6 @@ export const searchPanelMethods = { const { sync = true } = options || {}; const state = this.getSearchState(missing); const nextBaseModel = baseModel || this.getDefaultSearchBaseModel(); - const changed = (state.selectedBaseModel || this.getDefaultSearchBaseModel()) !== nextBaseModel; - if (changed) { - this.clearSearchProgressTimers(state.activeSearchRunId); - state.results = this.createEmptySearchState().results; - state.lastAttemptSources = []; - state.lastAttemptBaseModelContext = ''; - state.lastAttemptFound = null; - state.lastAttemptError = null; - state.sourceProgress = {}; - state.activeSearchRunId = null; - const resultsDiv = container?.querySelector?.(`#search-results-${missing.node_id}-${missing.widget_index}`); - if (resultsDiv) { - resultsDiv.classList.remove('mr-is-visible'); - resultsDiv.classList.add('mr-is-hidden'); - resultsDiv.innerHTML = ''; - } - const searchBtn = container?.querySelector?.(`#search-${missing.node_id}-${missing.widget_index}`); - if (searchBtn) { - searchBtn.disabled = false; - searchBtn.innerHTML = this.renderSearchButtonContent('Search Online'); - } - } state.selectedBaseModel = nextBaseModel; this.persistSearchStateForActiveWorkflow(); if (sync) {