mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-01-26 19:09:52 +00:00
[feat] Rename license filter to 'Runs On' filter in template selector (#6543)
## Summary Renamed the templates license filter to better reflect its actual purpose - showing where a template executes (locally in ComfyUI vs external/remote API). The current "License" filter has been causing confusion with model licensing terms (e.g., Apache vs flux-dev licensing). This PR clarifies the filter's purpose by renaming it to "Runs On" and updating the options to be more descriptive of inference location. <img width="196" height="230" alt="image" src="https://github.com/user-attachments/assets/8cbea263-f399-4945-82c1-357ec185f5a7" /> <img width="861" height="597" alt="image" src="https://github.com/user-attachments/assets/af116876-d7a5-49c5-b791-1fda637ff3a3" /> ## Changes - **Filter name**: "License" → "Runs On" - **Filter options**: - "Open Source" → "ComfyUI" - "Closed Source (API Nodes)" → "External or Remote API" - **Icon**: Changed from `file-text` to `server` for better visual representation - **Variable naming**: Updated all related variables, types, and tests to use `runsOn` naming convention - **Telemetry**: Updated metadata to track `selected_runs_on` instead of `selected_licenses` ## Why "Runs On"? - **Clear intent**: Users want to know if a template runs locally or requires an API call - **Avoids confusion**: Separates the concept from model licensing terms - **Inclusive wording**: "Remote" is included alongside "API" to help users who may not be familiar with API terminology - **Cloud-agnostic**: "Runs On" works whether the app itself is running locally or in the cloud ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-6543-feat-Rename-license-filter-to-Runs-On-filter-in-template-selector-29f6d73d3650811f935bc1f3fce7d7ad) by [Unito](https://www.unito.io)
This commit is contained in:
@@ -68,17 +68,17 @@
|
||||
</template>
|
||||
</MultiSelect>
|
||||
|
||||
<!-- License Filter -->
|
||||
<!-- Runs On Filter -->
|
||||
<MultiSelect
|
||||
v-model="selectedLicenseObjects"
|
||||
:label="licenseFilterLabel"
|
||||
:options="licenseOptions"
|
||||
v-model="selectedRunsOnObjects"
|
||||
:label="runsOnFilterLabel"
|
||||
:options="runsOnOptions"
|
||||
:show-search-box="true"
|
||||
:show-selected-count="true"
|
||||
:show-clear-button="true"
|
||||
>
|
||||
<template #icon>
|
||||
<i class="icon-[lucide--file-text]" />
|
||||
<i class="icon-[lucide--server]" />
|
||||
</template>
|
||||
</MultiSelect>
|
||||
</div>
|
||||
@@ -528,12 +528,12 @@ const {
|
||||
searchQuery,
|
||||
selectedModels,
|
||||
selectedUseCases,
|
||||
selectedLicenses,
|
||||
selectedRunsOn,
|
||||
sortBy,
|
||||
filteredTemplates,
|
||||
availableModels,
|
||||
availableUseCases,
|
||||
availableLicenses,
|
||||
availableRunsOn,
|
||||
filteredCount,
|
||||
totalCount,
|
||||
resetFilters
|
||||
@@ -561,15 +561,15 @@ const selectedUseCaseObjects = computed({
|
||||
}
|
||||
})
|
||||
|
||||
const selectedLicenseObjects = computed({
|
||||
const selectedRunsOnObjects = computed({
|
||||
get() {
|
||||
return selectedLicenses.value.map((license) => ({
|
||||
name: license,
|
||||
value: license
|
||||
return selectedRunsOn.value.map((runsOn) => ({
|
||||
name: runsOn,
|
||||
value: runsOn
|
||||
}))
|
||||
},
|
||||
set(value: { name: string; value: string }[]) {
|
||||
selectedLicenses.value = value.map((item) => item.value)
|
||||
selectedRunsOn.value = value.map((item) => item.value)
|
||||
}
|
||||
})
|
||||
|
||||
@@ -602,10 +602,10 @@ const useCaseOptions = computed(() =>
|
||||
}))
|
||||
)
|
||||
|
||||
const licenseOptions = computed(() =>
|
||||
availableLicenses.value.map((license) => ({
|
||||
name: license,
|
||||
value: license
|
||||
const runsOnOptions = computed(() =>
|
||||
availableRunsOn.value.map((runsOn) => ({
|
||||
name: runsOn,
|
||||
value: runsOn
|
||||
}))
|
||||
)
|
||||
|
||||
@@ -634,14 +634,14 @@ const useCaseFilterLabel = computed(() => {
|
||||
}
|
||||
})
|
||||
|
||||
const licenseFilterLabel = computed(() => {
|
||||
if (selectedLicenseObjects.value.length === 0) {
|
||||
return t('templateWorkflows.licenseFilter', 'License')
|
||||
} else if (selectedLicenseObjects.value.length === 1) {
|
||||
return selectedLicenseObjects.value[0].name
|
||||
const runsOnFilterLabel = computed(() => {
|
||||
if (selectedRunsOnObjects.value.length === 0) {
|
||||
return t('templateWorkflows.runsOnFilter', 'Runs On')
|
||||
} else if (selectedRunsOnObjects.value.length === 1) {
|
||||
return selectedRunsOnObjects.value[0].name
|
||||
} else {
|
||||
return t('templateWorkflows.licensesSelected', {
|
||||
count: selectedLicenseObjects.value.length
|
||||
return t('templateWorkflows.runsOnSelected', {
|
||||
count: selectedRunsOnObjects.value.length
|
||||
})
|
||||
}
|
||||
})
|
||||
@@ -708,7 +708,7 @@ watch(
|
||||
sortBy,
|
||||
selectedModels,
|
||||
selectedUseCases,
|
||||
selectedLicenses
|
||||
selectedRunsOn
|
||||
],
|
||||
() => {
|
||||
resetPagination()
|
||||
|
||||
@@ -13,7 +13,7 @@ export function useTemplateFiltering(
|
||||
const searchQuery = ref('')
|
||||
const selectedModels = ref<string[]>([])
|
||||
const selectedUseCases = ref<string[]>([])
|
||||
const selectedLicenses = ref<string[]>([])
|
||||
const selectedRunsOn = ref<string[]>([])
|
||||
const sortBy = ref<
|
||||
| 'default'
|
||||
| 'alphabetical'
|
||||
@@ -63,8 +63,8 @@ export function useTemplateFiltering(
|
||||
return Array.from(tagSet).sort()
|
||||
})
|
||||
|
||||
const availableLicenses = computed(() => {
|
||||
return ['Open Source', 'Closed Source (API Nodes)']
|
||||
const availableRunsOn = computed(() => {
|
||||
return ['ComfyUI', 'External or Remote API']
|
||||
})
|
||||
|
||||
const debouncedSearchQuery = refDebounced(searchQuery, 50)
|
||||
@@ -108,21 +108,21 @@ export function useTemplateFiltering(
|
||||
})
|
||||
})
|
||||
|
||||
const filteredByLicenses = computed(() => {
|
||||
if (selectedLicenses.value.length === 0) {
|
||||
const filteredByRunsOn = computed(() => {
|
||||
if (selectedRunsOn.value.length === 0) {
|
||||
return filteredByUseCases.value
|
||||
}
|
||||
|
||||
return filteredByUseCases.value.filter((template) => {
|
||||
// Check if template has API in its tags or name (indicating it's a closed source API node)
|
||||
// Check if template has API in its tags or name (indicating it runs on external/remote API)
|
||||
const isApiTemplate =
|
||||
template.tags?.includes('API') ||
|
||||
template.name?.toLowerCase().includes('api_')
|
||||
|
||||
return selectedLicenses.value.some((selectedLicense) => {
|
||||
if (selectedLicense === 'Closed Source (API Nodes)') {
|
||||
return selectedRunsOn.value.some((selectedRunsOn) => {
|
||||
if (selectedRunsOn === 'External or Remote API') {
|
||||
return isApiTemplate
|
||||
} else if (selectedLicense === 'Open Source') {
|
||||
} else if (selectedRunsOn === 'ComfyUI') {
|
||||
return !isApiTemplate
|
||||
}
|
||||
return false
|
||||
@@ -142,7 +142,7 @@ export function useTemplateFiltering(
|
||||
}
|
||||
|
||||
const sortedTemplates = computed(() => {
|
||||
const templates = [...filteredByLicenses.value]
|
||||
const templates = [...filteredByRunsOn.value]
|
||||
|
||||
switch (sortBy.value) {
|
||||
case 'alphabetical':
|
||||
@@ -195,7 +195,7 @@ export function useTemplateFiltering(
|
||||
searchQuery.value = ''
|
||||
selectedModels.value = []
|
||||
selectedUseCases.value = []
|
||||
selectedLicenses.value = []
|
||||
selectedRunsOn.value = []
|
||||
sortBy.value = 'default'
|
||||
}
|
||||
|
||||
@@ -207,8 +207,8 @@ export function useTemplateFiltering(
|
||||
selectedUseCases.value = selectedUseCases.value.filter((t) => t !== tag)
|
||||
}
|
||||
|
||||
const removeLicenseFilter = (license: string) => {
|
||||
selectedLicenses.value = selectedLicenses.value.filter((l) => l !== license)
|
||||
const removeRunsOnFilter = (runsOn: string) => {
|
||||
selectedRunsOn.value = selectedRunsOn.value.filter((r) => r !== runsOn)
|
||||
}
|
||||
|
||||
const filteredCount = computed(() => filteredTemplates.value.length)
|
||||
@@ -220,7 +220,7 @@ export function useTemplateFiltering(
|
||||
search_query: searchQuery.value || undefined,
|
||||
selected_models: selectedModels.value,
|
||||
selected_use_cases: selectedUseCases.value,
|
||||
selected_licenses: selectedLicenses.value,
|
||||
selected_runs_on: selectedRunsOn.value,
|
||||
sort_by: sortBy.value,
|
||||
filtered_count: filteredCount.value,
|
||||
total_count: totalCount.value
|
||||
@@ -229,14 +229,14 @@ export function useTemplateFiltering(
|
||||
|
||||
// Watch for filter changes and track them
|
||||
watch(
|
||||
[searchQuery, selectedModels, selectedUseCases, selectedLicenses, sortBy],
|
||||
[searchQuery, selectedModels, selectedUseCases, selectedRunsOn, sortBy],
|
||||
() => {
|
||||
// Only track if at least one filter is active (to avoid tracking initial state)
|
||||
const hasActiveFilters =
|
||||
searchQuery.value.trim() !== '' ||
|
||||
selectedModels.value.length > 0 ||
|
||||
selectedUseCases.value.length > 0 ||
|
||||
selectedLicenses.value.length > 0 ||
|
||||
selectedRunsOn.value.length > 0 ||
|
||||
sortBy.value !== 'default'
|
||||
|
||||
if (hasActiveFilters) {
|
||||
@@ -251,14 +251,14 @@ export function useTemplateFiltering(
|
||||
searchQuery,
|
||||
selectedModels,
|
||||
selectedUseCases,
|
||||
selectedLicenses,
|
||||
selectedRunsOn,
|
||||
sortBy,
|
||||
|
||||
// Computed
|
||||
filteredTemplates,
|
||||
availableModels,
|
||||
availableUseCases,
|
||||
availableLicenses,
|
||||
availableRunsOn,
|
||||
filteredCount,
|
||||
totalCount,
|
||||
|
||||
@@ -266,6 +266,6 @@ export function useTemplateFiltering(
|
||||
resetFilters,
|
||||
removeModelFilter,
|
||||
removeUseCaseFilter,
|
||||
removeLicenseFilter
|
||||
removeRunsOnFilter
|
||||
}
|
||||
}
|
||||
|
||||
@@ -766,7 +766,7 @@
|
||||
"modelFilter": "Model Filter",
|
||||
"modelsSelected": "{count} Models",
|
||||
"useCasesSelected": "{count} Use Cases",
|
||||
"licensesSelected": "{count} Licenses",
|
||||
"runsOnSelected": "{count} Runs On",
|
||||
"resultsCount": "Showing {count} of {total} templates",
|
||||
"sort": {
|
||||
"recommended": "Recommended",
|
||||
|
||||
@@ -190,7 +190,7 @@ export interface TemplateFilterMetadata {
|
||||
search_query?: string
|
||||
selected_models: string[]
|
||||
selected_use_cases: string[]
|
||||
selected_licenses: string[]
|
||||
selected_runs_on: string[]
|
||||
sort_by:
|
||||
| 'default'
|
||||
| 'alphabetical'
|
||||
|
||||
@@ -101,11 +101,11 @@ describe('useTemplateFiltering', () => {
|
||||
searchQuery,
|
||||
selectedModels,
|
||||
selectedUseCases,
|
||||
selectedLicenses,
|
||||
selectedRunsOn,
|
||||
filteredTemplates,
|
||||
availableModels,
|
||||
availableUseCases,
|
||||
availableLicenses,
|
||||
availableRunsOn,
|
||||
filteredCount,
|
||||
totalCount,
|
||||
removeUseCaseFilter,
|
||||
@@ -120,10 +120,7 @@ describe('useTemplateFiltering', () => {
|
||||
'Portrait',
|
||||
'Video'
|
||||
])
|
||||
expect(availableLicenses.value).toEqual([
|
||||
'Open Source',
|
||||
'Closed Source (API Nodes)'
|
||||
])
|
||||
expect(availableRunsOn.value).toEqual(['ComfyUI', 'External or Remote API'])
|
||||
|
||||
searchQuery.value = 'enterprise'
|
||||
await nextTick()
|
||||
@@ -133,7 +130,7 @@ describe('useTemplateFiltering', () => {
|
||||
'api-template'
|
||||
])
|
||||
|
||||
selectedLicenses.value = ['Closed Source (API Nodes)']
|
||||
selectedRunsOn.value = ['External or Remote API']
|
||||
await nextTick()
|
||||
expect(filteredTemplates.value.map((template) => template.name)).toEqual([
|
||||
'api-template'
|
||||
|
||||
Reference in New Issue
Block a user