Skip to content

Commit d2d5505

Browse files
committed
fix: dropdown widget fetching output files
1 parent 7a0302b commit d2d5505

File tree

3 files changed

+113
-38
lines changed

3 files changed

+113
-38
lines changed

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

Lines changed: 32 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
<script setup lang="ts">
22
import { capitalize } from 'es-toolkit'
3-
import { computed, provide, ref, toRef, watch } from 'vue'
3+
import { computed, onMounted, provide, ref, toRef, watch } from 'vue'
44
55
import { useWidgetValue } from '@/composables/graph/useWidgetValue'
66
import { useTransformCompatOverlayProps } from '@/composables/useTransformCompatOverlayProps'
@@ -19,7 +19,7 @@ import { useAssetWidgetData } from '@/renderer/extensions/vueNodes/widgets/compo
1919
import type { ResultItemType } from '@/schemas/apiSchema'
2020
import { api } from '@/scripts/api'
2121
import { useAssetsStore } from '@/stores/assetsStore'
22-
import { useQueueStore } from '@/stores/queueStore'
22+
import { useOutputsStore } from '@/stores/outputsStore'
2323
import type { SimplifiedWidget } from '@/types/simplifiedWidget'
2424
import type { AssetKind } from '@/types/widgetTypes'
2525
import {
@@ -55,7 +55,8 @@ const { localValue, onChange } = useWidgetValue({
5555
})
5656
5757
const toastStore = useToastStore()
58-
const queueStore = useQueueStore()
58+
59+
const outputsStore = useOutputsStore()
5960
6061
const transformCompatProps = useTransformCompatOverlayProps()
6162
@@ -121,33 +122,30 @@ const inputItems = computed<DropdownItem[]>(() => {
121122
const outputItems = computed<DropdownItem[]>(() => {
122123
if (!['image', 'video'].includes(props.assetKind ?? '')) return []
123124
124-
const outputs = new Set<string>()
125-
126-
// Extract output images/videos from queue history
127-
queueStore.historyTasks.forEach((task) => {
128-
task.flatOutputs.forEach((output) => {
129-
const isTargetType =
130-
(props.assetKind === 'image' && output.mediaType === 'images') ||
131-
(props.assetKind === 'video' && output.mediaType === 'video')
132-
133-
if (output.type === 'output' && isTargetType) {
134-
const path = output.subfolder
135-
? `${output.subfolder}/${output.filename}`
136-
: output.filename
137-
// Add [output] annotation so the preview component knows the type
138-
const annotatedPath = `${path} [output]`
139-
outputs.add(annotatedPath)
140-
}
141-
})
125+
const outputFiles = ((): string[] => {
126+
switch (props.assetKind) {
127+
case 'image':
128+
return outputsStore.outputImages
129+
case 'video':
130+
return outputsStore.outputVideos
131+
case 'audio':
132+
return outputsStore.outputAudios
133+
default:
134+
return []
135+
}
136+
})()
137+
138+
return outputFiles.map((filename, index) => {
139+
// Add [output] annotation so the preview component knows the type
140+
const annotatedPath = `${filename} [output]`
141+
return {
142+
id: `output-${index}`,
143+
mediaSrc: getMediaUrl(filename, 'output'),
144+
name: annotatedPath,
145+
label: getDisplayLabel(annotatedPath),
146+
metadata: ''
147+
}
142148
})
143-
144-
return Array.from(outputs).map((output, index) => ({
145-
id: `output-${index}`,
146-
mediaSrc: getMediaUrl(output.replace(' [output]', ''), 'output'),
147-
name: output,
148-
label: getDisplayLabel(output),
149-
metadata: ''
150-
}))
151149
})
152150
153151
const allItems = computed<DropdownItem[]>(() => {
@@ -337,6 +335,11 @@ function getMediaUrl(
337335
if (!['image', 'video'].includes(props.assetKind ?? '')) return ''
338336
return `/api/view?filename=${encodeURIComponent(filename)}&type=${type}`
339337
}
338+
339+
// Fetch output files on component mount
340+
onMounted(() => {
341+
outputsStore.fetchOutputFiles()
342+
})
340343
</script>
341344

342345
<template>

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

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -67,15 +67,15 @@ const searchQuery = defineModel<string>('searchQuery')
6767
"
6868
>
6969
<div class="pointer-events-none absolute inset-x-3 top-0 z-10 h-5" />
70-
<div
71-
v-if="items.length === 0"
72-
class="absolute inset-0 flex items-center justify-center"
73-
>
74-
<i
75-
title="No items"
76-
class="icon-[lucide--circle-off] size-30 text-zinc-500/20"
77-
/>
78-
</div>
70+
<template v-if="items.length === 0">
71+
<div class="absolute inset-0 flex items-center justify-center">
72+
<i
73+
title="No items"
74+
class="icon-[lucide--circle-off] size-30 text-zinc-500/20"
75+
/>
76+
</div>
77+
<div class="min-h-50" />
78+
</template>
7979
<!-- Item -->
8080
<FormDropdownMenuItem
8181
v-for="(item, index) in items"

src/stores/outputsStore.ts

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
import { api } from '@/scripts/api'
2+
import { defineStore } from 'pinia'
3+
import { computed, ref } from 'vue'
4+
5+
export const useOutputsStore = defineStore('outputs', () => {
6+
const outputs = ref<string[]>([])
7+
const isLoadingOutputFiles = ref(false)
8+
const thePromise = ref<Promise<void> | null>(null)
9+
10+
const outputImages = computed(() => {
11+
return outputs.value.filter((filename) => {
12+
const ext = filename.toLowerCase().split('.').pop() || ''
13+
return ['png', 'jpg', 'jpeg', 'webp', 'gif', 'bmp', 'tiff'].includes(ext)
14+
})
15+
})
16+
const outputVideos = computed(() => {
17+
return outputs.value.filter((filename) => {
18+
const ext = filename.toLowerCase().split('.').pop() || ''
19+
return ['mp4', 'webm', 'mov', 'avi', 'mkv'].includes(ext)
20+
})
21+
})
22+
const outputAudios = computed(() => {
23+
return outputs.value.filter((filename) => {
24+
const ext = filename.toLowerCase().split('.').pop() || ''
25+
return ['mp3', 'ogg', 'wav', 'flac'].includes(ext)
26+
})
27+
})
28+
29+
/**
30+
* Fetch output files from the backend API
31+
*/
32+
async function _fetchOutputFiles() {
33+
isLoadingOutputFiles.value = true
34+
try {
35+
const response = await fetch(api.internalURL('/files/output'), {
36+
headers: {
37+
'Comfy-User': api.user
38+
}
39+
})
40+
41+
if (!response.ok) {
42+
console.error('Failed to fetch output files:', response.statusText)
43+
return
44+
}
45+
46+
outputs.value = await response.json()
47+
} catch (error) {
48+
console.error('Error fetching output files:', error)
49+
} finally {
50+
isLoadingOutputFiles.value = false
51+
}
52+
}
53+
54+
const fetchOutputFiles = async () => {
55+
if (!thePromise.value) {
56+
thePromise.value = _fetchOutputFiles()
57+
thePromise.value.finally(() => {
58+
thePromise.value = null
59+
})
60+
}
61+
return thePromise.value
62+
}
63+
64+
return {
65+
isLoadingOutputFiles,
66+
outputs,
67+
fetchOutputFiles,
68+
outputImages,
69+
outputVideos,
70+
outputAudios
71+
}
72+
})

0 commit comments

Comments
 (0)