Files
ComfyUI_frontend/src/utils/executionErrorUtil.ts
jaeone94 46c40c755e feat: node-specific error tab with selection-aware grouping and error overlay (#8956)
## Summary
Enhances the error panel with node-specific views: single-node selection
shows errors grouped by message in compact mode, container nodes
(subgraph/group) expose child errors via a badge and "See Error" button,
and a floating ErrorOverlay appears after execution failure with a
deduplicated summary and quick navigation to the errors tab.

## Changes
- **Consolidate error tab**: Remove `TabError.vue`; merge all error
display into `TabErrors.vue` and drop the separate `error` tab type from
`rightSidePanelStore`
- **Selection-aware grouping**: Single-node selection regroups errors by
message (not `class_type`) and renders `ErrorNodeCard` in compact mode
- **Container node support**: Detect child-node errors in subgraph/group
nodes via execution ID prefix matching; show error badge and "See Error"
button in `SectionWidgets`
- **ErrorOverlay**: New floating card shown after execution failure with
deduplicated error messages, "Dismiss" and "See Errors" actions;
`isErrorOverlayOpen` / `showErrorOverlay` / `dismissErrorOverlay` added
to `executionStore`
- **Refactor**: Centralize error ID collection in `executionStore`
(`allErrorExecutionIds`, `hasInternalErrorForNode`); split `errorGroups`
into `allErrorGroups` (unfiltered) and `tabErrorGroups`
(selection-filtered); move `ErrorOverlay` business logic into
`useErrorGroups`

## Review Focus
- `useErrorGroups.ts`: split into `allErrorGroups` / `tabErrorGroups`
and the new `filterBySelection` parameter flow
- `executionStore.ts`: `hasInternalErrorForNode` helper and
`allErrorExecutionIds` computed
- `ErrorOverlay.vue`: integration with `executionStore` overlay state
and `useErrorGroups`

## Screenshots
<img width="853" height="461" alt="image"
src="https://github.com/user-attachments/assets/a49ab620-4209-4ae7-b547-fba13da0c633"
/>
<img width="854" height="203" alt="image"
src="https://github.com/user-attachments/assets/c119da54-cd78-4e7a-8b7a-456cfd348f1d"
/>
<img width="497" height="361" alt="image"
src="https://github.com/user-attachments/assets/74b16161-cf45-454b-ae60-24922fe36931"
/>

---------

Co-authored-by: GitHub Action <action@github.com>
Co-authored-by: github-actions <github-actions@github.com>
2026-02-20 12:14:52 -08:00

93 lines
2.7 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
}