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:
Rizumu Ayaka
2025-11-22 07:10:58 +09:00
committed by GitHub
parent d2d5505c76
commit 705947dfd7
3 changed files with 35 additions and 96 deletions

View File

@@ -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>

View File

@@ -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

View File

@@ -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
}
})