diff --git a/web/resolver/actions/queue_methods.js b/web/resolver/actions/queue_methods.js index 75fdfd2..aa9547d 100644 --- a/web/resolver/actions/queue_methods.js +++ b/web/resolver/actions/queue_methods.js @@ -1280,8 +1280,7 @@ export const queueMethods = { this.rebuildPendingIndex(); this.savePendingQueueForActiveWorkflow(); // Update per-item selected bar - const m = { node_id: r.node_id, widget_index: r.widget_index, subgraph_id: r.subgraph_id, is_top_level: r.is_top_level }; - this.updateSelectedBarForMissing?.(m); + this.updateSelectedBarForMissing?.(r); this.updateApplyPendingButton?.(); this.updateQueuePanel(); this.refreshMissingModelsBrowserFromCache?.(); @@ -1303,13 +1302,10 @@ export const queueMethods = { // Update selected bar for a specific missing model slot updateSelectedBarForMissing(missing) { if (!missing) return; - const nodeId = missing.node_id; - const widgetIndex = missing.widget_index; - const subgraphId = missing.subgraph_id || ''; - const isTopLevel = missing.is_top_level !== false; - const key = `${nodeId}:${widgetIndex}:${subgraphId}:${isTopLevel ? 'T' : 'F'}`; + const key = this.getMissingModelKey(missing); + const domKey = this.getMissingModelDomKey(missing); - const selectedBar = document.getElementById(`selected-bar-${nodeId}-${widgetIndex}`); + const selectedBar = document.getElementById(`selected-bar-${domKey}`); if (!selectedBar) return; // Find selection for this slot @@ -1360,8 +1356,8 @@ export const queueMethods = { selectedBar.removeAttribute('data-tooltip'); selectedBar.oncontextmenu = null; } - const applyBtnId = `selected-apply-${nodeId}-${widgetIndex}`; - const removeBtnId = `selected-remove-${nodeId}-${widgetIndex}`; + const applyBtnId = `selected-apply-${domKey}`; + const removeBtnId = `selected-remove-${domKey}`; selectedBar.innerHTML = `
`; selectedBar.innerHTML += `✓ Selected:`; @@ -1398,7 +1394,7 @@ export const queueMethods = { if (idx >= 0 && idx < this.pendingResolutions.length) { const r = this.pendingResolutions[idx]; // Update the selected bar before removing - const m = { node_id: r.node_id, widget_index: r.widget_index, subgraph_id: r.subgraph_id, is_top_level: r.is_top_level }; + const m = r; // Remove from array this.pendingResolutions.splice(idx, 1); @@ -1438,11 +1434,7 @@ export const queueMethods = { }, getResolutionQueueKey(resolution = {}) { - const nodeId = resolution.node_id ?? ''; - const widgetIndex = resolution.widget_index ?? ''; - const subgraphId = resolution.subgraph_id || ''; - const scope = resolution.is_top_level !== false ? 'T' : 'F'; - return `${nodeId}:${widgetIndex}:${subgraphId}:${scope}`; + return resolution.missing_key || this.getMissingModelKey(resolution); }, getResolutionNodeRefs(missing = {}) { @@ -1719,17 +1711,22 @@ export const queueMethods = { return; } + const missingKey = this.getMissingModelKey(missing); const resolution = { + missing_key: missingKey, node_id: missing.node_id, widget_index: missing.widget_index, resolved_path: resolvedModel.path, category: missing.category, resolved_model: resolvedModel, original_path: missing.original_path, + expected_filename: missing.expected_filename || missing.civitai_info?.expected_filename || missing.download_source?.filename, + filename: missing.filename, + urn_string: missing.urn_string, subgraph_id: missing.subgraph_id, is_top_level: missing.is_top_level, is_lora_v2: missing.is_lora_v2, - original_lora_name: missing.name || missing.original_path, + original_lora_name: missing.original_lora_name || missing.name || missing.original_path, nested_key: missing.nested_key, node_refs: this.getResolutionNodeRefs(missing), node_type: missing.node_type, diff --git a/web/resolver/actions/resolve_download_methods.js b/web/resolver/actions/resolve_download_methods.js index 95a741b..a7b1c36 100644 --- a/web/resolver/actions/resolve_download_methods.js +++ b/web/resolver/actions/resolve_download_methods.js @@ -891,7 +891,7 @@ export const resolveDownloadMethods = { { category, downloadPath, downloadDirectory, filename: targetFilename } ); } - const body = this.contentElement?.querySelector(`#local-matches-body-${missing.node_id}-${missing.widget_index}`); + const body = this.contentElement?.querySelector(`#local-matches-body-${this.getMissingModelDomKey(missing)}`); if (body) { body.innerHTML = `
Checking local matches for ${this.escapeHtml(targetFilename)}...
`; } @@ -931,7 +931,7 @@ export const resolveDownloadMethods = { refreshLocalMatchesUiForMissing(missing) { if (!missing || !this.contentElement) return; - const body = this.contentElement.querySelector(`#local-matches-body-${missing.node_id}-${missing.widget_index}`); + const body = this.contentElement.querySelector(`#local-matches-body-${this.getMissingModelDomKey(missing)}`); const displayIndex = Number.isFinite(missing.__displayIndex) ? missing.__displayIndex : 0; if (body) { body.innerHTML = this.renderLocalMatchesContent(missing, displayIndex); @@ -962,7 +962,7 @@ export const resolveDownloadMethods = { const minRefreshFeedback = new Promise(resolve => setTimeout(resolve, 420)); const refreshAnimation = this.startRefreshButtonAnimation?.(button); - const body = this.contentElement?.querySelector(`#local-matches-body-${missing.node_id}-${missing.widget_index}`); + const body = this.contentElement?.querySelector(`#local-matches-body-${this.getMissingModelDomKey(missing)}`); const displayName = targetFilename.split('/').pop()?.split('\\').pop() || targetFilename; const missingKey = this.getMissingModelKey(missing); const currentMissing = (this.missingModels || []).find(item => this.getMissingModelKey(item) === missingKey) || missing; diff --git a/web/resolver/search/search_panel.js b/web/resolver/search/search_panel.js index 2299242..d2958ef 100644 --- a/web/resolver/search/search_panel.js +++ b/web/resolver/search/search_panel.js @@ -2111,7 +2111,7 @@ export const searchPanelMethods = { async refreshUrnLocalMatches(missing) { if (!missing?.civitai_info?.expected_filename || !this.contentElement) return; - const bodyId = `local-matches-body-${missing.node_id}-${missing.widget_index}`; + const bodyId = `local-matches-body-${this.getMissingModelDomKey(missing)}`; const container = this.contentElement.querySelector(`#${bodyId}`); if (container) { container.innerHTML = `
Searching local matches for "${missing.civitai_info.expected_filename}"...
`; diff --git a/web/resolver/shell/lifecycle_graph_methods.js b/web/resolver/shell/lifecycle_graph_methods.js index 098d746..0e2db73 100644 --- a/web/resolver/shell/lifecycle_graph_methods.js +++ b/web/resolver/shell/lifecycle_graph_methods.js @@ -745,11 +745,49 @@ export const lifecycleGraphMethods = { } }, + encodeMissingModelKeyPart(value) { + return encodeURIComponent(String(value ?? '').trim()); + }, + + getMissingModelIdentityPart(missing = {}) { + return missing.original_lora_name + || missing.original_path + || missing.expected_filename + || missing.name + || missing.filename + || missing.urn_string + || ''; + }, + getMissingModelKey(missing = {}) { + if (missing.missing_key) { + return String(missing.missing_key); + } + const nodeId = missing.node_id ?? ''; const widgetIndex = missing.widget_index ?? ''; const subgraphId = missing.subgraph_id || ''; const scope = missing.is_top_level !== false ? 'T' : 'F'; - return `${nodeId}:${widgetIndex}:${subgraphId}:${scope}`; + const nestedKey = missing.nested_key || ''; + const category = missing.category || ''; + const identity = this.getMissingModelIdentityPart(missing); + return [ + nodeId, + widgetIndex, + subgraphId, + scope, + nestedKey, + category, + identity + ].map(value => this.encodeMissingModelKeyPart(value)).join(':'); + }, + + getMissingModelDomKey(missing = {}) { + const key = this.getMissingModelKey(missing); + try { + return btoa(key).replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/g, ''); + } catch (e) { + return key.replace(/[^A-Za-z0-9_-]/g, char => `_${char.charCodeAt(0).toString(16)}_`); + } } }; diff --git a/web/resolver/views/missing_browser_methods.js b/web/resolver/views/missing_browser_methods.js index 6769ebb..4e962c1 100644 --- a/web/resolver/views/missing_browser_methods.js +++ b/web/resolver/views/missing_browser_methods.js @@ -225,9 +225,7 @@ export const missingBrowserMethods = { isMissingModelResolved(missing = {}) { if (!missing) return false; - if (missing.__isExistingResolved) return true; - const key = this.getMissingModelKey(missing); - return Boolean(key && this.pendingIndex?.has?.(key)); + return Boolean(missing.__isExistingResolved); }, getResolvedWorkflowModels(data = {}) { @@ -686,7 +684,8 @@ export const missingBrowserMethods = { this.wireDownloadSearchPanel(container, missing); this.updateSelectedBarForMissing(missing); - const localRefreshId = `local-matches-refresh-${missing.node_id}-${missing.widget_index}`; + const detailDomKey = this.getMissingModelDomKey(missing); + const localRefreshId = `local-matches-refresh-${detailDomKey}`; const localRefreshBtn = container.querySelector(`#${localRefreshId}`); if (localRefreshBtn) { localRefreshBtn.addEventListener('click', () => this.refreshLocalMatchesForMissing?.(missing, { @@ -694,7 +693,7 @@ export const missingBrowserMethods = { })); } - const locateId = `locate-${missing.node_id}-${missing.widget_index}`; + const locateId = `locate-${detailDomKey}`; const locateBtn = container.querySelector(`#${locateId}`); const locateTarget = this.getMissingLocateTarget(missing); if (locateBtn && locateTarget.nodeId !== undefined && locateTarget.nodeId !== null && locateTarget.nodeId !== '') { @@ -706,7 +705,7 @@ export const missingBrowserMethods = { }); } - const comboId = `combo-${missing.node_id}-${missing.widget_index}`; + const comboId = `combo-${detailDomKey}`; const comboInput = container.querySelector(`#combo-input-${comboId}`); const comboList = container.querySelector(`#combo-list-${comboId}`); const comboRefresh = container.querySelector(`#combo-refresh-${comboId}`); @@ -1102,7 +1101,8 @@ export const missingBrowserMethods = { html += titleSecondaryHtml; } html += `
`; - const locateId = `locate-${missing.node_id}-${missing.widget_index}`; + const detailDomKey = this.getMissingModelDomKey(missing); + const locateId = `locate-${detailDomKey}`; const nodeChipClasses = nodeDisplay.canLocate ? 'mr-node-chip is-locatable' : 'mr-node-chip'; const nodeChipTitle = nodeDisplay.canLocate ? nodeDisplay.locateTooltip : ''; @@ -1120,7 +1120,7 @@ export const missingBrowserMethods = { html += ``; // Selected bar - shows if this slot has a queued selection (BELOW card header) - const selectedBarId = `selected-bar-${missing.node_id}-${missing.widget_index}`; + const selectedBarId = `selected-bar-${detailDomKey}`; html += `
`; // Two-column layout @@ -1128,17 +1128,17 @@ export const missingBrowserMethods = { // LEFT COLUMN: Local Matches html += `
`; - const localRefreshId = `local-matches-refresh-${missing.node_id}-${missing.widget_index}`; + const localRefreshId = `local-matches-refresh-${detailDomKey}`; html += `
`; html += `Local Matches`; html += ``; html += `
`; - html += `
`; + html += `
`; html += this.renderLocalMatchesContent(missing, missingIndex); html += `
`; // Add all-models search picker - combo-style dropdown - const comboId = `combo-${missing.node_id}-${missing.widget_index}`; + const comboId = `combo-${detailDomKey}`; html += `
`; html += `
`; html += ``;