mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-05-12 16:56:23 +00:00
## Summary Automatically clears transient validation errors (`value_bigger_than_max`, `value_smaller_than_min`, `value_not_in_list`, `required_input_missing`) when the user modifies a widget value or connects an input slot, so resolved errors don't linger in the error panel. Also clears missing model state when the user changes a combo widget value. ## Changes - **`useNodeErrorAutoResolve` composable**: watches widget changes and slot connections, clears matching errors via `executionErrorStore` - **`executionErrorStore`**: adds `clearSimpleNodeErrors` and `clearSimpleWidgetErrorIfValid` with granular per-slot error removal - **`executionErrorUtil`**: adds `isValueStillOutOfRange` to prevent premature clearing when a new value still violates the constraint - **`graphTraversalUtil`**: adds `getExecutionIdFromNodeData` for subgraph-aware execution ID resolution - **`GraphCanvas.vue`**: fixes subgraph error key lookup by using `getExecutionIdByNode` instead of raw `node.id` - **`NodeWidgets.vue`**: wires up the new composable to the widget layer - **`missingModelStore`**: adds `removeMissingModelByWidget` to clear missing model state on widget value change - **`useGraphNodeManager`**: registers composable per node - **Tests**: 126 new unit tests covering error clearing, range validation, and graph traversal edge cases ## Screenshots https://github.com/user-attachments/assets/515ea811-ff84-482a-a866-a17e5c779c39 https://github.com/user-attachments/assets/a2b30f02-4929-4537-952c-a0febe20f02e ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-9464-feat-auto-resolve-simple-validation-errors-on-widget-change-and-slot-connection-31b6d73d3650816b8afdc34f4b40295a) by [Unito](https://www.unito.io) --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
128 lines
3.9 KiB
TypeScript
128 lines
3.9 KiB
TypeScript
import type { NodeId } from '@/platform/workflow/validation/schemas/workflowSchema'
|
|
import type { NodeError, PromptError } from '@/schemas/apiSchema'
|
|
|
|
/**
|
|
* The standard prompt validation response shape (`{ error, node_errors }`).
|
|
* In cloud, this is embedded as JSON inside `execution_error.exception_message`
|
|
* because prompts are queued asynchronously and errors arrive via WebSocket
|
|
* rather than as direct HTTP responses.
|
|
*/
|
|
interface CloudValidationError {
|
|
error?: { type?: string; message?: string; details?: string } | string
|
|
node_errors?: Record<NodeId, NodeError>
|
|
}
|
|
|
|
export function isCloudValidationError(
|
|
value: unknown
|
|
): value is CloudValidationError {
|
|
return (
|
|
value !== null &&
|
|
typeof value === 'object' &&
|
|
('error' in value || 'node_errors' in value)
|
|
)
|
|
}
|
|
|
|
/**
|
|
* Extracts a prompt validation response embedded in an exception message string.
|
|
*
|
|
* Cloud example `exception_message`:
|
|
* "Failed to send prompt request: ... 400: {\"error\":{...},\"node_errors\":{...}}"
|
|
*
|
|
* This function finds the first '{' and parses the trailing JSON.
|
|
*/
|
|
export function tryExtractValidationError(
|
|
exceptionMessage: string
|
|
): CloudValidationError | null {
|
|
const jsonStart = exceptionMessage.indexOf('{')
|
|
const jsonEnd = exceptionMessage.lastIndexOf('}')
|
|
if (jsonStart === -1 || jsonEnd === -1) return null
|
|
|
|
try {
|
|
const parsed: unknown = JSON.parse(
|
|
exceptionMessage.substring(jsonStart, jsonEnd + 1)
|
|
)
|
|
return isCloudValidationError(parsed) ? parsed : null
|
|
} catch {
|
|
return null
|
|
}
|
|
}
|
|
|
|
type CloudValidationResult =
|
|
| { kind: 'nodeErrors'; nodeErrors: Record<NodeId, NodeError> }
|
|
| { kind: 'promptError'; promptError: PromptError }
|
|
|
|
/**
|
|
* Classifies an embedded cloud validation error from `exception_message`
|
|
* as either node-level errors or a prompt-level error.
|
|
*
|
|
* Returns `null` if the message does not contain a recognizable validation error.
|
|
*/
|
|
export function classifyCloudValidationError(
|
|
exceptionMessage: string
|
|
): CloudValidationResult | null {
|
|
const extracted = tryExtractValidationError(exceptionMessage)
|
|
if (!extracted) return null
|
|
|
|
const { error, node_errors } = extracted
|
|
const hasNodeErrors = node_errors && Object.keys(node_errors).length > 0
|
|
|
|
if (hasNodeErrors) {
|
|
return { kind: 'nodeErrors', nodeErrors: node_errors }
|
|
}
|
|
|
|
if (error && typeof error === 'object') {
|
|
return {
|
|
kind: 'promptError',
|
|
promptError: {
|
|
type: error.type ?? 'error',
|
|
message: error.message ?? '',
|
|
details: error.details ?? ''
|
|
}
|
|
}
|
|
}
|
|
|
|
if (typeof error === 'string') {
|
|
return {
|
|
kind: 'promptError',
|
|
promptError: { type: 'error', message: error, details: '' }
|
|
}
|
|
}
|
|
|
|
return null
|
|
}
|
|
|
|
/**
|
|
* Error types that can be resolved automatically when the user changes a
|
|
* widget value or establishes a connection, without requiring a re-run.
|
|
*
|
|
* When adding new types, review {@link isValueStillOutOfRange} to ensure
|
|
* the new type does not require range validation before auto-clearing.
|
|
*/
|
|
export const SIMPLE_ERROR_TYPES = new Set([
|
|
'value_bigger_than_max',
|
|
'value_smaller_than_min',
|
|
'value_not_in_list',
|
|
'required_input_missing'
|
|
])
|
|
|
|
/**
|
|
* Returns true if `value` still violates a recorded range constraint.
|
|
* Pass errors already filtered to the target widget (by `input_name`).
|
|
* `options` should contain the widget's configured `min` / `max`.
|
|
*
|
|
* Returns true (keeps the error) when a bound is unknown (`undefined`).
|
|
*/
|
|
export function isValueStillOutOfRange(
|
|
value: number,
|
|
errors: NodeError['errors'],
|
|
options: { min?: number; max?: number }
|
|
): boolean {
|
|
const hasMaxError = errors.some((e) => e.type === 'value_bigger_than_max')
|
|
const hasMinError = errors.some((e) => e.type === 'value_smaller_than_min')
|
|
|
|
return (
|
|
(hasMaxError && (options.max === undefined || value > options.max)) ||
|
|
(hasMinError && (options.min === undefined || value < options.min))
|
|
)
|
|
}
|