mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-04-28 02:02:08 +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>
|
</template>
|
||||||
</MultiSelect>
|
</MultiSelect>
|
||||||
|
|
||||||
<!-- License Filter -->
|
<!-- Runs On Filter -->
|
||||||
<MultiSelect
|
<MultiSelect
|
||||||
v-model="selectedLicenseObjects"
|
v-model="selectedRunsOnObjects"
|
||||||
:label="licenseFilterLabel"
|
:label="runsOnFilterLabel"
|
||||||
:options="licenseOptions"
|
:options="runsOnOptions"
|
||||||
:show-search-box="true"
|
:show-search-box="true"
|
||||||
:show-selected-count="true"
|
:show-selected-count="true"
|
||||||
:show-clear-button="true"
|
:show-clear-button="true"
|
||||||
>
|
>
|
||||||
<template #icon>
|
<template #icon>
|
||||||
<i class="icon-[lucide--file-text]" />
|
<i class="icon-[lucide--server]" />
|
||||||
</template>
|
</template>
|
||||||
</MultiSelect>
|
</MultiSelect>
|
||||||
</div>
|
</div>
|
||||||
@@ -528,12 +528,12 @@ const {
|
|||||||
searchQuery,
|
searchQuery,
|
||||||
selectedModels,
|
selectedModels,
|
||||||
selectedUseCases,
|
selectedUseCases,
|
||||||
selectedLicenses,
|
selectedRunsOn,
|
||||||
sortBy,
|
sortBy,
|
||||||
filteredTemplates,
|
filteredTemplates,
|
||||||
availableModels,
|
availableModels,
|
||||||
availableUseCases,
|
availableUseCases,
|
||||||
availableLicenses,
|
availableRunsOn,
|
||||||
filteredCount,
|
filteredCount,
|
||||||
totalCount,
|
totalCount,
|
||||||
resetFilters
|
resetFilters
|
||||||
@@ -561,15 +561,15 @@ const selectedUseCaseObjects = computed({
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
const selectedLicenseObjects = computed({
|
const selectedRunsOnObjects = computed({
|
||||||
get() {
|
get() {
|
||||||
return selectedLicenses.value.map((license) => ({
|
return selectedRunsOn.value.map((runsOn) => ({
|
||||||
name: license,
|
name: runsOn,
|
||||||
value: license
|
value: runsOn
|
||||||
}))
|
}))
|
||||||
},
|
},
|
||||||
set(value: { name: string; value: string }[]) {
|
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(() =>
|
const runsOnOptions = computed(() =>
|
||||||
availableLicenses.value.map((license) => ({
|
availableRunsOn.value.map((runsOn) => ({
|
||||||
name: license,
|
name: runsOn,
|
||||||
value: license
|
value: runsOn
|
||||||
}))
|
}))
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -634,14 +634,14 @@ const useCaseFilterLabel = computed(() => {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
const licenseFilterLabel = computed(() => {
|
const runsOnFilterLabel = computed(() => {
|
||||||
if (selectedLicenseObjects.value.length === 0) {
|
if (selectedRunsOnObjects.value.length === 0) {
|
||||||
return t('templateWorkflows.licenseFilter', 'License')
|
return t('templateWorkflows.runsOnFilter', 'Runs On')
|
||||||
} else if (selectedLicenseObjects.value.length === 1) {
|
} else if (selectedRunsOnObjects.value.length === 1) {
|
||||||
return selectedLicenseObjects.value[0].name
|
return selectedRunsOnObjects.value[0].name
|
||||||
} else {
|
} else {
|
||||||
return t('templateWorkflows.licensesSelected', {
|
return t('templateWorkflows.runsOnSelected', {
|
||||||
count: selectedLicenseObjects.value.length
|
count: selectedRunsOnObjects.value.length
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -708,7 +708,7 @@ watch(
|
|||||||
sortBy,
|
sortBy,
|
||||||
selectedModels,
|
selectedModels,
|
||||||
selectedUseCases,
|
selectedUseCases,
|
||||||
selectedLicenses
|
selectedRunsOn
|
||||||
],
|
],
|
||||||
() => {
|
() => {
|
||||||
resetPagination()
|
resetPagination()
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ export function useTemplateFiltering(
|
|||||||
const searchQuery = ref('')
|
const searchQuery = ref('')
|
||||||
const selectedModels = ref<string[]>([])
|
const selectedModels = ref<string[]>([])
|
||||||
const selectedUseCases = ref<string[]>([])
|
const selectedUseCases = ref<string[]>([])
|
||||||
const selectedLicenses = ref<string[]>([])
|
const selectedRunsOn = ref<string[]>([])
|
||||||
const sortBy = ref<
|
const sortBy = ref<
|
||||||
| 'default'
|
| 'default'
|
||||||
| 'alphabetical'
|
| 'alphabetical'
|
||||||
@@ -63,8 +63,8 @@ export function useTemplateFiltering(
|
|||||||
return Array.from(tagSet).sort()
|
return Array.from(tagSet).sort()
|
||||||
})
|
})
|
||||||
|
|
||||||
const availableLicenses = computed(() => {
|
const availableRunsOn = computed(() => {
|
||||||
return ['Open Source', 'Closed Source (API Nodes)']
|
return ['ComfyUI', 'External or Remote API']
|
||||||
})
|
})
|
||||||
|
|
||||||
const debouncedSearchQuery = refDebounced(searchQuery, 50)
|
const debouncedSearchQuery = refDebounced(searchQuery, 50)
|
||||||
@@ -108,21 +108,21 @@ export function useTemplateFiltering(
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
const filteredByLicenses = computed(() => {
|
const filteredByRunsOn = computed(() => {
|
||||||
if (selectedLicenses.value.length === 0) {
|
if (selectedRunsOn.value.length === 0) {
|
||||||
return filteredByUseCases.value
|
return filteredByUseCases.value
|
||||||
}
|
}
|
||||||
|
|
||||||
return filteredByUseCases.value.filter((template) => {
|
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 =
|
const isApiTemplate =
|
||||||
template.tags?.includes('API') ||
|
template.tags?.includes('API') ||
|
||||||
template.name?.toLowerCase().includes('api_')
|
template.name?.toLowerCase().includes('api_')
|
||||||
|
|
||||||
return selectedLicenses.value.some((selectedLicense) => {
|
return selectedRunsOn.value.some((selectedRunsOn) => {
|
||||||
if (selectedLicense === 'Closed Source (API Nodes)') {
|
if (selectedRunsOn === 'External or Remote API') {
|
||||||
return isApiTemplate
|
return isApiTemplate
|
||||||
} else if (selectedLicense === 'Open Source') {
|
} else if (selectedRunsOn === 'ComfyUI') {
|
||||||
return !isApiTemplate
|
return !isApiTemplate
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
@@ -142,7 +142,7 @@ export function useTemplateFiltering(
|
|||||||
}
|
}
|
||||||
|
|
||||||
const sortedTemplates = computed(() => {
|
const sortedTemplates = computed(() => {
|
||||||
const templates = [...filteredByLicenses.value]
|
const templates = [...filteredByRunsOn.value]
|
||||||
|
|
||||||
switch (sortBy.value) {
|
switch (sortBy.value) {
|
||||||
case 'alphabetical':
|
case 'alphabetical':
|
||||||
@@ -195,7 +195,7 @@ export function useTemplateFiltering(
|
|||||||
searchQuery.value = ''
|
searchQuery.value = ''
|
||||||
selectedModels.value = []
|
selectedModels.value = []
|
||||||
selectedUseCases.value = []
|
selectedUseCases.value = []
|
||||||
selectedLicenses.value = []
|
selectedRunsOn.value = []
|
||||||
sortBy.value = 'default'
|
sortBy.value = 'default'
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -207,8 +207,8 @@ export function useTemplateFiltering(
|
|||||||
selectedUseCases.value = selectedUseCases.value.filter((t) => t !== tag)
|
selectedUseCases.value = selectedUseCases.value.filter((t) => t !== tag)
|
||||||
}
|
}
|
||||||
|
|
||||||
const removeLicenseFilter = (license: string) => {
|
const removeRunsOnFilter = (runsOn: string) => {
|
||||||
selectedLicenses.value = selectedLicenses.value.filter((l) => l !== license)
|
selectedRunsOn.value = selectedRunsOn.value.filter((r) => r !== runsOn)
|
||||||
}
|
}
|
||||||
|
|
||||||
const filteredCount = computed(() => filteredTemplates.value.length)
|
const filteredCount = computed(() => filteredTemplates.value.length)
|
||||||
@@ -220,7 +220,7 @@ export function useTemplateFiltering(
|
|||||||
search_query: searchQuery.value || undefined,
|
search_query: searchQuery.value || undefined,
|
||||||
selected_models: selectedModels.value,
|
selected_models: selectedModels.value,
|
||||||
selected_use_cases: selectedUseCases.value,
|
selected_use_cases: selectedUseCases.value,
|
||||||
selected_licenses: selectedLicenses.value,
|
selected_runs_on: selectedRunsOn.value,
|
||||||
sort_by: sortBy.value,
|
sort_by: sortBy.value,
|
||||||
filtered_count: filteredCount.value,
|
filtered_count: filteredCount.value,
|
||||||
total_count: totalCount.value
|
total_count: totalCount.value
|
||||||
@@ -229,14 +229,14 @@ export function useTemplateFiltering(
|
|||||||
|
|
||||||
// Watch for filter changes and track them
|
// Watch for filter changes and track them
|
||||||
watch(
|
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)
|
// Only track if at least one filter is active (to avoid tracking initial state)
|
||||||
const hasActiveFilters =
|
const hasActiveFilters =
|
||||||
searchQuery.value.trim() !== '' ||
|
searchQuery.value.trim() !== '' ||
|
||||||
selectedModels.value.length > 0 ||
|
selectedModels.value.length > 0 ||
|
||||||
selectedUseCases.value.length > 0 ||
|
selectedUseCases.value.length > 0 ||
|
||||||
selectedLicenses.value.length > 0 ||
|
selectedRunsOn.value.length > 0 ||
|
||||||
sortBy.value !== 'default'
|
sortBy.value !== 'default'
|
||||||
|
|
||||||
if (hasActiveFilters) {
|
if (hasActiveFilters) {
|
||||||
@@ -251,14 +251,14 @@ export function useTemplateFiltering(
|
|||||||
searchQuery,
|
searchQuery,
|
||||||
selectedModels,
|
selectedModels,
|
||||||
selectedUseCases,
|
selectedUseCases,
|
||||||
selectedLicenses,
|
selectedRunsOn,
|
||||||
sortBy,
|
sortBy,
|
||||||
|
|
||||||
// Computed
|
// Computed
|
||||||
filteredTemplates,
|
filteredTemplates,
|
||||||
availableModels,
|
availableModels,
|
||||||
availableUseCases,
|
availableUseCases,
|
||||||
availableLicenses,
|
availableRunsOn,
|
||||||
filteredCount,
|
filteredCount,
|
||||||
totalCount,
|
totalCount,
|
||||||
|
|
||||||
@@ -266,6 +266,6 @@ export function useTemplateFiltering(
|
|||||||
resetFilters,
|
resetFilters,
|
||||||
removeModelFilter,
|
removeModelFilter,
|
||||||
removeUseCaseFilter,
|
removeUseCaseFilter,
|
||||||
removeLicenseFilter
|
removeRunsOnFilter
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -766,7 +766,7 @@
|
|||||||
"modelFilter": "Model Filter",
|
"modelFilter": "Model Filter",
|
||||||
"modelsSelected": "{count} Models",
|
"modelsSelected": "{count} Models",
|
||||||
"useCasesSelected": "{count} Use Cases",
|
"useCasesSelected": "{count} Use Cases",
|
||||||
"licensesSelected": "{count} Licenses",
|
"runsOnSelected": "{count} Runs On",
|
||||||
"resultsCount": "Showing {count} of {total} templates",
|
"resultsCount": "Showing {count} of {total} templates",
|
||||||
"sort": {
|
"sort": {
|
||||||
"recommended": "Recommended",
|
"recommended": "Recommended",
|
||||||
|
|||||||
@@ -190,7 +190,7 @@ export interface TemplateFilterMetadata {
|
|||||||
search_query?: string
|
search_query?: string
|
||||||
selected_models: string[]
|
selected_models: string[]
|
||||||
selected_use_cases: string[]
|
selected_use_cases: string[]
|
||||||
selected_licenses: string[]
|
selected_runs_on: string[]
|
||||||
sort_by:
|
sort_by:
|
||||||
| 'default'
|
| 'default'
|
||||||
| 'alphabetical'
|
| 'alphabetical'
|
||||||
|
|||||||
@@ -101,11 +101,11 @@ describe('useTemplateFiltering', () => {
|
|||||||
searchQuery,
|
searchQuery,
|
||||||
selectedModels,
|
selectedModels,
|
||||||
selectedUseCases,
|
selectedUseCases,
|
||||||
selectedLicenses,
|
selectedRunsOn,
|
||||||
filteredTemplates,
|
filteredTemplates,
|
||||||
availableModels,
|
availableModels,
|
||||||
availableUseCases,
|
availableUseCases,
|
||||||
availableLicenses,
|
availableRunsOn,
|
||||||
filteredCount,
|
filteredCount,
|
||||||
totalCount,
|
totalCount,
|
||||||
removeUseCaseFilter,
|
removeUseCaseFilter,
|
||||||
@@ -120,10 +120,7 @@ describe('useTemplateFiltering', () => {
|
|||||||
'Portrait',
|
'Portrait',
|
||||||
'Video'
|
'Video'
|
||||||
])
|
])
|
||||||
expect(availableLicenses.value).toEqual([
|
expect(availableRunsOn.value).toEqual(['ComfyUI', 'External or Remote API'])
|
||||||
'Open Source',
|
|
||||||
'Closed Source (API Nodes)'
|
|
||||||
])
|
|
||||||
|
|
||||||
searchQuery.value = 'enterprise'
|
searchQuery.value = 'enterprise'
|
||||||
await nextTick()
|
await nextTick()
|
||||||
@@ -133,7 +130,7 @@ describe('useTemplateFiltering', () => {
|
|||||||
'api-template'
|
'api-template'
|
||||||
])
|
])
|
||||||
|
|
||||||
selectedLicenses.value = ['Closed Source (API Nodes)']
|
selectedRunsOn.value = ['External or Remote API']
|
||||||
await nextTick()
|
await nextTick()
|
||||||
expect(filteredTemplates.value.map((template) => template.name)).toEqual([
|
expect(filteredTemplates.value.map((template) => template.name)).toEqual([
|
||||||
'api-template'
|
'api-template'
|
||||||
|
|||||||
Reference in New Issue
Block a user