import sharp from "sharp"; import { puterChatComplete } from "../puter-inference.js"; import type { RenderResult, WallpaperBundle, AppIconBundle } from "./types.js"; // ─── Platform dimensions ──────────────────────────────────────────────────────── export const PLATFORM_DIMENSIONS: Record< string, { width: number; height: number; label: string } > = { "desktop-hd": { width: 2560, height: 1440, label: "Desktop HD (2560×1440)" }, "desktop-fhd": { width: 1920, height: 1080, label: "Desktop FHD (1920×1080)" }, "desktop-4k": { width: 3840, height: 2160, label: "Desktop 4K (3840×2160)" }, "mobile-portrait": { width: 1080, height: 1920, label: "Mobile Portrait (1080×1920)" }, "mobile-landscape": { width: 1920, height: 1080, label: "Mobile Landscape (1920×1080)" }, "og-image": { width: 1200, height: 630, label: "OG Image (1200×630)" }, "twitter-card": { width: 1200, height: 628, label: "Twitter Card (1200×628)" }, "instagram-post": { width: 1080, height: 1080, label: "Instagram Post (1080×1080)" }, "instagram-banner": { width: 1080, height: 566, label: "Instagram Banner (1080×566)" }, "linkedin-banner": { width: 1584, height: 396, label: "LinkedIn Banner (1584×396)" }, "app-icon": { width: 1024, height: 1024, label: "App Icon (1024×1024)" }, "favicon": { width: 32, height: 32, label: "Favicon (32×32)" }, }; export const APP_ICON_SIZES = [1024, 512, 256, 64, 32] as const; // ─── SVG generation ───────────────────────────────────────────────────────────── function buildWallpaperSystemPrompt(width: number, height: number): string { return [ `You are an SVG artwork generator.`, `Output ONLY valid SVG — no markdown fences, no explanation, no surrounding text.`, `Use viewBox="0 0 ${width} ${height}" matching the target dimensions exactly.`, `Create a visually rich scene based on the user prompt.`, `Use gradients, shapes, patterns, and organic forms to fill the canvas.`, `Do NOT include any text elements (, ).`, `Do NOT include external images or scripts.`, `The SVG must start with .`, ].join("\n"); } function stripMarkdownFences(raw: string): string { return raw .trim() .replace(/^```(?:svg|xml)?\s*/i, "") .replace(/\s*```$/, "") .trim(); } // ─── Rasterization helpers ────────────────────────────────────────────────────── async function rasterizeSvgToPng( svgString: string, width: number, height: number, ): Promise { return sharp(Buffer.from(svgString), { density: 300 }) .resize(width, height, { fit: "fill" }) .png({ compressionLevel: 9 }) .toBuffer(); } // ─── Main renderer ────────────────────────────────────────────────────────────── export async function renderWallpaper( input: Record, ): Promise { const prompt = typeof input.prompt === "string" ? input.prompt : "abstract digital art"; const platform = typeof input.platform === "string" ? input.platform : "desktop-fhd"; const dims = PLATFORM_DIMENSIONS[platform]; if (!dims) { throw new Error( `renderWallpaper: unknown platform "${platform}". ` + `Available: ${Object.keys(PLATFORM_DIMENSIONS).join(", ")}`, ); } const { width, height } = dims; const systemPrompt = buildWallpaperSystemPrompt(width, height); const rawSvg = await puterChatComplete([ { role: "system", content: systemPrompt }, { role: "user", content: prompt }, ]); const svgString = stripMarkdownFences(rawSvg); // App icon / favicon → multi-size bundle if (platform === "app-icon" || platform === "favicon") { const sizes: AppIconBundle["sizes"] = []; for (const size of APP_ICON_SIZES) { const pngBuffer = await rasterizeSvgToPng(svgString, size, size); sizes.push({ size, pngBase64: pngBuffer.toString("base64") }); } const bundle: AppIconBundle = { type: "app-icon-bundle", sizes, prompt, }; return { filename: "app-icon-bundle.json", contentType: "application/json", buffer: Buffer.from(JSON.stringify(bundle)), }; } // All other platforms → single PNG at exact target dimensions const pngBuffer = await rasterizeSvgToPng(svgString, width, height); const bundle: WallpaperBundle = { type: "wallpaper-bundle", platform, width, height, pngBase64: pngBuffer.toString("base64"), prompt, }; return { filename: `wallpaper-${platform}.png`, contentType: "image/png", buffer: pngBuffer, }; }