mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-02-19 22:34:15 +00:00
## Summary Add a dedicated **Errors tab** to the Right Side Panel that displays prompt-level, node validation, and runtime execution errors in a unified, searchable, grouped view — replacing the need to rely solely on modal dialogs for error inspection. ## Changes - **What**: - **New components** (`errors/` directory): - `TabErrors.vue` — Main error tab with search, grouping by class type, and canvas navigation (locate node / enter subgraph). - `ErrorNodeCard.vue` — Renders a single error card with node ID badge, title, action buttons, and error details. - `types.ts` — Shared type definitions (`ErrorItem`, `ErrorCardData`, `ErrorGroup`). - **`executionStore.ts`** — Added `PromptError` interface, `lastPromptError` ref and `hasAnyError` computed getter. Clears `lastPromptError` alongside existing error state on execution start and graph clear. - **`rightSidePanelStore.ts`** — Registered `'errors'` as a valid tab value. - **`app.ts`** — On prompt submission failure (`PromptExecutionError`), stores prompt-level errors (when no node errors exist) into `lastPromptError`. On both runtime execution error and prompt error, deselects all nodes and opens the errors tab automatically. - **`RightSidePanel.vue`** — Shows the `'errors'` tab (with ⚠ icon) when errors exist and no node is selected. Routes to `TabErrors` component. - **`TopMenuSection.vue`** — Highlights the action bar with a red border when any error exists, using `hasAnyError`. - **`SectionWidgets.vue`** — Detects per-node errors by matching execution IDs to graph node IDs. Shows an error icon (⚠) and "See Error" button that navigates to the errors tab. - **`en/main.json`** — Added i18n keys: `errors`, `noErrors`, `enterSubgraph`, `seeError`, `promptErrors.*`, and `errorHelp*`. - **Testing**: 6 unit tests (`TabErrors.test.ts`) covering prompt/node/runtime errors, search filtering, and clipboard copy. - **Storybook**: 7 stories (`ErrorNodeCard.stories.ts`) for badge visibility, subgraph buttons, multiple errors, runtime tracebacks, and prompt-only errors. - **Breaking**: None - **Dependencies**: None — uses only existing project dependencies (`vue-i18n`, `pinia`, `primevue`) ## Related Work > **Note**: Upstream PR #8603 (`New bottom button and badges`) introduced a separate `TabError.vue` (singular) that shows per-node errors when a specific node is selected. Our `TabErrors.vue` (plural) provides the **global error overview** — a different scope. The two tabs coexist: > - `'error'` (singular) → appears when a node with errors is selected → shows only that node's errors > - `'errors'` (plural) → appears when no node is selected and errors exist → shows all errors grouped by class type > > A future consolidation of these two tabs may be desirable after design review. ## Architecture ``` executionStore ├── lastPromptError: PromptError | null ← NEW (prompt-level errors without node IDs) ├── lastNodeErrors: Record<string, NodeError> (existing) ├── lastExecutionError: ExecutionError (existing) └── hasAnyError: ComputedRef<boolean> ← NEW (centralized error detection) TabErrors.vue (errors tab - global view) ├── errorGroups: ComputedRef<ErrorGroup[]> ← normalizes all 3 error sources ├── filteredGroups ← search-filtered view ├── locateNode() ← pan canvas to node ├── enterSubgraph() ← navigate into subgraph └── ErrorNodeCard.vue ← per-node card with copy/locate actions types.ts ├── ErrorItem { message, details?, isRuntimeError? } ├── ErrorCardData { id, title, nodeId?, errors[] } └── ErrorGroup { title, cards[], priority } ``` ## Review Focus 1. **Error normalization logic** (`TabErrors.vue` L75–150): Three different error sources (prompt, node validation, runtime) are normalized into a common `ErrorGroup → ErrorCardData → ErrorItem` hierarchy. Edge cases to verify: - Prompt errors with known vs unknown types (known types use localized descriptions) - Multiple errors on the same node (grouped into one card) - Runtime errors with long tracebacks (capped height with scroll) 2. **Canvas navigation** (`TabErrors.vue` L210–250): The `locateNode` and `enterSubgraph` functions navigate to potentially nested subgraphs. The double `requestAnimationFrame` is required due to LiteGraph's asynchronous subgraph switching — worth verifying this timing is sufficient. 3. **Store getter consolidation**: `hasAnyError` replaces duplicated logic in `TopMenuSection` and `RightSidePanel`. Confirm that the reactive dependency chain works correctly (it depends on 3 separate refs). 4. **Coexistence with upstream `TabError.vue`**: The singular `'error'` tab (upstream, PR #8603) and our plural `'errors'` tab serve different purposes but share similar naming. Consider whether a unified approach is preferred. ## Test Results ``` ✓ renders "no errors" state when store is empty ✓ renders prompt-level errors (Group title = error message) ✓ renders node validation errors grouped by class_type ✓ renders runtime execution errors from WebSocket ✓ filters errors based on search query ✓ calls copyToClipboard when copy button is clicked Test Files 1 passed (1) Tests 6 passed (6) ``` ## Screenshots (if applicable) <img width="1238" height="1914" alt="image" src="https://github.com/user-attachments/assets/ec39b872-cca1-4076-8795-8bc7c05dc665" /> <img width="669" height="1028" alt="image" src="https://github.com/user-attachments/assets/bdcaa82a-34b0-46a5-a08f-14950c5a479b" /> <img width="644" height="1005" alt="image" src="https://github.com/user-attachments/assets/ffef38c6-8f42-4c01-a0de-11709d54b638" /> <img width="672" height="505" alt="image" src="https://github.com/user-attachments/assets/5cff7f57-8d79-4808-a71e-9ad05bab6e17" /> ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-8807-Feat-errors-tab-panel-3046d73d36508127981ac670a70da467) by [Unito](https://www.unito.io)
99 lines
2.5 KiB
TypeScript
99 lines
2.5 KiB
TypeScript
import { defineStore } from 'pinia'
|
|
import { computed, ref, watch } from 'vue'
|
|
|
|
import { useSettingStore } from '@/platform/settings/settingStore'
|
|
|
|
export type RightSidePanelTab =
|
|
| 'error'
|
|
| 'parameters'
|
|
| 'nodes'
|
|
| 'settings'
|
|
| 'info'
|
|
| 'subgraph'
|
|
| 'errors'
|
|
|
|
type RightSidePanelSection = 'advanced-inputs' | string
|
|
|
|
/**
|
|
* Store for managing the right side panel state.
|
|
* This panel displays properties and settings for selected nodes.
|
|
*/
|
|
export const useRightSidePanelStore = defineStore('rightSidePanel', () => {
|
|
const settingStore = useSettingStore()
|
|
|
|
const isLegacyMenu = computed(
|
|
() => settingStore.get('Comfy.UseNewMenu') === 'Disabled'
|
|
)
|
|
|
|
const isOpen = computed({
|
|
get: () =>
|
|
!isLegacyMenu.value && settingStore.get('Comfy.RightSidePanel.IsOpen'),
|
|
set: (value: boolean) =>
|
|
settingStore.set('Comfy.RightSidePanel.IsOpen', value)
|
|
})
|
|
const activeTab = ref<RightSidePanelTab>('parameters')
|
|
const isEditingSubgraph = computed(() => activeTab.value === 'subgraph')
|
|
const focusedSection = ref<RightSidePanelSection | null>(null)
|
|
/**
|
|
* Graph node ID to focus in the errors tab.
|
|
* Set by SectionWidgets when the user clicks "See Error", consumed and
|
|
* cleared by TabErrors after expanding the relevant error group.
|
|
*/
|
|
const focusedErrorNodeId = ref<string | null>(null)
|
|
const searchQuery = ref('')
|
|
|
|
// Auto-close panel when switching to legacy menu mode
|
|
watch(isLegacyMenu, (legacy) => {
|
|
if (legacy) {
|
|
void settingStore.set('Comfy.RightSidePanel.IsOpen', false)
|
|
}
|
|
})
|
|
|
|
function openPanel(tab?: RightSidePanelTab) {
|
|
if (isLegacyMenu.value) return
|
|
isOpen.value = true
|
|
if (tab) {
|
|
activeTab.value = tab
|
|
}
|
|
}
|
|
|
|
function closePanel() {
|
|
isOpen.value = false
|
|
}
|
|
|
|
function togglePanel() {
|
|
isOpen.value = !isOpen.value
|
|
}
|
|
|
|
/**
|
|
* Focus on a specific section in the right side panel.
|
|
* This will open the panel, switch to the parameters tab, and signal
|
|
* the component to expand and scroll to the section.
|
|
*/
|
|
function focusSection(section: RightSidePanelSection) {
|
|
openPanel('parameters')
|
|
focusedSection.value = section
|
|
}
|
|
|
|
/**
|
|
* Clear the focused section after it has been handled.
|
|
*/
|
|
function clearFocusedSection() {
|
|
focusedSection.value = null
|
|
}
|
|
|
|
return {
|
|
isOpen,
|
|
activeTab,
|
|
isEditingSubgraph,
|
|
focusedSection,
|
|
focusedErrorNodeId,
|
|
searchQuery,
|
|
openPanel,
|
|
closePanel,
|
|
togglePanel,
|
|
focusSection,
|
|
clearFocusedSection
|
|
}
|
|
})
|