mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-02-27 02:04:09 +00:00
refactor: encapsulate error extraction in TaskItemImpl getters (#7650)
## Summary - Add `errorMessage` and `executionError` getters to `TaskItemImpl` that extract error info from status messages - Update `useJobErrorReporting` composable to use these getters instead of standalone function - Remove the standalone `extractExecutionError` function This encapsulates error extraction within `TaskItemImpl`, preparing for the Jobs API migration where the underlying data format will change but the getter interface will remain stable. ## Test plan - [x] All existing tests pass - [x] New tests added for `TaskItemImpl.errorMessage` and `TaskItemImpl.executionError` getters - [x] TypeScript, lint, and knip checks pass 🤖 Generated with [Claude Code](https://claude.com/claude-code) ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-7650-refactor-encapsulate-error-extraction-in-TaskItemImpl-getters-2ce6d73d365081caae33dcc7e1e07720) by [Unito](https://www.unito.io) --------- Co-authored-by: Claude <noreply@anthropic.com> Co-authored-by: Christian Byrne <cbyrne@comfy.org>
This commit is contained in:
@@ -10,6 +10,7 @@ import type {
|
||||
} from '@/platform/assets/schemas/assetSchema'
|
||||
import { isCloud } from '@/platform/distribution/types'
|
||||
import { useToastStore } from '@/platform/updates/common/toastStore'
|
||||
import type { IFuseOptions } from 'fuse.js'
|
||||
import {
|
||||
type TemplateInfo,
|
||||
type WorkflowTemplates
|
||||
@@ -31,16 +32,13 @@ import type {
|
||||
ExecutionSuccessWsMessage,
|
||||
ExtensionsResponse,
|
||||
FeatureFlagsWsMessage,
|
||||
HistoryTaskItem,
|
||||
LogsRawResponse,
|
||||
LogsWsMessage,
|
||||
NotificationWsMessage,
|
||||
PendingTaskItem,
|
||||
ProgressStateWsMessage,
|
||||
ProgressTextWsMessage,
|
||||
ProgressWsMessage,
|
||||
PromptResponse,
|
||||
RunningTaskItem,
|
||||
Settings,
|
||||
StatusWsMessage,
|
||||
StatusWsMessageStatus,
|
||||
@@ -49,12 +47,19 @@ import type {
|
||||
UserDataFullInfo,
|
||||
PreviewMethod
|
||||
} from '@/schemas/apiSchema'
|
||||
import type {
|
||||
JobDetail,
|
||||
JobListItem
|
||||
} from '@/platform/remote/comfyui/jobs/jobTypes'
|
||||
import type { ComfyNodeDef } from '@/schemas/nodeDefSchema'
|
||||
import type { useFirebaseAuthStore } from '@/stores/firebaseAuthStore'
|
||||
import type { AuthHeader } from '@/types/authTypes'
|
||||
import type { NodeExecutionId } from '@/types/nodeIdentification'
|
||||
import { fetchHistory } from '@/platform/remote/comfyui/history'
|
||||
import type { IFuseOptions } from 'fuse.js'
|
||||
import {
|
||||
fetchHistory,
|
||||
fetchJobDetail,
|
||||
fetchQueue
|
||||
} from '@/platform/remote/comfyui/jobs/fetchJobs'
|
||||
|
||||
interface QueuePromptRequestBody {
|
||||
client_id: string
|
||||
@@ -670,7 +675,6 @@ export class ComfyApi extends EventTarget {
|
||||
case 'logs':
|
||||
case 'b_preview':
|
||||
case 'notification':
|
||||
case 'asset_download':
|
||||
this.dispatchCustomEvent(msg.type, msg.data)
|
||||
break
|
||||
case 'feature_flags':
|
||||
@@ -893,53 +897,13 @@ export class ComfyApi extends EventTarget {
|
||||
* @returns The currently running and queued items
|
||||
*/
|
||||
async getQueue(): Promise<{
|
||||
Running: RunningTaskItem[]
|
||||
Pending: PendingTaskItem[]
|
||||
Running: JobListItem[]
|
||||
Pending: JobListItem[]
|
||||
}> {
|
||||
try {
|
||||
const res = await this.fetchApi('/queue')
|
||||
const data = await res.json()
|
||||
// Normalize queue tuple shape across backends:
|
||||
// - Backend (V1): [idx, prompt_id, inputs, extra_data(object), outputs_to_execute(array)]
|
||||
// - Cloud: [idx, prompt_id, inputs, outputs_to_execute(array), metadata(object{create_time})]
|
||||
const normalizeQueuePrompt = (prompt: any): any => {
|
||||
if (!Array.isArray(prompt)) return prompt
|
||||
// Ensure 5-tuple
|
||||
const p = prompt.slice(0, 5)
|
||||
const fourth = p[3]
|
||||
const fifth = p[4]
|
||||
// Cloud shape: 4th is array, 5th is metadata object
|
||||
if (
|
||||
Array.isArray(fourth) &&
|
||||
fifth &&
|
||||
typeof fifth === 'object' &&
|
||||
!Array.isArray(fifth)
|
||||
) {
|
||||
const meta: any = fifth
|
||||
const extraData = { ...meta }
|
||||
return [p[0], p[1], p[2], extraData, fourth]
|
||||
}
|
||||
// V1 shape already: return as-is
|
||||
return p
|
||||
}
|
||||
return {
|
||||
// Running action uses a different endpoint for cancelling
|
||||
Running: data.queue_running.map((prompt: any) => {
|
||||
const np = normalizeQueuePrompt(prompt)
|
||||
return {
|
||||
taskType: 'Running',
|
||||
prompt: np,
|
||||
// prompt[1] is the prompt id
|
||||
remove: { name: 'Cancel', cb: () => api.interrupt(np[1]) }
|
||||
}
|
||||
}),
|
||||
Pending: data.queue_pending.map((prompt: any) => ({
|
||||
taskType: 'Pending',
|
||||
prompt: normalizeQueuePrompt(prompt)
|
||||
}))
|
||||
}
|
||||
return await fetchQueue(this.fetchApi.bind(this))
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
console.error('Failed to fetch queue:', error)
|
||||
return { Running: [], Pending: [] }
|
||||
}
|
||||
}
|
||||
@@ -951,7 +915,7 @@ export class ComfyApi extends EventTarget {
|
||||
async getHistory(
|
||||
max_items: number = 200,
|
||||
options?: { offset?: number }
|
||||
): Promise<{ History: HistoryTaskItem[] }> {
|
||||
): Promise<JobListItem[]> {
|
||||
try {
|
||||
return await fetchHistory(
|
||||
this.fetchApi.bind(this),
|
||||
@@ -960,10 +924,19 @@ export class ComfyApi extends EventTarget {
|
||||
)
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
return { History: [] }
|
||||
return []
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets detailed job info including outputs and workflow
|
||||
* @param jobId The job/prompt ID
|
||||
* @returns Full job details or undefined if not found
|
||||
*/
|
||||
async getJobDetail(jobId: string): Promise<JobDetail | undefined> {
|
||||
return fetchJobDetail(this.fetchApi.bind(this), jobId)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets system & device stats
|
||||
* @returns System stats such as python version, OS, per device info
|
||||
@@ -1273,29 +1246,6 @@ export class ComfyApi extends EventTarget {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the Fuse options from the server.
|
||||
*
|
||||
* @returns The Fuse options, or null if not found or invalid
|
||||
*/
|
||||
async getFuseOptions(): Promise<IFuseOptions<TemplateInfo> | null> {
|
||||
try {
|
||||
const res = await axios.get(
|
||||
this.fileURL('/templates/fuse_options.json'),
|
||||
{
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
}
|
||||
)
|
||||
const contentType = res.headers['content-type']
|
||||
return contentType?.includes('application/json') ? res.data : null
|
||||
} catch (error) {
|
||||
console.error('Error loading fuse options:', error)
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the custom nodes i18n data from the server.
|
||||
*
|
||||
@@ -1331,6 +1281,24 @@ export class ComfyApi extends EventTarget {
|
||||
getServerFeatures(): Record<string, unknown> {
|
||||
return { ...this.serverFeatureFlags }
|
||||
}
|
||||
|
||||
async getFuseOptions(): Promise<IFuseOptions<TemplateInfo> | null> {
|
||||
try {
|
||||
const res = await axios.get(
|
||||
this.fileURL('/templates/fuse_options.json'),
|
||||
{
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
}
|
||||
)
|
||||
const contentType = res.headers['content-type']
|
||||
return contentType?.includes('application/json') ? res.data : null
|
||||
} catch (error) {
|
||||
console.error('Error loading fuse options:', error)
|
||||
return null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const api = new ComfyApi()
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { useSettingStore } from '@/platform/settings/settingStore'
|
||||
import { WORKFLOW_ACCEPT_STRING } from '@/platform/workflow/core/types/formats'
|
||||
import { type StatusWsMessageStatus, type TaskItem } from '@/schemas/apiSchema'
|
||||
import { type StatusWsMessageStatus } from '@/schemas/apiSchema'
|
||||
import { useDialogService } from '@/services/dialogService'
|
||||
import { isCloud } from '@/platform/distribution/types'
|
||||
import { useTelemetry } from '@/platform/telemetry'
|
||||
@@ -33,6 +33,17 @@ type Props = {
|
||||
|
||||
type Children = Element[] | Element | string | string[]
|
||||
|
||||
/**
|
||||
* @deprecated Legacy queue item structure from old history API.
|
||||
* Will be removed when ComfyList is migrated to Jobs API.
|
||||
*/
|
||||
interface LegacyQueueItem {
|
||||
prompt: [unknown, string, unknown, { extra_pnginfo: { workflow: unknown } }]
|
||||
outputs?: Record<string, unknown>
|
||||
meta?: Record<string, { display_node?: string }>
|
||||
remove?: { name: string; cb: () => Promise<void> | void }
|
||||
}
|
||||
|
||||
type ElementType<K extends string> = K extends keyof HTMLElementTagNameMap
|
||||
? HTMLElementTagNameMap[K]
|
||||
: HTMLElement
|
||||
@@ -259,29 +270,28 @@ class ComfyList {
|
||||
$el('div.comfy-list-items', [
|
||||
// @ts-expect-error fixme ts strict error
|
||||
...(this.#reverse ? items[section].reverse() : items[section]).map(
|
||||
(item: TaskItem) => {
|
||||
(item: LegacyQueueItem) => {
|
||||
// Allow items to specify a custom remove action (e.g. for interrupt current prompt)
|
||||
const removeAction =
|
||||
'remove' in item
|
||||
? item.remove
|
||||
: {
|
||||
name: 'Delete',
|
||||
cb: () => api.deleteItem(this.#type, item.prompt[1])
|
||||
}
|
||||
const removeAction = item.remove ?? {
|
||||
name: 'Delete',
|
||||
cb: () => api.deleteItem(this.#type, item.prompt[1])
|
||||
}
|
||||
return $el('div', { textContent: item.prompt[0] + ': ' }, [
|
||||
$el('button', {
|
||||
textContent: 'Load',
|
||||
onclick: async () => {
|
||||
await app.loadGraphData(
|
||||
// @ts-expect-error fixme ts strict error
|
||||
item.prompt[3].extra_pnginfo.workflow,
|
||||
item.prompt[3].extra_pnginfo.workflow as Parameters<
|
||||
typeof app.loadGraphData
|
||||
>[0],
|
||||
true,
|
||||
false
|
||||
)
|
||||
if ('outputs' in item) {
|
||||
if ('outputs' in item && item.outputs) {
|
||||
app.nodeOutputs = {}
|
||||
for (const [key, value] of Object.entries(item.outputs)) {
|
||||
const realKey = item['meta']?.[key]?.display_node ?? key
|
||||
// @ts-expect-error fixme ts strict error
|
||||
app.nodeOutputs[realKey] = value
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user