mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-01-30 12:59:55 +00:00
Use new error dialog for queue prompt errors (#3266)
Co-authored-by: github-actions <github-actions@github.com>
This commit is contained in:
@@ -326,4 +326,18 @@ test.describe('Error dialog', () => {
|
||||
const errorDialog = comfyPage.page.locator('.comfy-error-report')
|
||||
await expect(errorDialog).toBeVisible()
|
||||
})
|
||||
|
||||
test('Should display an error dialog when prompt execution fails', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
await comfyPage.page.evaluate(async () => {
|
||||
const app = window['app']
|
||||
app.api.queuePrompt = () => {
|
||||
throw new Error('Error on queuePrompt!')
|
||||
}
|
||||
await app.queuePrompt(0)
|
||||
})
|
||||
const errorDialog = comfyPage.page.locator('.comfy-error-report')
|
||||
await expect(errorDialog).toBeVisible()
|
||||
})
|
||||
})
|
||||
|
||||
@@ -965,7 +965,8 @@
|
||||
"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"
|
||||
"extensionFileHint": "This may be due to the following script",
|
||||
"promptExecutionError": "Prompt execution failed"
|
||||
},
|
||||
"desktopUpdate": {
|
||||
"title": "Updating ComfyUI Desktop",
|
||||
|
||||
@@ -119,7 +119,8 @@
|
||||
"defaultTitle": "Ocurrió un error",
|
||||
"extensionFileHint": "Esto puede deberse al siguiente script",
|
||||
"loadWorkflowTitle": "La carga se interrumpió debido a un error al recargar los datos del flujo de trabajo",
|
||||
"noStackTrace": "No hay seguimiento de pila disponible"
|
||||
"noStackTrace": "No hay seguimiento de pila disponible",
|
||||
"promptExecutionError": "La ejecución del prompt falló"
|
||||
},
|
||||
"g": {
|
||||
"about": "Acerca de",
|
||||
|
||||
@@ -119,7 +119,8 @@
|
||||
"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"
|
||||
"noStackTrace": "Aucune trace de pile disponible",
|
||||
"promptExecutionError": "L'exécution de l'invite a échoué"
|
||||
},
|
||||
"g": {
|
||||
"about": "À propos",
|
||||
|
||||
@@ -119,7 +119,8 @@
|
||||
"defaultTitle": "エラーが発生しました",
|
||||
"extensionFileHint": "これは次のスクリプトが原因かもしれません",
|
||||
"loadWorkflowTitle": "ワークフローデータの再読み込みエラーにより、読み込みが中止されました",
|
||||
"noStackTrace": "スタックトレースは利用できません"
|
||||
"noStackTrace": "スタックトレースは利用できません",
|
||||
"promptExecutionError": "プロンプトの実行に失敗しました"
|
||||
},
|
||||
"g": {
|
||||
"about": "情報",
|
||||
|
||||
@@ -119,7 +119,8 @@
|
||||
"defaultTitle": "오류가 발생했습니다",
|
||||
"extensionFileHint": "다음 스크립트 때문일 수 있습니다",
|
||||
"loadWorkflowTitle": "워크플로우 데이터를 다시 로드하는 중 오류로 인해 로드가 중단되었습니다",
|
||||
"noStackTrace": "스택 추적이 사용할 수 없습니다"
|
||||
"noStackTrace": "스택 추적이 사용할 수 없습니다",
|
||||
"promptExecutionError": "프롬프트 실행 실패"
|
||||
},
|
||||
"g": {
|
||||
"about": "정보",
|
||||
|
||||
@@ -119,7 +119,8 @@
|
||||
"defaultTitle": "Произошла ошибка",
|
||||
"extensionFileHint": "Это может быть связано со следующим скриптом",
|
||||
"loadWorkflowTitle": "Загрузка прервана из-за ошибки при перезагрузке данных рабочего процесса",
|
||||
"noStackTrace": "Стек вызовов недоступен"
|
||||
"noStackTrace": "Стек вызовов недоступен",
|
||||
"promptExecutionError": "Ошибка выполнения запроса"
|
||||
},
|
||||
"g": {
|
||||
"about": "О программе",
|
||||
|
||||
@@ -119,7 +119,8 @@
|
||||
"defaultTitle": "发生错误",
|
||||
"extensionFileHint": "这可能是由于以下脚本",
|
||||
"loadWorkflowTitle": "由于重新加载工作流数据出错,加载被中止",
|
||||
"noStackTrace": "无可用堆栈跟踪"
|
||||
"noStackTrace": "无可用堆栈跟踪",
|
||||
"promptExecutionError": "提示执行失败"
|
||||
},
|
||||
"g": {
|
||||
"about": "关于",
|
||||
|
||||
@@ -255,14 +255,26 @@ export function validateTaskItem(taskItem: unknown) {
|
||||
|
||||
const zEmbeddingsResponse = z.array(z.string())
|
||||
const zExtensionsResponse = z.array(z.string())
|
||||
const zError = z.object({
|
||||
type: z.string(),
|
||||
message: z.string(),
|
||||
details: z.string(),
|
||||
extra_info: z.record(z.string(), z.any())
|
||||
})
|
||||
const zNodeError = z.object({
|
||||
errors: z.array(zError),
|
||||
class_type: z.string(),
|
||||
dependent_outputs: z.array(z.any())
|
||||
})
|
||||
const zPromptResponse = z.object({
|
||||
node_errors: z.array(z.string()).optional(),
|
||||
node_errors: z.record(zNodeId, zNodeError).optional(),
|
||||
prompt_id: z.string().optional(),
|
||||
exec_info: z
|
||||
.object({
|
||||
queue_remaining: z.number().optional()
|
||||
})
|
||||
.optional()
|
||||
.optional(),
|
||||
error: z.union([z.string(), zError])
|
||||
})
|
||||
|
||||
const zDeviceStats = z.object({
|
||||
@@ -414,6 +426,7 @@ const zSettings = z.record(z.any()).and(
|
||||
export type EmbeddingsResponse = z.infer<typeof zEmbeddingsResponse>
|
||||
export type ExtensionsResponse = z.infer<typeof zExtensionsResponse>
|
||||
export type PromptResponse = z.infer<typeof zPromptResponse>
|
||||
export type NodeError = z.infer<typeof zNodeError>
|
||||
export type Settings = z.infer<typeof zSettings>
|
||||
export type DeviceStats = z.infer<typeof zDeviceStats>
|
||||
export type SystemStats = z.infer<typeof zSystemStats>
|
||||
|
||||
@@ -145,6 +145,35 @@ export interface ComfyApi extends EventTarget {
|
||||
): void
|
||||
}
|
||||
|
||||
export class PromptExecutionError extends Error {
|
||||
response: PromptResponse
|
||||
|
||||
constructor(response: PromptResponse) {
|
||||
super('Prompt execution failed')
|
||||
this.response = response
|
||||
}
|
||||
|
||||
override toString() {
|
||||
let message = super.message
|
||||
if (typeof this.response.error === 'string') {
|
||||
message += ': ' + this.response.error
|
||||
} else if (this.response.error.details) {
|
||||
message += ': ' + this.response.error.details
|
||||
}
|
||||
|
||||
for (const [_, nodeError] of Object.entries(
|
||||
this.response.node_errors ?? []
|
||||
)) {
|
||||
message += '\n' + nodeError.class_type + ':'
|
||||
for (const errorReason of nodeError.errors) {
|
||||
message += '\n - ' + errorReason.message + ': ' + errorReason.details
|
||||
}
|
||||
}
|
||||
|
||||
return message
|
||||
}
|
||||
}
|
||||
|
||||
export class ComfyApi extends EventTarget {
|
||||
#registered = new Set()
|
||||
api_host: string
|
||||
@@ -464,9 +493,10 @@ export class ComfyApi extends EventTarget {
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Queues a prompt to be executed
|
||||
* @param {number} number The index at which to queue the prompt, passing -1 will insert the prompt at the front of the queue
|
||||
* @param {object} prompt The prompt data to queue
|
||||
* @throws {PromptExecutionError} If the prompt fails to execute
|
||||
*/
|
||||
async queuePrompt(
|
||||
number: number,
|
||||
@@ -496,9 +526,7 @@ export class ComfyApi extends EventTarget {
|
||||
})
|
||||
|
||||
if (res.status !== 200) {
|
||||
throw {
|
||||
response: await res.json()
|
||||
}
|
||||
throw new PromptExecutionError(await res.json())
|
||||
}
|
||||
|
||||
return await res.json()
|
||||
|
||||
@@ -12,7 +12,7 @@ import type { ToastMessageOptions } from 'primevue/toast'
|
||||
import { reactive } from 'vue'
|
||||
|
||||
import { st, t } from '@/i18n'
|
||||
import type { ResultItem } from '@/schemas/apiSchema'
|
||||
import type { NodeError, ResultItem } from '@/schemas/apiSchema'
|
||||
import {
|
||||
ComfyApiWorkflow,
|
||||
type ComfyWorkflowJSON,
|
||||
@@ -47,7 +47,7 @@ import { executeWidgetsCallback, isImageNode } from '@/utils/litegraphUtil'
|
||||
import { migrateLegacyRerouteNodes } from '@/utils/migration/migrateReroute'
|
||||
import { deserialiseAndCreate } from '@/utils/vintageClipboard'
|
||||
|
||||
import { type ComfyApi, api } from './api'
|
||||
import { type ComfyApi, PromptExecutionError, api } from './api'
|
||||
import { defaultGraph } from './defaultGraph'
|
||||
import {
|
||||
getFlacMetadata,
|
||||
@@ -121,7 +121,7 @@ export class ComfyApp {
|
||||
dragOverNode: LGraphNode | null = null
|
||||
// @ts-expect-error fixme ts strict error
|
||||
canvasEl: HTMLCanvasElement
|
||||
lastNodeErrors: any[] | null = null
|
||||
lastNodeErrors: Record<NodeId, NodeError> | null = null
|
||||
/** @type {ExecutionErrorWsMessage} */
|
||||
lastExecutionError: { node_id?: NodeId } | null = null
|
||||
configuringGraph: boolean = false
|
||||
@@ -244,7 +244,7 @@ export class ComfyApp {
|
||||
}
|
||||
|
||||
getPreviewFormatParam() {
|
||||
let preview_format = this.ui.settings.getSettingValue('Comfy.PreviewFormat')
|
||||
let preview_format = useSettingStore().get('Comfy.PreviewFormat')
|
||||
if (preview_format) return `&preview=${preview_format}`
|
||||
else return ''
|
||||
}
|
||||
@@ -570,7 +570,6 @@ export class ComfyApp {
|
||||
// @ts-expect-error fixme ts strict error
|
||||
const res = origDrawNodeShape.apply(this, arguments)
|
||||
|
||||
// @ts-expect-error fixme ts strict error
|
||||
const nodeErrors = self.lastNodeErrors?.[node.id]
|
||||
|
||||
let color = null
|
||||
@@ -1223,32 +1222,6 @@ export class ComfyApp {
|
||||
})
|
||||
}
|
||||
|
||||
// @ts-expect-error fixme ts strict error
|
||||
#formatPromptError(error) {
|
||||
if (error == null) {
|
||||
return '(unknown error)'
|
||||
} else if (typeof error === 'string') {
|
||||
return error
|
||||
} else if (error.stack && error.message) {
|
||||
return error.toString()
|
||||
} else if (error.response) {
|
||||
let message = error.response.error.message
|
||||
if (error.response.error.details)
|
||||
message += ': ' + error.response.error.details
|
||||
for (const [_, nodeError] of Object.entries(error.response.node_errors)) {
|
||||
// @ts-expect-error
|
||||
message += '\n' + nodeError.class_type + ':'
|
||||
// @ts-expect-error
|
||||
for (const errorReason of nodeError.errors) {
|
||||
message +=
|
||||
'\n - ' + errorReason.message + ': ' + errorReason.details
|
||||
}
|
||||
}
|
||||
return message
|
||||
}
|
||||
return '(unknown error)'
|
||||
}
|
||||
|
||||
async queuePrompt(number: number, batchCount: number = 1): Promise<boolean> {
|
||||
this.#queueItems.push({ number, batchCount })
|
||||
|
||||
@@ -1287,13 +1260,14 @@ export class ComfyApp {
|
||||
}
|
||||
} catch (error) {}
|
||||
}
|
||||
} catch (error) {
|
||||
const formattedError = this.#formatPromptError(error)
|
||||
this.ui.dialog.show(formattedError)
|
||||
// @ts-expect-error fixme ts strict error
|
||||
if (error.response) {
|
||||
// @ts-expect-error fixme ts strict error
|
||||
this.lastNodeErrors = error.response.node_errors
|
||||
} catch (error: unknown) {
|
||||
useDialogService().showErrorDialog(error, {
|
||||
title: t('errorDialog.promptExecutionError'),
|
||||
reportType: 'promptExecutionError'
|
||||
})
|
||||
|
||||
if (error instanceof PromptExecutionError) {
|
||||
this.lastNodeErrors = error.response.node_errors ?? null
|
||||
this.canvas.draw(true, true)
|
||||
}
|
||||
break
|
||||
|
||||
Reference in New Issue
Block a user