mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-02-23 16:24:06 +00:00
remove dummy type; revert queuestore
This commit is contained in:
@@ -238,7 +238,7 @@ export function useJobList() {
|
||||
const activeId = workflowStore.activeWorkflow?.activeState?.id
|
||||
if (!activeId) return []
|
||||
entries = entries.filter(({ task }) => {
|
||||
const wid = task.workflow?.id
|
||||
const wid = task.workflowId
|
||||
return !!wid && wid === activeId
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,23 +1,10 @@
|
||||
import { ref, shallowRef } from 'vue'
|
||||
|
||||
import type { JobListItem } from '@/composables/queue/useJobList'
|
||||
import type { ResultItemImpl } from '@/stores/queueStore'
|
||||
import type { ResultItemImpl, TaskItemImpl } from '@/stores/queueStore'
|
||||
|
||||
type FetchApi = (url: string) => Promise<Response>
|
||||
|
||||
/**
|
||||
* Minimal interface for tasks used by the result gallery.
|
||||
* This allows the gallery to work with any object that provides these properties,
|
||||
* without coupling to the full TaskItemImpl class.
|
||||
*/
|
||||
interface GalleryTask {
|
||||
readonly promptId: string
|
||||
readonly outputsCount?: number
|
||||
readonly flatOutputs: readonly ResultItemImpl[]
|
||||
readonly previewOutput?: ResultItemImpl
|
||||
loadFullOutputs(fetchApi: FetchApi): Promise<GalleryTask>
|
||||
}
|
||||
|
||||
const getPreviewableOutputs = (outputs?: readonly ResultItemImpl[]) =>
|
||||
outputs?.filter((o) => o.supportsPreview) ?? []
|
||||
|
||||
@@ -31,17 +18,17 @@ const findActiveIndex = (items: ResultItemImpl[], url?: string): number => {
|
||||
* Manages result gallery state and activation for queue items.
|
||||
*/
|
||||
export function useResultGallery(
|
||||
getFilteredTasks: () => GalleryTask[],
|
||||
getFilteredTasks: () => TaskItemImpl[],
|
||||
fetchApi?: FetchApi
|
||||
) {
|
||||
const galleryActiveIndex = ref(-1)
|
||||
const galleryItems = shallowRef<ResultItemImpl[]>([])
|
||||
|
||||
const loadedTasksCache = new Map<string, GalleryTask>()
|
||||
const loadedTasksCache = new Map<string, TaskItemImpl>()
|
||||
let currentRequestId = 0
|
||||
|
||||
const getOutputsForTask = async (
|
||||
task: GalleryTask
|
||||
task: TaskItemImpl
|
||||
): Promise<ResultItemImpl[]> => {
|
||||
const outputsCount = task.outputsCount ?? 0
|
||||
const needsLazyLoad = outputsCount > 1 && fetchApi
|
||||
@@ -67,7 +54,7 @@ export function useResultGallery(
|
||||
|
||||
const requestId = ++currentRequestId
|
||||
|
||||
const targetTask = item.taskRef as GalleryTask | undefined
|
||||
const targetTask = item.taskRef
|
||||
let targetOutputs: ResultItemImpl[] = []
|
||||
|
||||
if (targetTask) {
|
||||
|
||||
@@ -57,6 +57,8 @@ const zRawJobListItem = z
|
||||
id: z.string(),
|
||||
status: zJobStatus,
|
||||
create_time: z.number(),
|
||||
execution_start_time: z.number().nullable().optional(),
|
||||
execution_end_time: z.number().nullable().optional(),
|
||||
preview_output: zPreviewOutput.nullable().optional(),
|
||||
outputs_count: z.number().optional(),
|
||||
error_message: z.string().nullable().optional(),
|
||||
|
||||
@@ -5,10 +5,7 @@ import { computed, ref, shallowRef, toRaw, toValue } from 'vue'
|
||||
import { reconcileJobs } from '@/platform/remote/comfyui/history/reconciliation'
|
||||
import { extractWorkflow, fetchJobDetail } from '@/platform/remote/comfyui/jobs'
|
||||
import type { JobListItem } from '@/platform/remote/comfyui/jobs'
|
||||
import type {
|
||||
ComfyWorkflowJSON,
|
||||
NodeId
|
||||
} from '@/platform/workflow/validation/schemas/workflowSchema'
|
||||
import type { NodeId } from '@/platform/workflow/validation/schemas/workflowSchema'
|
||||
import type {
|
||||
ResultItem,
|
||||
StatusWsMessageStatus,
|
||||
@@ -321,8 +318,17 @@ export class TaskItemImpl {
|
||||
return this.job.execution_error ?? undefined
|
||||
}
|
||||
|
||||
get workflow(): ComfyWorkflowJSON | undefined {
|
||||
// Workflow is only available after lazy loading via getWorkflowFromHistory
|
||||
/**
|
||||
* Workflow ID if available from the job
|
||||
*/
|
||||
get workflowId(): string | undefined {
|
||||
return this.job.workflow_id ?? undefined
|
||||
}
|
||||
|
||||
/**
|
||||
* Full workflow data - not available in list response, use loadWorkflow()
|
||||
*/
|
||||
get workflow(): undefined {
|
||||
return undefined
|
||||
}
|
||||
|
||||
@@ -334,10 +340,9 @@ export class TaskItemImpl {
|
||||
}
|
||||
|
||||
/**
|
||||
* Execution messages - not available in JobListItem, would need JobDetail
|
||||
* @deprecated Use job status instead
|
||||
* Execution messages - not available in Jobs API
|
||||
*/
|
||||
get messages(): Array<[string, any]> {
|
||||
get messages(): Array<[string, unknown]> {
|
||||
return []
|
||||
}
|
||||
|
||||
@@ -348,38 +353,6 @@ export class TaskItemImpl {
|
||||
return this.job.status === 'cancelled'
|
||||
}
|
||||
|
||||
/**
|
||||
* Execution start timestamp - not available in JobListItem
|
||||
* @deprecated Not available in jobs list API
|
||||
*/
|
||||
get executionStartTimestamp(): number | undefined {
|
||||
return undefined
|
||||
}
|
||||
|
||||
/**
|
||||
* Execution end timestamp - not available in JobListItem
|
||||
* @deprecated Not available in jobs list API
|
||||
*/
|
||||
get executionEndTimestamp(): number | undefined {
|
||||
return undefined
|
||||
}
|
||||
|
||||
/**
|
||||
* Execution time in ms - not available in JobListItem
|
||||
* @deprecated Not available in jobs list API
|
||||
*/
|
||||
get executionTime(): number | undefined {
|
||||
return undefined
|
||||
}
|
||||
|
||||
/**
|
||||
* Execution time in seconds - not available in JobListItem
|
||||
* @deprecated Not available in jobs list API
|
||||
*/
|
||||
get executionTimeInSeconds(): number | undefined {
|
||||
return undefined
|
||||
}
|
||||
|
||||
get isHistory() {
|
||||
return this.taskType === 'History'
|
||||
}
|
||||
@@ -403,6 +376,27 @@ export class TaskItemImpl {
|
||||
}
|
||||
}
|
||||
|
||||
get executionStartTimestamp() {
|
||||
return this.job.execution_start_time ?? undefined
|
||||
}
|
||||
|
||||
get executionEndTimestamp() {
|
||||
return this.job.execution_end_time ?? undefined
|
||||
}
|
||||
|
||||
get executionTime() {
|
||||
if (!this.executionStartTimestamp || !this.executionEndTimestamp) {
|
||||
return undefined
|
||||
}
|
||||
return this.executionEndTimestamp - this.executionStartTimestamp
|
||||
}
|
||||
|
||||
get executionTimeInSeconds() {
|
||||
return this.executionTime !== undefined
|
||||
? this.executionTime / 1000
|
||||
: undefined
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads full outputs for tasks that only have preview data
|
||||
* Returns a new TaskItemImpl with full outputs and execution status
|
||||
@@ -547,7 +541,7 @@ export const useQueueStore = defineStore('queue', () => {
|
||||
const executionStore = useExecutionStore()
|
||||
appearedTasks.forEach((task) => {
|
||||
const promptIdString = String(task.promptId)
|
||||
const workflowId = task.workflow?.id
|
||||
const workflowId = task.workflowId
|
||||
if (workflowId && promptIdString) {
|
||||
executionStore.registerPromptWorkflowIdMapping(
|
||||
promptIdString,
|
||||
|
||||
@@ -13,7 +13,7 @@ type TestTask = {
|
||||
executionTime?: number
|
||||
executionEndTimestamp?: number
|
||||
createTime?: number
|
||||
workflow?: { id?: string }
|
||||
workflowId?: string
|
||||
}
|
||||
|
||||
const translations: Record<string, string> = {
|
||||
@@ -185,7 +185,7 @@ const createTask = (
|
||||
executionTime: overrides.executionTime,
|
||||
executionEndTimestamp: overrides.executionEndTimestamp,
|
||||
createTime: overrides.createTime,
|
||||
workflow: overrides.workflow
|
||||
workflowId: overrides.workflowId
|
||||
})
|
||||
|
||||
const mountUseJobList = () => {
|
||||
@@ -387,13 +387,13 @@ describe('useJobList', () => {
|
||||
promptId: 'wf-1',
|
||||
queueIndex: 2,
|
||||
mockState: 'pending',
|
||||
workflow: { id: 'workflow-1' }
|
||||
workflowId: 'workflow-1'
|
||||
}),
|
||||
createTask({
|
||||
promptId: 'wf-2',
|
||||
queueIndex: 1,
|
||||
mockState: 'pending',
|
||||
workflow: { id: 'workflow-2' }
|
||||
workflowId: 'workflow-2'
|
||||
})
|
||||
]
|
||||
|
||||
|
||||
@@ -1,71 +1,78 @@
|
||||
import { describe, it, expect } from 'vitest'
|
||||
|
||||
import { useResultGallery } from '@/composables/queue/useResultGallery'
|
||||
import type { JobListItem } from '@/composables/queue/useJobList'
|
||||
import type { ResultItemImpl } from '@/stores/queueStore'
|
||||
import type { JobListItem as JobListViewItem } from '@/composables/queue/useJobList'
|
||||
import type { JobListItem } from '@/platform/remote/comfyui/jobs'
|
||||
import { ResultItemImpl, TaskItemImpl } from '@/stores/queueStore'
|
||||
|
||||
type PreviewLike = Pick<ResultItemImpl, 'url' | 'supportsPreview'>
|
||||
|
||||
/**
|
||||
* Mock task interface matching what useResultGallery expects.
|
||||
* Uses structural typing - no need to import the internal GalleryTask type.
|
||||
*/
|
||||
interface MockTask {
|
||||
readonly promptId: string
|
||||
readonly outputsCount?: number
|
||||
readonly flatOutputs: readonly ResultItemImpl[]
|
||||
readonly previewOutput?: ResultItemImpl
|
||||
loadFullOutputs(
|
||||
fetchApi: (url: string) => Promise<Response>
|
||||
): Promise<MockTask>
|
||||
const createResultItem = (
|
||||
url: string,
|
||||
supportsPreview = true
|
||||
): ResultItemImpl => {
|
||||
const item = new ResultItemImpl({
|
||||
filename: url,
|
||||
subfolder: '',
|
||||
type: 'output',
|
||||
nodeId: 'node-1',
|
||||
mediaType: supportsPreview ? 'images' : 'unknown'
|
||||
})
|
||||
// Override url getter for test matching
|
||||
Object.defineProperty(item, 'url', { get: () => url })
|
||||
Object.defineProperty(item, 'supportsPreview', { get: () => supportsPreview })
|
||||
return item
|
||||
}
|
||||
|
||||
const createPreview = (url: string, supportsPreview = true): PreviewLike => ({
|
||||
url,
|
||||
supportsPreview
|
||||
const createMockJob = (id: string, outputsCount = 1): JobListItem => ({
|
||||
id,
|
||||
status: 'completed',
|
||||
create_time: Date.now(),
|
||||
preview_output: null,
|
||||
outputs_count: outputsCount,
|
||||
priority: 0
|
||||
})
|
||||
|
||||
const createMockTask = (
|
||||
preview?: PreviewLike,
|
||||
allOutputs?: PreviewLike[]
|
||||
): MockTask => ({
|
||||
previewOutput: preview as ResultItemImpl | undefined,
|
||||
flatOutputs: (allOutputs ?? (preview ? [preview] : [])) as ResultItemImpl[],
|
||||
outputsCount: 1,
|
||||
promptId: `task-${Math.random().toString(36).slice(2)}`,
|
||||
loadFullOutputs: async () => createMockTask(preview, allOutputs)
|
||||
})
|
||||
const createTask = (
|
||||
preview?: ResultItemImpl,
|
||||
allOutputs?: ResultItemImpl[],
|
||||
outputsCount = 1
|
||||
): TaskItemImpl => {
|
||||
const job = createMockJob(
|
||||
`task-${Math.random().toString(36).slice(2)}`,
|
||||
outputsCount
|
||||
)
|
||||
const flatOutputs = allOutputs ?? (preview ? [preview] : [])
|
||||
return new TaskItemImpl(job, {}, flatOutputs)
|
||||
}
|
||||
|
||||
const createJobItem = (
|
||||
const createJobViewItem = (
|
||||
id: string,
|
||||
preview?: PreviewLike,
|
||||
taskRef?: MockTask
|
||||
): JobListItem =>
|
||||
taskRef?: TaskItemImpl
|
||||
): JobListViewItem =>
|
||||
({
|
||||
id,
|
||||
title: `Job ${id}`,
|
||||
meta: '',
|
||||
state: 'completed',
|
||||
showClear: false,
|
||||
taskRef: taskRef ?? (preview ? { previewOutput: preview } : undefined)
|
||||
}) as JobListItem
|
||||
taskRef
|
||||
}) as JobListViewItem
|
||||
|
||||
describe('useResultGallery', () => {
|
||||
it('collects only previewable outputs and preserves their order', async () => {
|
||||
const previewable = [createPreview('p-1'), createPreview('p-2')]
|
||||
const nonPreviewable = { url: 'skip-me', supportsPreview: false }
|
||||
const previewable = [createResultItem('p-1'), createResultItem('p-2')]
|
||||
const nonPreviewable = createResultItem('skip-me', false)
|
||||
const tasks = [
|
||||
createMockTask(previewable[0]),
|
||||
createMockTask(nonPreviewable),
|
||||
createMockTask(previewable[1]),
|
||||
createMockTask()
|
||||
createTask(previewable[0]),
|
||||
createTask(nonPreviewable),
|
||||
createTask(previewable[1]),
|
||||
createTask()
|
||||
]
|
||||
|
||||
const { galleryItems, galleryActiveIndex, onViewItem } = useResultGallery(
|
||||
() => tasks
|
||||
)
|
||||
|
||||
await onViewItem(createJobItem('job-1', previewable[0], tasks[0]))
|
||||
await onViewItem(createJobViewItem('job-1', tasks[0]))
|
||||
|
||||
expect(galleryItems.value).toEqual([previewable[0]])
|
||||
expect(galleryActiveIndex.value).toBe(0)
|
||||
@@ -76,7 +83,7 @@ describe('useResultGallery', () => {
|
||||
() => []
|
||||
)
|
||||
|
||||
await onViewItem(createJobItem('job-missing'))
|
||||
await onViewItem(createJobViewItem('job-missing'))
|
||||
|
||||
expect(galleryItems.value).toEqual([])
|
||||
expect(galleryActiveIndex.value).toBe(-1)
|
||||
@@ -84,81 +91,77 @@ describe('useResultGallery', () => {
|
||||
|
||||
it('activates the index that matches the viewed preview URL', async () => {
|
||||
const previewable = [
|
||||
createPreview('p-1'),
|
||||
createPreview('p-2'),
|
||||
createPreview('p-3')
|
||||
createResultItem('p-1'),
|
||||
createResultItem('p-2'),
|
||||
createResultItem('p-3')
|
||||
]
|
||||
const tasks = previewable.map((preview) => createMockTask(preview))
|
||||
const tasks = previewable.map((preview) => createTask(preview))
|
||||
|
||||
const { galleryItems, galleryActiveIndex, onViewItem } = useResultGallery(
|
||||
() => tasks
|
||||
)
|
||||
|
||||
const targetPreview = createPreview('p-2')
|
||||
await onViewItem(createJobItem('job-2', targetPreview, tasks[1]))
|
||||
await onViewItem(createJobViewItem('job-2', tasks[1]))
|
||||
|
||||
expect(galleryItems.value).toEqual([previewable[1]])
|
||||
expect(galleryActiveIndex.value).toBe(0)
|
||||
})
|
||||
|
||||
it('defaults to the first entry when the clicked job lacks a preview', async () => {
|
||||
const previewable = [createPreview('p-1'), createPreview('p-2')]
|
||||
const tasks = previewable.map((preview) => createMockTask(preview))
|
||||
const previewable = [createResultItem('p-1'), createResultItem('p-2')]
|
||||
const tasks = previewable.map((preview) => createTask(preview))
|
||||
|
||||
const { galleryItems, galleryActiveIndex, onViewItem } = useResultGallery(
|
||||
() => tasks
|
||||
)
|
||||
|
||||
await onViewItem(createJobItem('job-no-preview'))
|
||||
await onViewItem(createJobViewItem('job-no-preview'))
|
||||
|
||||
expect(galleryItems.value).toEqual(previewable)
|
||||
expect(galleryActiveIndex.value).toBe(0)
|
||||
})
|
||||
|
||||
it('defaults to the first entry when no gallery item matches the preview URL', async () => {
|
||||
const previewable = [createPreview('p-1'), createPreview('p-2')]
|
||||
const tasks = previewable.map((preview) => createMockTask(preview))
|
||||
const previewable = [createResultItem('p-1'), createResultItem('p-2')]
|
||||
const tasks = previewable.map((preview) => createTask(preview))
|
||||
|
||||
const { galleryItems, galleryActiveIndex, onViewItem } = useResultGallery(
|
||||
() => tasks
|
||||
)
|
||||
|
||||
await onViewItem(createJobItem('job-mismatch', createPreview('missing')))
|
||||
const taskWithMismatchedPreview = createTask(createResultItem('missing'))
|
||||
await onViewItem(
|
||||
createJobViewItem('job-mismatch', taskWithMismatchedPreview)
|
||||
)
|
||||
|
||||
expect(galleryItems.value).toEqual(previewable)
|
||||
expect(galleryItems.value).toEqual([createResultItem('missing')])
|
||||
expect(galleryActiveIndex.value).toBe(0)
|
||||
})
|
||||
|
||||
it('loads full outputs when task has only preview outputs', async () => {
|
||||
const previewOutput = createPreview('preview-1')
|
||||
const previewOutput = createResultItem('preview-1')
|
||||
const fullOutputs = [
|
||||
createPreview('full-1'),
|
||||
createPreview('full-2'),
|
||||
createPreview('full-3')
|
||||
] as ResultItemImpl[]
|
||||
createResultItem('full-1'),
|
||||
createResultItem('full-2'),
|
||||
createResultItem('full-3')
|
||||
]
|
||||
|
||||
const mockTask: MockTask = {
|
||||
promptId: 'task-1',
|
||||
previewOutput: previewOutput as ResultItemImpl,
|
||||
flatOutputs: [previewOutput] as ResultItemImpl[],
|
||||
outputsCount: 3, // More than 1 triggers lazy loading
|
||||
loadFullOutputs: async () => ({
|
||||
promptId: 'task-1',
|
||||
previewOutput: previewOutput as ResultItemImpl,
|
||||
flatOutputs: fullOutputs,
|
||||
outputsCount: 3,
|
||||
loadFullOutputs: async () => mockTask
|
||||
})
|
||||
}
|
||||
// Create a task with outputsCount > 1 to trigger lazy loading
|
||||
const job = createMockJob('task-1', 3)
|
||||
const task = new TaskItemImpl(job, {}, [previewOutput])
|
||||
|
||||
// Mock loadFullOutputs to return full outputs
|
||||
const loadedTask = new TaskItemImpl(job, {}, fullOutputs)
|
||||
task.loadFullOutputs = async () => loadedTask
|
||||
|
||||
const mockFetchApi = async () => new Response()
|
||||
|
||||
const { galleryItems, galleryActiveIndex, onViewItem } = useResultGallery(
|
||||
() => [mockTask],
|
||||
() => [task],
|
||||
mockFetchApi
|
||||
)
|
||||
|
||||
await onViewItem(createJobItem('job-1', previewOutput, mockTask))
|
||||
await onViewItem(createJobViewItem('job-1', task))
|
||||
|
||||
expect(galleryItems.value).toEqual(fullOutputs)
|
||||
expect(galleryActiveIndex.value).toBe(0)
|
||||
|
||||
Reference in New Issue
Block a user