[feat] Implement media asset workflow actions with shared utilities (#6696)

## 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>
This commit is contained in:
Jin Yi
2025-11-18 09:04:45 +09:00
committed by GitHub
parent 2f81e5b30a
commit a4d979e4c9
12 changed files with 633 additions and 124 deletions

View File

@@ -0,0 +1,24 @@
/**
* Utilities for working with asset types
*/
import type { AssetItem } from '../schemas/assetSchema'
/**
* Extract asset type from an asset's tags array
* Falls back to a default type if tags are not present
*
* @param asset The asset to extract type from
* @param defaultType Default type to use if tags are empty (default: 'output')
* @returns The asset type ('input', 'output', 'temp', etc.)
*
* @example
* getAssetType(asset) // Returns 'output' or first tag
* getAssetType(asset, 'input') // Returns 'input' if no tags
*/
export function getAssetType(
asset: AssetItem,
defaultType: 'input' | 'output' = 'output'
): string {
return asset.tags?.[0] || defaultType
}

View File

@@ -0,0 +1,29 @@
/**
* Utilities for constructing asset URLs
*/
import { api } from '@/scripts/api'
import type { AssetItem } from '../schemas/assetSchema'
import { getAssetType } from './assetTypeUtil'
/**
* Get the download/view URL for an asset
* Constructs the proper URL with filename encoding and type parameter
*
* @param asset The asset to get URL for
* @param defaultType Default type if asset doesn't have tags (default: 'output')
* @returns Full URL for viewing/downloading the asset
*
* @example
* const url = getAssetUrl(asset)
* downloadFile(url, asset.name)
*/
export function getAssetUrl(
asset: AssetItem,
defaultType: 'input' | 'output' = 'output'
): string {
const assetType = getAssetType(asset, defaultType)
return api.apiURL(
`/view?filename=${encodeURIComponent(asset.name)}&type=${assetType}`
)
}