diff --git a/src/scripts/app.ts b/src/scripts/app.ts index 067b6177d..7c1749982 100644 --- a/src/scripts/app.ts +++ b/src/scripts/app.ts @@ -70,6 +70,8 @@ import type { ComfyExtension, MissingNodeType } from '@/types/comfy' import { type ExtensionManager } from '@/types/extensionTypes' import type { NodeExecutionId } from '@/types/nodeIdentification' import { graphToPrompt } from '@/utils/executionUtil' +import type { MissingNodeTypeExtraInfo } from '@/workbench/extensions/manager/types/missingNodeErrorTypes' +import { createMissingNodeTypeFromError } from '@/workbench/extensions/manager/utils/missingNodeErrorUtil' import { anyItemOverlapsRect } from '@/utils/mathUtil' import { collectAllNodes, forEachNode } from '@/utils/graphTraversalUtil' import { @@ -1394,10 +1396,21 @@ export class ComfyApp { } catch (error) {} } } catch (error: unknown) { - useDialogService().showErrorDialog(error, { - title: t('errorDialog.promptExecutionError'), - reportType: 'promptExecutionError' - }) + if ( + error instanceof PromptExecutionError && + typeof error.response.error === 'object' && + error.response.error?.type === 'missing_node_type' + ) { + const extraInfo = (error.response.error.extra_info ?? + {}) as MissingNodeTypeExtraInfo + const missingNodeType = createMissingNodeTypeFromError(extraInfo) + this.showMissingNodesError([missingNodeType]) + } else { + useDialogService().showErrorDialog(error, { + title: t('errorDialog.promptExecutionError'), + reportType: 'promptExecutionError' + }) + } console.error(error) if (error instanceof PromptExecutionError) { diff --git a/src/workbench/extensions/manager/types/missingNodeErrorTypes.ts b/src/workbench/extensions/manager/types/missingNodeErrorTypes.ts new file mode 100644 index 000000000..8af9b21d2 --- /dev/null +++ b/src/workbench/extensions/manager/types/missingNodeErrorTypes.ts @@ -0,0 +1,9 @@ +/** + * Extra info returned by the backend for missing_node_type errors + * from the /prompt endpoint validation. + */ +export interface MissingNodeTypeExtraInfo { + class_type?: string | null + node_title?: string | null + node_id?: string +} diff --git a/src/workbench/extensions/manager/utils/missingNodeErrorUtil.test.ts b/src/workbench/extensions/manager/utils/missingNodeErrorUtil.test.ts new file mode 100644 index 000000000..0200fa20e --- /dev/null +++ b/src/workbench/extensions/manager/utils/missingNodeErrorUtil.test.ts @@ -0,0 +1,90 @@ +import { describe, expect, it } from 'vitest' + +import { + buildMissingNodeHint, + createMissingNodeTypeFromError +} from './missingNodeErrorUtil' + +describe('buildMissingNodeHint', () => { + it('returns hint with title and node ID when both available', () => { + expect(buildMissingNodeHint('My Node', 'MyNodeClass', '42')).toBe( + '"My Node" (Node ID #42)' + ) + }) + + it('returns hint with title only when no node ID', () => { + expect(buildMissingNodeHint('My Node', 'MyNodeClass', undefined)).toBe( + '"My Node"' + ) + }) + + it('returns hint with node ID only when title matches class type', () => { + expect(buildMissingNodeHint('MyNodeClass', 'MyNodeClass', '42')).toBe( + 'Node ID #42' + ) + }) + + it('returns undefined when title matches class type and no node ID', () => { + expect( + buildMissingNodeHint('MyNodeClass', 'MyNodeClass', undefined) + ).toBeUndefined() + }) + + it('returns undefined when title is null and no node ID', () => { + expect(buildMissingNodeHint(null, 'MyNodeClass', undefined)).toBeUndefined() + }) + + it('returns node ID hint when title is null but node ID exists', () => { + expect(buildMissingNodeHint(null, 'MyNodeClass', '42')).toBe('Node ID #42') + }) +}) + +describe('createMissingNodeTypeFromError', () => { + it('returns string type when no hint is generated', () => { + const result = createMissingNodeTypeFromError({ + class_type: 'MyNodeClass', + node_title: 'MyNodeClass' + }) + expect(result).toBe('MyNodeClass') + }) + + it('returns object with hint when title differs from class type', () => { + const result = createMissingNodeTypeFromError({ + class_type: 'MyNodeClass', + node_title: 'My Custom Title', + node_id: '42' + }) + expect(result).toEqual({ + type: 'MyNodeClass', + hint: '"My Custom Title" (Node ID #42)' + }) + }) + + it('handles null class_type by defaulting to Unknown', () => { + const result = createMissingNodeTypeFromError({ + class_type: null, + node_title: 'Some Title', + node_id: '42' + }) + expect(result).toEqual({ + type: 'Unknown', + hint: '"Some Title" (Node ID #42)' + }) + }) + + it('handles empty extra_info', () => { + const result = createMissingNodeTypeFromError({}) + expect(result).toBe('Unknown') + }) + + it('returns object with node ID hint when only node_id is available', () => { + const result = createMissingNodeTypeFromError({ + class_type: 'MyNodeClass', + node_id: '123' + }) + expect(result).toEqual({ + type: 'MyNodeClass', + hint: 'Node ID #123' + }) + }) +}) diff --git a/src/workbench/extensions/manager/utils/missingNodeErrorUtil.ts b/src/workbench/extensions/manager/utils/missingNodeErrorUtil.ts new file mode 100644 index 000000000..34f84f88d --- /dev/null +++ b/src/workbench/extensions/manager/utils/missingNodeErrorUtil.ts @@ -0,0 +1,37 @@ +import type { MissingNodeType } from '@/types/comfy' + +import type { MissingNodeTypeExtraInfo } from '../types/missingNodeErrorTypes' + +/** + * Builds a hint string from missing node metadata. + * Provides context about which node is missing (title, ID) when available. + */ +export function buildMissingNodeHint( + nodeTitle: string | null | undefined, + classType: string, + nodeId: string | undefined +): string | undefined { + const hasTitle = nodeTitle && nodeTitle !== classType + if (hasTitle && nodeId) { + return `"${nodeTitle}" (Node ID #${nodeId})` + } else if (hasTitle) { + return `"${nodeTitle}"` + } else if (nodeId) { + return `Node ID #${nodeId}` + } + return undefined +} + +/** + * Creates a MissingNodeType from backend error extra_info. + * Used when the /prompt endpoint returns a missing_node_type error. + */ +export function createMissingNodeTypeFromError( + extraInfo: MissingNodeTypeExtraInfo +): MissingNodeType { + const classType = extraInfo.class_type ?? 'Unknown' + const nodeTitle = extraInfo.node_title ?? classType + const hint = buildMissingNodeHint(nodeTitle, classType, extraInfo.node_id) + + return hint ? { type: classType, hint } : classType +}