mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-04-30 03:01:54 +00:00
Media Assets Management Sidebar Tab Implementation (#6112)
## 📋 Overview Implemented a new Media Assets sidebar tab in ComfyUI for managing user-uploaded input files and generated output files. This feature supports both local and cloud environments and is currently enabled only in development mode. ## 🎯 Key Features ### 1. Media Assets Sidebar Tab - **Imported** / **Generated** files separated by tabs - Visual display with file preview cards - Gallery view support (navigable with arrow keys) ### 2. Environment-Specific Implementation - **`useInternalMediaAssets`**: For local environment - Fetches file list via `/files` API - Retrieves generation task execution time via `/history` API - Processes history data using the same logic as QueueSidebarTab - **`useCloudMediaAssets`**: For cloud environment - File retrieval through assetService - History data processing using TaskItemImpl - Auto-truncation of long filenames over 20 characters (e.g., `very_long_filename_here.png` → `very_long_...here.png`) ### 3. Execution Time Display - Shows task execution time on generated image cards (e.g., "2.3s") - Calculated from History API's `execution_start` and `execution_success` messages - Displayed at MediaAssetCard's duration chip location ### 4. Gallery Feature - Full-screen gallery mode on image click - Navigate between images with keyboard arrows - Exit gallery with ESC key - Reuses ResultGallery component from QueueSidebarTab ### 5. Development Mode Only - Excluded from production builds using `import.meta.env.DEV` condition - Feature in development, scheduled for official release after stabilization ## 🛠️ Technical Changes ### New Files Added - `src/components/sidebar/tabs/AssetsSidebarTab.vue` - Main sidebar tab component - `src/composables/sidebarTabs/useAssetsSidebarTab.ts` - Sidebar tab definition - `src/composables/useInternalMediaAssets.ts` - Local environment implementation - `src/composables/useCloudMediaAssets.ts` - Cloud environment implementation - `packages/design-system/src/icons/image-ai-edit.svg` - Icon addition ### Modified Files - `src/stores/workspace/sidebarTabStore.ts` - Added dev mode only tab display logic - `src/platform/assets/components/MediaAssetCard.vue` - Added execution time display, zoom event - `src/platform/assets/components/MediaImageTop.vue` - Added image dimension detection - `packages/shared-frontend-utils/src/formatUtil.ts` - Added media type determination utility functions - `src/locales/en/main.json` - Added translation keys [media_asset_OSS_cloud.webm](https://github.com/user-attachments/assets/a6ee3b49-19ed-4735-baad-c2ac2da868ef) --------- Co-authored-by: Claude <noreply@anthropic.com> Co-authored-by: github-actions <github-actions@github.com>
This commit is contained in:
133
src/stores/assetsStore.ts
Normal file
133
src/stores/assetsStore.ts
Normal file
@@ -0,0 +1,133 @@
|
||||
import { useAsyncState } from '@vueuse/core'
|
||||
import { defineStore } from 'pinia'
|
||||
|
||||
import {
|
||||
mapInputFileToAssetItem,
|
||||
mapTaskOutputToAssetItem
|
||||
} from '@/platform/assets/composables/media/assetMappers'
|
||||
import type { AssetItem } from '@/platform/assets/schemas/assetSchema'
|
||||
import { assetService } from '@/platform/assets/services/assetService'
|
||||
import { isCloud } from '@/platform/distribution/types'
|
||||
import { api } from '@/scripts/api'
|
||||
|
||||
import { TaskItemImpl } from './queueStore'
|
||||
|
||||
/**
|
||||
* Fetch input files from the internal API (OSS version)
|
||||
*/
|
||||
async function fetchInputFilesFromAPI(): Promise<AssetItem[]> {
|
||||
const response = await fetch(api.internalURL('/files/input'), {
|
||||
headers: {
|
||||
'Comfy-User': api.user
|
||||
}
|
||||
})
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error('Failed to fetch input files')
|
||||
}
|
||||
|
||||
const filenames: string[] = await response.json()
|
||||
return filenames.map((name, index) =>
|
||||
mapInputFileToAssetItem(name, index, 'input')
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch input files from cloud service
|
||||
*/
|
||||
async function fetchInputFilesFromCloud(): Promise<AssetItem[]> {
|
||||
return await assetService.getAssetsByTag('input', false)
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert history task items to asset items
|
||||
*/
|
||||
function mapHistoryToAssets(historyItems: any[]): AssetItem[] {
|
||||
const assetItems: AssetItem[] = []
|
||||
|
||||
for (const item of historyItems) {
|
||||
if (!item.outputs || !item.status || item.status?.status_str === 'error') {
|
||||
continue
|
||||
}
|
||||
|
||||
const task = new TaskItemImpl(
|
||||
'History',
|
||||
item.prompt,
|
||||
item.status,
|
||||
item.outputs
|
||||
)
|
||||
|
||||
if (!task.previewOutput) {
|
||||
continue
|
||||
}
|
||||
|
||||
const assetItem = mapTaskOutputToAssetItem(task, task.previewOutput)
|
||||
|
||||
const supportedOutputs = task.flatOutputs.filter((o) => o.supportsPreview)
|
||||
assetItem.user_metadata = {
|
||||
...assetItem.user_metadata,
|
||||
outputCount: supportedOutputs.length,
|
||||
allOutputs: supportedOutputs
|
||||
}
|
||||
|
||||
assetItems.push(assetItem)
|
||||
}
|
||||
|
||||
return assetItems.sort(
|
||||
(a, b) =>
|
||||
new Date(b.created_at).getTime() - new Date(a.created_at).getTime()
|
||||
)
|
||||
}
|
||||
|
||||
export const useAssetsStore = defineStore('assets', () => {
|
||||
const maxHistoryItems = 200
|
||||
|
||||
const fetchInputFiles = isCloud
|
||||
? fetchInputFilesFromCloud
|
||||
: fetchInputFilesFromAPI
|
||||
|
||||
const {
|
||||
state: inputAssets,
|
||||
isLoading: inputLoading,
|
||||
error: inputError,
|
||||
execute: updateInputs
|
||||
} = useAsyncState(fetchInputFiles, [], {
|
||||
immediate: false,
|
||||
resetOnExecute: false,
|
||||
onError: (err) => {
|
||||
console.error('Error fetching input assets:', err)
|
||||
}
|
||||
})
|
||||
|
||||
const fetchHistoryAssets = async (): Promise<AssetItem[]> => {
|
||||
const history = await api.getHistory(maxHistoryItems)
|
||||
return mapHistoryToAssets(history.History)
|
||||
}
|
||||
|
||||
const {
|
||||
state: historyAssets,
|
||||
isLoading: historyLoading,
|
||||
error: historyError,
|
||||
execute: updateHistory
|
||||
} = useAsyncState(fetchHistoryAssets, [], {
|
||||
immediate: false,
|
||||
resetOnExecute: false,
|
||||
onError: (err) => {
|
||||
console.error('Error fetching history assets:', err)
|
||||
}
|
||||
})
|
||||
|
||||
return {
|
||||
// States
|
||||
inputAssets,
|
||||
historyAssets,
|
||||
inputLoading,
|
||||
historyLoading,
|
||||
inputError,
|
||||
historyError,
|
||||
|
||||
// Actions
|
||||
updateInputs,
|
||||
updateHistory
|
||||
}
|
||||
})
|
||||
Reference in New Issue
Block a user