mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-03-01 19:20:10 +00:00
Implement load workflow error dialog in Vue (#3225)
Co-authored-by: github-actions <github-actions@github.com>
This commit is contained in:
@@ -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()
|
||||
})
|
||||
})
|
||||
|
||||
80
src/components/dialog/content/ErrorDialogContent.vue
Normal file
80
src/components/dialog/content/ErrorDialogContent.vue
Normal 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>
|
||||
@@ -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.",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -115,6 +115,12 @@
|
||||
"paused": "一時停止",
|
||||
"resume": "ダウンロードを再開"
|
||||
},
|
||||
"errorDialog": {
|
||||
"defaultTitle": "エラーが発生しました",
|
||||
"extensionFileHint": "これは次のスクリプトが原因かもしれません",
|
||||
"loadWorkflowTitle": "ワークフローデータの再読み込みエラーにより、読み込みが中止されました",
|
||||
"noStackTrace": "スタックトレースは利用できません"
|
||||
},
|
||||
"g": {
|
||||
"about": "情報",
|
||||
"add": "追加",
|
||||
|
||||
@@ -115,6 +115,12 @@
|
||||
"paused": "일시 중지됨",
|
||||
"resume": "다운로드 재개"
|
||||
},
|
||||
"errorDialog": {
|
||||
"defaultTitle": "오류가 발생했습니다",
|
||||
"extensionFileHint": "다음 스크립트 때문일 수 있습니다",
|
||||
"loadWorkflowTitle": "워크플로우 데이터를 다시 로드하는 중 오류로 인해 로드가 중단되었습니다",
|
||||
"noStackTrace": "스택 추적이 사용할 수 없습니다"
|
||||
},
|
||||
"g": {
|
||||
"about": "정보",
|
||||
"add": "추가",
|
||||
|
||||
@@ -115,6 +115,12 @@
|
||||
"paused": "Приостановлено",
|
||||
"resume": "Возобновить загрузку"
|
||||
},
|
||||
"errorDialog": {
|
||||
"defaultTitle": "Произошла ошибка",
|
||||
"extensionFileHint": "Это может быть связано со следующим скриптом",
|
||||
"loadWorkflowTitle": "Загрузка прервана из-за ошибки при перезагрузке данных рабочего процесса",
|
||||
"noStackTrace": "Стек вызовов недоступен"
|
||||
},
|
||||
"g": {
|
||||
"about": "О программе",
|
||||
"add": "Добавить",
|
||||
|
||||
@@ -115,6 +115,12 @@
|
||||
"paused": "已暂停",
|
||||
"resume": "恢复下载"
|
||||
},
|
||||
"errorDialog": {
|
||||
"defaultTitle": "发生错误",
|
||||
"extensionFileHint": "这可能是由于以下脚本",
|
||||
"loadWorkflowTitle": "由于重新加载工作流数据出错,加载被中止",
|
||||
"noStackTrace": "无可用堆栈跟踪"
|
||||
},
|
||||
"g": {
|
||||
"about": "关于",
|
||||
"add": "添加",
|
||||
|
||||
@@ -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}` })
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user