import type { LoraStackItem } from '~/utils/loras' import type { VideoWorkflowId } from '~/utils/videoModels' import type { ImageV2PresetSettings } from '~/utils/imageV2' export type GenerationPresetKind = 'video' | 'image' | 'imagev2' export type VideoPresetSettings = { workflow?: VideoWorkflowId turbo?: boolean cfg?: number fps?: number samplerName?: string scheduler?: string duration?: number aspect?: string sound?: boolean } export type ImagePresetSettings = { steps?: number cfg?: number negative?: string scaleToTotalPixels?: boolean scaleMegapixels?: number } export const IMAGE_STEPS_MIN = 1 export const IMAGE_STEPS_MAX = 50 export const IMAGE_CFG_MIN = 0 export const IMAGE_CFG_MAX = 20 export const IMAGE_CFG_STEP = 0.1 export const IMAGE_SCALE_MP_MIN = 0.01 export const IMAGE_SCALE_MP_MAX = 16 export const IMAGE_SCALE_MP_STEP = 0.01 export const IMAGE_SCALE_MP_DEFAULT = 1 export const VIDEO_CFG_MIN = 0 export const VIDEO_CFG_MAX = 20 export const VIDEO_CFG_STEP = 0.1 export function clampImageSteps(raw: unknown, fallback = 20) { const value = Number(raw) if (!Number.isFinite(value)) return fallback return Math.min(IMAGE_STEPS_MAX, Math.max(IMAGE_STEPS_MIN, Math.round(value))) } export function clampImageCfg(raw: unknown, fallback = 1) { const value = Number(raw) if (!Number.isFinite(value)) return fallback const snapped = Math.round(value / IMAGE_CFG_STEP) * IMAGE_CFG_STEP return Math.min(IMAGE_CFG_MAX, Math.max(IMAGE_CFG_MIN, Math.round(snapped * 10) / 10)) } export function clampImageScaleMegapixels(raw: unknown, fallback = IMAGE_SCALE_MP_DEFAULT) { const value = Number(raw) if (!Number.isFinite(value)) return fallback const snapped = Math.round(value / IMAGE_SCALE_MP_STEP) * IMAGE_SCALE_MP_STEP return Math.min(IMAGE_SCALE_MP_MAX, Math.max(IMAGE_SCALE_MP_MIN, Math.round(snapped * 100) / 100)) } export function parseImageScaleToTotalPixels(raw: unknown) { return raw === true || raw === 'true' || raw === '1' || raw === 1 } export function clampVideoCfg(raw: unknown, fallback = 1.5) { const value = Number(raw) if (!Number.isFinite(value)) return fallback const snapped = Math.round(value / VIDEO_CFG_STEP) * VIDEO_CFG_STEP return Math.min(VIDEO_CFG_MAX, Math.max(VIDEO_CFG_MIN, Math.round(snapped * 10) / 10)) } export type GenerationPreset = { id: string name: string kind: GenerationPresetKind createdAt: number updatedAt: number loraStack: LoraStackItem[] settings: VideoPresetSettings | ImagePresetSettings | ImageV2PresetSettings } export function parseGenerationPresetKind(raw: unknown): GenerationPresetKind { if (raw === 'image') return 'image' if (raw === 'imagev2') return 'imagev2' return 'video' } export function normalizePresetName(raw: unknown) { return String(raw || '').trim().replace(/\s+/g, ' ').slice(0, 80) }