Spaces:
Sleeping
Sleeping
CarouselForge Developer
feat: complete backend engine logic (llm parsing, puppeteer renderer, sqlite analytics)
d9ba1d6 | import puppeteer from 'puppeteer-core'; | |
| import type { ApiResponse } from '@/types/api'; | |
| import type { Slide } from '@/types/carousel'; | |
| export interface RenderRequest { | |
| slides: Slide[]; | |
| template: string; | |
| palette: string; | |
| variant?: string; | |
| } | |
| export interface RenderResult { | |
| images: string[]; | |
| format: string; | |
| } | |
| // OS-specific chromium paths | |
| const CHROMIUM_PATHS: Record<string, string[]> = { | |
| win32: [ | |
| 'C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe', | |
| 'C:\\Program Files (x86)\\Google\\Chrome\\Application\\chrome.exe', | |
| 'C:\\Program Files\\Microsoft\\Edge\\Application\\msedge.exe' | |
| ], | |
| linux: [ | |
| '/usr/bin/chromium', | |
| '/usr/bin/chromium-browser', | |
| '/usr/bin/google-chrome' | |
| ], | |
| darwin: [ | |
| '/Applications/Google Chrome.app/Contents/MacOS/Google Chrome' | |
| ] | |
| }; | |
| function getExecutablePath(): string | undefined { | |
| const platform = process.platform as string; | |
| const paths = CHROMIUM_PATHS[platform] || CHROMIUM_PATHS.linux; | |
| const fs = require('fs'); | |
| for (const p of paths) { | |
| if (fs.existsSync(p)) return p; | |
| } | |
| return undefined; | |
| } | |
| const PALETTE_COLORS: Record<string, { bg: string, text: string }> = { | |
| blue: { bg: '#2563eb', text: '#ffffff' }, | |
| green: { bg: '#16a34a', text: '#ffffff' }, | |
| orange: { bg: '#ea580c', text: '#ffffff' }, | |
| purple: { bg: '#9333ea', text: '#ffffff' }, | |
| mono: { bg: '#111827', text: '#ffffff' }, | |
| }; | |
| function buildSlideHTML(slide: Slide, palette: string, index: number, total: number): string { | |
| const colors = PALETTE_COLORS[palette] || PALETTE_COLORS.blue; | |
| return ` | |
| <html> | |
| <head> | |
| <style> | |
| body { | |
| margin: 0; padding: 0; | |
| width: 1080px; height: 1080px; | |
| background-color: ${colors.bg}; | |
| color: ${colors.text}; | |
| font-family: system-ui, -apple-system, sans-serif; | |
| display: flex; flex-direction: column; justify-content: center; align-items: center; | |
| text-align: center; padding: 80px; box-sizing: border-box; | |
| } | |
| .title { font-size: 80px; font-weight: 800; margin-bottom: 40px; } | |
| .body { font-size: 40px; font-weight: 400; line-height: 1.4; opacity: 0.9; } | |
| .footer { position: absolute; bottom: 60px; right: 60px; font-size: 24px; font-weight: bold; opacity: 0.7; } | |
| .cta { position: absolute; bottom: 80px; font-size: 40px; font-weight: bold; padding: 20px 40px; border-radius: 100px; background: ${colors.text}; color: ${colors.bg}; } | |
| </style> | |
| </head> | |
| <body> | |
| <div class="title">${slide.headline}</div> | |
| <div class="body">${slide.body}</div> | |
| ${slide.cta ? `<div class="cta">${slide.cta}</div>` : ''} | |
| <div class="footer">${index + 1} / ${total}</div> | |
| </body> | |
| </html> | |
| `; | |
| } | |
| export async function renderCarousel(req: RenderRequest): Promise<ApiResponse<RenderResult>> { | |
| let browser; | |
| try { | |
| const executablePath = getExecutablePath(); | |
| if (!executablePath) { | |
| throw new Error('Chromium executable not found. Install Chrome/Chromium.'); | |
| } | |
| browser = await puppeteer.launch({ | |
| executablePath, | |
| args: ['--no-sandbox', '--disable-setuid-sandbox', '--disable-dev-shm-usage'] | |
| }); | |
| const page = await browser.newPage(); | |
| await page.setViewport({ width: 1080, height: 1080 }); | |
| const images: string[] = []; | |
| for (let i = 0; i < req.slides.length; i++) { | |
| const slide = req.slides[i]; | |
| const html = buildSlideHTML(slide, req.palette, i, req.slides.length); | |
| await page.setContent(html, { waitUntil: 'load' }); | |
| const buffer = await page.screenshot({ type: 'png' }); | |
| images.push('data:image/png;base64,' + buffer.toString('base64')); | |
| } | |
| await browser.close(); | |
| return { | |
| success: true, | |
| data: { images, format: 'png' }, | |
| }; | |
| } catch (error) { | |
| if (browser) await browser.close(); | |
| console.error('[render] puppeteer failed:', error); | |
| const msg = error instanceof Error ? error.message : String(error); | |
| return { | |
| success: false, | |
| error: \`Failed to render images: \${msg}\`, | |
| }; | |
| } | |
| } | |