mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-04-20 06:20:11 +00:00
fix: block workflow queue when file input nodes have empty selections
Prevents submitting workflows where LoadImage, LoadAudio, Load3D, or LoadVideo nodes have no file selected. Shows a localized error dialog listing the affected nodes instead of sending an invalid request. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1889,7 +1889,9 @@
|
||||
"extensionFileHint": "This may be due to the following script",
|
||||
"promptExecutionError": "Prompt execution failed",
|
||||
"accessRestrictedTitle": "Access Restricted",
|
||||
"accessRestrictedMessage": "Your account is not authorized for this feature."
|
||||
"accessRestrictedMessage": "Your account is not authorized for this feature.",
|
||||
"emptyFileInputTitle": "Missing File Inputs",
|
||||
"emptyFileInputMessage": "The following nodes require a file to be selected: {nodeList}. Please upload or select files before running."
|
||||
},
|
||||
"apiNodesSignInDialog": {
|
||||
"title": "Sign In Required to Use API Nodes",
|
||||
|
||||
@@ -146,6 +146,30 @@ import {
|
||||
|
||||
export const ANIM_PREVIEW_WIDGET = '$$comfy_animation_preview'
|
||||
|
||||
export const FILE_INPUT_FIELDS: Record<string, string> = {
|
||||
LoadImage: 'image',
|
||||
LoadAudio: 'audio',
|
||||
Load3D: 'model_file',
|
||||
LoadVideo: 'video'
|
||||
}
|
||||
|
||||
export function findEmptyFileInputNodes(
|
||||
output: ComfyApiWorkflow
|
||||
): { nodeId: string; classType: string; title: string }[] {
|
||||
const result: { nodeId: string; classType: string; title: string }[] = []
|
||||
for (const [nodeId, node] of Object.entries(output)) {
|
||||
const field = FILE_INPUT_FIELDS[node.class_type]
|
||||
if (field && node.inputs[field] === '') {
|
||||
result.push({
|
||||
nodeId,
|
||||
classType: node.class_type,
|
||||
title: node._meta?.title ?? node.class_type
|
||||
})
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
export function sanitizeNodeName(string: string) {
|
||||
let entityMap = {
|
||||
'&': '',
|
||||
@@ -1615,6 +1639,21 @@ export class ComfyApp {
|
||||
const queuedWorkflow = useWorkspaceStore().workflow
|
||||
.activeWorkflow as ComfyWorkflow
|
||||
const p = await this.graphToPrompt(this.rootGraph)
|
||||
|
||||
const emptyFileInputNodes = findEmptyFileInputNodes(p.output)
|
||||
if (emptyFileInputNodes.length) {
|
||||
const nodeList = emptyFileInputNodes
|
||||
.map((n) => `#${n.nodeId} ${n.title}`)
|
||||
.join(', ')
|
||||
useDialogService().showErrorDialog(
|
||||
new Error(
|
||||
t('errorDialog.emptyFileInputMessage', { nodeList })
|
||||
),
|
||||
{ title: t('errorDialog.emptyFileInputTitle') }
|
||||
)
|
||||
break
|
||||
}
|
||||
|
||||
const queuedNodes = collectAllNodes(this.rootGraph)
|
||||
try {
|
||||
api.authToken = comfyOrgAuthToken
|
||||
|
||||
73
src/scripts/findEmptyFileInputNodes.test.ts
Normal file
73
src/scripts/findEmptyFileInputNodes.test.ts
Normal file
@@ -0,0 +1,73 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
|
||||
import type { ComfyApiWorkflow } from '@/platform/workflow/validation/schemas/workflowSchema'
|
||||
import { findEmptyFileInputNodes } from './app'
|
||||
|
||||
function makeNode(
|
||||
classType: string,
|
||||
inputs: Record<string, unknown>,
|
||||
title?: string
|
||||
) {
|
||||
return {
|
||||
class_type: classType,
|
||||
inputs,
|
||||
_meta: { title: title ?? classType }
|
||||
}
|
||||
}
|
||||
|
||||
describe('findEmptyFileInputNodes', () => {
|
||||
it('detects LoadImage with empty image field', () => {
|
||||
const output: ComfyApiWorkflow = {
|
||||
'1': makeNode('LoadImage', { image: '' }),
|
||||
'2': makeNode('KSampler', { seed: 42 })
|
||||
}
|
||||
expect(findEmptyFileInputNodes(output)).toEqual([
|
||||
{ nodeId: '1', classType: 'LoadImage', title: 'LoadImage' }
|
||||
])
|
||||
})
|
||||
|
||||
it('detects multiple empty file input nodes', () => {
|
||||
const output: ComfyApiWorkflow = {
|
||||
'1': makeNode('LoadImage', { image: '' }, 'My Image'),
|
||||
'2': makeNode('LoadAudio', { audio: '' }),
|
||||
'3': makeNode('LoadVideo', { video: 'file.mp4' })
|
||||
}
|
||||
const result = findEmptyFileInputNodes(output)
|
||||
expect(result).toHaveLength(2)
|
||||
expect(result[0]).toEqual({
|
||||
nodeId: '1',
|
||||
classType: 'LoadImage',
|
||||
title: 'My Image'
|
||||
})
|
||||
expect(result[1]).toEqual({
|
||||
nodeId: '2',
|
||||
classType: 'LoadAudio',
|
||||
title: 'LoadAudio'
|
||||
})
|
||||
})
|
||||
|
||||
it('returns empty array when all file inputs are populated', () => {
|
||||
const output: ComfyApiWorkflow = {
|
||||
'1': makeNode('LoadImage', { image: 'photo.png' }),
|
||||
'2': makeNode('Load3D', { model_file: 'model.glb' })
|
||||
}
|
||||
expect(findEmptyFileInputNodes(output)).toEqual([])
|
||||
})
|
||||
|
||||
it('returns empty array when no file input nodes exist', () => {
|
||||
const output: ComfyApiWorkflow = {
|
||||
'1': makeNode('KSampler', { seed: 42 }),
|
||||
'2': makeNode('CLIPTextEncode', { text: 'hello' })
|
||||
}
|
||||
expect(findEmptyFileInputNodes(output)).toEqual([])
|
||||
})
|
||||
|
||||
it('detects Load3D with empty model_file', () => {
|
||||
const output: ComfyApiWorkflow = {
|
||||
'5': makeNode('Load3D', { model_file: '' })
|
||||
}
|
||||
expect(findEmptyFileInputNodes(output)).toEqual([
|
||||
{ nodeId: '5', classType: 'Load3D', title: 'Load3D' }
|
||||
])
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user