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:
@@ -1,4 +1,5 @@
|
||||
import { LTX_NEGATIVE } from '~/utils/videoModels'
|
||||
import { comfyJobPrefix } from '~/utils/outputNames'
|
||||
|
||||
let comfyHostOverride = ''
|
||||
|
||||
@@ -103,8 +104,7 @@ export function comfyInputFilename(original: string, jobId?: string) {
|
||||
const dot = raw.lastIndexOf('.')
|
||||
const ext = (dot >= 0 ? raw.slice(dot) : '.png').replace(/[^.a-zA-Z0-9]/g, '') || '.png'
|
||||
const base = (dot >= 0 ? raw.slice(0, dot) : raw).replace(/[^a-zA-Z0-9._-]+/g, '_').slice(0, 48) || 'still'
|
||||
const id = String(jobId || crypto.randomUUID()).replace(/-/g, '').slice(0, 10)
|
||||
return `aigen_${id}_${base}${ext}`
|
||||
return `${comfyJobPrefix(jobId)}_${base}${ext}`
|
||||
}
|
||||
|
||||
function comfyQueueMessage(payload: unknown, status: number) {
|
||||
|
||||
@@ -9,6 +9,7 @@ import {
|
||||
clampVideoCfg,
|
||||
normalizePresetName,
|
||||
parseGenerationPresetKind,
|
||||
PRESET_PROMPT_MAX,
|
||||
type GenerationPreset,
|
||||
type GenerationPresetKind,
|
||||
type ImagePresetSettings,
|
||||
@@ -105,14 +106,20 @@ function sanitizeVideoSettings(raw: unknown): VideoPresetSettings {
|
||||
}
|
||||
}
|
||||
|
||||
function sanitizePrompt(raw: unknown) {
|
||||
return typeof raw === 'string' ? raw.slice(0, PRESET_PROMPT_MAX) : undefined
|
||||
}
|
||||
|
||||
function sanitizeImageSettings(raw: unknown): ImagePresetSettings {
|
||||
const rec = raw && typeof raw === 'object' ? raw as Record<string, unknown> : {}
|
||||
return {
|
||||
prompt: sanitizePrompt(rec.prompt),
|
||||
steps: clampImageSteps(rec.steps),
|
||||
cfg: clampImageCfg(rec.cfg),
|
||||
negative: String(rec.negative || '').slice(0, 2000),
|
||||
scaleToTotalPixels: rec.scaleToTotalPixels === true,
|
||||
scaleMegapixels: clampImageScaleMegapixels(rec.scaleMegapixels)
|
||||
scaleMegapixels: clampImageScaleMegapixels(rec.scaleMegapixels),
|
||||
aspect: rec.aspect === '16:9' || rec.aspect === '9:16' || rec.aspect === '1:1' || rec.aspect === 'auto' ? rec.aspect : 'auto'
|
||||
}
|
||||
}
|
||||
|
||||
@@ -125,6 +132,7 @@ function sanitizeImageV2Settings(raw: unknown): ImageV2PresetSettings {
|
||||
mode,
|
||||
engine,
|
||||
task: mode === 'compose' ? parseImageV2Task(rec.task, 'scene') : mode === 'refine' ? 'refine' : mode === 'generate' ? 't2i' : 'scene',
|
||||
prompt: sanitizePrompt(rec.prompt),
|
||||
negative: String(rec.negative || '').slice(0, 2000),
|
||||
snofsModel: isXaigenStudio() ? clampImageV2Strength(rec.snofsModel ?? rec.snofs_model, IMAGE_V2_SNOFS_MODEL) : 0,
|
||||
snofsClip: isXaigenStudio() ? clampImageV2Strength(rec.snofsClip ?? rec.snofs_clip, IMAGE_V2_SNOFS_CLIP) : 0,
|
||||
@@ -141,7 +149,8 @@ function sanitizeImageV2Settings(raw: unknown): ImageV2PresetSettings {
|
||||
mode === 'compose' ? IMAGE_V2_KREA_COMPOSE_DENOISE : mode === 'edit' ? IMAGE_V2_KREA_EDIT_DENOISE : IMAGE_V2_DENOISE_DEFAULT
|
||||
),
|
||||
width: mode === 'generate' ? clampImageV2Size(rec.width, rec.height, rec.aspect).width : undefined,
|
||||
height: mode === 'generate' ? clampImageV2Size(rec.width, rec.height, rec.aspect).height : undefined
|
||||
height: mode === 'generate' ? clampImageV2Size(rec.width, rec.height, rec.aspect).height : undefined,
|
||||
aspect: rec.aspect === '16:9' || rec.aspect === '9:16' || rec.aspect === '1:1' || rec.aspect === 'auto' ? String(rec.aspect) : (mode === 'generate' ? '1:1' : 'auto')
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ import { ensureComfyLoraNames } from '~/server/utils/loras'
|
||||
import { imageDimensions } from '~/server/utils/resolution'
|
||||
import { emitChainJob } from '~/server/utils/watch'
|
||||
import { saveStill, stillChainName } from '~/server/utils/library'
|
||||
import { comfyJobPrefix } from '~/utils/outputNames'
|
||||
|
||||
export type EditImageFile = { filename: string; data: Buffer; type?: string }
|
||||
|
||||
@@ -19,6 +20,7 @@ export type EditRunParams = {
|
||||
steps: number
|
||||
seed: number
|
||||
cfg: number
|
||||
aspect?: string
|
||||
loraName?: string
|
||||
loraStack?: import('~/utils/loras').LoraStackItem[]
|
||||
scaleToTotalPixels?: boolean
|
||||
@@ -46,12 +48,22 @@ export async function runEdit(job: Job, params: EditRunParams) {
|
||||
job.imageComfyHost = getComfyHost()
|
||||
|
||||
await withImageComfyHost(job.imageComfyHost, async () => {
|
||||
let current = params.image
|
||||
const { applyImageFill } = await import('~/server/utils/imagePad')
|
||||
const { composeFillPrompt } = await import('~/utils/imageAspect')
|
||||
const filled = await applyImageFill({ image: params.image, aspect: params.aspect || 'auto' })
|
||||
if (filled.padded) {
|
||||
emitChainJob(job, {
|
||||
type: 'status',
|
||||
message: `Filling to ${filled.aspect} without stretching...`,
|
||||
progress: 7
|
||||
})
|
||||
}
|
||||
let current = filled.image
|
||||
let parentStillId: string | undefined
|
||||
for (let index = 0; index < prompts.length; index++) {
|
||||
if (job.status === 'cancelled') throw new Error('Job interrupted.')
|
||||
if (index > 0 && library.stopAfterCurrent === true) break
|
||||
const prompt = prompts[index]
|
||||
const prompt = index === 0 ? composeFillPrompt(prompts[index], filled.padded) : prompts[index]
|
||||
const last = index === prompts.length - 1
|
||||
if (index > 0) {
|
||||
await ensureComfyReady((status) => {
|
||||
@@ -104,7 +116,7 @@ export async function runEdit(job: Job, params: EditRunParams) {
|
||||
steps: params.steps,
|
||||
seed,
|
||||
cfg: params.cfg,
|
||||
filenamePrefix: `aigen_edit_${job.id.slice(0, 8)}_p${index + 1}`,
|
||||
filenamePrefix: `${comfyJobPrefix(job.id)}_p${index + 1}`,
|
||||
loraStack: params.loraStack || params.loraName,
|
||||
scaleToTotalPixels: params.scaleToTotalPixels,
|
||||
scaleMegapixels: params.scaleMegapixels
|
||||
@@ -161,6 +173,7 @@ export async function runEdit(job: Job, params: EditRunParams) {
|
||||
seed,
|
||||
scaleToTotalPixels: params.scaleToTotalPixels === true,
|
||||
scaleMegapixels: params.scaleMegapixels,
|
||||
aspect: params.aspect || 'auto',
|
||||
loraName: params.loraName,
|
||||
loraStack: params.loraStack,
|
||||
sourceStillId: library.stillId
|
||||
|
||||
@@ -7,6 +7,7 @@ import { ensureComfyLoraNames, persistLoraFields } from '~/server/utils/loras'
|
||||
import { imageDimensions } from '~/server/utils/resolution'
|
||||
import { emitChainJob } from '~/server/utils/watch'
|
||||
import { saveStill } from '~/server/utils/library'
|
||||
import { comfyJobPrefix } from '~/utils/outputNames'
|
||||
import { parseImageV2Engine, type ImageV2Engine, type ImageV2Mode, type ImageV2Task } from '~/utils/imageV2'
|
||||
import type { EditImageFile } from '~/server/utils/imageChain'
|
||||
|
||||
@@ -31,6 +32,7 @@ export type EditV2RunParams = {
|
||||
width?: number
|
||||
height?: number
|
||||
turbo?: boolean
|
||||
aspect?: string
|
||||
sourceStillId?: string
|
||||
referenceStillId?: string
|
||||
loraStack?: import('~/utils/loras').LoraStackItem[]
|
||||
@@ -77,7 +79,31 @@ export async function runEditV2(job: Job, params: EditV2RunParams) {
|
||||
: params.mode === 'compose' ? 'Uploading stills A and B to Beast...' : 'Uploading still A to Beast...',
|
||||
progress: generate ? 12 : 8
|
||||
})
|
||||
const uploaded = generate || !params.image ? null : await uploadImage(params.image, job.id)
|
||||
const { applyImageFill } = await import('~/server/utils/imagePad')
|
||||
const { composeFillPrompt } = await import('~/utils/imageAspect')
|
||||
let image = params.image
|
||||
let mask = params.mask
|
||||
let prompt = params.prompt
|
||||
let padded = false
|
||||
if (!generate && image) {
|
||||
const filled = await applyImageFill({
|
||||
image,
|
||||
mask: params.mode === 'refine' ? mask : null,
|
||||
aspect: params.aspect || 'auto'
|
||||
})
|
||||
image = filled.image
|
||||
if (filled.mask) mask = filled.mask
|
||||
padded = filled.padded
|
||||
prompt = composeFillPrompt(prompt, padded)
|
||||
if (padded) {
|
||||
emitChainJob(job, {
|
||||
type: 'status',
|
||||
message: `Filling to ${filled.aspect} without stretching...`,
|
||||
progress: 7
|
||||
})
|
||||
}
|
||||
}
|
||||
const uploaded = generate || !image ? null : await uploadImage(image, job.id)
|
||||
const uploadedRef = generate || params.mode === 'refine'
|
||||
? null
|
||||
: params.reference
|
||||
@@ -86,10 +112,10 @@ export async function runEditV2(job: Job, params: EditV2RunParams) {
|
||||
filename: `ref_${params.reference.filename || 'image_b.png'}`
|
||||
}, job.id)
|
||||
: null
|
||||
const uploadedMask = !generate && params.mode === 'refine' && params.mask
|
||||
const uploadedMask = !generate && params.mode === 'refine' && mask
|
||||
? await uploadImage({
|
||||
...params.mask,
|
||||
filename: `mask_${params.mask.filename || 'refine-mask.png'}`
|
||||
...mask,
|
||||
filename: `mask_${mask.filename || 'refine-mask.png'}`
|
||||
}, job.id)
|
||||
: null
|
||||
if (job.status === 'cancelled') throw new Error('Job interrupted.')
|
||||
@@ -112,7 +138,7 @@ export async function runEditV2(job: Job, params: EditV2RunParams) {
|
||||
mode: params.mode,
|
||||
engine,
|
||||
task: params.task,
|
||||
prompt: params.prompt,
|
||||
prompt,
|
||||
negative: params.negative,
|
||||
imageAName: uploaded?.name,
|
||||
imageBName: uploadedRef?.name,
|
||||
@@ -129,7 +155,7 @@ export async function runEditV2(job: Job, params: EditV2RunParams) {
|
||||
width: params.width,
|
||||
height: params.height,
|
||||
turbo: params.turbo === true,
|
||||
filenamePrefix: `aigen_v2_${job.id.slice(0, 8)}`,
|
||||
filenamePrefix: comfyJobPrefix(job.id),
|
||||
kreaUnetName: krea?.unet,
|
||||
kreaClipName: krea?.clip,
|
||||
kreaVaeName: krea?.vae,
|
||||
@@ -174,7 +200,7 @@ export async function runEditV2(job: Job, params: EditV2RunParams) {
|
||||
const still = await saveStill({
|
||||
ownerKey: library.ownerKey,
|
||||
folderId: library.folderId,
|
||||
filename: output.filename,
|
||||
filename: library.name ? `${library.name}.png` : output.filename,
|
||||
data: buffer,
|
||||
width: size?.width || 0,
|
||||
height: size?.height || 0,
|
||||
@@ -199,6 +225,7 @@ export async function runEditV2(job: Job, params: EditV2RunParams) {
|
||||
strength: params.mode === 'generate' ? undefined : params.strength,
|
||||
width: params.mode === 'generate' ? params.width : undefined,
|
||||
height: params.mode === 'generate' ? params.height : undefined,
|
||||
aspect: params.aspect || 'auto',
|
||||
seed: params.seed,
|
||||
sourceStillId: params.sourceStillId || library.stillId,
|
||||
referenceStillId: params.referenceStillId,
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { AsyncLocalStorage } from 'node:async_hooks'
|
||||
import { getComfyHost } from '~/server/utils/comfy'
|
||||
import { comfyJobPrefix } from '~/utils/outputNames'
|
||||
|
||||
const imageHostAls = new AsyncLocalStorage<string>()
|
||||
let imageComfyHostOverride = ''
|
||||
@@ -98,8 +99,7 @@ function imageInputFilename(original: string, jobId?: string) {
|
||||
const dot = raw.lastIndexOf('.')
|
||||
const ext = (dot >= 0 ? raw.slice(dot) : '.png').replace(/[^.a-zA-Z0-9]/g, '') || '.png'
|
||||
const base = (dot >= 0 ? raw.slice(0, dot) : raw).replace(/[^a-zA-Z0-9._-]+/g, '_').slice(0, 48) || 'still'
|
||||
const id = String(jobId || crypto.randomUUID()).replace(/-/g, '').slice(0, 10)
|
||||
return `aigen_edit_${id}_${base}${ext}`
|
||||
return `${comfyJobPrefix(jobId)}_${base}${ext}`
|
||||
}
|
||||
|
||||
export async function uploadImageEdit(file: { filename: string; data: Buffer; type?: string }, jobId?: string) {
|
||||
|
||||
@@ -0,0 +1,79 @@
|
||||
import { mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs'
|
||||
import { tmpdir } from 'node:os'
|
||||
import { extname, join } from 'node:path'
|
||||
import { runFfmpeg } from '~/server/utils/ffmpeg'
|
||||
import { imageDimensions } from '~/server/utils/resolution'
|
||||
import { padExtents, parseImageAspect, type ImageAspectId } from '~/utils/imageAspect'
|
||||
|
||||
export type PaddedImage = { filename: string; data: Buffer; type?: string }
|
||||
|
||||
function sourceExt(filename: string, type?: string) {
|
||||
const fromName = extname(filename || '').toLowerCase()
|
||||
if (fromName === '.jpg' || fromName === '.jpeg' || fromName === '.png' || fromName === '.webp') return fromName
|
||||
if (type?.includes('jpeg')) return '.jpg'
|
||||
if (type?.includes('webp')) return '.webp'
|
||||
return '.png'
|
||||
}
|
||||
|
||||
export async function applyImageFill(opts: {
|
||||
image: PaddedImage
|
||||
mask?: PaddedImage | null
|
||||
aspect: string
|
||||
}): Promise<{ image: PaddedImage; mask?: PaddedImage; padded: boolean; aspect: ImageAspectId }> {
|
||||
const aspect = parseImageAspect(opts.aspect)
|
||||
if (aspect === 'auto') {
|
||||
return { image: opts.image, mask: opts.mask || undefined, padded: false, aspect }
|
||||
}
|
||||
const size = imageDimensions(opts.image.data)
|
||||
if (!size?.width || !size.height) {
|
||||
return { image: opts.image, mask: opts.mask || undefined, padded: false, aspect }
|
||||
}
|
||||
const box = padExtents(size.width, size.height, aspect)
|
||||
if (!box) {
|
||||
return { image: opts.image, mask: opts.mask || undefined, padded: false, aspect }
|
||||
}
|
||||
|
||||
const dir = mkdtempSync(join(tmpdir(), 'aigen-pad-'))
|
||||
try {
|
||||
const srcPath = join(dir, `src${sourceExt(opts.image.filename, opts.image.type)}`)
|
||||
const outPath = join(dir, 'out.png')
|
||||
writeFileSync(srcPath, opts.image.data)
|
||||
await runFfmpeg([
|
||||
'-y',
|
||||
'-i', srcPath,
|
||||
'-vf', `pad=${box.canvasW}:${box.canvasH}:${box.x}:${box.y}:black`,
|
||||
'-frames:v', '1',
|
||||
outPath
|
||||
])
|
||||
const padded: PaddedImage = {
|
||||
filename: 'padded.png',
|
||||
data: readFileSync(outPath),
|
||||
type: 'image/png'
|
||||
}
|
||||
|
||||
if (!opts.mask?.data?.length) {
|
||||
return { image: padded, padded: true, aspect }
|
||||
}
|
||||
|
||||
const userPath = join(dir, `mask${sourceExt(opts.mask.filename, opts.mask.type)}`)
|
||||
const maskPath = join(dir, 'pad-mask.png')
|
||||
writeFileSync(userPath, opts.mask.data)
|
||||
await runFfmpeg([
|
||||
'-y',
|
||||
'-f', 'lavfi',
|
||||
'-i', `color=c=white:s=${box.canvasW}x${box.canvasH}:d=1:r=1`,
|
||||
'-i', userPath,
|
||||
'-filter_complex', `[1]scale=${box.srcW}:${box.srcH}[m];[0][m]overlay=${box.x}:${box.y}`,
|
||||
'-frames:v', '1',
|
||||
maskPath
|
||||
])
|
||||
return {
|
||||
image: padded,
|
||||
mask: { filename: 'pad-mask.png', data: readFileSync(maskPath), type: 'image/png' },
|
||||
padded: true,
|
||||
aspect
|
||||
}
|
||||
} finally {
|
||||
rmSync(dir, { recursive: true, force: true })
|
||||
}
|
||||
}
|
||||
@@ -43,7 +43,7 @@ export function buildEditWorkflow(params: EditParams) {
|
||||
const dual = Boolean(params.referenceImageName)
|
||||
const prompt = String(params.prompt || '').trim()
|
||||
const negative = String(params.negative || '')
|
||||
const prefix = params.filenamePrefix || 'aigen-edit'
|
||||
const prefix = params.filenamePrefix || 'edit'
|
||||
|
||||
setImage(graph, IMAGE1, params.imageName)
|
||||
setText(graph, SINGLE_PROMPT, prompt)
|
||||
|
||||
@@ -400,7 +400,7 @@ function buildKreaItiWorkflow(params: ImageV2BuildParams) {
|
||||
setInput(graph, '15', 'sampler_name', 'euler')
|
||||
setInput(graph, '15', 'scheduler', 'simple')
|
||||
setInput(graph, '15', 'denoise', denoise)
|
||||
setInput(graph, SAVE, 'filename_prefix', params.filenamePrefix || `aigen-v2-krea-${params.mode}`)
|
||||
setInput(graph, SAVE, 'filename_prefix', params.filenamePrefix || `v2-krea-${params.mode}`)
|
||||
patchScaleMegapixels(graph, params.megapixels ?? 1)
|
||||
const userLoras = applyImageV2UserLoras(graph, imageV2UserStack(params, 'krea'), 'krea')
|
||||
|
||||
@@ -448,7 +448,7 @@ function buildKreaGenerateWorkflow(params: ImageV2BuildParams) {
|
||||
setInput(graph, '15', 'cfg', cfg)
|
||||
setInput(graph, '15', 'sampler_name', 'euler')
|
||||
setInput(graph, '15', 'scheduler', 'simple')
|
||||
setInput(graph, SAVE, 'filename_prefix', params.filenamePrefix || 'aigen-v2-krea')
|
||||
setInput(graph, SAVE, 'filename_prefix', params.filenamePrefix || 'v2-krea')
|
||||
const userLoras = applyImageV2UserLoras(graph, imageV2UserStack(params, 'krea'), 'krea')
|
||||
|
||||
assertImageV2Graph(graph, 'generate', undefined, 'krea')
|
||||
@@ -512,7 +512,7 @@ export function buildImageV2Workflow(params: ImageV2BuildParams) {
|
||||
setInput(graph, NOISE, 'noise_seed', params.seed)
|
||||
setInput(graph, SCHEDULER, 'steps', steps)
|
||||
setInput(graph, CFG, 'cfg', cfg)
|
||||
setInput(graph, SAVE, 'filename_prefix', params.filenamePrefix || (generate ? 'aigen-v2-generate' : 'aigen-v2'))
|
||||
setInput(graph, SAVE, 'filename_prefix', params.filenamePrefix || (generate ? 'v2-generate' : 'v2'))
|
||||
if (!generate) patchScaleMegapixels(graph, params.megapixels ?? 1)
|
||||
|
||||
const userLoras = applyImageV2UserLoras(graph, imageV2UserStack(params, 'flux'), 'flux')
|
||||
|
||||
+54
-4
@@ -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 || {}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ import { getSidecarImageHost } from '~/server/utils/imageComfy'
|
||||
import { ensureSidecarReady } from '~/server/utils/imageComfyLifecycle'
|
||||
import { buildVisionPromptWorkflow } from '~/server/utils/promptWorkflow'
|
||||
import { normalizeShotScript } from '~/utils/parseRecommend'
|
||||
import { comfyJobPrefix } from '~/utils/outputNames'
|
||||
|
||||
export type PromptJobStatus = 'queued' | 'running' | 'complete' | 'error'
|
||||
|
||||
@@ -46,8 +47,7 @@ function promptInputFilename(original: string, jobId?: string) {
|
||||
const dot = raw.lastIndexOf('.')
|
||||
const ext = (dot >= 0 ? raw.slice(dot) : '.png').replace(/[^.a-zA-Z0-9]/g, '') || '.png'
|
||||
const base = (dot >= 0 ? raw.slice(0, dot) : raw).replace(/[^a-zA-Z0-9._-]+/g, '_').slice(0, 48) || 'still'
|
||||
const id = String(jobId || crypto.randomUUID()).replace(/-/g, '').slice(0, 10)
|
||||
return `aigen_prompt_${id}_${base}${ext}`
|
||||
return `${comfyJobPrefix(jobId)}_${base}${ext}`
|
||||
}
|
||||
|
||||
async function uploadPromptImage(file: { filename: string; data: Buffer; type?: string }, jobId?: string) {
|
||||
|
||||
@@ -803,6 +803,7 @@ async function startStudioEditJob(item: StudioJob) {
|
||||
consistencyClip: payload.consistencyClip ?? (mode === 'generate' || payload.engine === 'krea' ? 0 : 0.7),
|
||||
megapixels: payload.scaleMegapixels ?? 1,
|
||||
strength: payload.refineStrength,
|
||||
aspect: payload.aspect || 'auto',
|
||||
width: payload.width,
|
||||
height: payload.height,
|
||||
turbo: payload.turbo === true,
|
||||
@@ -860,6 +861,7 @@ async function startStudioEditJob(item: StudioJob) {
|
||||
steps: payload.steps,
|
||||
seed: live.library?.seed || payload.seed,
|
||||
cfg: payload.cfg,
|
||||
aspect: payload.aspect || 'auto',
|
||||
scaleToTotalPixels: payload.scaleToTotalPixels === true,
|
||||
scaleMegapixels: payload.scaleMegapixels,
|
||||
...persistLoraFields(payload.loraStack || payload.loraName)
|
||||
|
||||
Reference in New Issue
Block a user