Implement load workflow error dialog in Vue (#3225)

Co-authored-by: github-actions <github-actions@github.com>
This commit is contained in:
Chenlei Hu
2025-03-24 21:00:50 -04:00
committed by GitHub
parent abe65e58a0
commit ae64721555
10 changed files with 188 additions and 53 deletions

View File

@@ -309,3 +309,21 @@ test.describe('Feedback dialog', () => {
await expect(feedbackHeader).not.toBeVisible()
})
})
test.describe('Error dialog', () => {
test('Should display an error dialog when graph configure fails', async ({
comfyPage
}) => {
await comfyPage.page.evaluate(() => {
const graph = window['graph']
graph.configure = () => {
throw new Error('Error on configure!')
}
})
await comfyPage.loadWorkflow('default')
const errorDialog = comfyPage.page.locator('.error-dialog-content')
await expect(errorDialog).toBeVisible()
})
})

View File

@@ -0,0 +1,80 @@
<template>
<div class="error-dialog-content flex flex-col gap-4">
<NoResultsPlaceholder
class="pb-0"
icon="pi pi-exclamation-circle"
:title="title"
:message="errorMessage"
/>
<pre
class="stack-trace p-5 text-neutral-400 text-xs max-h-[50vh] overflow-auto bg-black/20"
>
{{ stackTrace }}
</pre>
<template v-if="extensionFile">
<span>{{ t('errorDialog.extensionFileHint') }}:</span>
<br />
<span class="font-bold">{{ extensionFile }}</span>
</template>
<Button
v-show="!sendReportOpen"
text
fluid
:label="$t('issueReport.helpFix')"
@click="showSendReport"
/>
<ReportIssuePanel
v-if="sendReportOpen"
:error-type="errorType"
:extra-fields="[
{
label: t('issueReport.stackTrace'),
value: 'StackTrace',
optIn: true,
getData: () => stackTrace
}
]"
:tags="{
exceptionMessage: errorMessage,
extensionFile: extensionFile ?? 'UNKNOWN'
}"
:title="t('issueReport.submitErrorReport')"
/>
</div>
</template>
<script setup lang="ts">
import Button from 'primevue/button'
import { computed, ref } from 'vue'
import { useI18n } from 'vue-i18n'
import NoResultsPlaceholder from '@/components/common/NoResultsPlaceholder.vue'
import ReportIssuePanel from './error/ReportIssuePanel.vue'
const { t } = useI18n()
const {
title: _title,
errorMessage,
stackTrace: _stackTrace,
extensionFile,
errorType = 'frontendError'
} = defineProps<{
title?: string
errorMessage: string
stackTrace?: string
extensionFile?: string
errorType?: string
}>()
const title = computed(() => _title ?? t('errorDialog.defaultTitle'))
const stackTrace = computed(() => _stackTrace ?? t('errorDialog.noStackTrace'))
const sendReportOpen = ref(false)
function showSendReport() {
sendReportOpen.value = true
}
</script>

View File

