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"; export const ENHANCE_PRO_KREA_MODELS = [ "Redefine", "Recovery", "Recovery V2", "Reimagine", ] as const; const TRAILING_SLASHES_REGEX = /\/+$/; export type EnhanceProKreaModel = (typeof ENHANCE_PRO_KREA_MODELS)[number]; 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; apiKeys: string[]; defaultModel: EnhanceProKreaModel; hiddenModels: EnhanceProKreaModel[]; defaultOutputFormat: EnhanceProOutputFormat; allowBatchSize: boolean; maxBatchSize: number; maxUpscaleFactor: 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}`; } function normalizeEnhanceProApiKey(value: unknown) { return String(value || "") .replace(/[\u2028\u2029]/g, "") .replace(/[\u0000-\u001F\u007F]/g, "") .replace(/^bearer\s+/i, "") .trim(); } function normalizeEnhanceProApiKeys(value: unknown) { const candidates = Array.isArray(value) ? value : typeof value === "string" ? value.split(/\r?\n|,/) : []; return Array.from( new Set( candidates .map((item) => normalizeEnhanceProApiKey(item)) .filter(Boolean) ) ); } export function isEnhanceProKreaModel( value: unknown, ): value is EnhanceProKreaModel { const model = String(value || "").trim(); return ENHANCE_PRO_KREA_MODELS.includes(model as EnhanceProKreaModel); } function normalizeEnhanceProKreaModel( value: unknown, fallback: EnhanceProKreaModel, ) { const model = String(value || "").trim(); return isEnhanceProKreaModel(model) ? model : fallback; } function normalizeEnhanceProHiddenModels( value: unknown, defaultModel: EnhanceProKreaModel, ) { const candidates = Array.isArray(value) ? value : typeof value === "string" ? value.split(/\r?\n|,/) : []; const hiddenModels = Array.from( new Set( candidates .map((item) => String(item || "").trim()) .filter((item): item is EnhanceProKreaModel => isEnhanceProKreaModel(item), ), ), ); if (hiddenModels.length >= ENHANCE_PRO_KREA_MODELS.length) { return hiddenModels.filter((model) => model !== defaultModel); } return hiddenModels; } function ensureEnhanceProPresetDefault(presets: EnhanceProPreset[]) { if (presets.length === 0) { return presets; } if (presets.some((preset) => preset.isDefault)) { return presets; } return presets.map((preset, index) => ({ ...preset, isDefault: index === 0, })); } export function getVisibleEnhanceProKreaModels( config: Pick, ) { const hiddenModels = new Set(config.hiddenModels); const visibleModels = ENHANCE_PRO_KREA_MODELS.filter( (model) => !hiddenModels.has(model), ); return visibleModels.length ? visibleModels : [...ENHANCE_PRO_KREA_MODELS]; } export function isEnhanceProKreaModelHidden( config: Pick, model: EnhanceProKreaModel, ) { return !getVisibleEnhanceProKreaModels(config).includes(model); } 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" | "maxUpscaleFactor" >, ): 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: normalizeEnhanceProKreaModel( model, defaults.model || configDefaults.defaultModel, ), upscalingActivated: typeof raw.upscalingActivated === "boolean" ? raw.upscalingActivated : defaults.upscalingActivated, imageScalingFactor: clampFloat( raw.imageScalingFactor, 1, configDefaults.maxUpscaleFactor, 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: "", apiKeys: [], defaultModel: ENHANCE_PRO_KREA_DEFAULT_MODEL, hiddenModels: [], defaultOutputFormat: ENHANCE_PRO_KREA_DEFAULT_OUTPUT_FORMAT, allowBatchSize: true, maxBatchSize: 4, maxUpscaleFactor: 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 rawDefaultModel = normalizeEnhanceProKreaModel( raw.defaultModel, defaults.defaultModel, ); const hiddenModels = normalizeEnhanceProHiddenModels( raw.hiddenModels || raw.disabledModels, rawDefaultModel, ); const visibleModels = ENHANCE_PRO_KREA_MODELS.filter( (model) => !hiddenModels.includes(model), ); const defaultModel = hiddenModels.includes(rawDefaultModel) ? visibleModels[0] || defaults.defaultModel : rawDefaultModel; const defaultOutputFormat = String(raw.defaultOutputFormat || "").trim(); const maxUpscaleFactor = clampFloat( raw.maxUpscaleFactor, 1, 32, defaults.maxUpscaleFactor, ); const normalizedPresets = ( Array.isArray(raw.presets) ? raw.presets : defaultEnhanceProPresets() ).map((item, index) => normalizeEnhanceProPreset(item, index, { defaultModel, defaultOutputFormat: defaults.defaultOutputFormat, maxBatchSize: clampInteger(raw.maxBatchSize, 1, 4, defaults.maxBatchSize), maxUpscaleFactor, }), ); const presetWithDefault = ensureEnhanceProPresetDefault(normalizedPresets); return { enabled: raw.enabled === true, providerName: String(raw.providerName || "").trim() || defaults.providerName, baseUrl: String(raw.baseUrl || "") .trim() .replace(TRAILING_SLASHES_REGEX, "") || defaults.baseUrl, apiKey: normalizeEnhanceProApiKey(raw.apiKey) || normalizeEnhanceProApiKey(apiKeyFallback), apiKeys: normalizeEnhanceProApiKeys(raw.apiKeys || raw.apiKeyPool), defaultModel, hiddenModels, defaultOutputFormat: defaultOutputFormat === "png" || defaultOutputFormat === "webp" || defaultOutputFormat === "jpg" ? defaultOutputFormat : defaults.defaultOutputFormat, allowBatchSize: raw.allowBatchSize !== false, maxBatchSize: clampInteger(raw.maxBatchSize, 1, 4, defaults.maxBatchSize), maxUpscaleFactor, maxOutputEdge: clampInteger( raw.maxOutputEdge, 512, 16_384, defaults.maxOutputEdge, ), enableAdvancedControls: raw.enableAdvancedControls !== false, presets: presetWithDefault, }; } export function sanitizeEnhanceProKreaConfigForClient( config: EnhanceProKreaConfig, ) { const availableModels = getVisibleEnhanceProKreaModels(config); const defaultModel = availableModels.includes(config.defaultModel) ? config.defaultModel : availableModels[0] || ENHANCE_PRO_KREA_DEFAULT_MODEL; const visiblePresets = config.presets.filter((preset) => availableModels.includes(preset.model), ); const fallbackPresets = defaultEnhanceProPresets().filter((preset) => availableModels.includes(preset.model), ); const presets = ensureEnhanceProPresetDefault( visiblePresets.length ? visiblePresets : fallbackPresets, ); return { enabled: config.enabled, providerName: config.providerName, baseUrl: config.baseUrl, keyPoolSize: config.apiKeys.length, defaultModel, availableModels, defaultOutputFormat: config.defaultOutputFormat, allowBatchSize: config.allowBatchSize, maxBatchSize: config.maxBatchSize, maxUpscaleFactor: config.maxUpscaleFactor, maxOutputEdge: config.maxOutputEdge, enableAdvancedControls: config.enableAdvancedControls, presets, }; }