File size: 4,114 Bytes
d9ba1d6
c96af53
 
 
 
 
 
 
d9ba1d6
c96af53
 
 
 
 
 
 
d9ba1d6
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
c96af53
d9ba1d6
c96af53
d9ba1d6
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
c96af53
 
 
d9ba1d6
c96af53
 
d9ba1d6
 
c96af53
 
 
d9ba1d6
c96af53
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
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}\`,
    };
  }
}