perf(booth): optimize assets and result flow
WebP reduces UI payload; JPEG keeps portrait saves compatible.\nFinish ends the visit without prompting another portrait.
This commit is contained in:
1 parent
56da9c86d2
commit
6772e0cf37
41 files changed
+225
-97
No files matched your search
@@ -41,7 +41,7 @@ export async function orchestrateGeneration(
|
||||
// Simulate generation delay (2 seconds)
|
||||
await new Promise((r) => setTimeout(r, 2000));
|
||||
return {
|
||||
imageUrl: style.sampleResult || style.thumbnail || "/assets/styles/sample_clay_pink.png",
|
||||
imageUrl: style.sampleResult || style.thumbnail || "/assets/styles/jack_o_lantern_sensei.webp",
|
||||
providerUsed: "demo_mock",
|
||||
modelUsed: "demo-simulator",
|
||||
durationMs: Date.now() - startTime,
|
||||
|
||||
+7
-7
@@ -47,7 +47,7 @@ const defaultConfig: BoothConfig = {
|
||||
accentColor: "#06b6d4",
|
||||
surfaceColor: "#170f29",
|
||||
textColor: "#ffffff",
|
||||
logoUrl: "/assets/brand/shanghai_karate_logo.png",
|
||||
logoUrl: "/assets/brand/shanghai_karate_logo.webp",
|
||||
logoWidth: 260,
|
||||
},
|
||||
camera: {
|
||||
@@ -164,20 +164,20 @@ const defaultConfig: BoothConfig = {
|
||||
zh: "扫码保存至手机相册",
|
||||
},
|
||||
downloadLabel: {
|
||||
en: "Save Portrait",
|
||||
zh: "保存高清照",
|
||||
en: "Save",
|
||||
zh: "保存",
|
||||
},
|
||||
wechatSaveTip: {
|
||||
en: "In WeChat: Long press photo above to save directly!",
|
||||
zh: "微信提示:长按上方图片即可直接保存到相册!",
|
||||
en: "In WeChat, long-press the photo to save it.",
|
||||
zh: "微信中长按照片即可保存。",
|
||||
},
|
||||
printLabel: {
|
||||
en: "Print Photo",
|
||||
zh: "打印照片",
|
||||
},
|
||||
startOverLabel: {
|
||||
en: "Train Again (Start Over)",
|
||||
zh: "再拍一张",
|
||||
en: "Finish",
|
||||
zh: "完成",
|
||||
},
|
||||
instagramShareLabel: {
|
||||
en: "Share Photo",
|
||||
|
||||
+23
-26
@@ -1,5 +1,6 @@
|
||||
import fs from "fs";
|
||||
import path from "path";
|
||||
import sharp from "sharp";
|
||||
import { validateAndSanitizeImage } from "@/lib/security";
|
||||
|
||||
export interface GenerationRecord {
|
||||
@@ -202,26 +203,34 @@ export async function persistImageFile(
|
||||
return { imageUrl: imageInput, filename: "" };
|
||||
}
|
||||
|
||||
// If it's a data URL or raw base64
|
||||
if (imageInput.startsWith("data:image/") || imageInput.startsWith("data:application/")) {
|
||||
const validation = validateAndSanitizeImage(imageInput, 15 * 1024 * 1024);
|
||||
if (validation.valid && validation.extension) {
|
||||
const ext = validation.extension; // Strictly "jpg" | "png" | "webp"
|
||||
const filename = `${safeId}.${ext}`;
|
||||
const filePath = path.join(IMAGES_DIR, filename);
|
||||
const persistJpeg = async (inputBuffer: Buffer) => {
|
||||
const sourcePath = path.join(IMAGES_DIR, `${safeId}.source`);
|
||||
const filename = `${safeId}.jpg`;
|
||||
const jpegPath = path.join(IMAGES_DIR, filename);
|
||||
fs.writeFileSync(sourcePath, inputBuffer);
|
||||
|
||||
const base64Data = validation.sanitizedBase64!.split(",")[1];
|
||||
fs.writeFileSync(filePath, Buffer.from(base64Data, "base64"));
|
||||
|
||||
// Automatically apply school branding: logo (top-left), WeChat QR (bottom-right), and footer text
|
||||
try {
|
||||
// Apply the existing branding before encoding the final downloadable image.
|
||||
try {
|
||||
const { applyWatermarkToFile } = await import("@/lib/watermark");
|
||||
await applyWatermarkToFile(filePath);
|
||||
await applyWatermarkToFile(sourcePath);
|
||||
} catch (wmErr) {
|
||||
console.warn("[Storage] Watermark application skipped:", wmErr);
|
||||
}
|
||||
|
||||
await sharp(sourcePath).jpeg({ quality: 92, mozjpeg: true }).toFile(jpegPath);
|
||||
return { imageUrl: `/api/images/${filename}`, filename };
|
||||
} finally {
|
||||
if (fs.existsSync(sourcePath)) fs.unlinkSync(sourcePath);
|
||||
}
|
||||
};
|
||||
|
||||
// If it's a data URL or raw base64
|
||||
if (imageInput.startsWith("data:image/") || imageInput.startsWith("data:application/")) {
|
||||
const validation = validateAndSanitizeImage(imageInput, 15 * 1024 * 1024);
|
||||
if (validation.valid) {
|
||||
const base64Data = validation.sanitizedBase64!.split(",")[1];
|
||||
return persistJpeg(Buffer.from(base64Data, "base64"));
|
||||
} else {
|
||||
console.warn(`[Storage Security Alert] Refused to persist invalid image: ${validation.error}`);
|
||||
return { imageUrl: imageInput, filename: "" };
|
||||
@@ -258,20 +267,8 @@ export async function persistImageFile(
|
||||
15 * 1024 * 1024
|
||||
);
|
||||
|
||||
if (validation.valid && validation.extension) {
|
||||
const filename = `${safeId}.${validation.extension}`;
|
||||
const filePath = path.join(IMAGES_DIR, filename);
|
||||
fs.writeFileSync(filePath, buffer);
|
||||
|
||||
// Automatically apply school branding: logo (top-left), WeChat QR (bottom-right), and footer text
|
||||
try {
|
||||
const { applyWatermarkToFile } = await import("@/lib/watermark");
|
||||
await applyWatermarkToFile(filePath);
|
||||
} catch (wmErr) {
|
||||
console.warn("[Storage] Watermark application skipped:", wmErr);
|
||||
}
|
||||
|
||||
return { imageUrl: `/api/images/${filename}`, filename };
|
||||
if (validation.valid) {
|
||||
return persistJpeg(buffer);
|
||||
} else {
|
||||
console.warn(`[Storage Security Alert] Remote file was not a valid image: ${validation.error}`);
|
||||
}
|
||||
|
||||
+1
-1
@@ -17,7 +17,7 @@ def watermark_portrait(image_path_or_buffer):
|
||||
bw, bh = base.size
|
||||
|
||||
# 1. School Logo (Top-Left)
|
||||
logo_path = os.path.join(process_cwd(), "public", "assets", "brand", "shanghai_karate_logo.png")
|
||||
logo_path = os.path.join(process_cwd(), "public", "assets", "brand", "shanghai_karate_logo.webp")
|
||||
if os.path.exists(logo_path):
|
||||
logo = Image.open(logo_path).convert("RGBA")
|
||||
target_logo_w = int(bw * 0.28)
|
||||
|
||||
+1
-1
@@ -16,7 +16,7 @@ export async function applyWatermarkToFile(filePath: string): Promise<void> {
|
||||
|
||||
try {
|
||||
const baseDir = process.cwd();
|
||||
const logoPath = path.join(baseDir, "public", "assets", "brand", "shanghai_karate_logo.png");
|
||||
const logoPath = path.join(baseDir, "public", "assets", "brand", "shanghai_karate_logo.webp");
|
||||
const qrPath = path.join(baseDir, "public", "assets", "brand", "dojo_qr.png");
|
||||
|
||||
const baseImage = sharp(filePath);
|
||||
|
||||
Reference in new issue
Block a user