mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-03-07 22:20:03 +00:00
## Summary Implements 4 missing media asset workflow features and creates shared utilities to eliminate code duplication. ## Implemented Features ### 1. Copy Job ID ✅ - Properly extracts promptId using `getOutputAssetMetadata` - Uses `useCopyToClipboard` composable ### 2. Add to Current Workflow ✅ - Adds LoadImage/LoadVideo/LoadAudio nodes to canvas - Supports all media file types (JPEG, PNG, MP4, etc.) - Auto-detects appropriate node type using `detectNodeTypeFromFilename` utility ### 3. Open Workflow in New Tab ✅ - Extracts workflow from asset metadata or embedded PNG - Opens workflow in new tab ### 4. Export Workflow ✅ - Exports workflow as JSON file - Supports optional filename prompt ## Code Refactoring ### Created Shared Utilities: 1. **`assetTypeUtil.ts`** - `getAssetType()` function eliminates 6 instances of `asset.tags?.[0] || 'output'` 2. **`assetUrlUtil.ts`** - `getAssetUrl()` function consolidates 3 URL construction patterns 3. **`workflowActionsService.ts`** - Shared service for workflow export/open operations 4. **`workflowExtractionUtil.ts`** - Extract workflows from jobs/assets 5. **`loaderNodeUtil.ts`** - Detect loader node types from filenames ### Improvements to Existing Code: - Refactored to use `formatUtil.getMediaTypeFromFilename()` - Extracted `deleteAssetApi()` helper to reduce deletion logic duplication (~40 lines) - Moved `isResultItemType` type guard to shared `typeGuardUtil.ts` - Added 9 i18n strings for proper localization - Added `@comfyorg/shared-frontend-utils` dependency ## Input Assets Support Improved input assets to support workflow features where applicable: - ✅ All media files (JPEG/PNG/MP4, etc.) → "Add to current workflow" enabled - ✅ PNG/WEBP/FLAC with embedded metadata → "Open/Export workflow" enabled ## Impact - **~150+ lines** of duplicate code eliminated - **5 new utility files** created to improve code reusability - **11 files** changed, **483 insertions**, **234 deletions** ## Testing ✅ TypeScript typecheck passed ✅ ESLint passed ✅ Knip passed 🤖 Generated with [Claude Code](https://claude.com/claude-code) ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-6696-feat-Implement-media-asset-workflow-actions-with-shared-utilities-2ab6d73d365081fb8ae9d71ce6e38589) by [Unito](https://www.unito.io) --------- Co-authored-by: Claude <noreply@anthropic.com> Co-authored-by: GitHub Action <action@github.com>
209 lines
5.4 KiB
Vue
209 lines
5.4 KiB
Vue
<template>
|
|
<div class="flex flex-col">
|
|
<!-- TODO: 3D assets currently excluded from inspection.
|
|
When 3D loader nodes are implemented, update detectNodeTypeFromFilename
|
|
to return appropriate node type for .gltf, .glb files and remove this exclusion -->
|
|
<IconTextButton
|
|
v-if="asset?.kind !== '3D'"
|
|
type="transparent"
|
|
label="Inspect asset"
|
|
@click="handleInspect"
|
|
>
|
|
<template #icon>
|
|
<i class="icon-[lucide--zoom-in] size-4" />
|
|
</template>
|
|
</IconTextButton>
|
|
|
|
<IconTextButton
|
|
v-if="showAddToWorkflow"
|
|
type="transparent"
|
|
label="Add to current workflow"
|
|
@click="handleAddToWorkflow"
|
|
>
|
|
<template #icon>
|
|
<i class="icon-[comfy--node] size-4" />
|
|
</template>
|
|
</IconTextButton>
|
|
|
|
<IconTextButton type="transparent" label="Download" @click="handleDownload">
|
|
<template #icon>
|
|
<i class="icon-[lucide--download] size-4" />
|
|
</template>
|
|
</IconTextButton>
|
|
|
|
<MediaAssetButtonDivider v-if="showAddToWorkflow || showWorkflowActions" />
|
|
|
|
<IconTextButton
|
|
v-if="showWorkflowActions"
|
|
type="transparent"
|
|
label="Open as workflow in new tab"
|
|
@click="handleOpenWorkflow"
|
|
>
|
|
<template #icon>
|
|
<i class="icon-[comfy--workflow] size-4" />
|
|
</template>
|
|
</IconTextButton>
|
|
|
|
<IconTextButton
|
|
v-if="showWorkflowActions"
|
|
type="transparent"
|
|
label="Export workflow"
|
|
@click="handleExportWorkflow"
|
|
>
|
|
<template #icon>
|
|
<i class="icon-[lucide--file-output] size-4" />
|
|
</template>
|
|
</IconTextButton>
|
|
|
|
<MediaAssetButtonDivider v-if="showWorkflowActions && showCopyJobId" />
|
|
|
|
<IconTextButton
|
|
v-if="showCopyJobId"
|
|
type="transparent"
|
|
label="Copy job ID"
|
|
@click="handleCopyJobId"
|
|
>
|
|
<template #icon>
|
|
<i class="icon-[lucide--copy] size-4" />
|
|
</template>
|
|
</IconTextButton>
|
|
|
|
<MediaAssetButtonDivider v-if="showCopyJobId && shouldShowDeleteButton" />
|
|
|
|
<IconTextButton
|
|
v-if="shouldShowDeleteButton"
|
|
type="transparent"
|
|
label="Delete"
|
|
@click="handleDelete"
|
|
>
|
|
<template #icon>
|
|
<i class="icon-[lucide--trash-2] size-4" />
|
|
</template>
|
|
</IconTextButton>
|
|
</div>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
import { computed, inject } from 'vue'
|
|
|
|
import IconTextButton from '@/components/button/IconTextButton.vue'
|
|
import { isCloud } from '@/platform/distribution/types'
|
|
import { supportsWorkflowMetadata } from '@/platform/workflow/utils/workflowExtractionUtil'
|
|
import { detectNodeTypeFromFilename } from '@/utils/loaderNodeUtil'
|
|
|
|
import { useMediaAssetActions } from '../composables/useMediaAssetActions'
|
|
import { MediaAssetKey } from '../schemas/mediaAssetSchema'
|
|
import MediaAssetButtonDivider from './MediaAssetButtonDivider.vue'
|
|
|
|
const { close, showDeleteButton } = defineProps<{
|
|
close: () => void
|
|
showDeleteButton?: boolean
|
|
}>()
|
|
|
|
const emit = defineEmits<{
|
|
inspect: []
|
|
'asset-deleted': []
|
|
}>()
|
|
|
|
const { asset, context } = inject(MediaAssetKey)!
|
|
const actions = useMediaAssetActions()
|
|
|
|
const assetType = computed(() => {
|
|
return asset.value?.tags?.[0] || context.value?.type || 'output'
|
|
})
|
|
|
|
// Show "Add to current workflow" for all media files (images, videos, audio)
|
|
// This works for any file type that has a corresponding loader node
|
|
const showAddToWorkflow = computed(() => {
|
|
// Output assets can always be added
|
|
if (assetType.value === 'output') return true
|
|
|
|
// Input assets: check if file type is supported by loader nodes
|
|
// Use the same utility as the actual addWorkflow function for consistency
|
|
if (assetType.value === 'input' && asset.value?.name) {
|
|
const { nodeType } = detectNodeTypeFromFilename(asset.value.name)
|
|
return nodeType !== null
|
|
}
|
|
|
|
return false
|
|
})
|
|
|
|
// Show "Open/Export workflow" only for files with workflow metadata
|
|
// This is more restrictive - only PNG, WEBP, FLAC support embedded workflows
|
|
const showWorkflowActions = computed(() => {
|
|
// Output assets always have workflow metadata
|
|
if (assetType.value === 'output') return true
|
|
|
|
// Input assets: only formats that support workflow metadata
|
|
if (assetType.value === 'input' && asset.value?.name) {
|
|
return supportsWorkflowMetadata(asset.value.name)
|
|
}
|
|
|
|
return false
|
|
})
|
|
|
|
// Only show Copy Job ID for output assets (not for imported/input assets)
|
|
const showCopyJobId = computed(() => {
|
|
return assetType.value !== 'input'
|
|
})
|
|
|
|
const shouldShowDeleteButton = computed(() => {
|
|
const propAllows = showDeleteButton ?? true
|
|
const typeAllows =
|
|
assetType.value === 'output' || (assetType.value === 'input' && isCloud)
|
|
|
|
return propAllows && typeAllows
|
|
})
|
|
|
|
const handleInspect = () => {
|
|
emit('inspect')
|
|
close()
|
|
}
|
|
|
|
const handleAddToWorkflow = () => {
|
|
if (asset.value) {
|
|
actions.addWorkflow()
|
|
}
|
|
close()
|
|
}
|
|
|
|
const handleDownload = () => {
|
|
if (asset.value) {
|
|
actions.downloadAsset()
|
|
}
|
|
close()
|
|
}
|
|
|
|
const handleOpenWorkflow = () => {
|
|
if (asset.value) {
|
|
actions.openWorkflow()
|
|
}
|
|
close()
|
|
}
|
|
|
|
const handleExportWorkflow = () => {
|
|
if (asset.value) {
|
|
actions.exportWorkflow()
|
|
}
|
|
close()
|
|
}
|
|
|
|
const handleCopyJobId = async () => {
|
|
if (asset.value) {
|
|
await actions.copyJobId()
|
|
}
|
|
close()
|
|
}
|
|
|
|
const handleDelete = async () => {
|
|
if (!asset.value) return
|
|
|
|
close() // Close the menu first
|
|
|
|
const success = await actions.confirmDelete(asset.value)
|
|
if (success) {
|
|
emit('asset-deleted')
|
|
}
|
|
}
|
|
</script>
|