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 { computed, ref, shallowRef } from 'vue'
import { SMALL_MODEL_SIZE_LIMIT } from '@/constants/templateWorkflows'
import { i18n, st } from '@/i18n'
import { api } from '@/scripts/api'
import type { NavGroupData, NavItemData } from '@/types/navTypes'
import { getCategoryIcon } from '@/utils/categoryIcons'
import { normalizeI18nKey } from '@/utils/formatUtil'
import {
import type {
TemplateGroup,
TemplateInfo,
WorkflowTemplates
@@ -20,9 +19,8 @@ interface EnhancedTemplate extends TemplateInfo {
sourceModule: string
category?: string
categoryType?: string
isAPI?: boolean
isPerformance?: boolean
isMacCompatible?: boolean
categoryGroup?: string // 'GENERATION TYPE' or 'CLOSED SOURCE MODELS'
isEssential?: boolean
searchableText?: string
}
@@ -33,6 +31,14 @@ export const useWorkflowTemplatesStore = defineStore(
const coreTemplates = shallowRef<WorkflowTemplates[]>([])
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.
*/
@@ -182,39 +188,13 @@ export const useWorkflowTemplatesStore = defineStore(
// Process core templates
coreTemplates.value.forEach((category) => {
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 = {
...template,
sourceModule: category.moduleName,
category: category.title,
categoryType: category.type,
isAPI,
isPerformance,
isMacCompatible,
categoryGroup: category.category,
isEssential: category.isEssential,
searchableText: [
template.title || template.name,
template.description || '',
@@ -241,9 +221,6 @@ export const useWorkflowTemplatesStore = defineStore(
sourceModule: moduleName,
category: 'Extensions',
categoryType: 'extension',
isAPI: false,
isPerformance: false,
isMacCompatible: false,
searchableText: `${name} ${moduleName} extension`
}
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) => {
if (categoryId === 'all') {
return enhancedTemplates.value
}
switch (categoryId) {
case 'getting-started':
return enhancedTemplates.value.filter((t) => t.category === 'Basics')
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
if (categoryId === 'basics') {
// Filter for templates from categories marked as essential
return enhancedTemplates.value.filter((t) => t.isEssential)
}
// 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)[]>(() => {
if (!isLoaded.value) return []
const items: (NavItemData | NavGroupData)[] = []
// Count templates for each category
const imageCounts = enhancedTemplates.value.filter(
(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
// Clear and rebuild filter mappings
categoryFilters.value.clear()
// All Templates - as a simple selector
// 1. All Templates - always first
items.push({
id: 'all',
label: st('templateWorkflows.category.All', 'All Templates'),
icon: getCategoryIcon('all')
})
// Getting Started - as a simple selector
if (gettingStartedCounts > 0) {
// 2. Basics (isEssential categories) - always second if it exists
const hasEssentialCategories = coreTemplates.value.some(
(cat) => cat.isEssential && cat.templates.length > 0
)
if (hasEssentialCategories) {
items.push({
id: 'getting-started',
label: st(
'templateWorkflows.category.GettingStarted',
'Getting Started'
),
icon: getCategoryIcon('getting-started')
id: 'basics',
label: st('templateWorkflows.category.Basics', 'Basics'),
icon: 'icon-[lucide--graduation-cap]'
})
}
// Generation Type - as a group with sub-items
if (
imageCounts > 0 ||
videoCounts > 0 ||
threeDCounts > 0 ||
audioCounts > 0 ||
llmCounts > 0
) {
const generationTypeItems: NavItemData[] = []
// 3. Group categories from JSON dynamically
const categoryGroups = new Map<
string,
{ title: string; items: NavItemData[] }
>()
if (imageCounts > 0) {
generationTypeItems.push({
id: 'generation-image',
label: st('templateWorkflows.category.Image', 'Image'),
icon: getCategoryIcon('generation-image')
// Process all categories from JSON
coreTemplates.value.forEach((category) => {
// Skip essential categories as they're handled as Basics
if (category.isEssential) return
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) {
generationTypeItems.push({
id: 'generation-video',
label: st('templateWorkflows.category.Video', 'Video'),
icon: getCategoryIcon('generation-video')
// Add grouped categories
categoryGroups.forEach((group, groupName) => {
if (group.items.length > 0) {
items.push({
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) {
generationTypeItems.push({
id: 'generation-3d',
label: st('templateWorkflows.category.3DModels', '3D Models'),
icon: getCategoryIcon('generation-3d')
})
}
// 4. Extensions - always last
const extensionCounts = enhancedTemplates.value.filter(
(t) => t.sourceModule !== 'default'
).length
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) {
// Get unique extension modules
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
})

View File

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