Files
ComfyUI_frontend/src/stores/templateRankingStore.ts
Christian Byrne fbdaf5d7f3 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>
2026-01-06 19:10:40 +01:00

67 lines
1.9 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* Store for template ranking scores.
* Loads pre-computed usage scores from static JSON.
* Internal ranks come from template.searchRank in index.json.
* See docs/TEMPLATE_RANKING.md for details.
*/
import { defineStore } from 'pinia'
import { ref } from 'vue'
export const useTemplateRankingStore = defineStore('templateRanking', () => {
const largestUsageScore = ref<number>()
const normalizeUsageScore = (usage: number): number => {
return usage / (largestUsageScore.value ?? usage)
}
/**
* Compute freshness score based on template date.
* Returns 1.0 for brand new, decays to 0.1 over ~6 months.
*/
const computeFreshness = (dateStr: string | undefined): number => {
if (!dateStr) return 0.5 // Default for templates without dates
const date = new Date(dateStr)
if (isNaN(date.getTime())) return 0.5
const daysSinceAdded = (Date.now() - date.getTime()) / (1000 * 60 * 60 * 24)
return Math.max(0.1, 1.0 / (1 + daysSinceAdded / 90))
}
/**
* Compute composite score for "default" sort.
* Formula: usage × 0.5 + internal × 0.3 + freshness × 0.2
*/
const computeDefaultScore = (
dateStr: string | undefined,
searchRank: number | undefined,
usage: number = 0
): number => {
const internal = (searchRank ?? 5) / 10 // Normalize 1-10 to 0-1
const freshness = computeFreshness(dateStr)
return normalizeUsageScore(usage) * 0.5 + internal * 0.3 + freshness * 0.2
}
/**
* Compute composite score for "popular" sort.
* Formula: usage × 0.9 + freshness × 0.1
*/
const computePopularScore = (
dateStr: string | undefined,
usage: number = 0
): number => {
const freshness = computeFreshness(dateStr)
return normalizeUsageScore(usage) * 0.9 + freshness * 0.1
}
return {
largestUsageScore,
computeFreshness,
computeDefaultScore,
computePopularScore
}
})