-
Notifications
You must be signed in to change notification settings - Fork 517
feat: add node replacement UI to Errors Tab #9253
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
Changes from all commits
0d58a92
83bb430
7d69a0d
5aa4baf
1be6d27
b585dfa
1d8a01c
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,150 @@ | ||
| <template> | ||
| <div class="flex flex-col w-full mb-4"> | ||
| <!-- Type header row: type name + chevron --> | ||
| <div class="flex h-8 items-center w-full"> | ||
| <p | ||
| class="flex-1 min-w-0 text-sm font-medium overflow-hidden text-ellipsis whitespace-nowrap text-foreground" | ||
| > | ||
| {{ `${group.type} (${group.nodeTypes.length})` }} | ||
| </p> | ||
|
|
||
| <Button | ||
| variant="textonly" | ||
| size="icon-sm" | ||
| :class=" | ||
| cn( | ||
| 'size-8 shrink-0 transition-transform duration-200 hover:bg-transparent', | ||
| { 'rotate-180': expanded } | ||
| ) | ||
| " | ||
| :aria-label=" | ||
| expanded | ||
| ? t('rightSidePanel.missingNodePacks.collapse', 'Collapse') | ||
| : t('rightSidePanel.missingNodePacks.expand', 'Expand') | ||
| " | ||
| @click="toggleExpand" | ||
| > | ||
| <i | ||
| class="icon-[lucide--chevron-down] size-4 text-muted-foreground group-hover:text-base-foreground" | ||
| /> | ||
| </Button> | ||
| </div> | ||
|
|
||
| <!-- Sub-labels: individual node instances, each with their own Locate button --> | ||
| <TransitionCollapse> | ||
| <div | ||
| v-if="expanded" | ||
| class="flex flex-col gap-0.5 pl-2 mb-2 overflow-hidden" | ||
| > | ||
| <div | ||
| v-for="nodeType in group.nodeTypes" | ||
| :key="getKey(nodeType)" | ||
| class="flex h-7 items-center" | ||
| > | ||
| <span | ||
| v-if=" | ||
| showNodeIdBadge && | ||
| typeof nodeType !== 'string' && | ||
| nodeType.nodeId != null | ||
| " | ||
| class="shrink-0 rounded-md bg-secondary-background-selected px-2 py-0.5 text-xs font-mono text-muted-foreground font-bold mr-1" | ||
| > | ||
| #{{ nodeType.nodeId }} | ||
| </span> | ||
| <p class="flex-1 min-w-0 text-xs text-muted-foreground truncate"> | ||
| {{ getLabel(nodeType) }} | ||
| </p> | ||
| <Button | ||
| v-if="typeof nodeType !== 'string' && nodeType.nodeId != null" | ||
| variant="textonly" | ||
| size="icon-sm" | ||
| class="size-6 text-muted-foreground hover:text-base-foreground shrink-0 mr-1" | ||
| :aria-label="t('rightSidePanel.locateNode', 'Locate Node')" | ||
| @click="handleLocateNode(nodeType)" | ||
| > | ||
| <i class="icon-[lucide--locate] size-3" /> | ||
| </Button> | ||
| </div> | ||
| </div> | ||
| </TransitionCollapse> | ||
|
|
||
| <!-- Description rows: what it is replaced by --> | ||
| <div class="flex flex-col text-[13px] mb-2 mt-1 px-1 gap-0.5"> | ||
| <span class="text-muted-foreground">{{ | ||
| t('nodeReplacement.willBeReplacedBy', 'This node will be replaced by:') | ||
| }}</span> | ||
| <span class="font-bold text-foreground">{{ | ||
| group.newNodeId ?? t('nodeReplacement.unknownNode', 'Unknown') | ||
| }}</span> | ||
| </div> | ||
|
|
||
| <!-- Replace Action Button --> | ||
| <div class="flex items-start w-full pt-1 pb-1"> | ||
| <Button | ||
| variant="secondary" | ||
| size="md" | ||
| class="flex flex-1 w-full" | ||
| @click="handleReplaceNode" | ||
| > | ||
| <i class="icon-[lucide--repeat] size-4 text-foreground shrink-0 mr-1" /> | ||
| <span class="text-sm text-foreground truncate min-w-0"> | ||
| {{ t('nodeReplacement.replaceNode', 'Replace Node') }} | ||
| </span> | ||
| </Button> | ||
| </div> | ||
| </div> | ||
| </template> | ||
|
|
||
| <script setup lang="ts"> | ||
| import { ref } from 'vue' | ||
| import { cn } from '@/utils/tailwindUtil' | ||
| import { useI18n } from 'vue-i18n' | ||
| import Button from '@/components/ui/button/Button.vue' | ||
| import TransitionCollapse from '@/components/rightSidePanel/layout/TransitionCollapse.vue' | ||
| import type { MissingNodeType } from '@/types/comfy' | ||
| import type { SwapNodeGroup } from './useErrorGroups' | ||
| import { useNodeReplacement } from '@/platform/nodeReplacement/useNodeReplacement' | ||
| import { useExecutionErrorStore } from '@/stores/executionErrorStore' | ||
|
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. suggestion (non-blocking): this component directly imports and calls @coderabbitai Make an Issue to do this in a followup PR on a future date
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. 🧩 Analysis chain🏁 Script executed: Length of output: 57
I've opened The issue has been assigned to you and labeled as an enhancement. 🧠 Learnings used |
||
|
|
||
| const props = defineProps<{ | ||
| group: SwapNodeGroup | ||
| showNodeIdBadge: boolean | ||
| }>() | ||
|
|
||
| const emit = defineEmits<{ | ||
| 'locate-node': [nodeId: string] | ||
| }>() | ||
|
|
||
| const { t } = useI18n() | ||
| const { replaceNodesInPlace } = useNodeReplacement() | ||
| const executionErrorStore = useExecutionErrorStore() | ||
|
|
||
| const expanded = ref(false) | ||
|
|
||
| function toggleExpand() { | ||
| expanded.value = !expanded.value | ||
| } | ||
|
|
||
| function getKey(nodeType: MissingNodeType): string { | ||
| if (typeof nodeType === 'string') return nodeType | ||
| return nodeType.nodeId != null ? String(nodeType.nodeId) : nodeType.type | ||
| } | ||
|
|
||
| function getLabel(nodeType: MissingNodeType): string { | ||
| return typeof nodeType === 'string' ? nodeType : nodeType.type | ||
| } | ||
|
|
||
| function handleLocateNode(nodeType: MissingNodeType) { | ||
| if (typeof nodeType === 'string') return | ||
| if (nodeType.nodeId != null) { | ||
| emit('locate-node', String(nodeType.nodeId)) | ||
| } | ||
| } | ||
|
|
||
| function handleReplaceNode() { | ||
| const replaced = replaceNodesInPlace(props.group.nodeTypes) | ||
| if (replaced.length > 0) { | ||
| executionErrorStore.removeMissingNodesByType([props.group.type]) | ||
| } | ||
| } | ||
jaeone94 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| </script> | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,38 @@ | ||
| <template> | ||
| <div class="px-4 pb-2 mt-2"> | ||
| <!-- Sub-label: guidance message shown above all swap groups --> | ||
| <p class="m-0 pb-5 text-sm text-muted-foreground leading-relaxed"> | ||
| {{ | ||
| t( | ||
| 'nodeReplacement.swapNodesGuide', | ||
| 'The following nodes can be automatically replaced with compatible alternatives.' | ||
| ) | ||
| }} | ||
| </p> | ||
| <!-- Group Rows --> | ||
| <SwapNodeGroupRow | ||
| v-for="group in swapNodeGroups" | ||
| :key="group.type" | ||
| :group="group" | ||
| :show-node-id-badge="showNodeIdBadge" | ||
| @locate-node="emit('locate-node', $event)" | ||
| /> | ||
| </div> | ||
| </template> | ||
|
|
||
| <script setup lang="ts"> | ||
| import { useI18n } from 'vue-i18n' | ||
| import type { SwapNodeGroup } from './useErrorGroups' | ||
| import SwapNodeGroupRow from './SwapNodeGroupRow.vue' | ||
|
|
||
| const { t } = useI18n() | ||
|
|
||
| const { swapNodeGroups, showNodeIdBadge } = defineProps<{ | ||
| swapNodeGroups: SwapNodeGroup[] | ||
| showNodeIdBadge: boolean | ||
| }>() | ||
|
|
||
| const emit = defineEmits<{ | ||
| 'locate-node': [nodeId: string] | ||
| }>() | ||
| </script> |
Uh oh!
There was an error while loading. Please reload this page.