mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-01-26 19:09:52 +00:00
refactor: use media assets in fix: dropdown widget fetching output files (#6809)
┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-6809-refactor-use-media-assets-in-fix-dropdown-widget-fetching-output-files-2b26d73d365081139a7af77a79693010) by [Unito](https://www.unito.io)
This commit is contained in:
@@ -1,10 +1,11 @@
|
||||
<script setup lang="ts">
|
||||
import { capitalize } from 'es-toolkit'
|
||||
import { computed, onMounted, provide, ref, toRef, watch } from 'vue'
|
||||
import { computed, provide, ref, toRef, watch } from 'vue'
|
||||
|
||||
import { useWidgetValue } from '@/composables/graph/useWidgetValue'
|
||||
import { useTransformCompatOverlayProps } from '@/composables/useTransformCompatOverlayProps'
|
||||
import { t } from '@/i18n'
|
||||
import { useMediaAssets } from '@/platform/assets/composables/media/useMediaAssets'
|
||||
import { useToastStore } from '@/platform/updates/common/toastStore'
|
||||
import FormDropdown from '@/renderer/extensions/vueNodes/widgets/components/form/dropdown/FormDropdown.vue'
|
||||
import { AssetKindKey } from '@/renderer/extensions/vueNodes/widgets/components/form/dropdown/types'
|
||||
@@ -19,9 +20,9 @@ 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 { useOutputsStore } from '@/stores/outputsStore'
|
||||
import type { SimplifiedWidget } from '@/types/simplifiedWidget'
|
||||
import type { AssetKind } from '@/types/widgetTypes'
|
||||
import { getMediaTypeFromFilename } from '@/utils/formatUtil'
|
||||
import {
|
||||
PANEL_EXCLUDED_PROPS,
|
||||
filterWidgetProps
|
||||
@@ -56,7 +57,7 @@ const { localValue, onChange } = useWidgetValue({
|
||||
|
||||
const toastStore = useToastStore()
|
||||
|
||||
const outputsStore = useOutputsStore()
|
||||
const outputMediaAssets = useMediaAssets('output')
|
||||
|
||||
const transformCompatProps = useTransformCompatOverlayProps()
|
||||
|
||||
@@ -120,27 +121,20 @@ const inputItems = computed<DropdownItem[]>(() => {
|
||||
}))
|
||||
})
|
||||
const outputItems = computed<DropdownItem[]>(() => {
|
||||
if (!['image', 'video'].includes(props.assetKind ?? '')) return []
|
||||
if (!['image', 'video', 'audio'].includes(props.assetKind ?? '')) return []
|
||||
|
||||
const outputFiles = ((): string[] => {
|
||||
switch (props.assetKind) {
|
||||
case 'image':
|
||||
return outputsStore.outputImages
|
||||
case 'video':
|
||||
return outputsStore.outputVideos
|
||||
case 'audio':
|
||||
return outputsStore.outputAudios
|
||||
default:
|
||||
return []
|
||||
}
|
||||
})()
|
||||
// Filter assets by media type using getMediaTypeFromFilename
|
||||
const outputFiles = outputMediaAssets.media.value.filter((asset) => {
|
||||
const mediaType = getMediaTypeFromFilename(asset.name)
|
||||
return toAssertType(mediaType) === props.assetKind
|
||||
})
|
||||
|
||||
return outputFiles.map((filename, index) => {
|
||||
return outputFiles.map((asset, index) => {
|
||||
// Add [output] annotation so the preview component knows the type
|
||||
const annotatedPath = `${filename} [output]`
|
||||
const annotatedPath = `${asset.name} [output]`
|
||||
return {
|
||||
id: `output-${index}`,
|
||||
mediaSrc: getMediaUrl(filename, 'output'),
|
||||
mediaSrc: asset.preview_url || getMediaUrl(asset.name, 'output'),
|
||||
name: annotatedPath,
|
||||
label: getDisplayLabel(annotatedPath),
|
||||
metadata: ''
|
||||
@@ -148,6 +142,21 @@ const outputItems = computed<DropdownItem[]>(() => {
|
||||
})
|
||||
})
|
||||
|
||||
function toAssertType(
|
||||
mediaType: ReturnType<typeof getMediaTypeFromFilename>
|
||||
): AssetKind {
|
||||
switch (mediaType) {
|
||||
case 'image':
|
||||
case 'video':
|
||||
case 'audio':
|
||||
return mediaType
|
||||
case '3D':
|
||||
return 'model'
|
||||
default:
|
||||
return 'unknown'
|
||||
}
|
||||
}
|
||||
|
||||
const allItems = computed<DropdownItem[]>(() => {
|
||||
if (props.isAssetMode && assetData) {
|
||||
return assetData.dropdownItems.value
|
||||
@@ -336,10 +345,11 @@ function getMediaUrl(
|
||||
return `/api/view?filename=${encodeURIComponent(filename)}&type=${type}`
|
||||
}
|
||||
|
||||
// Fetch output files on component mount
|
||||
onMounted(() => {
|
||||
outputsStore.fetchOutputFiles()
|
||||
})
|
||||
function handleIsOpenUpdate(isOpen: boolean) {
|
||||
if (isOpen && !outputMediaAssets.loading) {
|
||||
outputMediaAssets.refresh()
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -358,6 +368,7 @@ onMounted(() => {
|
||||
class="w-full"
|
||||
@update:selected="updateSelectedItems"
|
||||
@update:files="handleFilesUpdate"
|
||||
@update:is-open="handleIsOpenUpdate"
|
||||
/>
|
||||
</WidgetLayoutField>
|
||||
</template>
|
||||
|
||||
@@ -55,6 +55,7 @@ const props = withDefaults(defineProps<Props>(), {
|
||||
searcher: defaultSearcher
|
||||
})
|
||||
|
||||
const isOpen = defineModel<boolean>('isOpen', { default: false })
|
||||
const selected = defineModel<Set<SelectedKey>>('selected', {
|
||||
default: new Set()
|
||||
})
|
||||
@@ -75,7 +76,6 @@ const isQuerying = ref(false)
|
||||
const toastStore = useToastStore()
|
||||
const popoverRef = ref<InstanceType<typeof Popover>>()
|
||||
const triggerRef = useTemplateRef('triggerRef')
|
||||
const isOpen = ref(false)
|
||||
|
||||
const maxSelectable = computed(() => {
|
||||
if (props.multiple === true) return Infinity
|
||||
|
||||
@@ -1,72 +0,0 @@
|
||||
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