-
Notifications
You must be signed in to change notification settings - Fork 622
Feat/errors tab panel #8807
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Feat/errors tab panel #8807
Changes from 7 commits
7664203
de6f566
469cf80
4d9e49c
65c0c22
3a1b105
fdb93ba
ffe4148
321578d
428e75f
e0b89a9
1be63d2
f9cecff
3a844a3
3bbb3f8
3fa2e92
baa52a7
e58c3e4
d414f11
b1ec77d
b2208ef
c37898f
d3bec55
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,170 @@ | ||
| import type { Meta, StoryObj } from '@storybook/vue3-vite' | ||
| import ErrorNodeCard from './ErrorNodeCard.vue' | ||
| import type { ErrorCardData } from './types' | ||
|
|
||
| /** | ||
| * ErrorNodeCard displays a single error card inside the error tab. | ||
| * It shows the node header (ID badge, title, action buttons) | ||
| * and the list of error items (message, traceback, copy button). | ||
| */ | ||
| const meta: Meta<typeof ErrorNodeCard> = { | ||
| title: 'RightSidePanel/Errors/ErrorNodeCard', | ||
| component: ErrorNodeCard, | ||
| parameters: { | ||
| layout: 'centered' | ||
| }, | ||
| argTypes: { | ||
| showNodeIdBadge: { control: 'boolean' } | ||
| }, | ||
| decorators: [ | ||
| (story) => ({ | ||
| components: { story }, | ||
| template: | ||
| '<div class="w-[330px] bg-base-surface border border-interface-stroke rounded-lg p-4"><story /></div>' | ||
| }) | ||
| ] | ||
| } | ||
|
|
||
| export default meta | ||
| type Story = StoryObj<typeof meta> | ||
|
|
||
| // ─── Sample Data ──────────────────────────────────────────────────────────── | ||
|
|
||
| const singleErrorCard: ErrorCardData = { | ||
| id: 'node-10', | ||
| title: 'CLIPTextEncode', | ||
| nodeId: '10', | ||
| nodeTitle: 'CLIP Text Encode (Prompt)', | ||
| isSubgraphNode: false, | ||
| errors: [ | ||
| { | ||
| message: 'Required input "text" is missing.', | ||
| details: 'Input: text\nExpected: STRING' | ||
| } | ||
| ] | ||
| } | ||
|
|
||
| const multipleErrorsCard: ErrorCardData = { | ||
| id: 'node-24', | ||
| title: 'VAEDecode', | ||
| nodeId: '24', | ||
| nodeTitle: 'VAE Decode', | ||
| isSubgraphNode: false, | ||
| errors: [ | ||
| { | ||
| message: 'Required input "samples" is missing.', | ||
| details: '' | ||
| }, | ||
| { | ||
| message: 'Value "NaN" is not a valid number for "strength".', | ||
| details: 'Expected: FLOAT [0.0 .. 1.0]' | ||
| } | ||
| ] | ||
| } | ||
|
|
||
| const runtimeErrorCard: ErrorCardData = { | ||
| id: 'exec-45', | ||
| title: 'KSampler', | ||
| nodeId: '45', | ||
| nodeTitle: 'KSampler', | ||
| isSubgraphNode: false, | ||
| errors: [ | ||
| { | ||
| message: 'OutOfMemoryError: CUDA out of memory. Tried to allocate 1.2GB.', | ||
| details: [ | ||
| 'Traceback (most recent call last):', | ||
| ' File "ksampler.py", line 142, in sample', | ||
| ' samples = model.apply(latent)', | ||
| 'RuntimeError: CUDA out of memory.' | ||
| ].join('\n'), | ||
| isRuntimeError: true | ||
| } | ||
| ] | ||
| } | ||
|
|
||
| const subgraphErrorCard: ErrorCardData = { | ||
| id: 'node-3:15', | ||
| title: 'KSampler', | ||
| nodeId: '3:15', | ||
| nodeTitle: 'Nested KSampler', | ||
| isSubgraphNode: true, | ||
| errors: [ | ||
| { | ||
| message: 'Latent input is required.', | ||
| details: '' | ||
| } | ||
| ] | ||
| } | ||
|
|
||
| const promptOnlyCard: ErrorCardData = { | ||
| id: '__prompt__', | ||
| title: 'Prompt has no outputs.', | ||
| errors: [ | ||
| { | ||
| message: | ||
| 'The workflow does not contain any output nodes (e.g. Save Image, Preview Image) to produce a result.' | ||
| } | ||
| ] | ||
| } | ||
|
|
||
| // ─── Stories: Badge Visibility ────────────────────────────────────────────── | ||
|
christian-byrne marked this conversation as resolved.
Outdated
|
||
|
|
||
| /** Single validation error with node ID badge visible */ | ||
| export const WithNodeIdBadge: Story = { | ||
| args: { | ||
| card: singleErrorCard, | ||
| showNodeIdBadge: true | ||
| } | ||
| } | ||
|
|
||
| /** Single validation error without node ID badge */ | ||
| export const WithoutNodeIdBadge: Story = { | ||
| args: { | ||
| card: singleErrorCard, | ||
| showNodeIdBadge: false | ||
| } | ||
| } | ||
|
|
||
| // ─── Stories: Subgraph Button ─────────────────────────────────────────────── | ||
|
|
||
| /** Subgraph node error — shows "Enter subgraph" button */ | ||
| export const WithEnterSubgraphButton: Story = { | ||
| args: { | ||
| card: subgraphErrorCard, | ||
| showNodeIdBadge: true | ||
| } | ||
| } | ||
|
|
||
| /** Regular node error — no "Enter subgraph" button */ | ||
| export const WithoutEnterSubgraphButton: Story = { | ||
| args: { | ||
| card: singleErrorCard, | ||
| showNodeIdBadge: true | ||
| } | ||
| } | ||
|
|
||
| // ─── Stories: Error Variants ──────────────────────────────────────────────── | ||
|
|
||
| /** Multiple validation errors on one node */ | ||
| export const MultipleErrors: Story = { | ||
| args: { | ||
| card: multipleErrorsCard, | ||
| showNodeIdBadge: true | ||
| } | ||
| } | ||
|
|
||
| /** Runtime execution error with full traceback */ | ||
| export const RuntimeError: Story = { | ||
| args: { | ||
| card: runtimeErrorCard, | ||
| showNodeIdBadge: true | ||
| } | ||
| } | ||
|
|
||
| /** Prompt-level error (no node header) */ | ||
| export const PromptError: Story = { | ||
| args: { | ||
| card: promptOnlyCard, | ||
| showNodeIdBadge: false | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,111 @@ | ||
| <script setup lang="ts"> | ||
| /** | ||
| * ErrorNodeCard.vue | ||
| * | ||
| * Displays a single error card within the error tab. | ||
| * Contains the node header (ID badge, title, action buttons) | ||
| * and the list of errors (message, traceback, copy button). | ||
| */ | ||
| import { useI18n } from 'vue-i18n' | ||
|
|
||
| import Button from '@/components/ui/button/Button.vue' | ||
| import { cn } from '@/utils/tailwindUtil' | ||
|
|
||
| import type { ErrorCardData } from './types' | ||
|
|
||
| const { card, showNodeIdBadge = false } = defineProps<{ | ||
| card: ErrorCardData | ||
| showNodeIdBadge?: boolean | ||
| }>() | ||
|
|
||
| /** | ||
| * @event locateNode - Pans the canvas to center on the node with the given ID. | ||
| * @event enterSubgraph - Opens the subgraph that contains the specified node. | ||
| * @event copyToClipboard - Copies the provided error text to the system clipboard. | ||
| */ | ||
| const emit = defineEmits<{ | ||
| locateNode: [nodeId: string] | ||
| enterSubgraph: [nodeId: string] | ||
| copyToClipboard: [text: string] | ||
| }>() | ||
|
|
||
| const { t } = useI18n() | ||
| </script> | ||
|
|
||
| <template> | ||
| <div class="overflow-hidden"> | ||
| <!-- Card Header (Node ID & Actions) --> | ||
| <div v-if="card.nodeId" 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 text-[10px] font-mono text-muted-foreground font-bold" | ||
| > | ||
| #{{ card.nodeId }} | ||
| </span> | ||
| <span | ||
| v-if="card.nodeTitle" | ||
| class="flex-1 text-sm text-muted-foreground truncate font-medium" | ||
| > | ||
| {{ card.nodeTitle }} | ||
| </span> | ||
| <Button | ||
| v-if="card.isSubgraphNode" | ||
| variant="secondary" | ||
| size="sm" | ||
| class="rounded-lg text-sm shrink-0" | ||
| @click.stop="emit('enterSubgraph', card.nodeId!)" | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: Can we use a fallback instead of non-null assertion(
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done. |
||
| > | ||
| {{ t('rightSidePanel.enterSubgraph') }} | ||
| </Button> | ||
| <Button | ||
| variant="textonly" | ||
| size="icon-sm" | ||
| class="size-7 text-muted-foreground hover:text-base-foreground shrink-0" | ||
| @click.stop="emit('locateNode', card.nodeId!)" | ||
|
christian-byrne marked this conversation as resolved.
Outdated
|
||
| > | ||
| <i class="icon-[lucide--locate] size-3.5" /> | ||
| </Button> | ||
| </div> | ||
|
|
||
| <!-- Multiple Errors within one Card --> | ||
| <div class="divide-y divide-interface-stroke/20 space-y-4"> | ||
| <!-- Card Content --> | ||
| <div | ||
| v-for="(error, idx) in card.errors" | ||
| :key="idx" | ||
| class="flex flex-col gap-3" | ||
| > | ||
| <!-- Error Message --> | ||
| <p | ||
| v-if="error.message" | ||
| class="m-0 text-sm break-words whitespace-pre-wrap leading-relaxed px-0.5" | ||
| > | ||
| {{ error.message }} | ||
| </p> | ||
|
|
||
| <!-- Traceback / Details --> | ||
| <div | ||
| v-if="error.details" | ||
| :class="cn( | ||
| 'rounded-lg bg-secondary-background-hover p-2.5 overflow-y-auto border border-interface-stroke/30', | ||
| error.isRuntimeError ? 'max-h-[10lh]' : 'max-h-[6lh]' | ||
| )" | ||
| > | ||
|
coderabbitai[bot] marked this conversation as resolved.
|
||
| <p class="m-0 text-[11px] text-muted-foreground break-words whitespace-pre-wrap font-mono leading-relaxed"> | ||
| {{ error.details }} | ||
| </p> | ||
| </div> | ||
|
|
||
| <Button | ||
| variant="secondary" | ||
| size="sm" | ||
| class="w-full justify-center gap-2 h-8 text-[11px]" | ||
| @click="emit('copyToClipboard', [error.message, error.details].filter(Boolean).join('\n\n'))" | ||
| > | ||
| <i class="icon-[lucide--copy] size-3.5" /> | ||
| {{ t('g.copy') }} | ||
| </Button> | ||
| </div> | ||
| </div> | ||
| </div> | ||
| </template> | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
hasAnyErroris renamed tohasPromptErrorvia destructuring, but this computed covers node errors and runtime errors too — not just prompt errors. Why was it renamed?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done