diff --git a/src/platform/remote/comfyui/jobs/fetchJobs.test.ts b/src/platform/remote/comfyui/jobs/fetchJobs.test.ts index ec32cb3b38..41b01606e2 100644 --- a/src/platform/remote/comfyui/jobs/fetchJobs.test.ts +++ b/src/platform/remote/comfyui/jobs/fetchJobs.test.ts @@ -150,6 +150,41 @@ describe('fetchJobs', () => { expect(result).toEqual([]) }) + + it('parses batch containing text-only preview outputs', async () => { + const mockFetch = vi.fn().mockResolvedValue({ + ok: true, + json: () => + Promise.resolve( + createMockResponse([ + createMockJob('image-job', 'completed', { + preview_output: { + filename: 'output.png', + subfolder: '', + type: 'output', + nodeId: '1', + mediaType: 'images' + } + }), + createMockJob('text-job', 'completed', { + preview_output: { + content: 'some generated text', + nodeId: '5', + mediaType: 'text' + } + }), + createMockJob('no-preview-job', 'completed') + ]) + ) + }) + + const result = await fetchHistory(mockFetch) + + expect(result).toHaveLength(3) + expect(result[0].id).toBe('image-job') + expect(result[1].id).toBe('text-job') + expect(result[2].id).toBe('no-preview-job') + }) }) describe('fetchQueue', () => { diff --git a/src/platform/remote/comfyui/jobs/jobTypes.ts b/src/platform/remote/comfyui/jobs/jobTypes.ts index 5fb9236238..5704fba491 100644 --- a/src/platform/remote/comfyui/jobs/jobTypes.ts +++ b/src/platform/remote/comfyui/jobs/jobTypes.ts @@ -18,14 +18,16 @@ const zJobStatus = z.enum([ 'cancelled' ]) -const zPreviewOutput = z.object({ - filename: z.string(), - subfolder: z.string(), - type: resultItemType, - nodeId: z.string(), - mediaType: z.string(), - display_name: z.string().optional() -}) +const zPreviewOutput = z + .object({ + filename: z.string().optional(), + subfolder: z.string().optional(), + type: resultItemType.optional(), + nodeId: z.string(), + mediaType: z.string(), + display_name: z.string().optional() + }) + .passthrough() /** * Execution error from Jobs API. diff --git a/src/stores/assetsStore.test.ts b/src/stores/assetsStore.test.ts index 7fbfc102cf..dfce660207 100644 --- a/src/stores/assetsStore.test.ts +++ b/src/stores/assetsStore.test.ts @@ -72,6 +72,8 @@ vi.mock('@/stores/modelToNodeStore', () => ({ })) // Mock TaskItemImpl +const PREVIEWABLE_MEDIA_TYPES = new Set(['images', 'video', 'audio']) + vi.mock('@/stores/queueStore', () => ({ TaskItemImpl: class { public flatOutputs: Array<{ @@ -91,19 +93,28 @@ vi.mock('@/stores/queueStore', () => ({ } | undefined public jobId: string + public outputsCount: number | null constructor(public job: JobListItem) { this.jobId = job.id - this.flatOutputs = [ - { + this.outputsCount = job.outputs_count ?? null + const preview = job.preview_output + const isPreviewable = + !!preview?.filename && PREVIEWABLE_MEDIA_TYPES.has(preview.mediaType) + if (preview && isPreviewable) { + const item = { supportsPreview: true, - filename: 'test.png', - subfolder: '', - type: 'output', - url: 'http://test.com/test.png' + filename: preview.filename!, + subfolder: preview.subfolder ?? '', + type: preview.type ?? 'output', + url: `http://test.com/${preview.filename}` } - ] - this.previewOutput = this.flatOutputs[0] + this.flatOutputs = [item] + this.previewOutput = item + } else { + this.flatOutputs = [] + this.previewOutput = undefined + } } get previewableOutputs() { @@ -200,6 +211,33 @@ describe('assetsStore - Refactored (Option A)', () => { expect(store.historyError).toBe(error) expect(store.historyLoading).toBe(false) }) + + it('should skip text-only jobs without breaking sibling image jobs', async () => { + const mockHistory: JobListItem[] = [ + createMockJobItem(0), + { + id: 'text-only-job', + status: 'completed', + create_time: 2000, + priority: 2000, + preview_output: { + content: 'some generated text', + nodeId: '5', + mediaType: 'text' + } satisfies JobListItem['preview_output'] + }, + createMockJobItem(2) + ] + vi.mocked(api.getHistory).mockResolvedValue(mockHistory) + + await store.updateHistory() + + expect(store.historyAssets).toHaveLength(2) + expect(store.historyAssets.map((a) => a.id)).toEqual([ + 'prompt_0', + 'prompt_2' + ]) + }) }) describe('Pagination', () => { diff --git a/src/stores/queueStore.test.ts b/src/stores/queueStore.test.ts index b0c819a42f..3d0a4ad284 100644 --- a/src/stores/queueStore.test.ts +++ b/src/stores/queueStore.test.ts @@ -191,6 +191,23 @@ describe('TaskItemImpl', () => { }) }) + it('should produce no previewable outputs for text-only preview_output', () => { + const job: JobListItem = { + ...createHistoryJob(0, 'text-job'), + preview_output: { + nodeId: '5', + mediaType: 'text' + } satisfies JobListItem['preview_output'] + } + + const task = new TaskItemImpl(job) + + expect(task.flatOutputs).toHaveLength(1) + expect(task.flatOutputs[0].filename).toBe('') + expect(task.previewableOutputs).toHaveLength(0) + expect(task.previewOutput).toBeUndefined() + }) + describe('error extraction getters', () => { it('errorMessage returns undefined when no execution_error', () => { const job = createHistoryJob(0, 'job-id')