diff --git a/__init__.py b/__init__.py index 25810f4..9737197 100644 --- a/__init__.py +++ b/__init__.py @@ -1220,6 +1220,12 @@ class ModelResolverExtension: stage="cancelled", message="Metadata build cancelled", percent=100, + active_models=[], + active_worker_count=0, + current_model="", + current_path="", + bytes_read=0, + total_bytes=0, result=result, **result, ) @@ -1231,6 +1237,12 @@ class ModelResolverExtension: stage="done", message="Local metadata build completed.", percent=100, + active_models=[], + active_worker_count=0, + current_model="", + current_path="", + bytes_read=0, + total_bytes=0, result=result, **result, ) @@ -1242,6 +1254,12 @@ class ModelResolverExtension: stage="error", message=str(exc) or "Metadata build failed", percent=100, + active_models=[], + active_worker_count=0, + current_model="", + current_path="", + bytes_read=0, + total_bytes=0, error=str(exc) or "Metadata build failed", ) diff --git a/core/metadata_builder.py b/core/metadata_builder.py index abd745e..d758fec 100644 --- a/core/metadata_builder.py +++ b/core/metadata_builder.py @@ -470,7 +470,7 @@ def _build_result_payload( "errors": errors[:50], "updated": updated[:200], "updated_count": len(updated), - "history": history[:500], + "history": history, "history_count": len(history), } @@ -696,6 +696,12 @@ def _build_missing_local_metadata_parallel( "current": completed, "total": total, "percent": 100, + "active_models": [], + "active_worker_count": 0, + "current_model": "", + "current_path": "", + "bytes_read": 0, + "total_bytes": 0, **result, }, ) @@ -792,7 +798,7 @@ def build_missing_local_metadata( "error_count": len(errors), "errors": errors[:50], "updated": updated[:200], - "history": history[:500], + "history": history, "history_count": len(history), } @@ -1056,7 +1062,7 @@ def build_missing_local_metadata( "error_count": len(errors), "errors": errors[:50], "updated": updated[:200], - "history": history[:500], + "history": history, "history_count": len(history), } except Exception as exc: @@ -1110,7 +1116,7 @@ def build_missing_local_metadata( "errors": errors[:50], "updated": updated[:200], "updated_count": len(updated), - "history": history[:500], + "history": history, "history_count": len(history), } _emit( @@ -1122,6 +1128,12 @@ def build_missing_local_metadata( "current": total, "total": total, "percent": 100, + "active_models": [], + "active_worker_count": 0, + "current_model": "", + "current_path": "", + "bytes_read": 0, + "total_bytes": 0, **result, }, ) diff --git a/web/css/resolver-main.css b/web/css/resolver-main.css index 1ae8f84..98c648d 100644 --- a/web/css/resolver-main.css +++ b/web/css/resolver-main.css @@ -6348,6 +6348,11 @@ a.mr-image-preview-resource-name:hover { grid-template-rows: auto auto auto auto auto minmax(0, 1fr); } +.mr-options-build-stack { + --mr-options-build-surface: #121212; + --mr-tab-active-bg: var(--mr-options-build-surface); +} + .mr-options-panel-title { margin: 0 0 6px 0; font-size: 16px; @@ -6467,8 +6472,9 @@ a.mr-image-preview-resource-name:hover { height: 36px; min-height: 36px; margin-top: 12px; + margin-left: 14px; overflow: visible; - --mr-tab-active-bg: rgba(20, 20, 24, 0.96); + --mr-tab-active-bg: var(--mr-options-build-surface); } .mr-options-build-tabs .mr-options-build-tab { @@ -6510,21 +6516,51 @@ a.mr-image-preview-resource-name:hover { .mr-options-build-tab-panel { min-width: 0; + min-height: 0; + height: 100%; } .mr-options-build-tab-panel.is-hidden { display: none; } +#mr-options-metadata-build-progress-panel, +#mr-options-metadata-build-history-panel { + min-height: 0; + padding: 10px; + overflow: hidden; + border: 1px solid rgba(255, 255, 255, 0.08); + border-radius: 0 6px 6px 6px; + background: var(--mr-tab-active-bg, var(--mr-options-build-surface)); +} + +#mr-options-metadata-build-progress-panel { + display: grid; + grid-template-rows: minmax(0, 1fr); +} + +#mr-options-metadata-build-history-panel { + display: flex; + flex-direction: column; +} + +#mr-options-metadata-build-progress-panel.is-hidden, +#mr-options-metadata-build-history-panel.is-hidden { + display: none; +} + .mr-options-build-progress { display: grid; gap: 8px; - margin-top: 10px; + grid-template-rows: auto auto auto minmax(0, 1fr); + height: 100%; + margin-top: 0; min-width: 0; - padding: 10px; - border: 1px solid rgba(255, 255, 255, 0.08); - border-radius: 6px; - background: rgba(0, 0, 0, 0.14); + min-height: 0; + padding: 0; + border: 0; + border-radius: 0; + background: transparent; } .mr-options-build-progress[hidden] { @@ -6584,7 +6620,9 @@ a.mr-image-preview-resource-name:hover { .mr-options-build-worker-list { display: grid; gap: 8px; - max-height: 260px; + align-content: start; + height: 100%; + max-height: none; min-height: 0; overflow: auto; padding-right: 2px; @@ -6656,6 +6694,36 @@ a.mr-image-preview-resource-name:hover { transition: width 0.2s ease; } +.mr-options-history-toolbar { + display: flex; + align-items: center; + flex: 0 0 auto; + gap: 8px; + min-height: 32px; + margin-bottom: 10px; +} + +.mr-options-history-filter-label { + color: var(--mr-text-secondary); + font-size: 12px; + font-weight: 700; +} + +#model-resolver-modal .mr-options-history-filter-select { + width: auto; + min-width: 160px; + min-height: 32px; + padding: 6px 10px; + border-radius: 6px; + font-size: 12px; +} + +.mr-options-history-filter-info { + margin-left: auto; + color: var(--mr-text-muted); + font-size: 12px; +} + .mr-options-history-action { display: inline-flex; align-items: center; @@ -6693,6 +6761,25 @@ a.mr-image-preview-resource-name:hover { background: rgba(255, 255, 255, 0.06); } +.mr-options-build-tab-panel .mr-options-audit-results:not([hidden]) { + display: flex; + flex: 1 1 auto; + flex-direction: column; + height: 100%; + min-height: 0; + margin-top: 0; +} + +.mr-options-build-tab-panel .mr-options-audit-results[hidden] { + display: none; +} + +.mr-options-build-tab-panel .mr-options-audit-table-wrap, +.mr-options-build-tab-panel .mr-options-audit-empty { + border-top-left-radius: 0; + background: transparent; +} + .mr-options-audit-results { margin-top: 12px; min-width: 0; diff --git a/web/resolver/views/options_methods.js b/web/resolver/views/options_methods.js index b2d1414..286d792 100644 --- a/web/resolver/views/options_methods.js +++ b/web/resolver/views/options_methods.js @@ -860,6 +860,20 @@ export const optionsMethods = {
@@ -970,6 +984,8 @@ export const optionsMethods = { const metadataBuildProgressPanel = this.contentElement.querySelector('#mr-options-metadata-build-progress-panel'); const metadataBuildHistoryPanel = this.contentElement.querySelector('#mr-options-metadata-build-history-panel'); const metadataBuildHistoryCountEl = this.contentElement.querySelector('#mr-options-metadata-build-history-count'); + const metadataBuildHistoryFilterInput = this.contentElement.querySelector('#mr-options-metadata-build-history-filter'); + const metadataBuildHistoryFilterInfo = this.contentElement.querySelector('#mr-options-metadata-build-history-filter-info'); const metadataBuildCpuEl = this.contentElement.querySelector('#mr-options-metadata-build-cpu'); const metadataBuildWorkersEl = this.contentElement.querySelector('#mr-options-metadata-build-workers'); const metadataBuildWorkerInput = this.contentElement.querySelector('#mr-options-metadata-build-worker-count'); @@ -983,6 +999,13 @@ export const optionsMethods = { const optionsMain = this.contentElement.querySelector('.mr-options-main'); const navButtons = Array.from(this.contentElement.querySelectorAll('.mr-options-nav-btn')); const optionSections = Array.from(this.contentElement.querySelectorAll('.mr-options-section')); + const metadataBuildHistoryFilterValues = ['all', 'done', 'created', 'updated', 'skipped', 'checked', 'error', 'cancelled']; + if (!metadataBuildHistoryFilterValues.includes(this.metadataBuildHistoryFilter)) { + this.metadataBuildHistoryFilter = 'all'; + } + if (metadataBuildHistoryFilterInput) { + metadataBuildHistoryFilterInput.value = this.metadataBuildHistoryFilter; + } if (this.metadataBuildPollTimer) { window.clearTimeout(this.metadataBuildPollTimer); this.metadataBuildPollTimer = null; @@ -1356,10 +1379,6 @@ export const optionsMethods = { this.metadataBuildHistoryKeys.add(key); this.metadataBuildHistory.unshift(item); }); - if (this.metadataBuildHistory.length > 1000) { - this.metadataBuildHistory = this.metadataBuildHistory.slice(0, 1000); - this.metadataBuildHistoryKeys = new Set(this.metadataBuildHistory.map(getMetadataBuildHistoryKey)); - } if (metadataBuildHistoryCountEl) { metadataBuildHistoryCountEl.textContent = this.metadataBuildHistory.length.toLocaleString(); } @@ -1467,6 +1486,12 @@ export const optionsMethods = { const renderMetadataBuildWorkers = (data = {}, activeModels = []) => { if (!metadataBuildWorkerListEl) return; + const statusValue = String(data.status || data.stage || '').toLowerCase(); + if (['done', 'cancelled', 'error'].includes(statusValue)) { + metadataBuildWorkerListEl.innerHTML = ''; + metadataBuildWorkerListEl.hidden = true; + return; + } const workerItems = activeModels.length ? activeModels : (data.current_model ? [{ @@ -1525,7 +1550,8 @@ export const optionsMethods = { const current = Number(data.current || 0); const total = Number(data.total || data.total_models || 0); - const activeModels = Array.isArray(data.active_models) ? data.active_models : []; + const terminalStatus = ['done', 'cancelled', 'error'].includes(String(data.status || stage || '').toLowerCase()); + const activeModels = !terminalStatus && Array.isArray(data.active_models) ? data.active_models : []; const currentModel = data.current_model || data.filename || ''; const suffix = total > 0 ? ` (${current.toLocaleString()} / ${total.toLocaleString()})` : ''; if (metadataBuildCurrentModelEl) { @@ -1557,13 +1583,64 @@ export const optionsMethods = { renderMetadataBuildWorkers(data, activeModels); }; - const renderMetadataBuildResults = (data = null) => { + const metadataBuildHistoryFilterLabels = { + all: 'All', + done: 'Done', + created: 'Created', + updated: 'Updated', + skipped: 'Skipped', + checked: 'Checked', + error: 'Errors', + cancelled: 'Cancelled' + }; + const metadataBuildDoneActions = new Set(['created', 'updated', 'skipped', 'checked']); + const getMetadataBuildHistoryAction = (item = {}) => String(item.action || 'checked').toLowerCase(); + const getMetadataBuildHistoryFilter = () => { + const value = metadataBuildHistoryFilterInput?.value || this.metadataBuildHistoryFilter || 'all'; + return metadataBuildHistoryFilterValues.includes(value) ? value : 'all'; + }; + const metadataBuildHistoryMatchesFilter = (item, filter) => { + const action = getMetadataBuildHistoryAction(item); + if (filter === 'all') return true; + if (filter === 'done') return metadataBuildDoneActions.has(action); + return action === filter; + }; + const getMetadataBuildHistoryFilterCounts = (items = []) => { + const counts = Object.fromEntries(metadataBuildHistoryFilterValues.map(value => [value, 0])); + items.forEach((item) => { + const action = getMetadataBuildHistoryAction(item); + counts.all += 1; + if (metadataBuildDoneActions.has(action)) counts.done += 1; + if (Object.prototype.hasOwnProperty.call(counts, action)) counts[action] += 1; + }); + return counts; + }; + const updateMetadataBuildHistoryFilterUi = (allItems = [], filteredItems = [], filter = 'all') => { + const counts = getMetadataBuildHistoryFilterCounts(allItems); + if (metadataBuildHistoryFilterInput) { + Array.from(metadataBuildHistoryFilterInput.options).forEach((option) => { + const label = metadataBuildHistoryFilterLabels[option.value] || option.value; + option.textContent = `${label} (${Number(counts[option.value] || 0).toLocaleString()})`; + }); + metadataBuildHistoryFilterInput.value = filter; + } + if (metadataBuildHistoryFilterInfo) { + metadataBuildHistoryFilterInfo.textContent = `${filteredItems.length.toLocaleString()} shown of ${allItems.length.toLocaleString()}`; + } + }; + + const renderMetadataBuildResults = (data = undefined) => { if (!metadataBuildResults) return; - const source = data?.result && typeof data.result === 'object' ? data.result : data; + if (data !== undefined) { + this.metadataBuildLastResultsData = data; + } + const renderData = data === undefined ? this.metadataBuildLastResultsData : data; + const source = renderData?.result && typeof renderData.result === 'object' ? renderData.result : renderData; const historyItems = Array.isArray(this.metadataBuildHistory) ? this.metadataBuildHistory : []; if (!source && !historyItems.length) { + updateMetadataBuildHistoryFilterUi([], [], getMetadataBuildHistoryFilter()); metadataBuildResults.hidden = true; metadataBuildResults.innerHTML = ''; return; @@ -1574,7 +1651,11 @@ export const optionsMethods = { : (source && Array.isArray(source.updated) ? source.updated : []); const displayItems = historyItems.length ? historyItems : fallbackItems; const errors = Array.isArray(source?.errors) ? source.errors : []; - const visibleItems = displayItems.slice(0, 500); + const activeHistoryFilter = getMetadataBuildHistoryFilter(); + this.metadataBuildHistoryFilter = activeHistoryFilter; + const filteredItems = displayItems.filter(item => metadataBuildHistoryMatchesFilter(item, activeHistoryFilter)); + updateMetadataBuildHistoryFilterUi(displayItems, filteredItems, activeHistoryFilter); + const visibleItems = filteredItems; let html = ''; if (visibleItems.length) { @@ -1654,9 +1735,9 @@ export const optionsMethods = { `; - if (displayItems.length > visibleItems.length) { - html += ``; - } + } else if (displayItems.length) { + const label = metadataBuildHistoryFilterLabels[activeHistoryFilter] || activeHistoryFilter; + html += ``; } else if (source?.cancelled) { html += ''; } else { @@ -2896,6 +2977,13 @@ export const optionsMethods = { }); }); + if (metadataBuildHistoryFilterInput) { + metadataBuildHistoryFilterInput.addEventListener('change', () => { + this.metadataBuildHistoryFilter = getMetadataBuildHistoryFilter(); + renderMetadataBuildResults(); + }); + } + if (metadataBuildWorkerInput) { metadataBuildWorkerInput.addEventListener('change', () => { metadataBuildWorkerInput.dataset.userEdited = 'true';