ai-chatbot/lib/ai-wash/config.ts

314 lines
9.4 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.

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>
): AiWashControl {
const defaults: Record<
AiWashControlId,
Pick<AiWashControl, "label" | "helperText" | "kind">
> = {
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<string, unknown>)
: {};
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<string, unknown>)
: {};
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<string, unknown>)
: {};
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<string, unknown>)
: {};
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<string, unknown>)
: {};
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
);
}