Name outputs from the input, engine, and LoRA keys instead of aigen_0001. Image-edit presets now store prompts, and aspect fill pads stills without stretching.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Towsty
2026-08-30 16:24:49 -05:00
co-authored by Cursor
parent 4d411f83d4
commit 0e1222d035
23 changed files with 668 additions and 93 deletions
+54 -4
View File
@@ -8,6 +8,8 @@ import type { H3Event } from 'h3'
import { extractFirstFrame, extractFrameAt, extractLastFrame, probeDuration, remuxFaststart } from '~/server/utils/ffmpeg'
import { normalizePermanenceRefs, type PermanenceRef } from '~/utils/globalLocks'
import { normalizeStillSettings, type StillGenerationSettings } from '~/utils/libraryGeneration'
import { buildOutputBaseName, nextUniqueName, normalizeLoraAliases, sanitizeLoraAlias } from '~/utils/outputNames'
import { loraIdentityKey, type LoraStackItem } from '~/utils/loras'
const scryptAsync = promisify(scrypt)
@@ -128,6 +130,7 @@ interface Catalog {
clips: LibraryClip[]
stills: LibraryStill[]
drafts: RetryDraft[]
loraAliases: Record<string, string>
}
const writeChains = new Map<string, Promise<unknown>>()
@@ -166,7 +169,8 @@ function emptyCatalog(): Catalog {
}],
clips: [],
stills: [],
drafts: []
drafts: [],
loraAliases: {}
}
}
@@ -225,7 +229,8 @@ function normalizeCatalog(parsed: Partial<Catalog> & { passwordHash?: string | n
folders,
clips: Array.isArray(parsed.clips) ? parsed.clips : [],
stills: Array.isArray(parsed.stills) ? parsed.stills.map(normalizeStill) : [],
drafts: Array.isArray(parsed.drafts) ? parsed.drafts : []
drafts: Array.isArray(parsed.drafts) ? parsed.drafts : [],
loraAliases: normalizeLoraAliases(parsed.loraAliases)
}
}
@@ -359,7 +364,51 @@ export function stillChainName(base: string, index: number) {
export function safeDownloadName(name: string) {
const cleaned = name.replace(/[<>:"/\\|?*'`\u0000-\u001f]/g, '-').replace(/\s+/g, ' ').trim()
return cleaned.slice(0, 80) || 'aigen'
return cleaned.slice(0, 80) || 'output'
}
function existingOutputNames(catalog: Catalog, folderId: string) {
const stills = catalog.stills
.filter(item => item.folderId === folderId)
.map(item => String(item.name || '').trim())
.filter(Boolean)
const clips = catalog.clips
.filter(item => item.folderId === folderId)
.map(item => String(item.name || '').trim())
.filter(Boolean)
return [...stills, ...clips]
}
export function getLoraAliases(owner: string) {
return { ...(readCatalog(owner).loraAliases || {}) }
}
export async function setLoraAlias(owner: string, identity: string, alias: string) {
const key = loraIdentityKey(identity) || String(identity || '').trim().toLowerCase()
if (!key) throw createError({ statusCode: 400, statusMessage: 'A LoRA file is required' })
const cleaned = sanitizeLoraAlias(alias)
return mutate(owner, (catalog) => {
if (!catalog.loraAliases) catalog.loraAliases = {}
if (!cleaned) delete catalog.loraAliases[key]
else catalog.loraAliases[key] = cleaned
return { ...catalog.loraAliases }
})
}
export function allocateOutputName(owner: string, folderId: string, requested: string, opts: {
inputFilename?: string
engine: string
loraStack?: LoraStackItem[] | unknown
}) {
const catalog = readCatalog(owner)
const base = buildOutputBaseName({
requested,
inputFilename: opts.inputFilename,
engine: opts.engine,
loraStack: opts.loraStack,
aliases: catalog.loraAliases || {}
})
return nextUniqueName(existingOutputNames(catalog, folderId), base)
}
export function passwordVersion(hash: string | null) {
@@ -425,7 +474,8 @@ export function publicLibrary(event: H3Event) {
stills: catalog.stills
.filter(still => unlockedIds.has(still.folderId) && !isPipelineStill(catalog, still))
.map(still => ({ ...still, role: stillRole(still) })),
drafts: catalog.drafts
drafts: catalog.drafts,
loraAliases: catalog.loraAliases || {}
}
}