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 `
`;
@@ -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 += `
`;