mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-02-26 01:34:07 +00:00
[refactor] Extract executionErrorStore from executionStore (#9060)
## Summary Extracts error-related state and logic from `executionStore` into a dedicated `executionErrorStore` for better separation of concerns. ## Changes - **New store**: `executionErrorStore` with all error state (`lastNodeErrors`, `lastExecutionError`, `lastPromptError`), computed properties (`hasAnyError`, `totalErrorCount`, `activeGraphErrorNodeIds`), and UI state (`isErrorOverlayOpen`, `showErrorOverlay`, `dismissErrorOverlay`) - **Moved util**: `executionIdToNodeLocatorId` extracted to `graphTraversalUtil`, reusing `traverseSubgraphPath` and accepting `rootGraph` as parameter - **Updated consumers**: 12 files updated to import from `executionErrorStore` - **Backward compat**: Deprecated getters retained in `ComfyApp` for extension compatibility ## Review Focus - Deprecated getters in `app.ts` — can be removed in a future breaking-change PR once extension authors migrate ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-9060-refactor-Extract-executionErrorStore-from-executionStore-30e6d73d36508101973de835ab6b199f) by [Unito](https://www.unito.io)
This commit is contained in:
280
src/stores/executionErrorStore.ts
Normal file
280
src/stores/executionErrorStore.ts
Normal file
@@ -0,0 +1,280 @@
|
||||
import { defineStore } from 'pinia'
|
||||
import { computed, ref, watch } from 'vue'
|
||||
|
||||
import { useCanvasStore } from '@/renderer/core/canvas/canvasStore'
|
||||
import { useWorkflowStore } from '@/platform/workflow/management/stores/workflowStore'
|
||||
import { app } from '@/scripts/app'
|
||||
import type {
|
||||
ExecutionErrorWsMessage,
|
||||
NodeError,
|
||||
PromptError
|
||||
} from '@/schemas/apiSchema'
|
||||
import type { NodeId } from '@/platform/workflow/validation/schemas/workflowSchema'
|
||||
import type { NodeLocatorId } from '@/types/nodeIdentification'
|
||||
import {
|
||||
executionIdToNodeLocatorId,
|
||||
forEachNode,
|
||||
getNodeByExecutionId
|
||||
} from '@/utils/graphTraversalUtil'
|
||||
|
||||
/**
|
||||
* Store dedicated to execution error state management.
|
||||
*
|
||||
* Extracted from executionStore to separate error-related concerns
|
||||
* (state, computed properties, graph flag propagation, overlay UI)
|
||||
* from execution flow management (progress, queuing, events).
|
||||
*/
|
||||
export const useExecutionErrorStore = defineStore('executionError', () => {
|
||||
const workflowStore = useWorkflowStore()
|
||||
const canvasStore = useCanvasStore()
|
||||
|
||||
const lastNodeErrors = ref<Record<NodeId, NodeError> | null>(null)
|
||||
const lastExecutionError = ref<ExecutionErrorWsMessage | null>(null)
|
||||
const lastPromptError = ref<PromptError | null>(null)
|
||||
|
||||
const isErrorOverlayOpen = ref(false)
|
||||
|
||||
function showErrorOverlay() {
|
||||
isErrorOverlayOpen.value = true
|
||||
}
|
||||
|
||||
function dismissErrorOverlay() {
|
||||
isErrorOverlayOpen.value = false
|
||||
}
|
||||
|
||||
/** Clear all error state. Called at execution start. */
|
||||
function clearAllErrors() {
|
||||
lastExecutionError.value = null
|
||||
lastPromptError.value = null
|
||||
lastNodeErrors.value = null
|
||||
isErrorOverlayOpen.value = false
|
||||
}
|
||||
|
||||
/** Clear only prompt-level errors. Called during resetExecutionState. */
|
||||
function clearPromptError() {
|
||||
lastPromptError.value = null
|
||||
}
|
||||
|
||||
const lastExecutionErrorNodeLocatorId = computed(() => {
|
||||
const err = lastExecutionError.value
|
||||
if (!err) return null
|
||||
return executionIdToNodeLocatorId(app.rootGraph, String(err.node_id))
|
||||
})
|
||||
|
||||
const lastExecutionErrorNodeId = computed(() => {
|
||||
const locator = lastExecutionErrorNodeLocatorId.value
|
||||
if (!locator) return null
|
||||
const localId = workflowStore.nodeLocatorIdToNodeId(locator)
|
||||
return localId != null ? String(localId) : null
|
||||
})
|
||||
|
||||
/** Whether a runtime execution error is present */
|
||||
const hasExecutionError = computed(() => !!lastExecutionError.value)
|
||||
|
||||
/** Whether a prompt-level error is present (e.g. invalid_prompt, prompt_no_outputs) */
|
||||
const hasPromptError = computed(() => !!lastPromptError.value)
|
||||
|
||||
/** Whether any node validation errors are present */
|
||||
const hasNodeError = computed(
|
||||
() => !!lastNodeErrors.value && Object.keys(lastNodeErrors.value).length > 0
|
||||
)
|
||||
|
||||
/** Whether any error (node validation, runtime execution, or prompt-level) is present */
|
||||
const hasAnyError = computed(
|
||||
() => hasExecutionError.value || hasPromptError.value || hasNodeError.value
|
||||
)
|
||||
|
||||
const allErrorExecutionIds = computed<string[]>(() => {
|
||||
const ids: string[] = []
|
||||
if (lastNodeErrors.value) {
|
||||
ids.push(...Object.keys(lastNodeErrors.value))
|
||||
}
|
||||
if (lastExecutionError.value) {
|
||||
const nodeId = lastExecutionError.value.node_id
|
||||
if (nodeId !== null && nodeId !== undefined) {
|
||||
ids.push(String(nodeId))
|
||||
}
|
||||
}
|
||||
return ids
|
||||
})
|
||||
|
||||
/** Count of prompt-level errors (0 or 1) */
|
||||
const promptErrorCount = computed(() => (lastPromptError.value ? 1 : 0))
|
||||
|
||||
/** Count of all individual node validation errors */
|
||||
const nodeErrorCount = computed(() => {
|
||||
if (!lastNodeErrors.value) return 0
|
||||
let count = 0
|
||||
for (const nodeError of Object.values(lastNodeErrors.value)) {
|
||||
count += nodeError.errors.length
|
||||
}
|
||||
return count
|
||||
})
|
||||
|
||||
/** Count of runtime execution errors (0 or 1) */
|
||||
const executionErrorCount = computed(() => (lastExecutionError.value ? 1 : 0))
|
||||
|
||||
/** Total count of all individual errors */
|
||||
const totalErrorCount = computed(
|
||||
() =>
|
||||
promptErrorCount.value + nodeErrorCount.value + executionErrorCount.value
|
||||
)
|
||||
|
||||
/** Pre-computed Set of graph node IDs (as strings) that have errors in the current graph scope. */
|
||||
const activeGraphErrorNodeIds = computed<Set<string>>(() => {
|
||||
const ids = new Set<string>()
|
||||
if (!app.rootGraph) return ids
|
||||
|
||||
// Fall back to rootGraph when currentGraph hasn't been initialized yet
|
||||
const activeGraph = canvasStore.currentGraph ?? app.rootGraph
|
||||
|
||||
if (lastNodeErrors.value) {
|
||||
for (const executionId of Object.keys(lastNodeErrors.value)) {
|
||||
const graphNode = getNodeByExecutionId(app.rootGraph, executionId)
|
||||
if (graphNode?.graph === activeGraph) {
|
||||
ids.add(String(graphNode.id))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (lastExecutionError.value) {
|
||||
const execNodeId = String(lastExecutionError.value.node_id)
|
||||
const graphNode = getNodeByExecutionId(app.rootGraph, execNodeId)
|
||||
if (graphNode?.graph === activeGraph) {
|
||||
ids.add(String(graphNode.id))
|
||||
}
|
||||
}
|
||||
|
||||
return ids
|
||||
})
|
||||
|
||||
/** Map of node errors indexed by locator ID. */
|
||||
const nodeErrorsByLocatorId = computed<Record<NodeLocatorId, NodeError>>(
|
||||
() => {
|
||||
if (!lastNodeErrors.value) return {}
|
||||
|
||||
const map: Record<NodeLocatorId, NodeError> = {}
|
||||
|
||||
for (const [executionId, nodeError] of Object.entries(
|
||||
lastNodeErrors.value
|
||||
)) {
|
||||
const locatorId = executionIdToNodeLocatorId(app.rootGraph, executionId)
|
||||
if (locatorId) {
|
||||
map[locatorId] = nodeError
|
||||
}
|
||||
}
|
||||
|
||||
return map
|
||||
}
|
||||
)
|
||||
|
||||
/** Get node errors by locator ID. */
|
||||
const getNodeErrors = (
|
||||
nodeLocatorId: NodeLocatorId
|
||||
): NodeError | undefined => {
|
||||
return nodeErrorsByLocatorId.value[nodeLocatorId]
|
||||
}
|
||||
|
||||
/** Check if a specific slot has validation errors. */
|
||||
const slotHasError = (
|
||||
nodeLocatorId: NodeLocatorId,
|
||||
slotName: string
|
||||
): boolean => {
|
||||
const nodeError = getNodeErrors(nodeLocatorId)
|
||||
if (!nodeError) return false
|
||||
|
||||
return nodeError.errors.some((e) => e.extra_info?.input_name === slotName)
|
||||
}
|
||||
|
||||
function hasInternalErrorForNode(nodeId: string | number): boolean {
|
||||
const prefix = `${nodeId}:`
|
||||
return allErrorExecutionIds.value.some((id) => id.startsWith(prefix))
|
||||
}
|
||||
|
||||
/**
|
||||
* Update node and slot error flags when validation errors change.
|
||||
* Propagates errors up subgraph chains.
|
||||
*/
|
||||
watch(lastNodeErrors, () => {
|
||||
if (!app.rootGraph) return
|
||||
|
||||
// Clear all error flags
|
||||
forEachNode(app.rootGraph, (node) => {
|
||||
node.has_errors = false
|
||||
if (node.inputs) {
|
||||
for (const slot of node.inputs) {
|
||||
slot.hasErrors = false
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
if (!lastNodeErrors.value) return
|
||||
|
||||
// Set error flags on nodes and slots
|
||||
for (const [executionId, nodeError] of Object.entries(
|
||||
lastNodeErrors.value
|
||||
)) {
|
||||
const node = getNodeByExecutionId(app.rootGraph, executionId)
|
||||
if (!node) continue
|
||||
|
||||
node.has_errors = true
|
||||
|
||||
// Mark input slots with errors
|
||||
if (node.inputs) {
|
||||
for (const error of nodeError.errors) {
|
||||
const slotName = error.extra_info?.input_name
|
||||
if (!slotName) continue
|
||||
|
||||
const slot = node.inputs.find((s) => s.name === slotName)
|
||||
if (slot) {
|
||||
slot.hasErrors = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Propagate errors to parent subgraph nodes
|
||||
const parts = executionId.split(':')
|
||||
for (let i = parts.length - 1; i > 0; i--) {
|
||||
const parentExecutionId = parts.slice(0, i).join(':')
|
||||
const parentNode = getNodeByExecutionId(
|
||||
app.rootGraph,
|
||||
parentExecutionId
|
||||
)
|
||||
if (parentNode) {
|
||||
parentNode.has_errors = true
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
return {
|
||||
// Raw state
|
||||
lastNodeErrors,
|
||||
lastExecutionError,
|
||||
lastPromptError,
|
||||
|
||||
// Clearing
|
||||
clearAllErrors,
|
||||
clearPromptError,
|
||||
|
||||
// Overlay UI
|
||||
isErrorOverlayOpen,
|
||||
showErrorOverlay,
|
||||
dismissErrorOverlay,
|
||||
|
||||
// Derived state
|
||||
hasExecutionError,
|
||||
hasPromptError,
|
||||
hasNodeError,
|
||||
hasAnyError,
|
||||
allErrorExecutionIds,
|
||||
totalErrorCount,
|
||||
lastExecutionErrorNodeId,
|
||||
activeGraphErrorNodeIds,
|
||||
|
||||
// Lookup helpers
|
||||
getNodeErrors,
|
||||
slotHasError,
|
||||
hasInternalErrorForNode
|
||||
}
|
||||
})
|
||||
Reference in New Issue
Block a user