feat: New Template Library (#7062)

## Summary

Implement the new design for template library

## Changes

- What
  - New sort option: `Popular` and  `Recommended`
  - New category: `Popular`, leverage the `Popular` sorting
  - Support add category stick to top of the side bar 
- Support template customized visible in different platform by
`includeOnDistributions` field

### How to make `Popular` and `Recommended` work

Add usage-based ordering to workflow templates with position bias
correction, manual ranking (searchRank), and freshness boost.

New sort modes:
- "Recommended" (default): usage × 0.5 + searchRank × 0.3 + freshness ×
0.2
- "Popular": usage × 0.9 + freshness × 0.1

## Screenshots (if applicable)

New default ordering:

<img width="1812" height="1852" alt="Selection_2485"
src="https://github.com/user-attachments/assets/8f4ed6e9-9cf4-43a8-8796-022dcf4c277e"
/>

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-7062-feat-usage-based-template-ordering-2bb6d73d365081f1ac65f8ad55fe8ce6)
by [Unito](https://www.unito.io)

Popular category:

<img width="281" height="283" alt="image"
src="https://github.com/user-attachments/assets/fd54fcb8-6caa-4982-a6b6-1f70ca4b31e3"
/>

---------

Co-authored-by: Yourz <crazilou@vip.qq.com>
Co-authored-by: GitHub Action <action@github.com>
This commit is contained in:
Christian Byrne
2026-01-06 10:10:40 -08:00
committed by GitHub
parent a7d0825a14
commit fbdaf5d7f3
14 changed files with 476 additions and 26 deletions

View File

@@ -6,7 +6,7 @@ import { i18n, st } from '@/i18n'
import { isCloud } from '@/platform/distribution/types'
import { api } from '@/scripts/api'
import type { NavGroupData, NavItemData } from '@/types/navTypes'
import { getCategoryIcon } from '@/utils/categoryIcons'
import { generateCategoryId, getCategoryIcon } from '@/utils/categoryUtil'
import { normalizeI18nKey } from '@/utils/formatUtil'
import type {
@@ -276,9 +276,18 @@ export const useWorkflowTemplatesStore = defineStore(
return enhancedTemplates.value
}
if (categoryId === 'basics') {
if (categoryId.startsWith('basics-')) {
// Filter for templates from categories marked as essential
return enhancedTemplates.value.filter((t) => t.isEssential)
return enhancedTemplates.value.filter(
(t) =>
t.isEssential &&
t.category?.toLowerCase().replace(/\s+/g, '-') ===
categoryId.replace('basics-', '')
)
}
if (categoryId === 'popular') {
return enhancedTemplates.value
}
if (categoryId === 'partner-nodes') {
@@ -333,20 +342,34 @@ export const useWorkflowTemplatesStore = defineStore(
icon: getCategoryIcon('all')
})
// 2. Basics (isEssential categories) - always second if it exists
const essentialCat = coreTemplates.value.find(
// 1.5. Popular categories
items.push({
id: 'popular',
label: st('templateWorkflows.category.Popular', 'Popular'),
icon: 'icon-[lucide--flame]'
})
// 2. Basics (isEssential categories) - always beneath All Templates if they exist
const essentialCats = coreTemplates.value.filter(
(cat) => cat.isEssential && cat.templates.length > 0
)
if (essentialCat) {
const categoryTitle = essentialCat.title ?? 'Getting Started'
items.push({
id: 'basics',
label: st(
`templateWorkflows.category.${normalizeI18nKey(categoryTitle)}`,
categoryTitle
),
icon: 'icon-[lucide--graduation-cap]'
if (essentialCats.length > 0) {
essentialCats.forEach((essentialCat) => {
const categoryIcon = essentialCat.icon
const categoryTitle = essentialCat.title ?? 'Getting Started'
const categoryId = generateCategoryId('basics', essentialCat.title)
items.push({
id: categoryId,
label: st(
`templateWorkflows.category.${normalizeI18nKey(categoryTitle)}`,
categoryTitle
),
icon:
categoryIcon ||
getCategoryIcon(essentialCat.type || 'getting-started')
})
})
}
@@ -375,7 +398,7 @@ export const useWorkflowTemplatesStore = defineStore(
const group = categoryGroups.get(categoryGroup)!
// Generate unique ID for this category
const categoryId = `${categoryGroup.toLowerCase().replace(/\s+/g, '-')}-${category.title.toLowerCase().replace(/\s+/g, '-')}`
const categoryId = generateCategoryId(categoryGroup, category.title)
// Store the filter mapping
categoryFilters.value.set(categoryId, {

View File

@@ -32,6 +32,29 @@ export interface TemplateInfo {
* Templates with this field will be hidden on local installations temporarily.
*/
requiresCustomNodes?: string[]
/**
* Manual ranking boost/demotion for "Recommended" sort. Scale 1-10, default 5.
* Higher values promote the template, lower values demote it.
*/
searchRank?: number
/**
* Usage score based on real world usage statistics.
* Used for popular templates sort and for "Recommended" sort boost.
*/
usage?: number
/**
* Manage template's visibility across different distributions by specifying which distributions it should be included on.
* If not specified, the template will be included on all distributions.
*/
includeOnDistributions?: TemplateIncludeOnDistributionEnum[]
}
export enum TemplateIncludeOnDistributionEnum {
Cloud = 'cloud',
Local = 'local',
Desktop = 'desktop',
Mac = 'mac',
Windows = 'windows'
}
export interface WorkflowTemplates {