export const ENHANCE_PRO_KREA_SETTING_KEY = "enhance_pro_krea_config"; export const ENHANCE_PRO_KREA_DEFAULT_BASE_URL = "https://api.krea.ai"; export const ENHANCE_PRO_KREA_DEFAULT_PROVIDER_NAME = "Krea Topaz Generative"; export const ENHANCE_PRO_KREA_DEFAULT_MODEL = "Redefine"; export const ENHANCE_PRO_KREA_DEFAULT_OUTPUT_FORMAT = "jpg"; const TRAILING_SLASHES_REGEX = /\/+$/; export type EnhanceProKreaModel = | "Redefine" | "Recovery" | "Recovery V2" | "Reimagine"; export type EnhanceProOutputFormat = "png" | "jpg" | "webp"; export type EnhanceProSubjectDetection = "All" | "Foreground" | "Background"; export type EnhanceProPreset = { id: string; name: string; description: string; isDefault: boolean; prompt: string; batchSize: number; seed?: number; outputFormat: EnhanceProOutputFormat; subjectDetection: EnhanceProSubjectDetection; faceEnhancement: boolean; faceEnhancementCreativity: number; faceEnhancementStrength: number; cropToFill: boolean; model: EnhanceProKreaModel; upscalingActivated: boolean; imageScalingFactor: number; creativity: number; texture: number; sharpen: number; denoise: number; detail: number; }; export type EnhanceProKreaConfig = { enabled: boolean; providerName: string; baseUrl: string; apiKey: string; defaultModel: EnhanceProKreaModel; defaultOutputFormat: EnhanceProOutputFormat; allowBatchSize: boolean; maxBatchSize: number; maxOutputEdge: number; enableAdvancedControls: boolean; presets: EnhanceProPreset[]; }; function clampInteger( value: unknown, min: number, max: number, fallback: number, ) { const parsed = Number.parseInt(String(value ?? ""), 10); if (!Number.isFinite(parsed)) { return fallback; } return Math.min(max, Math.max(min, parsed)); } function clampFloat( value: unknown, min: number, max: number, fallback: number, ) { const parsed = Number.parseFloat(String(value ?? "")); if (!Number.isFinite(parsed)) { return fallback; } return Math.min(max, Math.max(min, parsed)); } function createEnhanceProPresetId(name: string, index: number) { const normalized = String(name || "") .trim() .toLowerCase() .replace(/[^a-z0-9]+/g, "-") .replace(/^-+|-+$/g, ""); return normalized || `enhance-pro-preset-${index + 1}`; } export function defaultEnhanceProPresets(): EnhanceProPreset[] { return [ { id: "fidelity-recovery", name: "保真修复", description: "优先保留原图结构和内容,适合老图修复、压缩损伤恢复、细节补全。", isDefault: true, prompt: "保持原图主体、构图和光影关系基本不变,忠实修复细节,减少压缩噪点和模糊感,让整体画质更干净自然。", batchSize: 1, seed: 1337, outputFormat: "jpg", subjectDetection: "All", faceEnhancement: false, faceEnhancementCreativity: 0.35, faceEnhancementStrength: 0.45, cropToFill: false, model: "Recovery V2", upscalingActivated: true, imageScalingFactor: 2, creativity: 1, texture: 2, sharpen: 0.35, denoise: 0.45, detail: 0.65, }, { id: "portrait-repair", name: "人像修复", description: "针对人物和面部细节优化,适合证件照、半身照、商品模特图。", isDefault: false, prompt: "重点增强人物面部和发丝细节,保持肤色自然真实,避免过度磨皮和塑料感,让五官更清晰但不过度重绘。", batchSize: 1, seed: 1337, outputFormat: "jpg", subjectDetection: "Foreground", faceEnhancement: true, faceEnhancementCreativity: 0.35, faceEnhancementStrength: 0.75, cropToFill: false, model: "Recovery V2", upscalingActivated: true, imageScalingFactor: 2, creativity: 2, texture: 3, sharpen: 0.4, denoise: 0.4, detail: 0.7, }, { id: "creative-reimagine", name: "创意重建", description: "允许模型更积极地重建高频细节,适合模糊图、概念图、二次质感提升。", isDefault: false, prompt: "在保持主体可识别的基础上大胆重建高频细节与纹理,提升画面层次、材质感和视觉冲击力,让结果更有高级感。", batchSize: 1, seed: 1337, outputFormat: "jpg", subjectDetection: "All", faceEnhancement: false, faceEnhancementCreativity: 0.6, faceEnhancementStrength: 0.55, cropToFill: false, model: "Reimagine", upscalingActivated: true, imageScalingFactor: 2, creativity: 5, texture: 4, sharpen: 0.55, denoise: 0.25, detail: 0.8, }, ]; } function normalizeEnhanceProPreset( value: unknown, index: number, configDefaults: Pick< EnhanceProKreaConfig, "defaultModel" | "defaultOutputFormat" | "maxBatchSize" >, ): EnhanceProPreset { const defaults = defaultEnhanceProPresets()[index] || defaultEnhanceProPresets()[0]; const raw = value && typeof value === "object" && !Array.isArray(value) ? (value as Record) : {}; const model = String(raw.model || "").trim(); const outputFormat = String( raw.outputFormat || raw.output_format || "", ).trim(); const subjectDetection = String( raw.subjectDetection || raw.subject_detection || "", ).trim(); return { id: String(raw.id || "").trim() || createEnhanceProPresetId(String(raw.name || defaults.name), index), name: String(raw.name || "").trim() || defaults.name, description: String(raw.description || "").trim() || defaults.description, isDefault: raw.isDefault === true, prompt: String(raw.prompt || "").trim() || defaults.prompt, batchSize: clampInteger( raw.batchSize, 1, configDefaults.maxBatchSize, defaults.batchSize, ), seed: Number.isFinite(Number(raw.seed)) ? Math.round(Number(raw.seed)) : defaults.seed, outputFormat: outputFormat === "png" || outputFormat === "webp" || outputFormat === "jpg" ? outputFormat : defaults.outputFormat || configDefaults.defaultOutputFormat, subjectDetection: subjectDetection === "Foreground" || subjectDetection === "Background" ? subjectDetection : defaults.subjectDetection, faceEnhancement: typeof raw.faceEnhancement === "boolean" ? raw.faceEnhancement : defaults.faceEnhancement, faceEnhancementCreativity: clampFloat( raw.faceEnhancementCreativity, 0, 1, defaults.faceEnhancementCreativity, ), faceEnhancementStrength: clampFloat( raw.faceEnhancementStrength, 0, 1, defaults.faceEnhancementStrength, ), cropToFill: typeof raw.cropToFill === "boolean" ? raw.cropToFill : defaults.cropToFill, model: model === "Recovery" || model === "Recovery V2" || model === "Reimagine" || model === "Redefine" ? model : defaults.model || configDefaults.defaultModel, upscalingActivated: typeof raw.upscalingActivated === "boolean" ? raw.upscalingActivated : defaults.upscalingActivated, imageScalingFactor: clampFloat( raw.imageScalingFactor, 1, 32, defaults.imageScalingFactor, ), creativity: clampInteger(raw.creativity, 1, 6, defaults.creativity), texture: clampInteger(raw.texture, 1, 5, defaults.texture), sharpen: clampFloat(raw.sharpen, 0, 1, defaults.sharpen), denoise: clampFloat(raw.denoise, 0, 1, defaults.denoise), detail: clampFloat(raw.detail, 0, 1, defaults.detail), }; } export function defaultEnhanceProKreaConfig(): EnhanceProKreaConfig { return { enabled: false, providerName: ENHANCE_PRO_KREA_DEFAULT_PROVIDER_NAME, baseUrl: ENHANCE_PRO_KREA_DEFAULT_BASE_URL, apiKey: "", defaultModel: ENHANCE_PRO_KREA_DEFAULT_MODEL, defaultOutputFormat: ENHANCE_PRO_KREA_DEFAULT_OUTPUT_FORMAT, allowBatchSize: true, maxBatchSize: 4, maxOutputEdge: 4096, enableAdvancedControls: true, presets: defaultEnhanceProPresets(), }; } export function normalizeEnhanceProKreaConfig( value: unknown, apiKeyFallback = "" ): EnhanceProKreaConfig { const defaults = defaultEnhanceProKreaConfig(); const raw = value && typeof value === "object" && !Array.isArray(value) ? (value as Record) : {}; const defaultModel = String(raw.defaultModel || "").trim(); const defaultOutputFormat = String(raw.defaultOutputFormat || "").trim(); const normalizedPresets = Array.isArray(raw.presets) ? raw.presets.map((item, index) => normalizeEnhanceProPreset(item, index, { defaultModel: defaults.defaultModel, defaultOutputFormat: defaults.defaultOutputFormat, maxBatchSize: clampInteger(raw.maxBatchSize, 1, 4, defaults.maxBatchSize), }), ) : defaultEnhanceProPresets(); const presetWithDefault = normalizedPresets.some((preset) => preset.isDefault) ? normalizedPresets : normalizedPresets.map((preset, index) => ({ ...preset, isDefault: index === 0, })); return { enabled: raw.enabled === true, providerName: String(raw.providerName || "").trim() || defaults.providerName, baseUrl: String(raw.baseUrl || "") .trim() .replace(TRAILING_SLASHES_REGEX, "") || defaults.baseUrl, apiKey: String(raw.apiKey || "").trim() || apiKeyFallback, defaultModel: defaultModel === "Recovery" || defaultModel === "Recovery V2" || defaultModel === "Reimagine" ? defaultModel : defaults.defaultModel, defaultOutputFormat: defaultOutputFormat === "png" || defaultOutputFormat === "webp" || defaultOutputFormat === "jpg" ? defaultOutputFormat : defaults.defaultOutputFormat, allowBatchSize: raw.allowBatchSize !== false, maxBatchSize: clampInteger(raw.maxBatchSize, 1, 4, defaults.maxBatchSize), maxOutputEdge: clampInteger( raw.maxOutputEdge, 512, 16_384, defaults.maxOutputEdge, ), enableAdvancedControls: raw.enableAdvancedControls !== false, presets: presetWithDefault, }; } export function sanitizeEnhanceProKreaConfigForClient( config: EnhanceProKreaConfig, ) { return { enabled: config.enabled, providerName: config.providerName, baseUrl: config.baseUrl, defaultModel: config.defaultModel, defaultOutputFormat: config.defaultOutputFormat, allowBatchSize: config.allowBatchSize, maxBatchSize: config.maxBatchSize, maxOutputEdge: config.maxOutputEdge, enableAdvancedControls: config.enableAdvancedControls, presets: config.presets, }; }