Files
aigen/utils/libraryGeneration.ts
T

244 lines
10 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { loraDisplayName, normalizeLoraStack, type LoraStackItem } from '~/utils/loras'
import type { ImageV2Engine, ImageV2Mode, ImageV2Task } from '~/utils/imageV2'
import { isXaigenStudio, type VideoWorkflowId } from '~/utils/videoModels'
export type StillGenerationKind = 'image' | 'imagev2'
export type StillGenerationSettings = {
kind: StillGenerationKind
mode?: ImageV2Mode
engine?: ImageV2Engine
task?: ImageV2Task
negative?: string
snofsModel?: number
snofsClip?: number
consistencyModel?: number
consistencyClip?: number
steps?: number
cfg?: number
megapixels?: number
turbo?: boolean
strength?: number
width?: number
height?: number
aspect?: string
seed?: number
scaleToTotalPixels?: boolean
scaleMegapixels?: number
loraName?: string
loraStack?: LoraStackItem[]
sourceStillId?: string
referenceStillId?: string
}
export type SettingsRow = { label: string; value: string }
function finiteNumber(raw: unknown): number | undefined {
const value = Number(raw)
return Number.isFinite(value) ? value : undefined
}
function formatNum(raw: unknown, digits = 2) {
const value = Number(raw)
if (!Number.isFinite(value)) return ''
const rounded = Number(value.toFixed(digits))
return Number.isInteger(rounded) ? String(rounded) : String(rounded)
}
function formatPair(model?: number, clip?: number) {
if (model == null && clip == null) return ''
return `${formatNum(model ?? 0)} / ${formatNum(clip ?? 0)}`
}
function formatLoras(stack?: LoraStackItem[], name?: string) {
const items = normalizeLoraStack(stack?.length ? stack : name)
if (!items.length) return ''
return items.map((item) => {
const label = loraDisplayName(item.name)
if (item.strengthModel === item.strengthClip) return `${label} ${formatNum(item.strengthModel)}`
return `${label} ${formatNum(item.strengthModel)} / ${formatNum(item.strengthClip)}`
}).join(', ')
}
export function normalizeStillSettings(raw: unknown): StillGenerationSettings | undefined {
if (!raw || typeof raw !== 'object') return undefined
const value = raw as Record<string, unknown>
const mode = value.mode === 'compose' || value.mode === 'refine' || value.mode === 'generate' || value.mode === 'edit'
? value.mode
: undefined
const engine = value.engine === 'krea' || value.engine === 'flux' ? value.engine : undefined
const kind: StillGenerationKind | undefined = value.kind === 'imagev2' || mode
? 'imagev2'
: value.kind === 'image'
? 'image'
: undefined
if (!kind) return undefined
const task = value.task === 'identity' || value.task === 'outfit' || value.task === 'face_lock' || value.task === 'refine' || value.task === 't2i' || value.task === 'scene'
? value.task
: undefined
const stack = normalizeLoraStack(value.loraStack || value.loraName)
return {
kind,
mode,
engine,
task,
negative: typeof value.negative === 'string' ? value.negative : undefined,
snofsModel: finiteNumber(value.snofsModel),
snofsClip: finiteNumber(value.snofsClip),
consistencyModel: finiteNumber(value.consistencyModel),
consistencyClip: finiteNumber(value.consistencyClip),
steps: finiteNumber(value.steps),
cfg: finiteNumber(value.cfg),
megapixels: finiteNumber(value.megapixels),
turbo: typeof value.turbo === 'boolean' ? value.turbo : undefined,
strength: finiteNumber(value.strength),
width: finiteNumber(value.width),
height: finiteNumber(value.height),
aspect: typeof value.aspect === 'string' ? value.aspect : undefined,
seed: finiteNumber(value.seed),
scaleToTotalPixels: typeof value.scaleToTotalPixels === 'boolean' ? value.scaleToTotalPixels : undefined,
scaleMegapixels: finiteNumber(value.scaleMegapixels),
loraName: typeof value.loraName === 'string' ? value.loraName : undefined,
loraStack: stack.length ? stack : undefined,
sourceStillId: typeof value.sourceStillId === 'string' ? value.sourceStillId : undefined,
referenceStillId: typeof value.referenceStillId === 'string' ? value.referenceStillId : undefined
}
}
export function inferStillPipeline(still: { filename?: string; settings?: StillGenerationSettings | null }) {
if (still.settings?.kind === 'imagev2' || still.settings?.mode) return 'imagev2' as const
if (still.settings?.kind === 'image') return 'image' as const
if (/aigen_v2_/i.test(String(still.filename || ''))) return 'imagev2' as const
if (/aigen[_-]?edit/i.test(String(still.filename || ''))) return 'image' as const
return null
}
function composeTaskLabel(task?: ImageV2Task) {
if (task === 'outfit') return 'Outfit'
if (task === 'identity') return 'Identity'
if (task === 'face_lock') return 'Face lock'
if (task === 'scene') return 'Scene'
return ''
}
export function stillModeLabel(still: { filename?: string; settings?: StillGenerationSettings | null }) {
const settings = still.settings
if (settings?.kind === 'imagev2' || settings?.mode) {
const mode = settings.mode === 'generate'
? 'Generate'
: settings.mode === 'compose'
? 'Compose'
: settings.mode === 'refine'
? 'Refine'
: 'Edit'
const task = settings.mode === 'compose' ? composeTaskLabel(settings.task) : ''
const engine = settings.engine === 'krea' ? 'Krea' : settings.engine === 'flux' ? 'Flux' : ''
return [engine ? `Image v2 · ${mode} · ${engine}` : `Image v2 · ${mode}`, task].filter(Boolean).join(' · ')
}
if (settings?.kind === 'image') return 'Image edit'
if (/aigen_v2_/i.test(String(still.filename || ''))) return 'Image v2'
if (/aigen[_-]?edit/i.test(String(still.filename || ''))) return 'Image edit'
return ''
}
export function stillSettingsRows(still: { width?: number; height?: number; settings?: StillGenerationSettings | null }) {
const settings = still.settings
const rows: SettingsRow[] = []
const mode = stillModeLabel(still)
if (mode) rows.push({ label: 'Mode', value: mode })
if (!settings) return rows
if (settings.kind === 'imagev2') {
if (settings.mode === 'generate' && settings.width && settings.height) {
rows.push({ label: 'Size', value: `${settings.width}×${settings.height}` })
} else if (settings.aspect && settings.aspect !== 'auto') {
rows.push({ label: 'Aspect', value: `${settings.aspect} fill` })
} else if (still.width && still.height) {
rows.push({ label: 'Output', value: `${still.width}×${still.height}` })
}
if (settings.engine) {
rows.push({ label: 'Engine', value: settings.engine === 'krea' ? 'Krea' : 'Flux' })
}
if (isXaigenStudio() && (settings.snofsModel != null || settings.snofsClip != null)) {
rows.push({ label: 'Concept LoRA', value: formatPair(settings.snofsModel, settings.snofsClip) })
}
if (settings.mode !== 'generate' && (settings.consistencyModel != null || settings.consistencyClip != null)) {
rows.push({ label: 'Consistency', value: formatPair(settings.consistencyModel, settings.consistencyClip) })
}
const loras = formatLoras(settings.loraStack, settings.loraName)
if (loras) rows.push({ label: 'LoRA', value: loras })
if (settings.mode === 'refine' && settings.strength != null) {
rows.push({ label: 'Strength', value: formatNum(settings.strength) })
}
if (settings.mode !== 'generate' && settings.megapixels != null) {
rows.push({ label: 'Scale to MP', value: formatNum(settings.megapixels) })
}
} else {
if (still.width && still.height) rows.push({ label: 'Output', value: `${still.width}×${still.height}` })
if (settings.aspect && settings.aspect !== 'auto') {
rows.push({ label: 'Aspect', value: `${settings.aspect} fill` })
}
if (settings.scaleToTotalPixels) {
rows.push({ label: 'Scale to MP', value: formatNum(settings.scaleMegapixels ?? 1) })
}
const loras = formatLoras(settings.loraStack, settings.loraName)
if (loras) rows.push({ label: 'LoRA', value: loras })
}
if (settings.steps != null) rows.push({ label: 'Steps', value: String(Math.round(settings.steps)) })
if (settings.cfg != null) rows.push({ label: 'CFG', value: formatNum(settings.cfg) })
if (settings.seed != null) rows.push({ label: 'Seed', value: String(Math.round(settings.seed)) })
if (settings.turbo === true) rows.push({ label: 'Turbo', value: 'On' })
if (settings.negative?.trim()) rows.push({ label: 'Negative', value: settings.negative.trim() })
return rows
}
function videoWorkflowLabel(workflow?: string) {
if (workflow === 'v2') return 'MiniMax I2V v2'
if (workflow === 't2v') return 'MiniMax T2V'
if (workflow === 'ltx') return 'LTX Pink Cherry'
if (workflow === 'ltx-t2v') return 'LTX T2V'
if (workflow === 'v1') return 'MiniMax I2V'
return workflow || ''
}
export function clipSettingsRows(clip: {
width?: number
height?: number
duration?: number
steps?: number
turbo?: boolean
seed?: number
cfg?: number
fps?: number
samplerName?: string
scheduler?: string
aspect?: string
sound?: boolean
workflow?: VideoWorkflowId | string
loraName?: string
loraStack?: LoraStackItem[]
}) {
const rows: SettingsRow[] = []
const workflow = videoWorkflowLabel(clip.workflow)
if (workflow) rows.push({ label: 'Workflow', value: workflow })
if (clip.width && clip.height) rows.push({ label: 'Size', value: `${clip.width}×${clip.height}` })
if (clip.aspect && clip.aspect !== 'auto') rows.push({ label: 'Aspect', value: clip.aspect })
if (typeof clip.duration === 'number' && clip.duration > 0) {
const rounded = Math.round(clip.duration * 10) / 10
rows.push({ label: 'Duration', value: Number.isInteger(rounded) ? `${rounded}s` : `${rounded.toFixed(1)}s` })
}
if (clip.steps != null) {
rows.push({ label: 'Steps', value: clip.turbo ? `${clip.steps} · Turbo` : String(clip.steps) })
} else if (clip.turbo) {
rows.push({ label: 'Turbo', value: 'On' })
}
if (clip.cfg != null) rows.push({ label: 'CFG', value: formatNum(clip.cfg) })
if (clip.fps != null) rows.push({ label: 'FPS', value: String(clip.fps) })
if (clip.samplerName) rows.push({ label: 'Sampler', value: clip.samplerName })
if (clip.scheduler) rows.push({ label: 'Scheduler', value: clip.scheduler })
if (typeof clip.sound === 'boolean') rows.push({ label: 'Sound', value: clip.sound ? 'On' : 'Off' })
if (clip.seed != null) rows.push({ label: 'Seed', value: String(clip.seed) })
const loras = formatLoras(clip.loraStack, clip.loraName)
if (loras) rows.push({ label: 'LoRA', value: loras })
return rows
}