Skip to content

Commit 7e4c756

Browse files
feat: inputs/outputs filter to widget dropdown (#5894)
related #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 <[email protected]>
1 parent 37fab21 commit 7e4c756

File tree

3 files changed

+71
-17
lines changed

3 files changed

+71
-17
lines changed

src/renderer/extensions/vueNodes/widgets/components/WidgetSelectDropdown.vue

Lines changed: 63 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import { t } from '@/i18n'
77
import { useToastStore } from '@/platform/updates/common/toastStore'
88
import type { ResultItemType } from '@/schemas/apiSchema'
99
import { api } from '@/scripts/api'
10+
import { useQueueStore } from '@/stores/queueStore'
1011
import type { SimplifiedWidget } from '@/types/simplifiedWidget'
1112
import type { AssetKind } from '@/types/widgetTypes'
1213
import {
@@ -42,6 +43,7 @@ const { localValue, onChange } = useWidgetValue({
4243
})
4344
4445
const toastStore = useToastStore()
46+
const queueStore = useQueueStore()
4547
4648
const transformCompatProps = useTransformCompatOverlayProps()
4749
@@ -50,21 +52,73 @@ const combinedProps = computed(() => ({
5052
...transformCompatProps.value
5153
}))
5254
55+
const filterSelected = ref('all')
56+
const filterOptions = ref<FilterOption[]>([
57+
{ id: 'all', name: 'All' },
58+
{ id: 'inputs', name: 'Inputs' },
59+
{ id: 'outputs', name: 'Outputs' }
60+
])
61+
5362
const selectedSet = ref<Set<SelectedKey>>(new Set())
54-
const dropdownItems = computed<DropdownItem[]>(() => {
63+
const inputItems = computed<DropdownItem[]>(() => {
5564
const values = props.widget.options?.values || []
5665
5766
if (!Array.isArray(values)) {
5867
return []
5968
}
6069
6170
return values.map((value: string, index: number) => ({
62-
id: index,
63-
imageSrc: getMediaUrl(value),
71+
id: `input-${index}`,
72+
imageSrc: getMediaUrl(value, 'input'),
6473
name: value,
6574
metadata: ''
6675
}))
6776
})
77+
const outputItems = computed<DropdownItem[]>(() => {
78+
if (!['image', 'video'].includes(props.assetKind ?? '')) return []
79+
80+
const outputs = new Set<string>()
81+
82+
// Extract output images/videos from queue history
83+
queueStore.historyTasks.forEach((task) => {
84+
task.flatOutputs.forEach((output) => {
85+
const isTargetType =
86+
(props.assetKind === 'image' && output.mediaType === 'images') ||
87+
(props.assetKind === 'video' && output.mediaType === 'video')
88+
89+
if (output.type === 'output' && isTargetType) {
90+
const path = output.subfolder
91+
? `${output.subfolder}/${output.filename}`
92+
: output.filename
93+
// Add [output] annotation so the preview component knows the type
94+
const annotatedPath = `${path} [output]`
95+
outputs.add(annotatedPath)
96+
}
97+
})
98+
})
99+
100+
return Array.from(outputs).map((output, index) => ({
101+
id: `output-${index}`,
102+
imageSrc: getMediaUrl(output.replace(' [output]', ''), 'output'),
103+
name: output,
104+
metadata: ''
105+
}))
106+
})
107+
108+
const allItems = computed<DropdownItem[]>(() => {
109+
return [...inputItems.value, ...outputItems.value]
110+
})
111+
const dropdownItems = computed<DropdownItem[]>(() => {
112+
switch (filterSelected.value) {
113+
case 'inputs':
114+
return inputItems.value
115+
case 'outputs':
116+
return outputItems.value
117+
case 'all':
118+
default:
119+
return allItems.value
120+
}
121+
})
68122
69123
const mediaPlaceholder = computed(() => {
70124
const options = props.widget.options
@@ -197,19 +251,13 @@ async function handleFilesUpdate(files: File[]) {
197251
}
198252
}
199253
200-
function getMediaUrl(filename: string): string {
201-
if (props.assetKind !== 'image') return ''
202-
// TODO: This needs to be adapted based on actual ComfyUI API structure
203-
return `/api/view?filename=${encodeURIComponent(filename)}&type=input`
254+
function getMediaUrl(
255+
filename: string,
256+
type: 'input' | 'output' = 'input'
257+
): string {
258+
if (!['image', 'video'].includes(props.assetKind ?? '')) return ''
259+
return `/api/view?filename=${encodeURIComponent(filename)}&type=${type}`
204260
}
205-
206-
// TODO handle filter logic
207-
const filterSelected = ref('all')
208-
const filterOptions = ref<FilterOption[]>([
209-
{ id: 'all', name: 'All' },
210-
{ id: 'image', name: 'Inputs' },
211-
{ id: 'video', name: 'Outputs' }
212-
])
213261
</script>
214262

215263
<template>

src/renderer/extensions/vueNodes/widgets/components/form/dropdown/FormDropdown.vue

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,7 @@ watch(searchQuery, (value) => {
8989
})
9090
9191
watch(
92-
debouncedSearchQuery,
92+
[debouncedSearchQuery, () => props.items],
9393
(_, __, onCleanup) => {
9494
let isCleanup = false
9595
let cleanupFn: undefined | (() => void)

tests-ui/tests/renderer/extensions/vueNodes/widgets/composables/useWidgetRenderer.test.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { describe, expect, it } from 'vitest'
1+
import { describe, expect, it, vi } from 'vitest'
22

33
import WidgetButton from '@/renderer/extensions/vueNodes/widgets/components/WidgetButton.vue'
44
import WidgetColorPicker from '@/renderer/extensions/vueNodes/widgets/components/WidgetColorPicker.vue'
@@ -15,6 +15,12 @@ import {
1515
shouldRenderAsVue
1616
} from '@/renderer/extensions/vueNodes/widgets/registry/widgetRegistry'
1717

18+
vi.mock('@/stores/queueStore', () => ({
19+
useQueueStore: vi.fn(() => ({
20+
historyTasks: []
21+
}))
22+
}))
23+
1824
describe('widgetRegistry', () => {
1925
describe('getComponent', () => {
2026
// Test number type mappings

0 commit comments

Comments
 (0)