Files
ComfyUI_frontend/src/services/jobOutputCache.ts
ric-yu d27e177d61 feat: Migrate to Jobs API (PR 2 of 3) (#7170)
## Summary

Migrate frontend from legacy `/history`, `/history_v2`, and `/queue`
endpoints to the unified `/jobs` API with memory optimization and lazy
loading.

**This is PR 2 of 3** - Core migration, depends on PR 1.

## Changes

- **What**:
- Replace `api.getQueue()` and `api.getHistory()` implementations to use
Jobs API fetchers
- Implement lazy loading for workflow and full outputs via `/jobs/{id}`
endpoint in `useJobMenu`
- Add `TaskItemImpl` class wrapping `JobListItem` for queue store
compatibility
  - Rename `reconcileHistory` to `reconcileJobs` for clarity
- Use `execution_start_time` and `execution_end_time` from API for
execution timing
  - Use `workflowId` from job instead of nested `workflow.id`
- Update `useJobMenu` to fetch job details on demand (`openJobWorkflow`,
`exportJobWorkflow`)

- **Breaking**: Requires backend Jobs API support (ComfyUI with `/jobs`
endpoint)

## Review Focus

1. **Lazy loading in `useJobMenu`**: `openJobWorkflow` and
`exportJobWorkflow` now fetch from API on demand instead of accessing
`taskRef.workflow`
2. **`TaskItemImpl` wrapper**: Adapts `JobListItem` to existing queue
store interface
3. **Error reporting**: Uses `execution_error` field from API for rich
error dialogs
4. **Memory optimization**: Only fetches full job details when needed

## Files Changed

- `src/scripts/api.ts` - Updated `getQueue()` and `getHistory()` to use
Jobs API
- `src/stores/queueStore.ts` - Added `TaskItemImpl`, updated to use
`JobListItem`
- `src/composables/useJobMenu.ts` - Lazy loading for workflow access
- `src/composables/useJobList.ts` - Updated types
- Various test files updated

## Dependencies

- **Depends on**: PR 1 (Jobs API Infrastructure) - #7169

## Next PR

- **PR 3**: Remove legacy history code and unused types

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-7170-feat-Migrate-to-Jobs-API-PR-2-of-3-2bf6d73d3650811b94f4fbe69944bba6)
by [Unito](https://www.unito.io)

---------

Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Christian Byrne <cbyrne@comfy.org>
2026-01-13 20:38:00 -07:00

104 lines
2.8 KiB
TypeScript

/**
* @fileoverview Job output cache for caching and managing job data
* @module services/jobOutputCache
*
* Centralizes job output and detail caching with LRU eviction.
* Provides helpers for working with previewable outputs and workflows.
*/
import QuickLRU from '@alloc/quick-lru'
import type { JobDetail } from '@/platform/remote/comfyui/jobs/jobTypes'
import { extractWorkflow } from '@/platform/remote/comfyui/jobs/fetchJobs'
import type { ComfyWorkflowJSON } from '@/platform/workflow/validation/schemas/workflowSchema'
import { api } from '@/scripts/api'
import { ResultItemImpl } from '@/stores/queueStore'
import type { TaskItemImpl } from '@/stores/queueStore'
const MAX_TASK_CACHE_SIZE = 50
const MAX_JOB_DETAIL_CACHE_SIZE = 50
const taskCache = new QuickLRU<string, TaskItemImpl>({
maxSize: MAX_TASK_CACHE_SIZE
})
const jobDetailCache = new QuickLRU<string, JobDetail>({
maxSize: MAX_JOB_DETAIL_CACHE_SIZE
})
// Track latest request to dedupe stale responses
let latestTaskRequestId: string | null = null
// ===== Task Output Caching =====
export function findActiveIndex(
items: readonly ResultItemImpl[],
url?: string
): number {
return ResultItemImpl.findByUrl(items, url)
}
/**
* Gets previewable outputs for a task, with lazy loading, caching, and request deduping.
* Returns null if a newer request superseded this one while loading.
*/
export async function getOutputsForTask(
task: TaskItemImpl
): Promise<ResultItemImpl[] | null> {
const requestId = String(task.promptId)
latestTaskRequestId = requestId
const outputsCount = task.outputsCount ?? 0
const needsLazyLoad = outputsCount > 1
if (!needsLazyLoad) {
return [...task.previewableOutputs]
}
const cached = taskCache.get(requestId)
if (cached) {
return [...cached.previewableOutputs]
}
try {
const loadedTask = await task.loadFullOutputs()
// Check if request was superseded while loading
if (latestTaskRequestId !== requestId) {
return null
}
taskCache.set(requestId, loadedTask)
return [...loadedTask.previewableOutputs]
} catch (error) {
console.warn('Failed to load full outputs, using preview:', error)
return [...task.previewableOutputs]
}
}
// ===== Job Detail Caching =====
export async function getJobDetail(
jobId: string
): Promise<JobDetail | undefined> {
const cached = jobDetailCache.get(jobId)
if (cached) return cached
try {
const detail = await api.getJobDetail(jobId)
if (detail) {
jobDetailCache.set(jobId, detail)
}
return detail
} catch (error) {
console.warn('Failed to fetch job detail:', error)
return undefined
}
}
export async function getJobWorkflow(
jobId: string
): Promise<ComfyWorkflowJSON | undefined> {
const detail = await getJobDetail(jobId)
return await extractWorkflow(detail)
}