feat: add KSampler live previews to assets sidebar jobs (#8723)

## Summary
Show live KSampler previews on active job cards/list items in the Assets
sidebar, while preserving existing fallback behavior.

## Changes
- **What**:
- Added a prompt-scoped job preview store (`jobPreviewStore`) gated by
`Comfy.Execution.PreviewMethod`.
- Wired `b_preview_with_metadata` handling to map previews by
`promptId`.
- Extended queue job view model with `livePreviewUrl` and consumed it in
both sidebar list and grid active job UIs.
  - Cleared prompt previews on execution reset.
- Added ref-counted shared blob URL lifecycle utility (`objectUrlUtil`)
and updated preview stores to retain/release shared URLs so each preview
event creates one object URL.
- Added/updated unit coverage in `useJobList.test.ts` for preview
enable/disable mapping.

## Review Focus
- Object URL lifecycle correctness across node previews and job previews
(retain/release behavior).
- Preview gating behavior when `Comfy.Execution.PreviewMethod` is
`none`.
- Active job UI fallback behavior (`livePreviewUrl` -> `iconImageUrl`).

## Screenshots (if applicable)
<img width="808" height="614" alt="image"
src="https://github.com/user-attachments/assets/37c66eb2-8c28-4eb4-bb86-5679cb77d740"
/>
<img width="775" height="345" alt="image"
src="https://github.com/user-attachments/assets/aa420642-b0d4-4ae6-b94a-e7934b5df9d6"
/>


┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-8723-feat-add-KSampler-live-previews-to-assets-sidebar-jobs-3006d73d365081aeb81dd8279bf99f94)
by [Unito](https://www.unito.io)
This commit is contained in:
Benjamin Lu
2026-02-09 10:49:27 -08:00
committed by GitHub
parent 815be49112
commit 9209badd37
7 changed files with 199 additions and 5 deletions

View File

@@ -0,0 +1,27 @@
const objectUrlRefCounts = new Map<string, number>()
const isBlobUrl = (url: string) => url.startsWith('blob:')
export function createSharedObjectUrl(blob: Blob): string {
const url = URL.createObjectURL(blob)
objectUrlRefCounts.set(url, 1)
return url
}
export function retainSharedObjectUrl(url: string | undefined): void {
if (!url || !isBlobUrl(url)) return
objectUrlRefCounts.set(url, (objectUrlRefCounts.get(url) ?? 0) + 1)
}
export function releaseSharedObjectUrl(url: string | undefined): void {
if (!url || !isBlobUrl(url)) return
const currentCount = objectUrlRefCounts.get(url)
if (currentCount === undefined || currentCount <= 1) {
objectUrlRefCounts.delete(url)
URL.revokeObjectURL(url)
return
}
objectUrlRefCounts.set(url, currentCount - 1)
}