[backport cloud/1.41] fix: make zPreviewOutput accept text-only job outputs (#9765)

Backport of #9724 to `cloud/1.41`

Cherry-pick applied cleanly with no conflicts.

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-9765-backport-cloud-1-41-fix-make-zPreviewOutput-accept-text-only-job-outputs-3216d73d365081f4880dca7bcf1d4bc9)
by [Unito](https://www.unito.io)
This commit is contained in:
Luke Mino-Altherr
2026-03-11 19:11:53 -07:00
committed by GitHub
parent a5e5e4813a
commit b1bfe5fb46
4 changed files with 108 additions and 16 deletions

View File

@@ -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', () => {

View File

@@ -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.

View File

@@ -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', () => {

View File

@@ -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')