Let clips move between folders, save busy jobs, and fail dropped Comfy runs.
Held jobs restore the form after a 3-minute wait, hidden stills stay blurred in the picker, and reloading Comfy no longer leaves xaigen spinning. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
+130
-8
@@ -40,6 +40,7 @@ export interface LibraryStill {
|
||||
height: number
|
||||
hash: string
|
||||
createdAt: number
|
||||
hideInput: boolean
|
||||
}
|
||||
|
||||
export interface PublicFolder {
|
||||
@@ -52,10 +53,31 @@ export interface PublicFolder {
|
||||
clipCount: number
|
||||
}
|
||||
|
||||
export interface RetryDraft {
|
||||
id: string
|
||||
createdAt: number
|
||||
folderId: string
|
||||
stillId?: string
|
||||
name?: string
|
||||
prompt: string
|
||||
aspect: string
|
||||
width: number
|
||||
height: number
|
||||
steps: number
|
||||
turbo: boolean
|
||||
seed: number
|
||||
duration: number
|
||||
hideThumbnail: boolean
|
||||
sound: boolean
|
||||
hideInput?: boolean
|
||||
stillFilename?: string
|
||||
}
|
||||
|
||||
interface Catalog {
|
||||
folders: LibraryFolder[]
|
||||
clips: LibraryClip[]
|
||||
stills: LibraryStill[]
|
||||
drafts: RetryDraft[]
|
||||
}
|
||||
|
||||
const writeChains = new Map<string, Promise<unknown>>()
|
||||
@@ -93,7 +115,8 @@ function emptyCatalog(): Catalog {
|
||||
passwordHash: null
|
||||
}],
|
||||
clips: [],
|
||||
stills: []
|
||||
stills: [],
|
||||
drafts: []
|
||||
}
|
||||
}
|
||||
|
||||
@@ -107,6 +130,19 @@ function normalizeFolder(folder: LibraryFolder): LibraryFolder {
|
||||
}
|
||||
}
|
||||
|
||||
function normalizeStill(still: LibraryStill): LibraryStill {
|
||||
return {
|
||||
id: still.id,
|
||||
folderId: still.folderId,
|
||||
filename: still.filename,
|
||||
width: still.width || 0,
|
||||
height: still.height || 0,
|
||||
hash: still.hash || '',
|
||||
createdAt: still.createdAt || Date.now(),
|
||||
hideInput: still.hideInput === true
|
||||
}
|
||||
}
|
||||
|
||||
function normalizeCatalog(parsed: Partial<Catalog> & { passwordHash?: string | null }): Catalog {
|
||||
const folders = Array.isArray(parsed.folders) && parsed.folders.length
|
||||
? parsed.folders.map(normalizeFolder)
|
||||
@@ -119,7 +155,8 @@ function normalizeCatalog(parsed: Partial<Catalog> & { passwordHash?: string | n
|
||||
return {
|
||||
folders,
|
||||
clips: Array.isArray(parsed.clips) ? parsed.clips : [],
|
||||
stills: Array.isArray(parsed.stills) ? parsed.stills : []
|
||||
stills: Array.isArray(parsed.stills) ? parsed.stills.map(normalizeStill) : [],
|
||||
drafts: Array.isArray(parsed.drafts) ? parsed.drafts : []
|
||||
}
|
||||
}
|
||||
|
||||
@@ -250,7 +287,8 @@ export function publicLibrary(event: H3Event) {
|
||||
name: clipTitle(clip),
|
||||
hasThumbnail: !clip.hideThumbnail && existsSync(clipThumbPath(owner, clip.id))
|
||||
})),
|
||||
stills: catalog.stills.filter(still => unlockedIds.has(still.folderId))
|
||||
stills: catalog.stills.filter(still => unlockedIds.has(still.folderId)),
|
||||
drafts: catalog.drafts
|
||||
}
|
||||
}
|
||||
|
||||
@@ -571,13 +609,23 @@ export async function saveStill(params: {
|
||||
data: Buffer
|
||||
width?: number
|
||||
height?: number
|
||||
hideInput?: boolean
|
||||
}) {
|
||||
const catalog = readCatalog(params.ownerKey)
|
||||
const folder = catalog.folders.find(item => item.id === params.folderId) || catalog.folders[0]
|
||||
if (!folder) throw createError({ statusCode: 400, statusMessage: 'No library folder available' })
|
||||
const hash = bytesHash(params.data)
|
||||
const existing = catalog.stills.find(item => item.folderId === folder.id && item.hash === hash)
|
||||
if (existing) return existing
|
||||
if (existing) {
|
||||
if (typeof params.hideInput === 'boolean' && existing.hideInput !== params.hideInput) {
|
||||
await mutate(params.ownerKey, (next) => {
|
||||
const item = next.stills.find(still => still.id === existing.id)
|
||||
if (item) item.hideInput = params.hideInput === true
|
||||
})
|
||||
existing.hideInput = params.hideInput === true
|
||||
}
|
||||
return existing
|
||||
}
|
||||
const still: LibraryStill = {
|
||||
id: crypto.randomUUID(),
|
||||
folderId: folder.id,
|
||||
@@ -585,7 +633,8 @@ export async function saveStill(params: {
|
||||
width: params.width || 0,
|
||||
height: params.height || 0,
|
||||
hash,
|
||||
createdAt: Date.now()
|
||||
createdAt: Date.now(),
|
||||
hideInput: params.hideInput === true
|
||||
}
|
||||
mkdirSync(join(ownerRoot(params.ownerKey), 'stills'), { recursive: true })
|
||||
writeFileSync(stillPath(params.ownerKey, still.id), params.data)
|
||||
@@ -595,6 +644,66 @@ export async function saveStill(params: {
|
||||
return still
|
||||
}
|
||||
|
||||
export async function saveRetryDraft(params: {
|
||||
ownerKey: string
|
||||
id?: string
|
||||
folderId: string
|
||||
stillId?: string
|
||||
stillFilename?: string
|
||||
name?: string
|
||||
prompt: string
|
||||
aspect: string
|
||||
width: number
|
||||
height: number
|
||||
steps: number
|
||||
turbo: boolean
|
||||
seed: number
|
||||
duration: number
|
||||
hideThumbnail: boolean
|
||||
sound: boolean
|
||||
hideInput?: boolean
|
||||
}) {
|
||||
return mutate(params.ownerKey, (catalog) => {
|
||||
const existing = params.id ? catalog.drafts.find(item => item.id === params.id) : undefined
|
||||
const draft: RetryDraft = {
|
||||
id: existing?.id || crypto.randomUUID(),
|
||||
createdAt: existing?.createdAt || Date.now(),
|
||||
folderId: params.folderId,
|
||||
stillId: params.stillId,
|
||||
stillFilename: params.stillFilename,
|
||||
name: params.name,
|
||||
prompt: params.prompt,
|
||||
aspect: params.aspect,
|
||||
width: params.width,
|
||||
height: params.height,
|
||||
steps: params.steps,
|
||||
turbo: params.turbo,
|
||||
seed: params.seed,
|
||||
duration: params.duration,
|
||||
hideThumbnail: params.hideThumbnail,
|
||||
sound: params.sound,
|
||||
hideInput: params.hideInput === true
|
||||
}
|
||||
catalog.drafts = [draft, ...catalog.drafts.filter(item => item.id !== draft.id)].slice(0, 20)
|
||||
return draft
|
||||
})
|
||||
}
|
||||
|
||||
export function getRetryDraft(owner: string, id: string) {
|
||||
const draft = readCatalog(owner).drafts.find(item => item.id === id)
|
||||
if (!draft) throw createError({ statusCode: 404, statusMessage: 'Held job not found' })
|
||||
return draft
|
||||
}
|
||||
|
||||
export function deleteRetryDraft(owner: string, id: string) {
|
||||
return mutate(owner, (catalog) => {
|
||||
const draft = catalog.drafts.find(item => item.id === id)
|
||||
if (!draft) throw createError({ statusCode: 404, statusMessage: 'Held job not found' })
|
||||
catalog.drafts = catalog.drafts.filter(item => item.id !== id)
|
||||
return draft
|
||||
})
|
||||
}
|
||||
|
||||
export function getStill(owner: string, id: string) {
|
||||
const still = readCatalog(owner).stills.find(item => item.id === id)
|
||||
if (!still) throw createError({ statusCode: 404, statusMessage: 'Image not found' })
|
||||
@@ -630,6 +739,7 @@ export async function saveClip(params: {
|
||||
turbo: boolean
|
||||
seed: number
|
||||
hideThumbnail: boolean
|
||||
hideInput?: boolean
|
||||
video: Buffer
|
||||
thumb?: Buffer | null
|
||||
comfyFilename?: string
|
||||
@@ -654,9 +764,9 @@ export async function saveClip(params: {
|
||||
}
|
||||
mkdirSync(clipDir(params.ownerKey, clip.id), { recursive: true })
|
||||
writeFileSync(clipVideoPath(params.ownerKey, clip.id), params.video)
|
||||
if (!params.hideThumbnail && params.thumb?.length) {
|
||||
if (params.thumb?.length) {
|
||||
writeFileSync(join(clipDir(params.ownerKey, clip.id), 'thumb'), params.thumb)
|
||||
} else if (!params.hideThumbnail) {
|
||||
} else {
|
||||
await extractVideoThumb(clipVideoPath(params.ownerKey, clip.id), join(clipDir(params.ownerKey, clip.id), 'thumb.jpg'))
|
||||
}
|
||||
if (params.thumb?.length) {
|
||||
@@ -666,7 +776,8 @@ export async function saveClip(params: {
|
||||
filename: `${clip.id}.png`,
|
||||
data: params.thumb,
|
||||
width: params.width,
|
||||
height: params.height
|
||||
height: params.height,
|
||||
hideInput: params.hideInput
|
||||
})
|
||||
}
|
||||
await mutate(params.ownerKey, (next) => {
|
||||
@@ -691,6 +802,17 @@ export function deleteClip(owner: string, id: string) {
|
||||
})
|
||||
}
|
||||
|
||||
export function moveClip(owner: string, id: string, folderId: string) {
|
||||
return mutate(owner, (catalog) => {
|
||||
const clip = catalog.clips.find(item => item.id === id)
|
||||
if (!clip) throw createError({ statusCode: 404, statusMessage: 'Clip not found' })
|
||||
const folder = catalog.folders.find(item => item.id === folderId)
|
||||
if (!folder) throw createError({ statusCode: 404, statusMessage: 'Folder not found' })
|
||||
clip.folderId = folder.id
|
||||
return clip
|
||||
})
|
||||
}
|
||||
|
||||
export async function downloadComfyVideo(video: { filename: string; subfolder: string; type: string }) {
|
||||
const subfolders = [...new Set([video.subfolder, 'video', ''])]
|
||||
let lastStatus = 0
|
||||
|
||||
Reference in New Issue
Block a user