3 Commits
v2.8.2 ... main

Author SHA1 Message Date
Ainaemaet
e88a77f224 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
2025-09-24 15:18:25 +08:00
Hayden
f3de2006ef Prepare release 2.8.3 2025-09-05 16:53:05 +08:00
Hayden
0295dd6288 fix: Validate existence of entry path after improvements previews (#205) 2025-09-05 16:51:54 +08:00
4 changed files with 53 additions and 9 deletions

View File

@@ -157,6 +157,8 @@ class ModelManager:
def get_all_files_entry(directory: str): def get_all_files_entry(directory: str):
entries: list[os.DirEntry[str]] = [] entries: list[os.DirEntry[str]] = []
if not os.path.exists(directory):
return []
with os.scandir(directory) as it: with os.scandir(directory) as it:
for entry in it: for entry in it:
if not include_hidden_files and entry.name.startswith("."): if not include_hidden_files and entry.name.startswith("."):

View File

@@ -1,7 +1,7 @@
[project] [project]
name = "comfyui-model-manager" name = "comfyui-model-manager"
description = "Manage models: browsing, download and delete." description = "Manage models: browsing, download and delete."
version = "2.8.2" version = "2.8.3"
license = { file = "LICENSE" } license = { file = "LICENSE" }
dependencies = ["markdownify"] dependencies = ["markdownify"]

View File

@@ -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 openManagerDialog = () => {
const { cardWidth, gutter, aspect, flat } = config 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) { if (firstOpenManager.value) {
models.refresh(true) models.refresh(true)
@@ -99,6 +116,14 @@ onMounted(() => {
icon: 'mdi mdi-folder-search-outline text-lg', icon: 'mdi mdi-folder-search-outline text-lg',
command: openModelScanning, command: openModelScanning,
}, },
{
key: 'toggle-layout',
icon: layoutIcon,
command: toggleLayout,
tooltip: flat.value
? t('switchToFolderView')
: t('switchToFlatView'),
},
{ {
key: 'refresh', key: 'refresh',
icon: 'pi pi-refresh', icon: 'pi pi-refresh',

View File

@@ -225,17 +225,33 @@ const list = computed(() => {
return !item.isFolder return !item.isFolder
}) })
const filterList = pureModels.filter((model) => { function buildRegex(raw: string): RegExp {
const showAllModel = currentType.value === allType 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 rawFilter = searchContent.value ?? ''
const matchSubFolder = model.subFolder.toLowerCase().includes(filter) const tokens = rawFilter.split(/\s+/).filter(Boolean)
const matchName = model.basename.toLowerCase().includes(filter) 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 let sortStrategy: (a: Model, b: Model) => number = () => 0
switch (sortOrder.value) { switch (sortOrder.value) {
@@ -262,6 +278,7 @@ const list = computed(() => {
}) })
}) })
const contentStyle = computed(() => ({ const contentStyle = computed(() => ({
gridTemplateColumns: `repeat(auto-fit, ${cardSize.value.width}px)`, gridTemplateColumns: `repeat(auto-fit, ${cardSize.value.width}px)`,
gap: `${gutter}px`, gap: `${gutter}px`,