refactor: unify image classification and fix cloud preview param handling (#9408)

## Summary

Addresses review feedback from PR #9298 and resolves the divergence
between `ResultItemImpl.isImage` and `appendCloudResParam`'s image
classification.

### Changes

- **Unify suffix-based classification**: Replace narrow
`isImageBySuffix` (gif/webp only), `isVideoBySuffix` (webm/mp4), and
`isAudioBySuffix` with `getMediaTypeFromFilename()` from
shared-frontend-utils, using the same `IMAGE_EXTENSIONS` set (png, jpg,
jpeg, gif, webp, bmp, avif, tif, tiff) that `appendCloudResParam` uses
- **imageCompare.ts**: Pass `record.filename` to `appendCloudResParam`
(was called without filename, bypassing image-extension guard)
- **imagePreviewStore.ts**: Use per-image `image.filename` instead of
first image's filename for all images in batch
- **LinearControls.vue**: Use `resultItem.filename` (already a string)
instead of `String(filename)` which converts undefined to `"undefined"`

### Related review comments

- [imageCompare.ts — missing
filename](https://github.com/Comfy-Org/ComfyUI_frontend/pull/9298#discussion_r2886137498)
- [imagePreviewStore.ts — per-image
filename](https://github.com/Comfy-Org/ComfyUI_frontend/pull/9298#discussion_r2886138718)
- [LinearControls.vue —
String(filename)](https://github.com/Comfy-Org/ComfyUI_frontend/pull/9298#discussion_r2886140159)
- [queueStore.ts — diverging image
classification](https://github.com/Comfy-Org/ComfyUI_frontend/pull/9298#discussion_r2886142886)

## Test plan

- [x] 66 unit tests pass (queueStore + cloudPreviewUtil)
- [x] `pnpm typecheck` passes
- [x] `pnpm lint` passes

- Fixes #9386

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Dante
2026-03-06 10:20:18 +09:00
committed by GitHub
parent 47f2b63628
commit 60267fc64c
4 changed files with 9 additions and 15 deletions

View File

@@ -29,7 +29,7 @@ useExtensionService().registerExtension({
const toUrl = (record: Record<string, string>) => {
const params = new URLSearchParams(record)
appendCloudResParam(params)
appendCloudResParam(params, record.filename)
return api.apiURL(`/view?${params}${rand}`)
}

View File

@@ -90,7 +90,7 @@ function getDropIndicator(node: LGraphNode) {
const buildImageUrl = () => {
if (!filename) return undefined
const params = new URLSearchParams(resultItem)
appendCloudResParam(params, String(filename))
appendCloudResParam(params, resultItem.filename)
return api.apiURL(`/view?${params}${app.getPreviewFormatParam()}`)
}

View File

@@ -121,11 +121,10 @@ export const useNodeOutputStore = defineStore('nodeOutput', () => {
const rand = app.getRandParam()
const previewParam = getPreviewParam(node, outputs)
const isImage = isImageOutputs(node, outputs)
const firstFilename = outputs.images[0]?.filename
return outputs.images.map((image) => {
const params = new URLSearchParams(image)
if (isImage) appendCloudResParam(params, firstFilename)
if (isImage) appendCloudResParam(params, image.filename)
return api.apiURL(`/view?${params}${previewParam}${rand}`)
})
}

View File

@@ -114,6 +114,9 @@ export class ResultItemImpl {
if (this.isMp4) {
return 'video/mp4'
}
if (this.filename.endsWith('.mov')) {
return 'video/quicktime'
}
if (this.isVhsFormat) {
if (this.format?.endsWith('webm')) {
@@ -142,14 +145,6 @@ export class ResultItemImpl {
return undefined
}
get isGif(): boolean {
return this.filename.endsWith('.gif')
}
get isWebp(): boolean {
return this.filename.endsWith('.webp')
}
get isWebm(): boolean {
return this.filename.endsWith('.webm')
}
@@ -159,11 +154,11 @@ export class ResultItemImpl {
}
get isVideoBySuffix(): boolean {
return this.isWebm || this.isMp4
return getMediaTypeFromFilename(this.filename) === 'video'
}
get isImageBySuffix(): boolean {
return this.isGif || this.isWebp
return getMediaTypeFromFilename(this.filename) === 'image'
}
get isMp3(): boolean {
@@ -183,7 +178,7 @@ export class ResultItemImpl {
}
get isAudioBySuffix(): boolean {
return this.isMp3 || this.isWav || this.isOgg || this.isFlac
return getMediaTypeFromFilename(this.filename) === 'audio'
}
get isVideo(): boolean {