export const AI_WASH_CONFIG_KEY = "ai_wash_config"; export const AI_WASH_DEFAULT_MODE_LABEL = "AI洗图"; export const AI_WASH_DEFAULT_IMAGE_SIZE = "2K"; export const AI_WASH_DEFAULT_HISTORY_LIMIT = 8; export const AI_WASH_CONTROL_ORDER = [ "creativity", "detail", "lighting", ] as const; export type AiWashControlId = (typeof AI_WASH_CONTROL_ORDER)[number]; export type AiWashControlKind = "model" | "prompt"; export type AiWashControlLevel = { id: string; label: string; modelId: string; promptFragment: string; }; export type AiWashControl = { id: AiWashControlId; label: string; helperText: string; kind: AiWashControlKind; levels: AiWashControlLevel[]; defaultLevel: number; }; export type AiWashPreset = { id: string; title: string; description: string; prompt: string; modelId: string; enabled: boolean; isDefault: boolean; controls: AiWashControl[]; }; export type AiWashConfig = { enabled: boolean; modeLabel: string; modeIconUrl: string; hideModelSelector: boolean; imageSize: string; promptExtend: boolean; historyLimit: number; presets: AiWashPreset[]; }; function normalizeText(value: unknown, maxLength: number) { return String(value || "").trim().slice(0, maxLength); } function createPresetId(title: string, index: number) { const normalized = String(title || "") .trim() .toLowerCase() .replace(/[^a-z0-9\u4e00-\u9fa5]+/g, "-") .replace(/^-+|-+$/g, ""); return normalized || `ai-wash-preset-${index + 1}`; } function createLevelId(controlId: AiWashControlId, index: number) { return `${controlId}-level-${index + 1}`; } function createDefaultControl( controlId: AiWashControlId, overrides?: Partial ): AiWashControl { const defaults: Record< AiWashControlId, Pick > = { creativity: { label: "创造力", helperText: "每一档都可以单独指定生图模型,也可以补充少量提示词片段。", kind: "model", }, detail: { label: "细节", helperText: "每一档都可以补充默认提示词,控制画面的细节强弱与精修程度。", kind: "prompt", }, lighting: { label: "光塑", helperText: "每一档都可以补充默认提示词,控制光影层次、氛围和空间塑造。", kind: "prompt", }, }; return { id: controlId, label: overrides?.label || defaults[controlId].label, helperText: overrides?.helperText || defaults[controlId].helperText, kind: overrides?.kind || defaults[controlId].kind, defaultLevel: typeof overrides?.defaultLevel === "number" ? Math.max(0, Math.min(23, Math.floor(overrides.defaultLevel))) : 1, levels: overrides?.levels && overrides.levels.length > 0 ? overrides.levels : Array.from({ length: 3 }, (_, index) => ({ id: createLevelId(controlId, index), label: String(index + 1), modelId: "", promptFragment: "", })), }; } function normalizeControlLevel( controlId: AiWashControlId, value: unknown, index: number ): AiWashControlLevel { const raw = value && typeof value === "object" && !Array.isArray(value) ? (value as Record) : {}; return { id: normalizeText(raw.id, 64) || createLevelId(controlId, index), label: normalizeText(raw.label, 24) || String(index + 1), modelId: normalizeText(raw.modelId, 128), promptFragment: normalizeText(raw.promptFragment, 1000), }; } function normalizeControl( controlId: AiWashControlId, value: unknown, fallback?: AiWashControl ): AiWashControl { const base = fallback || createDefaultControl(controlId); const raw = value && typeof value === "object" && !Array.isArray(value) ? (value as Record) : {}; const levelsInput = Array.isArray(raw.levels) ? raw.levels : base.levels; const levels = levelsInput .map((level, index) => normalizeControlLevel(controlId, level, index)) .slice(0, 24); const defaultLevel = Number.isFinite(Number(raw.defaultLevel)) ? Number(raw.defaultLevel) : base.defaultLevel; return { id: controlId, label: normalizeText(raw.label, 24) || base.label, helperText: normalizeText(raw.helperText, 200) || base.helperText, kind: raw.kind === "model" || raw.kind === "prompt" ? raw.kind : base.kind, levels: levels.length > 0 ? levels : base.levels, defaultLevel: Math.max(0, Math.min(levels.length - 1, Math.floor(defaultLevel))), }; } function normalizeControls(value: unknown): AiWashControl[] { const rawList = Array.isArray(value) ? value : []; return AI_WASH_CONTROL_ORDER.map((controlId) => { const matched = rawList.find((item) => { const raw = item && typeof item === "object" && !Array.isArray(item) ? (item as Record) : {}; return raw.id === controlId; }) || null; return normalizeControl(controlId, matched, createDefaultControl(controlId)); }); } function normalizePreset( value: unknown, index: number, fallback: AiWashPreset ): AiWashPreset { const raw = value && typeof value === "object" && !Array.isArray(value) ? (value as Record) : {}; const title = normalizeText(raw.title, 48) || fallback.title; const description = normalizeText(raw.description, 200) || fallback.description; const prompt = normalizeText(raw.prompt, 4000) || fallback.prompt; return { id: normalizeText(raw.id, 64) || createPresetId(title, index), title, description, prompt, modelId: normalizeText(raw.modelId, 128), enabled: raw.enabled !== false, isDefault: raw.isDefault === true, controls: normalizeControls(raw.controls), }; } function ensureDefaultPreset(presets: AiWashPreset[]) { if (presets.length === 0) return presets; if (presets.some((preset) => preset.isDefault && preset.enabled)) { return presets; } return presets.map((preset, index) => ({ ...preset, isDefault: index === 0, })); } function defaultControlsForPreset(): AiWashControl[] { return AI_WASH_CONTROL_ORDER.map((controlId) => createDefaultControl(controlId)); } export function defaultAiWashConfig(): AiWashConfig { return { enabled: false, modeLabel: AI_WASH_DEFAULT_MODE_LABEL, modeIconUrl: "", hideModelSelector: false, imageSize: AI_WASH_DEFAULT_IMAGE_SIZE, promptExtend: false, historyLimit: AI_WASH_DEFAULT_HISTORY_LIMIT, presets: [ { id: "texture-precise", title: "质感提升(细节更精准)", description: "保留原图结构,强化细节精度、材质质感和光影层级。", prompt: "保持原图主体、构图、色彩关系和空间透视基本不变,重点提升材质纹理、细节锐度、光影层次和整体清晰度,让画面更精致、更干净、更高级。", modelId: "", enabled: true, isDefault: true, controls: defaultControlsForPreset(), }, { id: "texture-expressive", title: "质感提升(细节更发散)", description: "允许模型更积极地重建纹理和氛围,适合需要更强视觉表现的场景。", prompt: "在保留主体识别和原始构图的基础上,允许更积极地重建纹理、材质和环境氛围,提升视觉张力与高级感,让结果更具设计感和表现力。", modelId: "", enabled: true, isDefault: false, controls: defaultControlsForPreset(), }, { id: "ai-render", title: "AI模型渲染(类似渲染器)", description: "转成更接近商业渲染图的质感,适合家居、空间、产品展示。", prompt: "将原图重塑为更接近专业商业渲染图的视觉效果,提升光影控制、材质真实度、环境反射和空间氛围,保持主体结构与镜头视角稳定。", modelId: "", enabled: true, isDefault: false, controls: defaultControlsForPreset(), }, ], }; } export function normalizeAiWashConfig(value: unknown): AiWashConfig { const fallback = defaultAiWashConfig(); const raw = value && typeof value === "object" && !Array.isArray(value) ? (value as Record) : {}; const presetsInput = Array.isArray(raw.presets) ? raw.presets : fallback.presets; const presets = ensureDefaultPreset( presetsInput .map((preset, index) => normalizePreset( preset, index, fallback.presets[index] || fallback.presets[0] ) ) .slice(0, 6) ); return { enabled: raw.enabled === true, modeLabel: normalizeText(raw.modeLabel, 32) || AI_WASH_DEFAULT_MODE_LABEL, modeIconUrl: normalizeText(raw.modeIconUrl, 2000), hideModelSelector: raw.hideModelSelector === true, imageSize: normalizeText(raw.imageSize, 16) || AI_WASH_DEFAULT_IMAGE_SIZE, promptExtend: raw.promptExtend === true, historyLimit: Math.max( 1, Math.min(24, Number(raw.historyLimit) || AI_WASH_DEFAULT_HISTORY_LIMIT) ), presets, }; } export function sanitizeAiWashConfigForClient( config: AiWashConfig ): AiWashConfig { return normalizeAiWashConfig(config); } export function getEnabledAiWashPresets(config: AiWashConfig) { return config.presets.filter( (preset) => preset.enabled && preset.prompt.trim().length > 0 ); }