mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-04-20 06:20:11 +00:00
- Remove TabError.vue; consolidate all error display into TabErrors.vue and remove the separate 'error' tab type from rightSidePanelStore - Single-node selection mode: regroup errors by message instead of class_type and render ErrorNodeCard in compact mode (hiding redundant header/message) - Container node support: detect internal errors in subgraph/group nodes by matching execution ID prefixes against selected container node IDs - SectionWidgets: show error badge and 'See Error' button for subgraph/group nodes that contain child-node errors, navigating directly to the errors tab - Add ErrorOverlay component: floating card after execution failure showing a deduplicated error summary with 'Dismiss' and 'See Errors' actions; 'See Errors' deselects all nodes and opens Errors tab in overview mode - Add isErrorOverlayOpen, showErrorOverlay, dismissErrorOverlay to executionStore; reset overlay state on execution_start
165 lines
5.2 KiB
Vue
165 lines
5.2 KiB
Vue
<template>
|
|
<div class="flex flex-col h-full min-w-0">
|
|
<!-- Search bar -->
|
|
<div
|
|
class="px-4 pt-1 pb-4 flex gap-2 border-b border-interface-stroke shrink-0 min-w-0"
|
|
>
|
|
<FormSearchInput v-model="searchQuery" />
|
|
</div>
|
|
|
|
<!-- Scrollable content -->
|
|
<div class="flex-1 overflow-y-auto min-w-0">
|
|
<div
|
|
v-if="filteredGroups.length === 0"
|
|
class="text-sm text-muted-foreground px-4 text-center pt-5 pb-15"
|
|
>
|
|
{{
|
|
searchQuery.trim()
|
|
? t('rightSidePanel.noneSearchDesc')
|
|
: t('rightSidePanel.noErrors')
|
|
}}
|
|
</div>
|
|
|
|
<div v-else>
|
|
<!-- Group by Class Type -->
|
|
<PropertiesAccordionItem
|
|
v-for="group in filteredGroups"
|
|
:key="group.title"
|
|
:collapse="collapseState[group.title] ?? false"
|
|
class="border-b border-interface-stroke"
|
|
@update:collapse="collapseState[group.title] = $event"
|
|
>
|
|
<template #label>
|
|
<div class="flex items-center gap-2 flex-1 min-w-0">
|
|
<span class="flex-1 flex items-center gap-2 min-w-0">
|
|
<i
|
|
class="icon-[lucide--octagon-alert] size-4 text-destructive-background-hover shrink-0"
|
|
/>
|
|
<span class="text-destructive-background-hover truncate">
|
|
{{ group.title }}
|
|
</span>
|
|
<span
|
|
v-if="group.cards.length > 1"
|
|
class="text-destructive-background-hover"
|
|
>
|
|
({{ group.cards.length }})
|
|
</span>
|
|
</span>
|
|
</div>
|
|
</template>
|
|
|
|
<!-- Cards in Group (default slot) -->
|
|
<div class="px-4 space-y-3">
|
|
<ErrorNodeCard
|
|
v-for="card in group.cards"
|
|
:key="card.id"
|
|
:card="card"
|
|
:show-node-id-badge="showNodeIdBadge"
|
|
:compact="isSingleNodeSelected"
|
|
@locate-node="handleLocateNode"
|
|
@enter-subgraph="handleEnterSubgraph"
|
|
@copy-to-clipboard="copyToClipboard"
|
|
/>
|
|
</div>
|
|
</PropertiesAccordionItem>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Fixed Footer: Help Links -->
|
|
<div class="shrink-0 border-t border-interface-stroke p-4 min-w-0">
|
|
<i18n-t
|
|
keypath="rightSidePanel.errorHelp"
|
|
tag="p"
|
|
class="m-0 text-sm text-muted-foreground leading-tight break-words"
|
|
>
|
|
<template #github>
|
|
<Button
|
|
variant="textonly"
|
|
size="unset"
|
|
class="inline underline text-inherit text-sm whitespace-nowrap"
|
|
@click="openGitHubIssues"
|
|
>
|
|
{{ t('rightSidePanel.errorHelpGithub') }}
|
|
</Button>
|
|
</template>
|
|
<template #support>
|
|
<Button
|
|
variant="textonly"
|
|
size="unset"
|
|
class="inline underline text-inherit text-sm whitespace-nowrap"
|
|
@click="contactSupport"
|
|
>
|
|
{{ t('rightSidePanel.errorHelpSupport') }}
|
|
</Button>
|
|
</template>
|
|
</i18n-t>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
import { computed, ref } from 'vue'
|
|
import { useI18n } from 'vue-i18n'
|
|
|
|
import { useCommandStore } from '@/stores/commandStore'
|
|
import { useCopyToClipboard } from '@/composables/useCopyToClipboard'
|
|
import { useFocusNode } from '@/composables/canvas/useFocusNode'
|
|
import { useExternalLink } from '@/composables/useExternalLink'
|
|
import { useSettingStore } from '@/platform/settings/settingStore'
|
|
import { useTelemetry } from '@/platform/telemetry'
|
|
import { useToastStore } from '@/platform/updates/common/toastStore'
|
|
import { NodeBadgeMode } from '@/types/nodeSource'
|
|
|
|
import PropertiesAccordionItem from '../layout/PropertiesAccordionItem.vue'
|
|
import FormSearchInput from '@/renderer/extensions/vueNodes/widgets/components/form/FormSearchInput.vue'
|
|
import ErrorNodeCard from './ErrorNodeCard.vue'
|
|
import Button from '@/components/ui/button/Button.vue'
|
|
import { useErrorGroups } from './useErrorGroups'
|
|
|
|
const { t } = useI18n()
|
|
const { copyToClipboard } = useCopyToClipboard()
|
|
const { focusNode, enterSubgraph } = useFocusNode()
|
|
const { staticUrls } = useExternalLink()
|
|
const settingStore = useSettingStore()
|
|
|
|
const searchQuery = ref('')
|
|
|
|
const showNodeIdBadge = computed(
|
|
() =>
|
|
(settingStore.get('Comfy.NodeBadge.NodeIdBadgeMode') as NodeBadgeMode) !==
|
|
NodeBadgeMode.None
|
|
)
|
|
|
|
const { filteredGroups, collapseState, isSingleNodeSelected, errorNodeCache } =
|
|
useErrorGroups(searchQuery, t)
|
|
|
|
function handleLocateNode(nodeId: string) {
|
|
focusNode(nodeId, errorNodeCache.value)
|
|
}
|
|
|
|
function handleEnterSubgraph(nodeId: string) {
|
|
enterSubgraph(nodeId, errorNodeCache.value)
|
|
}
|
|
|
|
function openGitHubIssues() {
|
|
useTelemetry()?.trackUiButtonClicked({
|
|
button_id: 'error_tab_github_issues_clicked'
|
|
})
|
|
window.open(staticUrls.githubIssues, '_blank', 'noopener,noreferrer')
|
|
}
|
|
|
|
async function contactSupport() {
|
|
useTelemetry()?.trackHelpResourceClicked({
|
|
resource_type: 'help_feedback',
|
|
is_external: true,
|
|
source: 'error_dialog'
|
|
})
|
|
try {
|
|
await useCommandStore().execute('Comfy.ContactSupport')
|
|
} catch (error) {
|
|
console.error(error)
|
|
useToastStore().addAlert(t('rightSidePanel.contactSupportFailed'))
|
|
}
|
|
}
|
|
</script>
|