105 lines
4.0 KiB
TypeScript
105 lines
4.0 KiB
TypeScript
import type { ImageTemplate } from "@/lib/types";
|
|
|
|
type ImageTemplateBody = {
|
|
name?: unknown;
|
|
description?: unknown;
|
|
prompt?: unknown;
|
|
previewImageUrl?: unknown;
|
|
settings?: unknown;
|
|
sortOrder?: unknown;
|
|
};
|
|
|
|
type ImageTemplatePatch = Partial<Omit<ImageTemplate, "id" | "ownerId" | "createdAt" | "updatedAt">>;
|
|
|
|
export function normalizeImageTemplateCreate(body: ImageTemplateBody): Omit<ImageTemplate, "id" | "ownerId" | "createdAt" | "updatedAt"> {
|
|
return {
|
|
name: requiredText(body.name, "模板名称", 80),
|
|
description: optionalText(body.description, 240),
|
|
prompt: requiredText(body.prompt, "预设提示词", 4000),
|
|
previewImageUrl: optionalPreviewUrl(body.previewImageUrl),
|
|
settings: normalizeTemplateSettings(body.settings),
|
|
sortOrder: optionalNumber(body.sortOrder) ?? 0
|
|
};
|
|
}
|
|
|
|
export function normalizeImageTemplateUpdate(body: ImageTemplateBody): ImageTemplatePatch {
|
|
const patch: ImageTemplatePatch = {};
|
|
if ("name" in body) patch.name = requiredText(body.name, "模板名称", 80);
|
|
if ("description" in body) patch.description = optionalText(body.description, 240);
|
|
if ("prompt" in body) patch.prompt = requiredText(body.prompt, "预设提示词", 4000);
|
|
if ("previewImageUrl" in body) patch.previewImageUrl = optionalPreviewUrl(body.previewImageUrl);
|
|
if ("settings" in body) patch.settings = normalizeTemplateSettings(body.settings);
|
|
if ("sortOrder" in body) patch.sortOrder = optionalNumber(body.sortOrder) ?? 0;
|
|
return patch;
|
|
}
|
|
|
|
function requiredText(value: unknown, label: string, maxLength: number): string {
|
|
const text = optionalText(value, maxLength);
|
|
if (!text) throw new Error(`${label}不能为空`);
|
|
return text;
|
|
}
|
|
|
|
function optionalText(value: unknown, maxLength: number): string | undefined {
|
|
if (value === undefined || value === null) return undefined;
|
|
const text = String(value).trim();
|
|
if (!text) return undefined;
|
|
return text.slice(0, maxLength);
|
|
}
|
|
|
|
function optionalPreviewUrl(value: unknown): string | undefined {
|
|
const url = optionalText(value, 1000);
|
|
if (!url) return undefined;
|
|
if (url.startsWith("/") || /^https?:\/\//i.test(url)) return url;
|
|
throw new Error("效果预览图地址必须是 http(s) 或站内路径");
|
|
}
|
|
|
|
function normalizeTemplateSettings(value: unknown): ImageTemplate["settings"] {
|
|
if (!isRecord(value)) return {};
|
|
const width = optionalPositiveInteger(value.width);
|
|
const height = optionalPositiveInteger(value.height);
|
|
const engine = normalizeImageEngine(value.engine);
|
|
const scale = optionalScale(value.scale);
|
|
const quality = normalizeQuality(value.quality);
|
|
return {
|
|
...(engine ? { engine } : {}),
|
|
...(width && height ? { width, height } : {}),
|
|
...(typeof value.forceSingle === "boolean" ? { forceSingle: value.forceSingle } : {}),
|
|
...(scale !== undefined ? { scale } : {}),
|
|
...(quality ? { quality } : {})
|
|
};
|
|
}
|
|
|
|
function optionalNumber(value: unknown): number | undefined {
|
|
if (value === undefined || value === null || value === "") return undefined;
|
|
const parsed = Number(value);
|
|
return Number.isFinite(parsed) ? Math.trunc(parsed) : undefined;
|
|
}
|
|
|
|
function optionalPositiveInteger(value: unknown): number | undefined {
|
|
const parsed = optionalNumber(value);
|
|
if (!parsed || parsed < 1) return undefined;
|
|
return Math.min(parsed, 8192);
|
|
}
|
|
|
|
function optionalScale(value: unknown): number | undefined {
|
|
const parsed = optionalNumber(value);
|
|
if (parsed === undefined) return undefined;
|
|
return Math.max(1, Math.min(100, parsed));
|
|
}
|
|
|
|
function normalizeImageEngine(value: unknown): ImageTemplate["settings"]["engine"] | undefined {
|
|
const text = optionalText(value, 20);
|
|
if (text === "jimeng" || text === "evolink" || text === "bailian") return text;
|
|
return undefined;
|
|
}
|
|
|
|
function normalizeQuality(value: unknown): ImageTemplate["settings"]["quality"] | undefined {
|
|
const text = optionalText(value, 20);
|
|
if (text === "low" || text === "medium" || text === "high") return text;
|
|
return undefined;
|
|
}
|
|
|
|
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
}
|