@@ -951,6 +951,12 @@
"missingModels": "Missing Models",
"missingModelsMessage": "When loading the graph, the following models were not found"
},
"errorDialog": {
"defaultTitle": "An error occurred",
"loadWorkflowTitle": "Loading aborted due to error reloading workflow data",
"noStackTrace": "No stacktrace available",
"extensionFileHint": "This may be due to the following script"
},
"desktopUpdate": {
"title": "Updating ComfyUI Desktop",
"description": "ComfyUI Desktop is installing new dependencies. This may take a few minutes.",

View File

@@ -115,6 +115,12 @@
"paused": "En pause",
"resume": "Reprendre le téléchargement"
},
"errorDialog": {
"defaultTitle": "Une erreur est survenue",
"extensionFileHint": "Cela peut être dû au script suivant",
"loadWorkflowTitle": "Chargement interrompu en raison d'une erreur de rechargement des données de workflow",
"noStackTrace": "Aucune trace de pile disponible"
},
"g": {
"about": "À propos",
"add": "Ajouter",

View File

@@ -115,6 +115,12 @@
"paused": "一時停止",
"resume": "ダウンロードを再開"
},
"errorDialog": {
"defaultTitle": "エラーが発生しました",
"extensionFileHint": "これは次のスクリプトが原因かもしれません",
"loadWorkflowTitle": "ワークフローデータの再読み込みエラーにより、読み込みが中止されました",
"noStackTrace": "スタックトレースは利用できません"
},
"g": {
"about": "情報",
"add": "追加",

View File

@@ -115,6 +115,12 @@
"paused": "일시 중지됨",
"resume": "다운로드 재개"
},
"errorDialog": {
"defaultTitle": "오류가 발생했습니다",
"extensionFileHint": "다음 스크립트 때문일 수 있습니다",
"loadWorkflowTitle": "워크플로우 데이터를 다시 로드하는 중 오류로 인해 로드가 중단되었습니다",
"noStackTrace": "스택 추적이 사용할 수 없습니다"
},
"g": {
"about": "정보",
"add": "추가",

View File

@@ -115,6 +115,12 @@
"paused": "Приостановлено",
"resume": "Возобновить загрузку"
},
"errorDialog": {
"defaultTitle": "Произошла ошибка",
"extensionFileHint": "Это может быть связано со следующим скриптом",
"loadWorkflowTitle": "Загрузка прервана из-за ошибки при перезагрузке данных рабочего процесса",
"noStackTrace": "Стек вызовов недоступен"
},
"g": {
"about": "О программе",
"add": "Добавить",

View File

@@ -115,6 +115,12 @@
"paused": "已暂停",
"resume": "恢复下载"
},
"errorDialog": {
"defaultTitle": "发生错误",
"extensionFileHint": "这可能是由于以下脚本",
"loadWorkflowTitle": "由于重新加载工作流数据出错,加载被中止",
"noStackTrace": "无可用堆栈跟踪"
},
"g": {
"about": "关于",
"add": "添加",

View File

@@ -11,7 +11,7 @@ import _ from 'lodash'
import type { ToastMessageOptions } from 'primevue/toast'
import { reactive } from 'vue'
import { st } from '@/i18n'
import { st, t } from '@/i18n'
import type { ResultItem } from '@/schemas/apiSchema'
import {
type ComfyWorkflowJSON,
@@ -1144,56 +1144,10 @@ export class ComfyApp {
this.canvas.ds.scale = graphData.extra.ds.scale
}
} catch (error) {
let errorHint = []
// Try extracting filename to see if it was caused by an extension script
const filename =
// @ts-expect-error fixme ts strict error
error.fileName ||
// @ts-expect-error fixme ts strict error
(error.stack || '').match(/(\/extensions\/.*\.js)/)?.[1]
const pos = (filename || '').indexOf('/extensions/')
if (pos > -1) {
errorHint.push(
$el('span', {
textContent: 'This may be due to the following script:'
}),
$el('br'),
$el('span', {
style: {
fontWeight: 'bold'
},
textContent: filename.substring(pos)
})
)
}
// Show dialog to let the user know something went wrong loading the data
this.ui.dialog.show(
$el('div', [
$el('p', {
textContent: 'Loading aborted due to error reloading workflow data'
}),
$el('pre', {
style: { padding: '5px', backgroundColor: 'rgba(255,0,0,0.2)' },
// @ts-expect-error fixme ts strict error
textContent: error.toString()
}),
$el('pre', {
style: {
padding: '5px',
color: '#ccc',
fontSize: '10px',
maxHeight: '50vh',
overflow: 'auto',
backgroundColor: 'rgba(0,0,0,0.2)'
},
// @ts-expect-error fixme ts strict error
textContent: error.stack || 'No stacktrace available'
}),
...errorHint
]).outerHTML
)
useDialogService().showErrorDialog(error, {
title: t('errorDialog.loadWorkflowTitle'),
errorType: 'loadWorkflowError'
})
return
}
for (const node of this.graph.nodes) {
@@ -1384,8 +1338,7 @@ export class ComfyApp {
return !this.lastNodeErrors
}
// @ts-expect-error fixme ts strict error
showErrorOnFileLoad(file) {
showErrorOnFileLoad(file: File) {
this.ui.dialog.show(
$el('div', [
$el('p', { textContent: `Unable to find workflow in ${file.name}` })

View File

@@ -1,4 +1,5 @@
import ConfirmationDialogContent from '@/components/dialog/content/ConfirmationDialogContent.vue'
import ErrorDialogContent from '@/components/dialog/content/ErrorDialogContent.vue'
import ExecutionErrorDialogContent from '@/components/dialog/content/ExecutionErrorDialogContent.vue'
import IssueReportDialogContent from '@/components/dialog/content/IssueReportDialogContent.vue'
import LoadWorkflowWarning from '@/components/dialog/content/LoadWorkflowWarning.vue'
@@ -147,6 +148,52 @@ export const useDialogService = () => {
})
}
function parseError(error: Error) {
const filename =
'fileName' in error
? (error.fileName as string)
: error.stack?.match(/(\/extensions\/.*\.js)/)?.[1]
const extensionFile = filename
? filename.substring(filename.indexOf('/extensions/'))
: undefined
return {
errorMessage: error.toString(),
stackTrace: error.stack,
extensionFile
}
}
/**
* Show a error dialog to the user when an error occurs.
* @param error The error to show
* @param options The options for the dialog
*/
function showErrorDialog(
error: unknown,
options: {
title?: string
errorType?: string
} = {}
) {
const props =
error instanceof Error
? parseError(error)
: {
errorMessage: String(error)
}
dialogStore.showDialog({
key: 'global-error',
component: ErrorDialogContent,
props: {
...props,
...options
}
})
}
async function prompt({
title,
message,
@@ -230,6 +277,7 @@ export const useDialogService = () => {
showIssueReportDialog,
showManagerDialog,
showManagerProgressDialog,
showErrorDialog,
prompt,
confirm
}