feat: inputs/outputs filter to widget dropdown (#5894)

related https://github.com/Comfy-Org/ComfyUI_frontend/issues/5827

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-5894-feat-inputs-outputs-filter-to-widget-dropdown-2806d73d365081498d92d0576b7da6a8)
by [Unito](https://www.unito.io)

---------

Co-authored-by: bymyself <cbyrne@comfy.org>
This commit is contained in:
Rizumu Ayaka
2025-10-03 04:06:08 +08:00
committed by GitHub
parent 37fab21daf
commit 7e4c756258
3 changed files with 71 additions and 17 deletions

View File

@@ -7,6 +7,7 @@ import { t } from '@/i18n'
import { useToastStore } from '@/platform/updates/common/toastStore'
import type { ResultItemType } from '@/schemas/apiSchema'
import { api } from '@/scripts/api'
import { useQueueStore } from '@/stores/queueStore'
import type { SimplifiedWidget } from '@/types/simplifiedWidget'
import type { AssetKind } from '@/types/widgetTypes'
import {
@@ -42,6 +43,7 @@ const { localValue, onChange } = useWidgetValue({
})
const toastStore = useToastStore()
const queueStore = useQueueStore()
const transformCompatProps = useTransformCompatOverlayProps()
@@ -50,8 +52,15 @@ const combinedProps = computed(() => ({
...transformCompatProps.value
}))
const filterSelected = ref('all')
const filterOptions = ref<FilterOption[]>([
{ id: 'all', name: 'All' },
{ id: 'inputs', name: 'Inputs' },
{ id: 'outputs', name: 'Outputs' }
])
const selectedSet = ref<Set<SelectedKey>>(new Set())
const dropdownItems = computed<DropdownItem[]>(() => {
const inputItems = computed<DropdownItem[]>(() => {
const values = props.widget.options?.values || []
if (!Array.isArray(values)) {
@@ -59,12 +68,57 @@ const dropdownItems = computed<DropdownItem[]>(() => {
}
return values.map((value: string, index: number) => ({
id: index,
imageSrc: getMediaUrl(value),
id: `input-${index}`,
imageSrc: getMediaUrl(value, 'input'),
name: value,
metadata: ''
}))
})
const outputItems = computed<DropdownItem[]>(() => {
if (!['image', 'video'].includes(props.assetKind ?? '')) return []
const outputs = new Set<string>()
// Extract output images/videos from queue history
queueStore.historyTasks.forEach((task) => {
task.flatOutputs.forEach((output) => {
const isTargetType =
(props.assetKind === 'image' && output.mediaType === 'images') ||
(props.assetKind === 'video' && output.mediaType === 'video')
if (output.type === 'output' && isTargetType) {
const path = output.subfolder
? `${output.subfolder}/${output.filename}`
: output.filename
// Add [output] annotation so the preview component knows the type
const annotatedPath = `${path} [output]`
outputs.add(annotatedPath)
}
})
})
return Array.from(outputs).map((output, index) => ({
id: `output-${index}`,
imageSrc: getMediaUrl(output.replace(' [output]', ''), 'output'),
name: output,
metadata: ''
}))
})
const allItems = computed<DropdownItem[]>(() => {
return [...inputItems.value, ...outputItems.value]
})
const dropdownItems = computed<DropdownItem[]>(() => {
switch (filterSelected.value) {
case 'inputs':
return inputItems.value
case 'outputs':
return outputItems.value
case 'all':
default:
return allItems.value
}
})
const mediaPlaceholder = computed(() => {
const options = props.widget.options
@@ -197,19 +251,13 @@ async function handleFilesUpdate(files: File[]) {
}
}
function getMediaUrl(filename: string): string {
if (props.assetKind !== 'image') return ''
// TODO: This needs to be adapted based on actual ComfyUI API structure
return `/api/view?filename=${encodeURIComponent(filename)}&type=input`
function getMediaUrl(
filename: string,
type: 'input' | 'output' = 'input'
): string {
if (!['image', 'video'].includes(props.assetKind ?? '')) return ''
return `/api/view?filename=${encodeURIComponent(filename)}&type=${type}`
}
// TODO handle filter logic
const filterSelected = ref('all')
const filterOptions = ref<FilterOption[]>([
{ id: 'all', name: 'All' },
{ id: 'image', name: 'Inputs' },
{ id: 'video', name: 'Outputs' }
])
</script>
<template>

View File

@@ -89,7 +89,7 @@ watch(searchQuery, (value) => {
})
watch(
debouncedSearchQuery,
[debouncedSearchQuery, () => props.items],
(_, __, onCleanup) => {
let isCleanup = false
let cleanupFn: undefined | (() => void)

View File

@@ -1,4 +1,4 @@
import { describe, expect, it } from 'vitest'
import { describe, expect, it, vi } from 'vitest'
import WidgetButton from '@/renderer/extensions/vueNodes/widgets/components/WidgetButton.vue'
import WidgetColorPicker from '@/renderer/extensions/vueNodes/widgets/components/WidgetColorPicker.vue'
@@ -15,6 +15,12 @@ import {
shouldRenderAsVue
} from '@/renderer/extensions/vueNodes/widgets/registry/widgetRegistry'
vi.mock('@/stores/queueStore', () => ({
useQueueStore: vi.fn(() => ({
historyTasks: []
}))
}))
describe('widgetRegistry', () => {
describe('getComponent', () => {
// Test number type mappings