mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-02-21 23:34:31 +00:00
feature: delete asset
This commit is contained in:
@@ -64,6 +64,7 @@
|
||||
@click="handleAssetSelect(item)"
|
||||
@zoom="handleZoomClick(item)"
|
||||
@output-count-click="enterFolderView(item)"
|
||||
@asset-deleted="refreshAssets"
|
||||
/>
|
||||
</template>
|
||||
</VirtualGrid>
|
||||
@@ -218,6 +219,7 @@ const mediaAssetsWithKey = computed(() => {
|
||||
const refreshAssets = async () => {
|
||||
const files = await fetchMediaList(activeTab.value)
|
||||
mediaAssets.value = files
|
||||
selectedAsset.value = null // Clear selection after refresh
|
||||
if (error.value) {
|
||||
console.error('Failed to refresh assets:', error.value)
|
||||
}
|
||||
|
||||
@@ -2053,6 +2053,8 @@
|
||||
"browseAssets": "Browse Assets",
|
||||
"noAssetsFound": "No assets found",
|
||||
"tryAdjustingFilters": "Try adjusting your search or filters",
|
||||
"deleteAssetTitle": "Delete this asset?",
|
||||
"deleteAssetDescription": "This asset will be permanently removed.",
|
||||
"loadingModels": "Loading {type}...",
|
||||
"connectionError": "Please check your connection and try again",
|
||||
"failedToCreateNode": "Failed to create node. Please try again or check console for details.",
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
<template>
|
||||
<IconGroup>
|
||||
<IconButton size="sm" @click="handleDelete">
|
||||
<IconButton v-if="showDeleteButton" size="sm" @click="handleDelete">
|
||||
<i class="icon-[lucide--trash-2] size-4" />
|
||||
</IconButton>
|
||||
<IconButton v-if="assetType !== 'input'" size="sm" @click="handleDownload">
|
||||
<IconButton size="sm" @click="handleDownload">
|
||||
<i class="icon-[lucide--download] size-4" />
|
||||
</IconButton>
|
||||
<MoreButton
|
||||
@@ -12,7 +12,11 @@
|
||||
@menu-closed="emit('menuStateChanged', false)"
|
||||
>
|
||||
<template #default="{ close }">
|
||||
<MediaAssetMoreMenu :close="close" @inspect="emit('inspect')" />
|
||||
<MediaAssetMoreMenu
|
||||
:close="close"
|
||||
@inspect="emit('inspect')"
|
||||
@asset-deleted="emit('asset-deleted')"
|
||||
/>
|
||||
</template>
|
||||
</MoreButton>
|
||||
</IconGroup>
|
||||
@@ -20,31 +24,63 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, inject } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
import IconButton from '@/components/button/IconButton.vue'
|
||||
import IconGroup from '@/components/button/IconGroup.vue'
|
||||
import MoreButton from '@/components/button/MoreButton.vue'
|
||||
import ConfirmationDialogContent from '@/components/dialog/content/ConfirmationDialogContent.vue'
|
||||
import { isCloud } from '@/platform/distribution/types'
|
||||
import { useDialogStore } from '@/stores/dialogStore'
|
||||
|
||||
import { useMediaAssetActions } from '../composables/useMediaAssetActions'
|
||||
import { MediaAssetKey } from '../schemas/mediaAssetSchema'
|
||||
import MediaAssetMoreMenu from './MediaAssetMoreMenu.vue'
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
const emit = defineEmits<{
|
||||
menuStateChanged: [isOpen: boolean]
|
||||
inspect: []
|
||||
'asset-deleted': []
|
||||
}>()
|
||||
|
||||
const { asset, context } = inject(MediaAssetKey)!
|
||||
const actions = useMediaAssetActions()
|
||||
const dialogStore = useDialogStore()
|
||||
|
||||
const assetType = computed(() => {
|
||||
return context?.value?.type || asset.value?.tags?.[0] || 'output'
|
||||
})
|
||||
|
||||
const showDeleteButton = computed(() => {
|
||||
return (
|
||||
assetType.value === 'output' || (assetType.value === 'input' && isCloud)
|
||||
)
|
||||
})
|
||||
|
||||
const handleDelete = () => {
|
||||
if (asset.value) {
|
||||
actions.deleteAsset(asset.value.id)
|
||||
}
|
||||
if (!asset.value?.id || !assetType.value) return
|
||||
|
||||
dialogStore.showDialog({
|
||||
key: 'delete-asset-confirmation',
|
||||
title: t('assetBrowser.deleteAssetTitle'),
|
||||
component: ConfirmationDialogContent,
|
||||
props: {
|
||||
message: t('assetBrowser.deleteAssetDescription'),
|
||||
type: 'delete',
|
||||
itemList: [asset.value.name],
|
||||
onConfirm: async () => {
|
||||
const success = await actions.deleteAsset(
|
||||
asset.value!.id,
|
||||
assetType.value
|
||||
)
|
||||
if (success) {
|
||||
emit('asset-deleted')
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const handleDownload = () => {
|
||||
|
||||
@@ -45,6 +45,7 @@
|
||||
<MediaAssetActions
|
||||
@menu-state-changed="isMenuOpen = $event"
|
||||
@inspect="handleZoomClick"
|
||||
@asset-deleted="handleAssetDelete"
|
||||
@mouseenter="handleOverlayMouseEnter"
|
||||
@mouseleave="handleOverlayMouseLeave"
|
||||
/>
|
||||
@@ -179,6 +180,7 @@ const { asset, loading, selected, showOutputCount, outputCount } = defineProps<{
|
||||
const emit = defineEmits<{
|
||||
zoom: [asset: AssetItem]
|
||||
'output-count-click': []
|
||||
'asset-deleted': []
|
||||
}>()
|
||||
|
||||
const cardContainerRef = ref<HTMLElement>()
|
||||
@@ -338,4 +340,8 @@ const handleImageLoaded = (dimensions: { width: number; height: number }) => {
|
||||
const handleOutputCountClick = () => {
|
||||
emit('output-count-click')
|
||||
}
|
||||
|
||||
const handleAssetDelete = () => {
|
||||
emit('asset-deleted')
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
</IconTextButton>
|
||||
|
||||
<IconTextButton
|
||||
v-if="showWorkflowOptions"
|
||||
type="transparent"
|
||||
class="dark-theme:text-white"
|
||||
label="Add to current workflow"
|
||||
@@ -34,7 +35,7 @@
|
||||
</template>
|
||||
</IconTextButton>
|
||||
|
||||
<MediaAssetButtonDivider />
|
||||
<MediaAssetButtonDivider v-if="showWorkflowOptions" />
|
||||
|
||||
<IconTextButton
|
||||
v-if="showWorkflowOptions"
|
||||
@@ -60,7 +61,7 @@
|
||||
</template>
|
||||
</IconTextButton>
|
||||
|
||||
<MediaAssetButtonDivider v-if="showWorkflowOptions" />
|
||||
<MediaAssetButtonDivider v-if="showWorkflowOptions && showCopyJobId" />
|
||||
|
||||
<IconTextButton
|
||||
v-if="showCopyJobId"
|
||||
@@ -74,9 +75,10 @@
|
||||
</template>
|
||||
</IconTextButton>
|
||||
|
||||
<MediaAssetButtonDivider v-if="showCopyJobId" />
|
||||
<MediaAssetButtonDivider v-if="showCopyJobId && showDeleteButton" />
|
||||
|
||||
<IconTextButton
|
||||
v-if="showDeleteButton"
|
||||
type="transparent"
|
||||
class="dark-theme:text-white"
|
||||
label="Delete"
|
||||
@@ -91,8 +93,12 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, inject } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
import IconTextButton from '@/components/button/IconTextButton.vue'
|
||||
import ConfirmationDialogContent from '@/components/dialog/content/ConfirmationDialogContent.vue'
|
||||
import { isCloud } from '@/platform/distribution/types'
|
||||
import { useDialogStore } from '@/stores/dialogStore'
|
||||
|
||||
import { useMediaAssetActions } from '../composables/useMediaAssetActions'
|
||||
import { MediaAssetKey } from '../schemas/mediaAssetSchema'
|
||||
@@ -104,17 +110,32 @@ const { close } = defineProps<{
|
||||
|
||||
const emit = defineEmits<{
|
||||
inspect: []
|
||||
'asset-deleted': []
|
||||
}>()
|
||||
|
||||
const { asset, context } = inject(MediaAssetKey)!
|
||||
const actions = useMediaAssetActions()
|
||||
const dialogStore = useDialogStore()
|
||||
const { t } = useI18n()
|
||||
|
||||
const showWorkflowOptions = computed(() => context.value.type)
|
||||
const assetType = computed(() => {
|
||||
return (asset.value as any)?.tags?.[0] || context.value?.type || 'output'
|
||||
})
|
||||
|
||||
const showWorkflowOptions = computed(() => assetType.value === 'output')
|
||||
|
||||
// Only show Copy Job ID for output assets (not for imported/input assets)
|
||||
const showCopyJobId = computed(() => {
|
||||
const assetType = (asset.value as any)?.tags?.[0] || context.value?.type
|
||||
return assetType !== 'input'
|
||||
return assetType.value !== 'input'
|
||||
})
|
||||
|
||||
// Delete button should be shown for:
|
||||
// - All output files (can be deleted via history)
|
||||
// - Input files only in cloud environment
|
||||
const showDeleteButton = computed(() => {
|
||||
return (
|
||||
assetType.value === 'output' || (assetType.value === 'input' && isCloud)
|
||||
)
|
||||
})
|
||||
|
||||
const handleInspect = () => {
|
||||
@@ -158,9 +179,29 @@ const handleCopyJobId = async () => {
|
||||
}
|
||||
|
||||
const handleDelete = () => {
|
||||
if (asset.value) {
|
||||
actions.deleteAsset(asset.value.id)
|
||||
}
|
||||
close()
|
||||
if (!asset.value?.id || !assetType.value) return
|
||||
|
||||
close() // Close the menu first
|
||||
|
||||
// Show confirmation dialog
|
||||
dialogStore.showDialog({
|
||||
key: 'delete-asset-confirmation',
|
||||
title: t('assetBrowser.deleteAssetTitle'),
|
||||
component: ConfirmationDialogContent,
|
||||
props: {
|
||||
message: t('assetBrowser.deleteAssetDescription'),
|
||||
type: 'delete',
|
||||
itemList: [asset.value.name],
|
||||
onConfirm: async () => {
|
||||
const success = await actions.deleteAsset(
|
||||
asset.value!.id,
|
||||
assetType.value as 'input' | 'output'
|
||||
)
|
||||
if (success) {
|
||||
emit('asset-deleted')
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -4,12 +4,14 @@ import { inject } from 'vue'
|
||||
|
||||
import { downloadFile } from '@/base/common/downloadUtil'
|
||||
import { t } from '@/i18n'
|
||||
import { isCloud } from '@/platform/distribution/types'
|
||||
import { api } from '@/scripts/api'
|
||||
import { extractPromptIdFromAssetId } from '@/utils/uuidUtil'
|
||||
|
||||
import type { AssetItem } from '../schemas/assetSchema'
|
||||
import type { AssetMeta } from '../schemas/mediaAssetSchema'
|
||||
import { MediaAssetKey } from '../schemas/mediaAssetSchema'
|
||||
import { assetService } from '../services/assetService'
|
||||
|
||||
export function useMediaAssetActions() {
|
||||
const toast = useToast()
|
||||
@@ -48,8 +50,64 @@ export function useMediaAssetActions() {
|
||||
}
|
||||
}
|
||||
|
||||
const deleteAsset = (assetId: string) => {
|
||||
console.log('Deleting asset:', assetId)
|
||||
const deleteAsset = async (
|
||||
assetId: string,
|
||||
assetType: 'input' | 'output'
|
||||
) => {
|
||||
try {
|
||||
if (assetType === 'output') {
|
||||
// For output files, delete from history
|
||||
const promptId = extractPromptIdFromAssetId(assetId)
|
||||
if (!promptId) {
|
||||
throw new Error('Unable to extract prompt ID from asset')
|
||||
}
|
||||
|
||||
await api.deleteItem('history', promptId)
|
||||
|
||||
toast.add({
|
||||
severity: 'success',
|
||||
summary: t('g.success'),
|
||||
detail: 'Asset deleted successfully',
|
||||
life: 2000
|
||||
})
|
||||
return true
|
||||
} else {
|
||||
// For input files, only allow deletion in cloud environment
|
||||
if (!isCloud) {
|
||||
toast.add({
|
||||
severity: 'warn',
|
||||
summary: t('g.warning'),
|
||||
detail:
|
||||
'Deleting imported files is only supported in cloud version',
|
||||
life: 3000
|
||||
})
|
||||
return false
|
||||
}
|
||||
|
||||
// In cloud environment, use the assets API to delete
|
||||
await assetService.deleteAsset(assetId)
|
||||
|
||||
toast.add({
|
||||
severity: 'success',
|
||||
summary: t('g.success'),
|
||||
detail: 'Asset deleted successfully',
|
||||
life: 2000
|
||||
})
|
||||
return true
|
||||
}
|
||||
|
||||
throw new Error('Unable to determine asset type')
|
||||
} catch (error) {
|
||||
console.error('Failed to delete asset:', error)
|
||||
toast.add({
|
||||
severity: 'error',
|
||||
summary: t('g.error'),
|
||||
detail:
|
||||
error instanceof Error ? error.message : 'Failed to delete asset',
|
||||
life: 3000
|
||||
})
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
const playAsset = (assetId: string) => {
|
||||
|
||||
@@ -213,13 +213,34 @@ function createAssetService() {
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes an asset by ID
|
||||
* Only available in cloud environment
|
||||
*
|
||||
* @param id - The asset ID (UUID)
|
||||
* @returns Promise<void>
|
||||
* @throws Error if deletion fails
|
||||
*/
|
||||
async function deleteAsset(id: string): Promise<void> {
|
||||
const res = await api.fetchApi(`${ASSETS_ENDPOINT}/${id}`, {
|
||||
method: 'DELETE'
|
||||
})
|
||||
|
||||
if (!res.ok) {
|
||||
throw new Error(
|
||||
`Unable to delete asset ${id}: Server returned ${res.status}`
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
getAssetModelFolders,
|
||||
getAssetModels,
|
||||
isAssetBrowserEligible,
|
||||
getAssetsForNodeType,
|
||||
getAssetDetails,
|
||||
getAssetsByTag
|
||||
getAssetsByTag,
|
||||
deleteAsset
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user