mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-06-08 23:39:23 +00:00
## Summary This PR introduces the frontend error catalog display resolver as the foundation for the DES-220 / FE-816 error messaging work. The main goal is to create a single FE boundary where raw Core/Cloud error payloads can be converted into human-friendly display fields, while preserving the original API contract fields (`message` and `details`) unchanged. UI components can now prefer resolved display copy when it exists and fall back to the raw API copy otherwise. As a small concrete sample, this PR implements the first cataloged validation error: - `required_input_missing` is resolved as the `missing_connection` catalog item. - Panel title: `Missing connection` - Panel message: `Required input slots have no connection feeding them.` - Detail/item copy can include the node and input name, e.g. `KSampler is missing a required input: model` and `KSampler - model`. - Single-error toast/overlay-oriented fields are added to the data model for follow-up UI work, but this PR does not redesign the overlay. ## What This PR Targets This PR is intentionally scoped as the skeleton PR for the error catalog UX system. It adds: - A new resolver module under `src/platform/errorCatalog`. - Shared resolved display fields: - `catalogId` - `displayTitle` - `displayMessage` - `displayDetails` - `displayItemLabel` - `toastTitle` - `toastMessage` - A resolver entry point for run-time workflow errors: - node validation errors - execution/runtime errors - prompt errors - A resolver entry point for pre-run missing-resource groups: - missing node packs - swap nodes - missing models - missing media - Error group wiring so `useErrorGroups` resolves display copy in one place instead of making UI components own message decisions. - The first real validation rule for `required_input_missing` / Missing connection. - The existing prompt error copy moved into the `errorCatalog.promptErrors` namespace in `src/locales/en/main.json`. - Tests covering the resolver, grouping behavior, panel rendering, prompt error copy, missing group copy, and fallback behavior. ## What This PR Deliberately Does Not Target This PR avoids the larger UX and product behavior changes so the foundation can land separately. It does not: - Redesign the error overlay. - Redesign the right-side error panel. - Change the shape of Core/Cloud API error payloads. - Replace raw `message` / `details`; those remain intact for API-contract alignment and technical debugging. - Re-group execution errors by final message type yet. - Add special runtime error messaging for credits, timeouts, content policy, OOM, or rate limits. - Render the new `displayItemLabel` everywhere it will eventually be useful. ## User-Facing Behavior Most behavior is preserved. The main visible change is for missing required input validation errors. Those now display as Missing connection copy instead of exposing the raw validation message directly. Prompt errors should keep the same user-facing wording as before, but the source of that wording now lives under the error catalog namespace. Missing node/model/media/swap-node groups still preserve the existing titles, counts, and friendly messages, but their display copy now flows through the same resolver boundary. Execution/runtime errors receive catalog fields for future toast/overlay usage, but the current runtime overlay path intentionally keeps the raw technical error copy until the overlay redesign PR decides how to consume the new fields. ## Screenshots Before <img width="505" height="266" alt="스크린샷 2026-05-22 오후 2 15 27" src="https://github.com/user-attachments/assets/09e8eb31-dca4-42d8-8237-9474cb71a14c" /> <img width="463" height="317" alt="스크린샷 2026-05-22 오후 2 16 09" src="https://github.com/user-attachments/assets/c0a0159e-5bd9-4b3f-9c21-c0040373fbca" /> After <img width="482" height="297" alt="스크린샷 2026-05-22 오후 2 14 25" src="https://github.com/user-attachments/assets/4ca10bf0-29d2-4b65-940e-0d78db3fd278" /> <img width="460" height="194" alt="스크린샷 2026-05-22 오후 2 16 55" src="https://github.com/user-attachments/assets/20848054-5012-4dd3-b903-ef8c920f70c8" /> ## Follow-Up PR Plan This PR is the first stacked PR in the error catalog work. Follow-up PRs are expected to build on this foundation in roughly this order: 1. Expand general execution error messaging. - Add broader validation error handling beyond `required_input_missing`, including list/range/value validation cases. - Add general runtime execution messaging. - Continue migrating prompt error display decisions into the catalog resolver. 2. Add special runtime error messaging. - Credits / insufficient credits. - Timeout. - Content not allowed / blocked content. - Server crash. - Out of memory. - Rate limiting. - Other high-volume Cloud-only runtime failures from DES-220. 3. Re-group execution errors by message/catalog type. - Move away from grouping primarily by node class when the cataloged error type is the more useful user-facing grouping key. - Keep raw technical details available inside cards/logs. 4. Update the error overlay behavior. - Use `toastTitle` and `toastMessage` for single-error cases. - Use aggregate copy such as "N errors found" for multi-error cases. - Add node navigation affordances where appropriate. 5. Update the right-side error panel design. - Render resolved item labels such as `Node name - Input name`. - Align expanded card details and logs with the new design. - Preserve copy/debug affordances for technical details. 6. Fold in related missing media/model/node messaging improvements. - FE-583 should become a child/follow-up issue in this stack for improving missing image/media messaging. ## Validation - `pnpm format` - `pnpm lint` - `pnpm typecheck` - `pnpm test:unit` - Targeted resolver/grouping tests during review iterations - `pnpm knip` `pnpm knip` passes with only the pre-existing tag hint: `Unused tag in src/scripts/metadata/flac.ts: getFromFlacBuffer → @knipIgnoreUnusedButUsedByCustomNodes`
194 lines
5.8 KiB
Vue
194 lines
5.8 KiB
Vue
<template>
|
|
<div class="flex min-h-0 flex-1 flex-col overflow-hidden">
|
|
<!-- Card Header -->
|
|
<div
|
|
v-if="card.nodeId && !compact"
|
|
class="flex flex-wrap items-center gap-2 py-2"
|
|
>
|
|
<span
|
|
v-if="showNodeIdBadge"
|
|
class="shrink-0 rounded-md bg-secondary-background-selected px-2 py-0.5 font-mono text-xs font-bold text-muted-foreground"
|
|
>
|
|
#{{ card.nodeId }}
|
|
</span>
|
|
<span
|
|
v-if="card.nodeTitle || card.title"
|
|
class="flex-1 truncate text-sm font-medium text-muted-foreground"
|
|
>
|
|
{{ card.nodeTitle || card.title }}
|
|
</span>
|
|
<div class="flex shrink-0 items-center">
|
|
<Button
|
|
v-if="card.isSubgraphNode"
|
|
variant="secondary"
|
|
size="sm"
|
|
class="h-8 shrink-0 rounded-lg text-sm"
|
|
@click.stop="handleEnterSubgraph"
|
|
>
|
|
{{ t('rightSidePanel.enterSubgraph') }}
|
|
</Button>
|
|
<Button
|
|
variant="textonly"
|
|
size="icon-sm"
|
|
class="size-8 shrink-0 text-muted-foreground hover:text-base-foreground"
|
|
:aria-label="t('rightSidePanel.locateNode')"
|
|
@click.stop="handleLocateNode"
|
|
>
|
|
<i class="icon-[lucide--locate] size-4" />
|
|
</Button>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Multiple Errors within one Card -->
|
|
<div
|
|
class="flex min-h-0 flex-1 flex-col space-y-4 divide-y divide-interface-stroke/20"
|
|
>
|
|
<!-- Card Content -->
|
|
<div
|
|
v-for="(error, idx) in card.errors"
|
|
:key="idx"
|
|
:class="
|
|
cn(
|
|
'flex min-h-0 flex-col gap-3',
|
|
fullHeight && error.isRuntimeError && 'flex-1'
|
|
)
|
|
"
|
|
>
|
|
<!-- Human-friendly category/title when resolved by the error catalog. -->
|
|
<p
|
|
v-if="error.displayTitle"
|
|
class="m-0 px-0.5 text-sm font-semibold text-destructive-background-hover"
|
|
>
|
|
{{ error.displayTitle }}
|
|
</p>
|
|
|
|
<!-- Error Message -->
|
|
<p
|
|
v-if="getDisplayMessage(error)"
|
|
class="m-0 max-h-[4lh] overflow-y-auto px-0.5 text-sm/relaxed wrap-break-word whitespace-pre-wrap"
|
|
>
|
|
{{ getDisplayMessage(error) }}
|
|
</p>
|
|
|
|
<!-- Traceback / Details (enriched with full report for runtime errors) -->
|
|
<div
|
|
v-if="displayedDetailsMap[idx]"
|
|
:class="
|
|
cn(
|
|
'overflow-y-auto rounded-lg border border-interface-stroke/30 bg-secondary-background-hover p-2.5',
|
|
error.isRuntimeError
|
|
? fullHeight
|
|
? 'min-h-0 flex-1'
|
|
: 'max-h-[15lh]'
|
|
: 'max-h-[6lh]'
|
|
)
|
|
"
|
|
>
|
|
<p
|
|
class="m-0 font-mono text-xs/relaxed wrap-break-word whitespace-pre-wrap text-muted-foreground"
|
|
>
|
|
{{ displayedDetailsMap[idx] }}
|
|
</p>
|
|
</div>
|
|
|
|
<div class="flex flex-col gap-2">
|
|
<div class="flex gap-2">
|
|
<Button
|
|
v-tooltip.top="t('rightSidePanel.findOnGithubTooltip')"
|
|
variant="secondary"
|
|
size="sm"
|
|
class="h-8 w-2/3 justify-center gap-1 rounded-lg text-xs"
|
|
data-testid="error-card-find-on-github"
|
|
@click="handleCheckGithub(error)"
|
|
>
|
|
{{ t('g.findOnGithub') }}
|
|
<i class="icon-[lucide--github] size-3.5" />
|
|
</Button>
|
|
<Button
|
|
variant="secondary"
|
|
size="sm"
|
|
class="h-8 w-1/3 justify-center gap-1 rounded-lg text-xs"
|
|
data-testid="error-card-copy"
|
|
@click="handleCopyError(idx)"
|
|
>
|
|
{{ t('g.copy') }}
|
|
<i class="icon-[lucide--copy] size-3.5" />
|
|
</Button>
|
|
</div>
|
|
<Button
|
|
v-tooltip.top="t('rightSidePanel.getHelpTooltip')"
|
|
variant="secondary"
|
|
size="sm"
|
|
class="h-8 w-full justify-center gap-1 rounded-lg text-xs"
|
|
@click="handleGetHelp"
|
|
>
|
|
{{ t('g.getHelpAction') }}
|
|
<i class="icon-[lucide--external-link] size-3.5" />
|
|
</Button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
import { useI18n } from 'vue-i18n'
|
|
|
|
import Button from '@/components/ui/button/Button.vue'
|
|
import { cn } from '@comfyorg/tailwind-utils'
|
|
|
|
import type { ErrorCardData, ErrorItem } from './types'
|
|
import { useErrorActions } from './useErrorActions'
|
|
import { useErrorReport } from './useErrorReport'
|
|
|
|
const {
|
|
card,
|
|
showNodeIdBadge = false,
|
|
compact = false,
|
|
fullHeight = false
|
|
} = defineProps<{
|
|
card: ErrorCardData
|
|
showNodeIdBadge?: boolean
|
|
/** Hide card header and error message (used in single-node selection mode) */
|
|
compact?: boolean
|
|
/** Allow runtime error details to fill available height (used in dedicated panel) */
|
|
fullHeight?: boolean
|
|
}>()
|
|
|
|
const emit = defineEmits<{
|
|
locateNode: [nodeId: string]
|
|
enterSubgraph: [nodeId: string]
|
|
copyToClipboard: [text: string]
|
|
}>()
|
|
|
|
const { t } = useI18n()
|
|
const { displayedDetailsMap } = useErrorReport(() => card)
|
|
const { findOnGitHub, contactSupport: handleGetHelp } = useErrorActions()
|
|
|
|
function handleLocateNode() {
|
|
if (card.nodeId) {
|
|
emit('locateNode', card.nodeId)
|
|
}
|
|
}
|
|
|
|
function handleEnterSubgraph() {
|
|
if (card.nodeId) {
|
|
emit('enterSubgraph', card.nodeId)
|
|
}
|
|
}
|
|
|
|
function handleCopyError(idx: number) {
|
|
const details = displayedDetailsMap.value[idx]
|
|
const message = getDisplayMessage(card.errors[idx])
|
|
emit('copyToClipboard', [message, details].filter(Boolean).join('\n\n'))
|
|
}
|
|
|
|
function handleCheckGithub(error: ErrorItem) {
|
|
findOnGitHub(error.message)
|
|
}
|
|
|
|
function getDisplayMessage(error: ErrorItem | undefined) {
|
|
return error?.displayMessage ?? error?.message
|
|
}
|
|
</script>
|