Move library collections as a group and map v2 identity refs.
Collapsed collection actions now move, rename, delete, and rerun every part so chains stay together. Identity mode follows the v2u3 Ref2VA picture wiring. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import { getImageComfyHost, setImageComfyHostOverride, imageComfyConfigured } from '~/server/utils/imageComfy'
|
||||
import { getImageComfyHost, setImageComfyHostOverride, imageComfyConfigured, getBeastImageHost, getSidecarImageHost, sameImageHost, withImageComfyHost, type ImageEditBox } from '~/server/utils/imageComfy'
|
||||
|
||||
export type ImageComfyState = 'online' | 'booting' | 'busy' | 'starting' | 'offline'
|
||||
|
||||
@@ -6,11 +6,18 @@ export interface ImageComfyStatus {
|
||||
state: ImageComfyState
|
||||
message: string
|
||||
host: string
|
||||
box?: ImageEditBox
|
||||
processRunning?: boolean
|
||||
queueRunning?: number
|
||||
queuePending?: number
|
||||
}
|
||||
|
||||
export interface ImageEditTarget {
|
||||
host: string
|
||||
box: ImageEditBox
|
||||
label: string
|
||||
}
|
||||
|
||||
type StatusFn = (status: ImageComfyStatus) => void
|
||||
|
||||
let gate: Promise<unknown> = Promise.resolve()
|
||||
@@ -51,6 +58,7 @@ export async function checkImageComfyHttp(timeoutMs = 2500, host?: string) {
|
||||
if (!imageComfyConfigured()) return { ok: false as const, host: '', status: 0, stats: null }
|
||||
resolved = getImageComfyHost()
|
||||
}
|
||||
if (!resolved) return { ok: false as const, host: '', status: 0, stats: null }
|
||||
const res = await fetch(`${resolved}/system_stats`, { signal: AbortSignal.timeout(timeoutMs) })
|
||||
if (!res.ok) return { ok: false as const, host: resolved, status: res.status, stats: null }
|
||||
const stats = await res.json().catch(() => null)
|
||||
@@ -60,9 +68,9 @@ export async function checkImageComfyHttp(timeoutMs = 2500, host?: string) {
|
||||
}
|
||||
}
|
||||
|
||||
export async function fetchImageQueue() {
|
||||
export async function fetchQueueAt(host: string) {
|
||||
if (!host) return { running: 0, pending: 0 }
|
||||
try {
|
||||
const host = getImageComfyHost()
|
||||
const res = await fetch(`${host}/queue`, { signal: AbortSignal.timeout(2500) })
|
||||
if (!res.ok) return { running: 0, pending: 0 }
|
||||
const payload = await res.json() as { queue_running?: unknown[]; queue_pending?: unknown[] }
|
||||
@@ -75,6 +83,10 @@ export async function fetchImageQueue() {
|
||||
}
|
||||
}
|
||||
|
||||
export async function fetchImageQueue() {
|
||||
return fetchQueueAt(getImageComfyHost())
|
||||
}
|
||||
|
||||
async function controlRequest(path: string, method = 'GET') {
|
||||
const { controlUrl, controlToken } = settings()
|
||||
if (!controlUrl) return null
|
||||
@@ -216,3 +228,70 @@ export function ensureImageComfyReady(onStatus: StatusFn = () => undefined) {
|
||||
gate = run.then(() => undefined, () => undefined)
|
||||
return run
|
||||
}
|
||||
|
||||
export async function pickImageEditHost(onStatus: StatusFn = () => undefined): Promise<ImageEditTarget> {
|
||||
const beast = getBeastImageHost()
|
||||
const sidecar = getSidecarImageHost()
|
||||
if (!beast && !sidecar) {
|
||||
throw createError({
|
||||
statusCode: 503,
|
||||
statusMessage: 'No image ComfyUI host is configured. Set COMFY_HOST (Beast) and/or IMAGE_COMFY_HOST (Sidecar).'
|
||||
})
|
||||
}
|
||||
|
||||
if (beast) {
|
||||
const health = await checkImageComfyHttp(2500, beast)
|
||||
if (health.ok) {
|
||||
const queue = await fetchQueueAt(beast)
|
||||
if (queue.running === 0) {
|
||||
onStatus({
|
||||
state: 'online',
|
||||
message: 'Beast is free. Editing on this machine.',
|
||||
host: beast,
|
||||
box: 'beast',
|
||||
queueRunning: 0,
|
||||
queuePending: queue.pending
|
||||
})
|
||||
return { host: beast, box: 'beast', label: 'Beast' }
|
||||
}
|
||||
const canOverflow = Boolean(sidecar && !sameImageHost(beast, sidecar))
|
||||
onStatus({
|
||||
state: 'busy',
|
||||
message: canOverflow
|
||||
? 'Beast is busy. Rolling over to Sidecar...'
|
||||
: `Beast is busy (${queue.running} running). Waiting for a free slot...`,
|
||||
host: beast,
|
||||
box: 'beast',
|
||||
queueRunning: queue.running,
|
||||
queuePending: queue.pending
|
||||
})
|
||||
if (!canOverflow) {
|
||||
await withImageComfyHost(beast, () => waitWhileBusy(onStatus))
|
||||
return { host: beast, box: 'beast', label: 'Beast' }
|
||||
}
|
||||
} else if (sidecar && !sameImageHost(beast, sidecar)) {
|
||||
onStatus({
|
||||
state: 'offline',
|
||||
message: 'Beast is offline. Trying Sidecar...',
|
||||
host: beast,
|
||||
box: 'beast'
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
if (sidecar && (!beast || !sameImageHost(beast, sidecar))) {
|
||||
onStatus({
|
||||
state: 'starting',
|
||||
message: 'Checking Sidecar for this edit...',
|
||||
host: sidecar,
|
||||
box: 'sidecar'
|
||||
})
|
||||
await withImageComfyHost(sidecar, () => ensureImageComfyReady(onStatus))
|
||||
return { host: sidecar, box: 'sidecar', label: 'Sidecar' }
|
||||
}
|
||||
|
||||
throw createError({
|
||||
statusCode: 503,
|
||||
statusMessage: 'Beast is unavailable and Sidecar is not configured.'
|
||||
})
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user