diff --git a/web/resolver/actions/resolve_download_methods.js b/web/resolver/actions/resolve_download_methods.js index c16ea66..b29b497 100644 --- a/web/resolver/actions/resolve_download_methods.js +++ b/web/resolver/actions/resolve_download_methods.js @@ -1968,17 +1968,31 @@ export const resolveDownloadMethods = { if (baseSelect && baseList && baseSelect.dataset.mlSearchBaseBound !== 'true') { baseSelect.dataset.mlSearchBaseBound = 'true'; - const renderBaseOptions = () => { - const options = this.getKnownBaseModelOptions(); - baseList.innerHTML = options + const renderBaseOptions = (filterText = '') => { + const filter = String(filterText || '').trim().toLowerCase(); + const normalizedFilter = this.normalizeBaseModelToken?.(filter) || filter.replace(/[^a-z0-9]+/g, ''); + const options = this.getKnownBaseModelOptions() .map(option => { const label = option.value === 'auto' ? this.getSearchBaseModelLabel('auto', missing) : option.label; + return { ...option, label }; + }) + .filter(option => { + if (!filter) return true; + const searchText = `${option.value} ${option.label}`.toLowerCase(); + const normalizedSearchText = this.normalizeBaseModelToken?.(searchText) + || searchText.replace(/[^a-z0-9]+/g, ''); + return searchText.includes(filter) + || (normalizedFilter && normalizedSearchText.includes(normalizedFilter)); + }); + baseList.innerHTML = options + .map(option => { + const label = option.label; return `
${this.escapeHtml(label)}
`; }) .join(''); - baseList.style.display = 'block'; + baseList.style.display = options.length ? 'block' : 'none'; baseList.querySelectorAll('.mr-download-target-option').forEach(optionEl => { optionEl.addEventListener('mousedown', (event) => { event.preventDefault(); @@ -1998,15 +2012,25 @@ export const resolveDownloadMethods = { }; this.enableWheelScrollChaining(baseList); - baseSelect.addEventListener('focus', () => renderBaseOptions()); - baseSelect.addEventListener('click', () => renderBaseOptions()); + baseSelect.addEventListener('focus', () => renderBaseOptions('')); + baseSelect.addEventListener('click', () => renderBaseOptions('')); + baseSelect.addEventListener('input', () => { + const value = this.getSearchBaseModelInputValue(baseSelect.value, missing); + baseSelect.dataset.value = value; + this.setSearchBaseModel(missing, value, container, { sync: false }); + renderBaseOptions(baseSelect.value); + }); baseSelect.addEventListener('keydown', (event) => { - if (event.key === 'Enter' || event.key === ' ' || event.key === 'ArrowDown') { + if (event.key === 'Enter' || event.key === 'ArrowDown') { event.preventDefault(); - renderBaseOptions(); + renderBaseOptions(baseSelect.value); } }); - baseSelect.addEventListener('blur', hideBaseList); + baseSelect.addEventListener('blur', () => { + const value = this.getDropdownValue(baseSelect); + this.setDropdownValue(baseSelect, value, this.getSearchBaseModelLabel(value, missing)); + hideBaseList(); + }); this.syncSearchSourceUi(missing, container); } diff --git a/web/resolver/search/download_target_methods.js b/web/resolver/search/download_target_methods.js index e67b6df..5f122d5 100644 --- a/web/resolver/search/download_target_methods.js +++ b/web/resolver/search/download_target_methods.js @@ -1447,7 +1447,7 @@ export const downloadTargetMethods = { html += ``; html += ``; html += `
`; - html += ``; + html += ``; html += `
`; html += `
`; html += `
`; @@ -1559,13 +1559,22 @@ export const downloadTargetMethods = { }); }; - const populateCategoryOptions = () => { + const populateCategoryOptions = (filterText = '') => { if (!categoryListEl) return; + const filter = String(filterText || '').trim().toLowerCase(); + const normalizedFilter = filter.replace(/[^a-z0-9]+/g, ''); const options = this.getDownloadCategoryOptions(this.getDropdownValue(categoryEl) || 'checkpoints') .map(category => ({ value: category, label: this.getCategoryDisplayName(category) - })); + })) + .filter(option => { + if (!filter) return true; + const searchText = `${option.value} ${option.label}`.toLowerCase(); + const normalizedSearchText = searchText.replace(/[^a-z0-9]+/g, ''); + return searchText.includes(filter) + || (normalizedFilter && normalizedSearchText.includes(normalizedFilter)); + }); renderOptions(categoryListEl, options, (value, label) => { this.setDropdownValue(categoryEl, value, label); subfolderEl.value = ''; @@ -1614,15 +1623,43 @@ export const downloadTargetMethods = { if (categoryListEl && categoryEl.dataset.mlCategoryBound !== 'true') { categoryEl.dataset.mlCategoryBound = 'true'; - categoryEl.addEventListener('focus', () => populateCategoryOptions()); - categoryEl.addEventListener('click', () => populateCategoryOptions()); + categoryEl.addEventListener('focus', () => populateCategoryOptions('')); + categoryEl.addEventListener('click', () => populateCategoryOptions('')); + categoryEl.addEventListener('input', () => { + const typed = categoryEl.value.trim(); + const category = typed ? this.normalizeDownloadCategory(typed) : ''; + const previousCategory = this.getDropdownValue(categoryEl); + categoryEl.dataset.value = category; + if (category !== previousCategory) { + subfolderEl.value = ''; + subfolderEl.dataset.baseDirectory = ''; + listEl.innerHTML = ''; + listEl.style.display = 'none'; + } + this.saveDownloadTargetSelection(missing, { + category, + subfolder: subfolderEl.value || '', + subfolderBaseDirectory: subfolderEl.dataset.baseDirectory || '', + categoryTouched: true, + subfolderTouched: Boolean(subfolderEl.value) + }); + this.syncDownloadTargetFolderContext(categoryEl, subfolderEl); + populateCategoryOptions(typed); + }); categoryEl.addEventListener('keydown', (event) => { - if (event.key === 'Enter' || event.key === ' ' || event.key === 'ArrowDown') { + if (event.key === 'Enter' || event.key === 'ArrowDown') { event.preventDefault(); - populateCategoryOptions(); + populateCategoryOptions(categoryEl.value); } }); - categoryEl.addEventListener('blur', () => hideList(categoryListEl)); + categoryEl.addEventListener('blur', () => { + const category = this.getDropdownValue(categoryEl); + const knownCategories = this.getKnownDownloadCategorySet(); + if (category && knownCategories.has(this.normalizeDownloadCategory(category))) { + this.setDropdownValue(categoryEl, category, this.getCategoryDisplayName(category)); + } + hideList(categoryListEl); + }); } subfolderEl.addEventListener('focus', () => { diff --git a/web/resolver/search/search_panel.js b/web/resolver/search/search_panel.js index d077874..9718a55 100644 --- a/web/resolver/search/search_panel.js +++ b/web/resolver/search/search_panel.js @@ -408,6 +408,25 @@ export const searchPanelMethods = { return option?.label || value || 'Auto'; }, + getSearchBaseModelInputValue(text = '', missing = {}) { + const raw = String(text || '').trim(); + if (!raw) return 'none'; + const normalized = this.normalizeBaseModelToken(raw); + for (const option of this.getKnownBaseModelOptions()) { + const label = option.value === 'auto' + ? this.getSearchBaseModelLabel('auto', missing) + : option.label; + const optionTokens = [ + option.value, + label + ].flatMap(value => [...this.getBaseModelTokenVariants(value)]); + if (optionTokens.some(token => token === normalized)) { + return option.value; + } + } + return this.resolveBaseModelAliasExact(raw) || raw; + }, + getSearchBaseModelContext(missing = {}) { const state = this.getSearchState(missing); const selected = state.selectedBaseModel || this.getDefaultSearchBaseModel(); @@ -1116,7 +1135,8 @@ export const searchPanelMethods = { this.syncSearchSourceUi(missing, container); }, - setSearchBaseModel(missing, baseModel, container) { + setSearchBaseModel(missing, baseModel, container, options = {}) { + const { sync = true } = options || {}; const state = this.getSearchState(missing); const nextBaseModel = baseModel || this.getDefaultSearchBaseModel(); const changed = (state.selectedBaseModel || this.getDefaultSearchBaseModel()) !== nextBaseModel; @@ -1143,7 +1163,11 @@ export const searchPanelMethods = { } state.selectedBaseModel = nextBaseModel; this.persistSearchStateForActiveWorkflow(); - this.syncSearchSourceUi(missing, container); + if (sync) { + this.syncSearchSourceUi(missing, container); + } else { + this.updateSearchBaseModelHelp(container, missing); + } this.refreshSearchUiForMissing?.(missing, state); }, @@ -1600,7 +1624,7 @@ export const searchPanelMethods = { html += `
`; html += ``; html += `
`; - html += ``; + html += ``; html += `
`; html += `
`; html += `
`;