diff --git a/web/css/linker-main.css b/web/css/linker-main.css index 52b5ec7..423bf34 100644 --- a/web/css/linker-main.css +++ b/web/css/linker-main.css @@ -441,36 +441,64 @@ color: rgba(255,255,255,0.42); } .ml-missing-row-sources { - display: flex; - align-items: center; - justify-content: flex-start; - gap: 5px; + position: relative; + display: block; + justify-self: center; min-width: 0; + width: 98px; + height: 44px; } .ml-missing-source-dot { + position: absolute; display: inline-flex; align-items: center; justify-content: center; - width: 20px; - height: 20px; + width: 30px; + height: 30px; border-radius: 999px; - background: var(--ml-missing-surface); + background: rgba(255,255,255,0.035); border: 1px solid var(--ml-missing-line-strong); color: rgba(255,255,255,0.42); font-size: 9px; font-weight: 800; + box-shadow: inset 0 1px 0 rgba(255,255,255,0.06); +} +.ml-missing-row-sources .ml-missing-source-dot:nth-child(1) { + left: 9px; + top: -3px; +} +.ml-missing-row-sources .ml-missing-source-dot:nth-child(2) { + left: 43px; + top: -3px; +} +.ml-missing-row-sources .ml-missing-source-dot:nth-child(3) { + left: 26px; + top: 17px; +} +.ml-missing-row-sources .ml-missing-source-dot:nth-child(4) { + left: 60px; + top: 17px; +} +.ml-missing-row-sources .ml-missing-source-dot:nth-child(3):last-child { + left: 36px; } .ml-missing-source-icon { display: block; - width: 12px; - height: 12px; - flex: 0 0 12px; + width: 17px; + height: 17px; + flex: 0 0 17px; } -.ml-missing-source-found { +.ml-missing-source-found, +.ml-missing-source-exact { color: #72d38a; border-color: rgba(114,211,138,0.55); background: rgba(114,211,138,0.11); } +.ml-missing-source-partial { + color: #f1cf64; + border-color: rgba(241,207,100,0.55); + background: rgba(241,207,100,0.11); +} .ml-missing-source-none { color: rgba(255,255,255,0.48); } diff --git a/web/linker.js b/web/linker.js index e83a771..bcbaafe 100644 --- a/web/linker.js +++ b/web/linker.js @@ -3970,17 +3970,27 @@ class LinkerManagerDialog extends ComfyDialog { getMissingSourceStatus(missing = {}, source = '') { const state = this.searchResultCache.get(this.getMissingSearchKey(missing)); const progress = state?.sourceProgress?.[source]; + const resultStatus = this.getMissingSourceResultStatus(missing, source, state); + if (progress?.status === 'found') return resultStatus || 'found'; if (progress?.status) return progress.status; + if (resultStatus) return resultStatus; + + return 'idle'; + } + + getMissingSourceResultStatus(missing = {}, source = '', state = null) { const results = state?.results || {}; - const resultKeys = { - local: 'model_list', - huggingface: 'huggingface', - civitai: 'civitai', - lora_manager_archive: 'lora_manager_archive' - }; - if (resultKeys[source] && results[resultKeys[source]]) { - return 'found'; + const candidates = []; + + if (source === 'local') { + candidates.push(results.model_list, results.popular); + } else if (source === 'huggingface') { + candidates.push(results.huggingface); + } else if (source === 'civitai') { + candidates.push(results.civitai); + } else if (source === 'lora_manager_archive') { + candidates.push(results.lora_manager_archive); } const downloadSource = missing.download_source || {}; @@ -3990,10 +4000,49 @@ class LinkerManagerDialog extends ComfyDialog { workflow: 'civitai' }[downloadSource.source] || downloadSource.source; if (mappedDownloadSource === source && downloadSource.url) { - return 'found'; + candidates.push(downloadSource); } - return 'idle'; + return this.getSearchResultStatusLevel(candidates); + } + + getSearchResultStatusLevel(resultOrResults) { + const results = Array.isArray(resultOrResults) + ? resultOrResults.flatMap(result => Array.isArray(result) ? result : [result]) + : [resultOrResults]; + let hasPartial = false; + let hasUnknownFound = false; + + for (const result of results) { + if (!result) continue; + + const confidence = Number(result.confidence); + const hasConfidence = Number.isFinite(confidence); + const matchType = String(result.match_type || '').toLowerCase(); + + if (hasConfidence) { + if (confidence >= 100) return 'exact'; + if (confidence > 0) { + hasPartial = true; + continue; + } + } + + if (matchType === 'exact' || result.source === 'popular') { + return 'exact'; + } + if (matchType === 'partial' || matchType === 'fuzzy' || matchType === 'similar') { + hasPartial = true; + continue; + } + if (result.url || result.download_url) { + hasUnknownFound = true; + } + } + + if (hasPartial) return 'partial'; + if (hasUnknownFound) return 'found'; + return ''; } renderMissingSourcesSummary(missing = {}) { @@ -4013,6 +4062,8 @@ class LinkerManagerDialog extends ComfyDialog { const statusLabels = { pending: 'Queued', running: 'Searching', + exact: 'Exact match', + partial: 'Partial match', found: 'Found', none: 'No match', error: 'Error',