mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-04-20 14:30:41 +00:00
## Problem Output assets in the assets panel show content hashes (e.g., `a1b2c3d4.png`) instead of display names (e.g., `ComfyUI_00001_.png`). ## Root Cause Cloud inference replaces `filename` with the content hash in the output transform pipeline. The hashed filename gets stored in the jobs table's `preview_output` JSONB. The frontend uses this hash as the display name. ## Solution - Add `display_name` field to `AssetItem` schema and `ResultItemImpl` - Backend (cloud PR) joins job→assets table to resolve the original name and injects `display_name` into job responses - Frontend prefers `display_name` over `name` **only for display text and download filenames** - `asset.name` remains unchanged (the hash) for URLs, drag-to-canvas, export filters, and output key dedup ## Backwards Compatible - OSS: `display_name` is undefined, falls back to `asset.name` (which is already the real filename in OSS) - Cloud pre-deploy: `display_name` absent from API, falls back gracefully - Old jobs with no assets: `display_name` not injected, no change ## Cloud PR https://github.com/Comfy-Org/cloud/pull/2747 https://github.com/user-attachments/assets/8a4c9cac-4ade-4ea2-9a70-9af240a56602
112 lines
2.8 KiB
TypeScript
112 lines
2.8 KiB
TypeScript
import type { OutputAssetMetadata } from '@/platform/assets/schemas/assetMetadataSchema'
|
|
import type { AssetItem } from '@/platform/assets/schemas/assetSchema'
|
|
import type { NodeId } from '@/platform/workflow/validation/schemas/workflowSchema'
|
|
import {
|
|
getJobDetail,
|
|
getPreviewableOutputsFromJobDetail
|
|
} from '@/services/jobOutputCache'
|
|
import type { ResultItemImpl } from '@/stores/queueStore'
|
|
|
|
type OutputAssetMapOptions = {
|
|
jobId: string
|
|
outputs: readonly ResultItemImpl[]
|
|
createdAt?: string
|
|
executionTimeInSeconds?: number
|
|
workflow?: OutputAssetMetadata['workflow']
|
|
excludeOutputKey?: string
|
|
}
|
|
|
|
type ResolveOutputAssetItemsOptions = {
|
|
createdAt?: string
|
|
excludeOutputKey?: string
|
|
}
|
|
|
|
type OutputKeyParts = {
|
|
nodeId?: NodeId | null
|
|
subfolder?: string | null
|
|
filename?: string | null
|
|
}
|
|
|
|
function shouldLoadFullOutputs(
|
|
outputCount: OutputAssetMetadata['outputCount'],
|
|
outputsLength: number
|
|
): boolean {
|
|
return (
|
|
typeof outputCount === 'number' &&
|
|
outputCount > 1 &&
|
|
outputsLength < outputCount
|
|
)
|
|
}
|
|
|
|
export function getOutputKey({
|
|
nodeId,
|
|
subfolder,
|
|
filename
|
|
}: OutputKeyParts): string | null {
|
|
if (nodeId == null || subfolder == null || !filename) {
|
|
return null
|
|
}
|
|
|
|
return `${nodeId}-${subfolder}-${filename}`
|
|
}
|
|
|
|
function mapOutputsToAssetItems({
|
|
jobId,
|
|
outputs,
|
|
createdAt,
|
|
executionTimeInSeconds,
|
|
workflow,
|
|
excludeOutputKey
|
|
}: OutputAssetMapOptions): AssetItem[] {
|
|
const createdAtValue = createdAt ?? new Date().toISOString()
|
|
|
|
return outputs.reduce<AssetItem[]>((items, output) => {
|
|
const outputKey = getOutputKey(output)
|
|
if (!output.filename || !outputKey || outputKey === excludeOutputKey) {
|
|
return items
|
|
}
|
|
|
|
items.push({
|
|
id: `${jobId}-${outputKey}`,
|
|
name: output.filename,
|
|
display_name: output.display_name,
|
|
size: 0,
|
|
created_at: createdAtValue,
|
|
tags: ['output'],
|
|
preview_url: output.previewUrl,
|
|
user_metadata: {
|
|
jobId,
|
|
nodeId: output.nodeId,
|
|
subfolder: output.subfolder,
|
|
executionTimeInSeconds,
|
|
workflow
|
|
}
|
|
})
|
|
|
|
return items
|
|
}, [])
|
|
}
|
|
|
|
export async function resolveOutputAssetItems(
|
|
metadata: OutputAssetMetadata,
|
|
{ createdAt, excludeOutputKey }: ResolveOutputAssetItemsOptions = {}
|
|
): Promise<AssetItem[]> {
|
|
let outputsToDisplay = metadata.allOutputs ?? []
|
|
if (shouldLoadFullOutputs(metadata.outputCount, outputsToDisplay.length)) {
|
|
const jobDetail = await getJobDetail(metadata.jobId)
|
|
const previewableOutputs = getPreviewableOutputsFromJobDetail(jobDetail)
|
|
if (previewableOutputs.length) {
|
|
outputsToDisplay = previewableOutputs
|
|
}
|
|
}
|
|
|
|
return mapOutputsToAssetItems({
|
|
jobId: metadata.jobId,
|
|
outputs: outputsToDisplay,
|
|
createdAt,
|
|
executionTimeInSeconds: metadata.executionTimeInSeconds,
|
|
workflow: metadata.workflow,
|
|
excludeOutputKey
|
|
})
|
|
}
|