mirror of
https://github.com/willmiao/ComfyUI-Lora-Manager.git
synced 2026-03-24 14:42:11 -03:00
Add name pattern filtering to LoRA Pool node allowing users to filter LoRAs by filename or model name using either plain text or regex patterns. Features: - Include patterns: only show LoRAs matching at least one pattern - Exclude patterns: exclude LoRAs matching any pattern - Regex toggle: switch between substring and regex matching - Case-insensitive matching for both modes - Invalid regex automatically falls back to substring matching - Filters apply to both file_name and model_name fields Backend: - Update LoraPoolLM._default_config() with namePatterns structure - Add name pattern filtering to _apply_pool_filters() and _apply_specific_filters() - Add API parameter parsing for name_pattern_include/exclude/use_regex - Update LoraPoolConfig type with namePatterns field Frontend: - Add NamePatternsSection.vue component with pattern input UI - Update useLoraPoolState to manage pattern state and API integration - Update LoraPoolSummaryView to display NamePatternsSection - Increase LORA_POOL_WIDGET_MIN_HEIGHT to accommodate new UI Tests: - Add 7 test cases covering text/regex include, exclude, combined filtering, model name fallback, and invalid regex handling Closes #839
128 lines
4.1 KiB
TypeScript
128 lines
4.1 KiB
TypeScript
import { ref } from 'vue'
|
|
import type { BaseModelOption, TagOption, FolderTreeNode, LoraItem } from './types'
|
|
|
|
export function useLoraPoolApi() {
|
|
const isLoading = ref(false)
|
|
|
|
const fetchBaseModels = async (limit = 50): Promise<BaseModelOption[]> => {
|
|
try {
|
|
const response = await fetch(`/api/lm/loras/base-models?limit=${limit}`)
|
|
const data = await response.json()
|
|
return data.base_models || []
|
|
} catch (error) {
|
|
console.error('[LoraPoolApi] Failed to fetch base models:', error)
|
|
return []
|
|
}
|
|
}
|
|
|
|
const fetchTags = async (limit = 0): Promise<TagOption[]> => {
|
|
try {
|
|
const response = await fetch(`/api/lm/loras/top-tags?limit=${limit}`)
|
|
const data = await response.json()
|
|
return data.tags || []
|
|
} catch (error) {
|
|
console.error('[LoraPoolApi] Failed to fetch tags:', error)
|
|
return []
|
|
}
|
|
}
|
|
|
|
const fetchFolderTree = async (): Promise<FolderTreeNode[]> => {
|
|
try {
|
|
const response = await fetch('/api/lm/loras/unified-folder-tree')
|
|
const data = await response.json()
|
|
return transformFolderTree(data.tree || {})
|
|
} catch (error) {
|
|
console.error('[LoraPoolApi] Failed to fetch folder tree:', error)
|
|
return []
|
|
}
|
|
}
|
|
|
|
const transformFolderTree = (tree: Record<string, any>, parentPath = ''): FolderTreeNode[] => {
|
|
if (!tree || typeof tree !== 'object') {
|
|
return []
|
|
}
|
|
|
|
return Object.entries(tree).map(([name, children]) => {
|
|
const path = parentPath ? `${parentPath}/${name}` : name
|
|
const childNodes = transformFolderTree(children as Record<string, any>, path)
|
|
|
|
return {
|
|
key: path,
|
|
label: name,
|
|
children: childNodes.length > 0 ? childNodes : undefined
|
|
}
|
|
})
|
|
}
|
|
|
|
interface FetchLorasParams {
|
|
baseModels?: string[]
|
|
tagsInclude?: string[]
|
|
tagsExclude?: string[]
|
|
foldersInclude?: string[]
|
|
foldersExclude?: string[]
|
|
noCreditRequired?: boolean
|
|
allowSelling?: boolean
|
|
namePatternsInclude?: string[]
|
|
namePatternsExclude?: string[]
|
|
namePatternsUseRegex?: boolean
|
|
page?: number
|
|
pageSize?: number
|
|
}
|
|
|
|
const fetchLoras = async (params: FetchLorasParams): Promise<{ items: LoraItem[]; total: number }> => {
|
|
isLoading.value = true
|
|
try {
|
|
const urlParams = new URLSearchParams()
|
|
urlParams.set('page', String(params.page || 1))
|
|
urlParams.set('page_size', String(params.pageSize || 6))
|
|
|
|
params.baseModels?.forEach(bm => urlParams.append('base_model', bm))
|
|
params.tagsInclude?.forEach(tag => urlParams.append('tag_include', tag))
|
|
params.tagsExclude?.forEach(tag => urlParams.append('tag_exclude', tag))
|
|
|
|
// Folder filters
|
|
if (params.foldersInclude && params.foldersInclude.length > 0) {
|
|
params.foldersInclude.forEach(folder => urlParams.append('folder_include', folder))
|
|
urlParams.set('recursive', 'true')
|
|
}
|
|
params.foldersExclude?.forEach(folder => urlParams.append('folder_exclude', folder))
|
|
|
|
if (params.noCreditRequired !== undefined) {
|
|
urlParams.set('credit_required', String(!params.noCreditRequired))
|
|
}
|
|
|
|
if (params.allowSelling !== undefined) {
|
|
urlParams.set('allow_selling_generated_content', String(params.allowSelling))
|
|
}
|
|
|
|
// Name pattern filters
|
|
params.namePatternsInclude?.forEach(pattern => urlParams.append('name_pattern_include', pattern))
|
|
params.namePatternsExclude?.forEach(pattern => urlParams.append('name_pattern_exclude', pattern))
|
|
if (params.namePatternsUseRegex !== undefined) {
|
|
urlParams.set('name_pattern_use_regex', String(params.namePatternsUseRegex))
|
|
}
|
|
|
|
const response = await fetch(`/api/lm/loras/list?${urlParams}`)
|
|
const data = await response.json()
|
|
|
|
return {
|
|
items: data.items || [],
|
|
total: data.total || 0
|
|
}
|
|
} catch (error) {
|
|
console.error('[LoraPoolApi] Failed to fetch loras:', error)
|
|
return { items: [], total: 0 }
|
|
} finally {
|
|
isLoading.value = false
|
|
}
|
|
}
|
|
|
|
return {
|
|
isLoading,
|
|
fetchBaseModels,
|
|
fetchTags,
|
|
fetchFolderTree,
|
|
fetchLoras
|
|
}
|
|
}
|