From e88a77f224b588c63ed289dfbb04ba6545cc7700 Mon Sep 17 00:00:00 2001 From: Ainaemaet Date: Wed, 24 Sep 2025 01:18:25 -0600 Subject: [PATCH] feat(search): add multi-token regex and wildcard support (#211) * feat(search): add multi-token regex and wildcard support * feat(ui): add layout toggle button in Model Manager header --- src/App.vue | 25 ++++++++++++++++++++++++ src/components/DialogManager.vue | 33 ++++++++++++++++++++++++-------- 2 files changed, 50 insertions(+), 8 deletions(-) diff --git a/src/App.vue b/src/App.vue index 6904d01..469381a 100644 --- a/src/App.vue +++ b/src/App.vue @@ -80,8 +80,25 @@ onMounted(() => { }) } + const toggleLayout = () => { + // flip the flat setting + const newValue = !config.flat.value + config.flat.value = newValue + + // persist so it survives reloads + app.ui?.settings.setSettingValue('ModelManager.UI.Flat', newValue) + + // close the current dialog (because it is keepAlive) + dialog.closeAll() + + // reopen with the new layout + openManagerDialog() + } + const openManagerDialog = () => { const { cardWidth, gutter, aspect, flat } = config + // choose icon depending on current layout + const layoutIcon = flat.value ? 'pi pi-folder-open' : 'pi pi-th-large' if (firstOpenManager.value) { models.refresh(true) @@ -99,6 +116,14 @@ onMounted(() => { icon: 'mdi mdi-folder-search-outline text-lg', command: openModelScanning, }, + { + key: 'toggle-layout', + icon: layoutIcon, + command: toggleLayout, + tooltip: flat.value + ? t('switchToFolderView') + : t('switchToFlatView'), + }, { key: 'refresh', icon: 'pi pi-refresh', diff --git a/src/components/DialogManager.vue b/src/components/DialogManager.vue index 976d824..4b3e10c 100644 --- a/src/components/DialogManager.vue +++ b/src/components/DialogManager.vue @@ -225,17 +225,33 @@ const list = computed(() => { return !item.isFolder }) - const filterList = pureModels.filter((model) => { - const showAllModel = currentType.value === allType +function buildRegex(raw: string): RegExp { + try { + // Escape regex specials, then restore * wildcards as .* + const escaped = raw + .replace(/[.*+?^${}()|[\]\\]/g, '\\$&') + .replace(/\\\*/g, '.*') + return new RegExp(escaped, 'i') // case-insensitive + } catch (e) { + return new RegExp(raw, 'i') + } +} - const matchType = showAllModel || model.type === currentType.value +const filterList = pureModels.filter((model) => { + const showAllModel = currentType.value === allType + const matchType = showAllModel || model.type === currentType.value - const filter = searchContent.value?.toLowerCase() ?? '' - const matchSubFolder = model.subFolder.toLowerCase().includes(filter) - const matchName = model.basename.toLowerCase().includes(filter) + const rawFilter = searchContent.value ?? '' + const tokens = rawFilter.split(/\s+/).filter(Boolean) + const regexes = tokens.map(buildRegex) - return matchType && (matchSubFolder || matchName) - }) + // Require every token to match either the folder or the name + const matchesAll = regexes.every((re) => + re.test(model.subFolder) || re.test(model.basename) + ) + + return matchType && matchesAll +}) let sortStrategy: (a: Model, b: Model) => number = () => 0 switch (sortOrder.value) { @@ -262,6 +278,7 @@ const list = computed(() => { }) }) + const contentStyle = computed(() => ({ gridTemplateColumns: `repeat(auto-fit, ${cardSize.value.width}px)`, gap: `${gutter}px`,