refactor: let categories on leftsidepanel be one to one with JSON

This commit is contained in:
Johnpaul
2025-09-22 22:48:04 +01:00
parent 699d4b4320
commit e72efade04
2 changed files with 120 additions and 195 deletions

View File

@@ -2,14 +2,13 @@ import Fuse from 'fuse.js'
import { defineStore } from 'pinia' import { defineStore } from 'pinia'
import { computed, ref, shallowRef } from 'vue' import { computed, ref, shallowRef } from 'vue'
import { SMALL_MODEL_SIZE_LIMIT } from '@/constants/templateWorkflows'
import { i18n, st } from '@/i18n' import { i18n, st } from '@/i18n'
import { api } from '@/scripts/api' import { api } from '@/scripts/api'
import type { NavGroupData, NavItemData } from '@/types/navTypes' import type { NavGroupData, NavItemData } from '@/types/navTypes'
import { getCategoryIcon } from '@/utils/categoryIcons' import { getCategoryIcon } from '@/utils/categoryIcons'
import { normalizeI18nKey } from '@/utils/formatUtil' import { normalizeI18nKey } from '@/utils/formatUtil'
import { import type {
TemplateGroup, TemplateGroup,
TemplateInfo, TemplateInfo,
WorkflowTemplates WorkflowTemplates
@@ -20,9 +19,8 @@ interface EnhancedTemplate extends TemplateInfo {
sourceModule: string sourceModule: string
category?: string category?: string
categoryType?: string categoryType?: string
isAPI?: boolean categoryGroup?: string // 'GENERATION TYPE' or 'CLOSED SOURCE MODELS'
isPerformance?: boolean isEssential?: boolean
isMacCompatible?: boolean
searchableText?: string searchableText?: string
} }
@@ -33,6 +31,14 @@ export const useWorkflowTemplatesStore = defineStore(
const coreTemplates = shallowRef<WorkflowTemplates[]>([]) const coreTemplates = shallowRef<WorkflowTemplates[]>([])
const isLoaded = ref(false) const isLoaded = ref(false)
// Store filter mappings for dynamic categories
type FilterData = {
category?: string
categoryGroup?: string
}
const categoryFilters = ref(new Map<string, FilterData>())
/** /**
* Add localization fields to a template. * Add localization fields to a template.
*/ */
@@ -182,39 +188,13 @@ export const useWorkflowTemplatesStore = defineStore(
// Process core templates // Process core templates
coreTemplates.value.forEach((category) => { coreTemplates.value.forEach((category) => {
category.templates.forEach((template) => { category.templates.forEach((template) => {
const isAPI = category.title?.includes('API') || false
// Determine performance ("Small Models") based primarily on explicit size prop (<=3GB)
// Fallback to heuristic based on model name keywords for backward compatibility.
const explicitSize = template.size
const heuristicPerformance = template.models?.some(
(model) =>
model.toLowerCase().includes('turbo') ||
model.toLowerCase().includes('fast') ||
model.toLowerCase().includes('schnell') ||
model.toLowerCase().includes('fp8')
)
const isPerformance =
(typeof explicitSize === 'number' &&
explicitSize <= SMALL_MODEL_SIZE_LIMIT) ||
(!!explicitSize === false && heuristicPerformance) ||
false
const isMacCompatible =
template.models?.some(
(model) =>
model.toLowerCase().includes('fp8') ||
model.toLowerCase().includes('turbo') ||
model.toLowerCase().includes('schnell')
) || false
const enhancedTemplate: EnhancedTemplate = { const enhancedTemplate: EnhancedTemplate = {
...template, ...template,
sourceModule: category.moduleName, sourceModule: category.moduleName,
category: category.title, category: category.title,
categoryType: category.type, categoryType: category.type,
isAPI, categoryGroup: category.category,
isPerformance, isEssential: category.isEssential,
isMacCompatible,
searchableText: [ searchableText: [
template.title || template.name, template.title || template.name,
template.description || '', template.description || '',
@@ -241,9 +221,6 @@ export const useWorkflowTemplatesStore = defineStore(
sourceModule: moduleName, sourceModule: moduleName,
category: 'Extensions', category: 'Extensions',
categoryType: 'extension', categoryType: 'extension',
isAPI: false,
isPerformance: false,
isMacCompatible: false,
searchableText: `${name} ${moduleName} extension` searchableText: `${name} ${moduleName} extension`
} }
allTemplates.push(enhancedTemplate) allTemplates.push(enhancedTemplate)
@@ -273,196 +250,145 @@ export const useWorkflowTemplatesStore = defineStore(
}) })
/** /**
* Filter templates by category using Fuse.js * Filter templates by category ID using stored filter mappings
*/ */
const filterTemplatesByCategory = (categoryId: string) => { const filterTemplatesByCategory = (categoryId: string) => {
if (categoryId === 'all') { if (categoryId === 'all') {
return enhancedTemplates.value return enhancedTemplates.value
} }
switch (categoryId) { if (categoryId === 'basics') {
case 'getting-started': // Filter for templates from categories marked as essential
return enhancedTemplates.value.filter((t) => t.category === 'Basics') return enhancedTemplates.value.filter((t) => t.isEssential)
case 'generation-image':
return enhancedTemplates.value.filter(
(t) => t.categoryType === 'image' && !t.isAPI
)
case 'generation-video':
return enhancedTemplates.value.filter(
(t) => t.categoryType === 'video' && !t.isAPI
)
case 'generation-3d':
return enhancedTemplates.value.filter(
(t) => t.categoryType === '3d' && !t.isAPI
)
case 'generation-audio':
return enhancedTemplates.value.filter(
(t) => t.categoryType === 'audio' && !t.isAPI
)
case 'generation-llm':
return enhancedTemplates.value.filter(
(t) => t.tags?.includes('LLM') || t.tags?.includes('Chat')
)
case 'api-nodes':
return enhancedTemplates.value.filter((t) => t.isAPI)
case 'extensions':
return enhancedTemplates.value.filter(
(t) => t.sourceModule !== 'default'
)
// Removed lora-training filter (deprecated)
case 'performance-small':
case 'performance-mac':
return enhancedTemplates.value // deprecated filters; return all
default:
// Handle extension-specific filters
if (categoryId.startsWith('extension-')) {
const moduleName = categoryId.replace('extension-', '')
return enhancedTemplates.value.filter(
(t) => t.sourceModule === moduleName
)
}
return enhancedTemplates.value
} }
// Handle extension-specific filters
if (categoryId.startsWith('extension-')) {
const moduleName = categoryId.replace('extension-', '')
return enhancedTemplates.value.filter(
(t) => t.sourceModule === moduleName
)
}
// Look up the filter from our stored mappings
const filter = categoryFilters.value.get(categoryId)
if (!filter) {
return enhancedTemplates.value
}
// Apply the filter
return enhancedTemplates.value.filter((template) => {
if (filter.category && template.category !== filter.category) {
return false
}
if (
filter.categoryGroup &&
template.categoryGroup !== filter.categoryGroup
) {
return false
}
return true
})
} }
/** /**
* New navigation structure matching NavItemData | NavGroupData format * New navigation structure dynamically built from JSON categories
*/ */
const navGroupedTemplates = computed<(NavItemData | NavGroupData)[]>(() => { const navGroupedTemplates = computed<(NavItemData | NavGroupData)[]>(() => {
if (!isLoaded.value) return [] if (!isLoaded.value) return []
const items: (NavItemData | NavGroupData)[] = [] const items: (NavItemData | NavGroupData)[] = []
// Count templates for each category // Clear and rebuild filter mappings
const imageCounts = enhancedTemplates.value.filter( categoryFilters.value.clear()
(t) => t.categoryType === 'image' && !t.isAPI
).length
const videoCounts = enhancedTemplates.value.filter(
(t) => t.categoryType === 'video' && !t.isAPI
).length
const audioCounts = enhancedTemplates.value.filter(
(t) => t.categoryType === 'audio' && !t.isAPI
).length
const llmCounts = enhancedTemplates.value.filter(
(t) => t.tags?.includes('LLM') || t.tags?.includes('Chat')
).length
const threeDCounts = enhancedTemplates.value.filter(
(t) => t.categoryType === '3d' && !t.isAPI
).length
const apiCounts = enhancedTemplates.value.filter((t) => t.isAPI).length
const gettingStartedCounts = enhancedTemplates.value.filter(
(t) => t.category === 'Basics'
).length
const extensionCounts = enhancedTemplates.value.filter(
(t) => t.sourceModule !== 'default'
).length
// All Templates - as a simple selector // 1. All Templates - always first
items.push({ items.push({
id: 'all', id: 'all',
label: st('templateWorkflows.category.All', 'All Templates'), label: st('templateWorkflows.category.All', 'All Templates'),
icon: getCategoryIcon('all') icon: getCategoryIcon('all')
}) })
// Getting Started - as a simple selector // 2. Basics (isEssential categories) - always second if it exists
if (gettingStartedCounts > 0) { const hasEssentialCategories = coreTemplates.value.some(
(cat) => cat.isEssential && cat.templates.length > 0
)
if (hasEssentialCategories) {
items.push({ items.push({
id: 'getting-started', id: 'basics',
label: st( label: st('templateWorkflows.category.Basics', 'Basics'),
'templateWorkflows.category.GettingStarted', icon: 'icon-[lucide--graduation-cap]'
'Getting Started'
),
icon: getCategoryIcon('getting-started')
}) })
} }
// Generation Type - as a group with sub-items // 3. Group categories from JSON dynamically
if ( const categoryGroups = new Map<
imageCounts > 0 || string,
videoCounts > 0 || { title: string; items: NavItemData[] }
threeDCounts > 0 || >()
audioCounts > 0 ||
llmCounts > 0
) {
const generationTypeItems: NavItemData[] = []
if (imageCounts > 0) { // Process all categories from JSON
generationTypeItems.push({ coreTemplates.value.forEach((category) => {
id: 'generation-image', // Skip essential categories as they're handled as Basics
label: st('templateWorkflows.category.Image', 'Image'), if (category.isEssential) return
icon: getCategoryIcon('generation-image')
const categoryGroup = category.category
const categoryIcon = category.icon
if (categoryGroup) {
if (!categoryGroups.has(categoryGroup)) {
categoryGroups.set(categoryGroup, {
title: categoryGroup,
items: []
})
}
const group = categoryGroups.get(categoryGroup)!
// Generate unique ID for this category
const categoryId = `${categoryGroup.toLowerCase().replace(/\s+/g, '-')}-${category.title.toLowerCase().replace(/\s+/g, '-')}`
// Store the filter mapping
categoryFilters.value.set(categoryId, {
category: category.title,
categoryGroup: categoryGroup
})
group.items.push({
id: categoryId,
label: st(
`templateWorkflows.category.${normalizeI18nKey(category.title)}`,
category.title
),
icon: categoryIcon || getCategoryIcon(category.type || 'default')
}) })
} }
})
if (videoCounts > 0) { // Add grouped categories
generationTypeItems.push({ categoryGroups.forEach((group, groupName) => {
id: 'generation-video', if (group.items.length > 0) {
label: st('templateWorkflows.category.Video', 'Video'), items.push({
icon: getCategoryIcon('generation-video') title: st(
`templateWorkflows.category.${normalizeI18nKey(groupName)}`,
groupName
.split(' ')
.map(
(word) =>
word.charAt(0).toUpperCase() + word.slice(1).toLowerCase()
)
.join(' ')
),
items: group.items
}) })
} }
})
if (threeDCounts > 0) { // 4. Extensions - always last
generationTypeItems.push({ const extensionCounts = enhancedTemplates.value.filter(
id: 'generation-3d', (t) => t.sourceModule !== 'default'
label: st('templateWorkflows.category.3DModels', '3D Models'), ).length
icon: getCategoryIcon('generation-3d')
})
}
if (audioCounts > 0) {
generationTypeItems.push({
id: 'generation-audio',
label: st('templateWorkflows.category.Audio', 'Audio'),
icon: getCategoryIcon('generation-audio')
})
}
if (llmCounts > 0) {
generationTypeItems.push({
id: 'generation-llm',
label: st('templateWorkflows.category.LLMs', 'LLMs'),
icon: getCategoryIcon('generation-llm')
})
}
items.push({
title: st(
'templateWorkflows.category.GenerationType',
'Generation Type'
),
items: generationTypeItems
})
}
// Closed Models (API nodes) - as a group
if (apiCounts > 0) {
items.push({
title: st(
'templateWorkflows.category.ClosedSourceModels',
'Closed Source Models'
),
items: [
{
id: 'api-nodes',
label: st('templateWorkflows.category.APINodes', 'API nodes'),
icon: getCategoryIcon('api-nodes')
}
]
})
}
// Extensions - as a group with sub-items
if (extensionCounts > 0) { if (extensionCounts > 0) {
// Get unique extension modules // Get unique extension modules
const extensionModules = Array.from( const extensionModules = Array.from(
@@ -491,10 +417,6 @@ export const useWorkflowTemplatesStore = defineStore(
}) })
} }
// Removed Model Training (LoRA) group
// Removed Performance group (Small Models / Runs on Mac) per request
return items return items
}) })

View File

@@ -11,6 +11,7 @@ export interface TemplateInfo {
description: string description: string
localizedTitle?: string localizedTitle?: string
localizedDescription?: string localizedDescription?: string
isEssential?: boolean
sourceModule?: string sourceModule?: string
tags?: string[] tags?: string[]
models?: string[] models?: string[]
@@ -27,6 +28,8 @@ export interface WorkflowTemplates {
localizedTitle?: string localizedTitle?: string
category?: string category?: string
type?: string type?: string
icon?: string
isEssential?: boolean
} }
export interface TemplateGroup { export interface TemplateGroup {