mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-02-10 18:10:08 +00:00
fix: dropdown widget fetching output files
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
<script setup lang="ts">
|
||||
import { capitalize } from 'es-toolkit'
|
||||
import { computed, provide, ref, toRef, watch } from 'vue'
|
||||
import { computed, onMounted, provide, ref, toRef, watch } from 'vue'
|
||||
|
||||
import { useWidgetValue } from '@/composables/graph/useWidgetValue'
|
||||
import { useTransformCompatOverlayProps } from '@/composables/useTransformCompatOverlayProps'
|
||||
@@ -19,7 +19,7 @@ import { useAssetWidgetData } from '@/renderer/extensions/vueNodes/widgets/compo
|
||||
import type { ResultItemType } from '@/schemas/apiSchema'
|
||||
import { api } from '@/scripts/api'
|
||||
import { useAssetsStore } from '@/stores/assetsStore'
|
||||
import { useQueueStore } from '@/stores/queueStore'
|
||||
import { useOutputsStore } from '@/stores/outputsStore'
|
||||
import type { SimplifiedWidget } from '@/types/simplifiedWidget'
|
||||
import type { AssetKind } from '@/types/widgetTypes'
|
||||
import {
|
||||
@@ -55,7 +55,8 @@ const { localValue, onChange } = useWidgetValue({
|
||||
})
|
||||
|
||||
const toastStore = useToastStore()
|
||||
const queueStore = useQueueStore()
|
||||
|
||||
const outputsStore = useOutputsStore()
|
||||
|
||||
const transformCompatProps = useTransformCompatOverlayProps()
|
||||
|
||||
@@ -121,33 +122,30 @@ const inputItems = computed<DropdownItem[]>(() => {
|
||||
const outputItems = computed<DropdownItem[]>(() => {
|
||||
if (!['image', 'video'].includes(props.assetKind ?? '')) return []
|
||||
|
||||
const outputs = new Set<string>()
|
||||
const outputFiles = ((): string[] => {
|
||||
switch (props.assetKind) {
|
||||
case 'image':
|
||||
return outputsStore.outputImages
|
||||
case 'video':
|
||||
return outputsStore.outputVideos
|
||||
case 'audio':
|
||||
return outputsStore.outputAudios
|
||||
default:
|
||||
return []
|
||||
}
|
||||
})()
|
||||
|
||||
// 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 outputFiles.map((filename, index) => {
|
||||
// Add [output] annotation so the preview component knows the type
|
||||
const annotatedPath = `${filename} [output]`
|
||||
return {
|
||||
id: `output-${index}`,
|
||||
mediaSrc: getMediaUrl(filename, 'output'),
|
||||
name: annotatedPath,
|
||||
label: getDisplayLabel(annotatedPath),
|
||||
metadata: ''
|
||||
}
|
||||
})
|
||||
|
||||
return Array.from(outputs).map((output, index) => ({
|
||||
id: `output-${index}`,
|
||||
mediaSrc: getMediaUrl(output.replace(' [output]', ''), 'output'),
|
||||
name: output,
|
||||
label: getDisplayLabel(output),
|
||||
metadata: ''
|
||||
}))
|
||||
})
|
||||
|
||||
const allItems = computed<DropdownItem[]>(() => {
|
||||
@@ -337,6 +335,11 @@ function getMediaUrl(
|
||||
if (!['image', 'video'].includes(props.assetKind ?? '')) return ''
|
||||
return `/api/view?filename=${encodeURIComponent(filename)}&type=${type}`
|
||||
}
|
||||
|
||||
// Fetch output files on component mount
|
||||
onMounted(() => {
|
||||
outputsStore.fetchOutputFiles()
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
||||
@@ -67,15 +67,15 @@ const searchQuery = defineModel<string>('searchQuery')
|
||||
"
|
||||
>
|
||||
<div class="pointer-events-none absolute inset-x-3 top-0 z-10 h-5" />
|
||||
<div
|
||||
v-if="items.length === 0"
|
||||
class="absolute inset-0 flex items-center justify-center"
|
||||
>
|
||||
<i
|
||||
title="No items"
|
||||
class="icon-[lucide--circle-off] size-30 text-zinc-500/20"
|
||||
/>
|
||||
</div>
|
||||
<template v-if="items.length === 0">
|
||||
<div class="absolute inset-0 flex items-center justify-center">
|
||||
<i
|
||||
title="No items"
|
||||
class="icon-[lucide--circle-off] size-30 text-zinc-500/20"
|
||||
/>
|
||||
</div>
|
||||
<div class="min-h-50" />
|
||||
</template>
|
||||
<!-- Item -->
|
||||
<FormDropdownMenuItem
|
||||
v-for="(item, index) in items"
|
||||
|
||||
72
src/stores/outputsStore.ts
Normal file
72
src/stores/outputsStore.ts
Normal file
@@ -0,0 +1,72 @@
|
||||
import { api } from '@/scripts/api'
|
||||
import { defineStore } from 'pinia'
|
||||
import { computed, ref } from 'vue'
|
||||
|
||||
export const useOutputsStore = defineStore('outputs', () => {
|
||||
const outputs = ref<string[]>([])
|
||||
const isLoadingOutputFiles = ref(false)
|
||||
const thePromise = ref<Promise<void> | null>(null)
|
||||
|
||||
const outputImages = computed(() => {
|
||||
return outputs.value.filter((filename) => {
|
||||
const ext = filename.toLowerCase().split('.').pop() || ''
|
||||
return ['png', 'jpg', 'jpeg', 'webp', 'gif', 'bmp', 'tiff'].includes(ext)
|
||||
})
|
||||
})
|
||||
const outputVideos = computed(() => {
|
||||
return outputs.value.filter((filename) => {
|
||||
const ext = filename.toLowerCase().split('.').pop() || ''
|
||||
return ['mp4', 'webm', 'mov', 'avi', 'mkv'].includes(ext)
|
||||
})
|
||||
})
|
||||
const outputAudios = computed(() => {
|
||||
return outputs.value.filter((filename) => {
|
||||
const ext = filename.toLowerCase().split('.').pop() || ''
|
||||
return ['mp3', 'ogg', 'wav', 'flac'].includes(ext)
|
||||
})
|
||||
})
|
||||
|
||||
/**
|
||||
* Fetch output files from the backend API
|
||||
*/
|
||||
async function _fetchOutputFiles() {
|
||||
isLoadingOutputFiles.value = true
|
||||
try {
|
||||
const response = await fetch(api.internalURL('/files/output'), {
|
||||
headers: {
|
||||
'Comfy-User': api.user
|
||||
}
|
||||
})
|
||||
|
||||
if (!response.ok) {
|
||||
console.error('Failed to fetch output files:', response.statusText)
|
||||
return
|
||||
}
|
||||
|
||||
outputs.value = await response.json()
|
||||
} catch (error) {
|
||||
console.error('Error fetching output files:', error)
|
||||
} finally {
|
||||
isLoadingOutputFiles.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const fetchOutputFiles = async () => {
|
||||
if (!thePromise.value) {
|
||||
thePromise.value = _fetchOutputFiles()
|
||||
thePromise.value.finally(() => {
|
||||
thePromise.value = null
|
||||
})
|
||||
}
|
||||
return thePromise.value
|
||||
}
|
||||
|
||||
return {
|
||||
isLoadingOutputFiles,
|
||||
outputs,
|
||||
fetchOutputFiles,
|
||||
outputImages,
|
||||
outputVideos,
|
||||
outputAudios
|
||||
}
|
||||
})
|
||||
Reference in New Issue
Block a user