|  | import express from 'express'; | 
					
						
						|  | import githubService from '../services/githubService.js'; | 
					
						
						|  | import screenshotService from '../services/screenshotService.js'; | 
					
						
						|  |  | 
					
						
						|  | import { generateSlideHTML, generateExportPage, exportPPTToJSON, generateHTMLPresentation } from '../../../shared/export-utils.js'; | 
					
						
						|  |  | 
					
						
						|  | const router = express.Router(); | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  | function generateSlideSVG(slide, pptData, options = {}) { | 
					
						
						|  | const { width = 1000, height = 562 } = options; | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  | let backgroundStyle = '#ffffff'; | 
					
						
						|  | if (slide.background) { | 
					
						
						|  | if (slide.background.type === 'solid') { | 
					
						
						|  | backgroundStyle = slide.background.color || '#ffffff'; | 
					
						
						|  | } else if (slide.background.type === 'gradient' && slide.background.gradient) { | 
					
						
						|  |  | 
					
						
						|  | const colors = slide.background.gradient.colors || []; | 
					
						
						|  | if (colors.length > 0) { | 
					
						
						|  | backgroundStyle = colors[0].color || '#ffffff'; | 
					
						
						|  | } | 
					
						
						|  | } | 
					
						
						|  | } | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  | let gradientDefs = ''; | 
					
						
						|  | if (slide.background?.type === 'gradient' && slide.background.gradient?.colors) { | 
					
						
						|  | const colors = slide.background.gradient.colors; | 
					
						
						|  | const gradientId = 'bg-gradient'; | 
					
						
						|  | if (slide.background.gradient.type === 'linear') { | 
					
						
						|  | gradientDefs = ` | 
					
						
						|  | <defs> | 
					
						
						|  | <linearGradient id="${gradientId}" x1="0%" y1="0%" x2="100%" y2="0%"> | 
					
						
						|  | ${colors.map((color, index) => | 
					
						
						|  | `<stop offset="${(index / (colors.length - 1)) * 100}%" style="stop-color:${color.color};stop-opacity:1" />` | 
					
						
						|  | ).join('')} | 
					
						
						|  | </linearGradient> | 
					
						
						|  | </defs> | 
					
						
						|  | `; | 
					
						
						|  | backgroundStyle = `url(#${gradientId})`; | 
					
						
						|  | } | 
					
						
						|  | } | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  | const elementsHTML = slide.elements.map(element => { | 
					
						
						|  | const x = element.left || 0; | 
					
						
						|  | const y = element.top || 0; | 
					
						
						|  | const w = element.width || 100; | 
					
						
						|  | const h = element.height || 100; | 
					
						
						|  | const rotation = element.rotate || 0; | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  | const transform = rotation !== 0 ? `transform="rotate(${rotation} ${x + w/2} ${y + h/2})"` : ''; | 
					
						
						|  |  | 
					
						
						|  | if (element.type === 'text') { | 
					
						
						|  | const fontSize = element.fontSize || 16; | 
					
						
						|  | const fontFamily = element.fontName || 'Arial, sans-serif'; | 
					
						
						|  | const color = element.defaultColor || element.color || '#000000'; | 
					
						
						|  | const fontWeight = element.bold ? 'bold' : 'normal'; | 
					
						
						|  | const fontStyle = element.italic ? 'italic' : 'normal'; | 
					
						
						|  | const textDecoration = element.underline ? 'underline' : 'none'; | 
					
						
						|  | const textAnchor = element.align === 'center' ? 'middle' : element.align === 'right' ? 'end' : 'start'; | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  | let textX = x + 10; | 
					
						
						|  | if (element.align === 'center') textX = x + w/2; | 
					
						
						|  | else if (element.align === 'right') textX = x + w - 10; | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  | const content = (element.content || '').replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>'); | 
					
						
						|  | const lines = content.split('\n'); | 
					
						
						|  |  | 
					
						
						|  | return ` | 
					
						
						|  | <g ${transform}> | 
					
						
						|  | ${lines.map((line, index) => ` | 
					
						
						|  | <text x="${textX}" y="${y + fontSize + (index * fontSize * 1.2)}" | 
					
						
						|  | font-family="${fontFamily}" | 
					
						
						|  | font-size="${fontSize}" | 
					
						
						|  | fill="${color}" | 
					
						
						|  | font-weight="${fontWeight}" | 
					
						
						|  | font-style="${fontStyle}" | 
					
						
						|  | text-decoration="${textDecoration}" | 
					
						
						|  | text-anchor="${textAnchor}">${line}</text> | 
					
						
						|  | `).join('')} | 
					
						
						|  | </g> | 
					
						
						|  | `; | 
					
						
						|  | } | 
					
						
						|  |  | 
					
						
						|  | if (element.type === 'shape') { | 
					
						
						|  | const fill = element.fill || '#cccccc'; | 
					
						
						|  | const stroke = element.outline?.color || 'none'; | 
					
						
						|  | const strokeWidth = element.outline?.width || 0; | 
					
						
						|  |  | 
					
						
						|  | if (element.shape === 'ellipse') { | 
					
						
						|  | const cx = x + w/2; | 
					
						
						|  | const cy = y + h/2; | 
					
						
						|  | const rx = w/2; | 
					
						
						|  | const ry = h/2; | 
					
						
						|  | return `<ellipse cx="${cx}" cy="${cy}" rx="${rx}" ry="${ry}" fill="${fill}" stroke="${stroke}" stroke-width="${strokeWidth}" ${transform}/>`; | 
					
						
						|  | } | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  | const borderRadius = element.borderRadius || 0; | 
					
						
						|  | if (borderRadius > 0) { | 
					
						
						|  | return `<rect x="${x}" y="${y}" width="${w}" height="${h}" rx="${borderRadius}" ry="${borderRadius}" fill="${fill}" stroke="${stroke}" stroke-width="${strokeWidth}" ${transform}/>`; | 
					
						
						|  | } | 
					
						
						|  | return `<rect x="${x}" y="${y}" width="${w}" height="${h}" fill="${fill}" stroke="${stroke}" stroke-width="${strokeWidth}" ${transform}/>`; | 
					
						
						|  | } | 
					
						
						|  |  | 
					
						
						|  | if (element.type === 'image' && element.src) { | 
					
						
						|  |  | 
					
						
						|  | if (element.src.startsWith('data:image/')) { | 
					
						
						|  | return `<image x="${x}" y="${y}" width="${w}" height="${h}" href="${element.src}" ${transform}/>`; | 
					
						
						|  | } | 
					
						
						|  |  | 
					
						
						|  | return `<rect x="${x}" y="${y}" width="${w}" height="${h}" fill="#f0f0f0" stroke="#ccc" stroke-width="1" ${transform}/> | 
					
						
						|  | <text x="${x + w/2}" y="${y + h/2}" text-anchor="middle" font-size="12" fill="#666">图片</text>`; | 
					
						
						|  | } | 
					
						
						|  |  | 
					
						
						|  | return ''; | 
					
						
						|  | }).filter(Boolean).join(''); | 
					
						
						|  |  | 
					
						
						|  | return `<svg width="${width}" height="${height}" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"> | 
					
						
						|  | ${gradientDefs} | 
					
						
						|  | <rect width="100%" height="100%" fill="${backgroundStyle}"/> | 
					
						
						|  | <g>${elementsHTML}</g> | 
					
						
						|  | </svg>`; | 
					
						
						|  | } | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  | function generateErrorPage(title, message) { | 
					
						
						|  | return ` | 
					
						
						|  | <!DOCTYPE html> | 
					
						
						|  | <html lang="zh-CN"> | 
					
						
						|  | <head> | 
					
						
						|  | <meta charset="UTF-8"> | 
					
						
						|  | <meta name="viewport" content="width=device-width, initial-scale=1.0"> | 
					
						
						|  | <title>${title} - PPT导出</title> | 
					
						
						|  | <style> | 
					
						
						|  | body { | 
					
						
						|  | font-family: 'Microsoft YaHei', sans-serif; | 
					
						
						|  | margin: 0; | 
					
						
						|  | padding: 0; | 
					
						
						|  | background: linear-gradient(135deg, #ff6b6b 0%, #feca57 100%); | 
					
						
						|  | min-height: 100vh; | 
					
						
						|  | display: flex; | 
					
						
						|  | align-items: center; | 
					
						
						|  | justify-content: center; | 
					
						
						|  | } | 
					
						
						|  | .error-container { | 
					
						
						|  | background: white; | 
					
						
						|  | padding: 40px; | 
					
						
						|  | border-radius: 15px; | 
					
						
						|  | box-shadow: 0 20px 40px rgba(0,0,0,0.1); | 
					
						
						|  | text-align: center; | 
					
						
						|  | max-width: 500px; | 
					
						
						|  | margin: 20px; | 
					
						
						|  | } | 
					
						
						|  | .error-icon { font-size: 64px; margin-bottom: 20px; } | 
					
						
						|  | .error-title { font-size: 24px; font-weight: bold; color: #333; margin-bottom: 15px; } | 
					
						
						|  | .error-message { font-size: 16px; color: #666; line-height: 1.6; margin-bottom: 30px; } | 
					
						
						|  | .error-actions { display: flex; gap: 15px; justify-content: center; } | 
					
						
						|  | .btn { | 
					
						
						|  | padding: 12px 24px; | 
					
						
						|  | border: none; | 
					
						
						|  | border-radius: 5px; | 
					
						
						|  | font-size: 16px; | 
					
						
						|  | cursor: pointer; | 
					
						
						|  | text-decoration: none; | 
					
						
						|  | display: inline-block; | 
					
						
						|  | transition: all 0.3s; | 
					
						
						|  | } | 
					
						
						|  | .btn-primary { background: #4CAF50; color: white; } | 
					
						
						|  | .btn-primary:hover { background: #45a049; } | 
					
						
						|  | .btn-secondary { background: #f8f9fa; color: #333; border: 1px solid #ddd; } | 
					
						
						|  | .btn-secondary:hover { background: #e9ecef; } | 
					
						
						|  | </style> | 
					
						
						|  | </head> | 
					
						
						|  | <body> | 
					
						
						|  | <div class="error-container"> | 
					
						
						|  | <div class="error-icon">❌</div> | 
					
						
						|  | <div class="error-title">${title}</div> | 
					
						
						|  | <div class="error-message">${message}</div> | 
					
						
						|  | <div class="error-actions"> | 
					
						
						|  | <button class="btn btn-secondary" onclick="history.back()">返回上页</button> | 
					
						
						|  | <a href="/" class="btn btn-primary">回到首页</a> | 
					
						
						|  | </div> | 
					
						
						|  | </div> | 
					
						
						|  | </body> | 
					
						
						|  | </html> | 
					
						
						|  | `; | 
					
						
						|  | } | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  | router.get('/view/:userId/:pptId/:slideIndex?', async (req, res, next) => { | 
					
						
						|  | try { | 
					
						
						|  | const { userId, pptId, slideIndex = 0 } = req.params; | 
					
						
						|  | const querySlideIndex = req.query.slide ? parseInt(req.query.slide) : parseInt(slideIndex); | 
					
						
						|  | const fileName = `${pptId}.json`; | 
					
						
						|  |  | 
					
						
						|  | let pptData = null; | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  | for (let i = 0; i < githubService.repositories.length; i++) { | 
					
						
						|  | try { | 
					
						
						|  | const result = await githubService.getFile(userId, fileName, i); | 
					
						
						|  | if (result) { | 
					
						
						|  | pptData = result.content; | 
					
						
						|  | break; | 
					
						
						|  | } | 
					
						
						|  | } catch (error) { | 
					
						
						|  | continue; | 
					
						
						|  | } | 
					
						
						|  | } | 
					
						
						|  |  | 
					
						
						|  | if (!pptData) { | 
					
						
						|  | return res.status(404).send(generateErrorPage('PPT Not Found', 'Please check if the link is correct')); | 
					
						
						|  | } | 
					
						
						|  |  | 
					
						
						|  | const slideIdx = querySlideIndex; | 
					
						
						|  | if (slideIdx >= pptData.slides.length || slideIdx < 0) { | 
					
						
						|  | return res.status(404).send(generateErrorPage('Slide Not Found', 'Please check if the slide index is correct')); | 
					
						
						|  | } | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  | const htmlContent = generateSlideHTML(pptData, slideIdx, { format: 'view' }); | 
					
						
						|  | res.setHeader('Content-Type', 'text/html; charset=utf-8'); | 
					
						
						|  | res.send(htmlContent); | 
					
						
						|  | } catch (error) { | 
					
						
						|  | next(error); | 
					
						
						|  | } | 
					
						
						|  | }); | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  | router.get('/api-view/:userId/:pptId/:slideIndex?', async (req, res, next) => { | 
					
						
						|  | try { | 
					
						
						|  | const { userId, pptId, slideIndex = 0 } = req.params; | 
					
						
						|  | const fileName = `${pptId}.json`; | 
					
						
						|  |  | 
					
						
						|  | let pptData = null; | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  | for (let i = 0; i < githubService.repositories.length; i++) { | 
					
						
						|  | try { | 
					
						
						|  | const result = await githubService.getFile(userId, fileName, i); | 
					
						
						|  | if (result) { | 
					
						
						|  | pptData = result.content; | 
					
						
						|  | break; | 
					
						
						|  | } | 
					
						
						|  | } catch (error) { | 
					
						
						|  | continue; | 
					
						
						|  | } | 
					
						
						|  | } | 
					
						
						|  |  | 
					
						
						|  | if (!pptData) { | 
					
						
						|  | return res.status(404).json({ error: 'PPT not found' }); | 
					
						
						|  | } | 
					
						
						|  |  | 
					
						
						|  | const slideIdx = parseInt(slideIndex); | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  | if (!pptData.slides || !Array.isArray(pptData.slides)) { | 
					
						
						|  | console.log(`❌ Invalid PPT data: slides array not found`); | 
					
						
						|  |  | 
					
						
						|  | if (format === 'svg') { | 
					
						
						|  | const invalidDataSvg = `<svg width="800" height="450" xmlns="http://www.w3.org/2000/svg"> | 
					
						
						|  | <rect width="100%" height="100%" fill="#f8f9fa"/> | 
					
						
						|  | <text x="400" y="200" text-anchor="middle" font-family="Arial, sans-serif" font-size="24" fill="#6c757d">Invalid PPT Data</text> | 
					
						
						|  | <text x="400" y="250" text-anchor="middle" font-family="Arial, sans-serif" font-size="16" fill="#6c757d">Slides data not found</text> | 
					
						
						|  | <text x="400" y="300" text-anchor="middle" font-family="Arial, sans-serif" font-size="12" fill="#999">${new Date().toISOString()}</text> | 
					
						
						|  | </svg>`; | 
					
						
						|  |  | 
					
						
						|  | res.setHeader('Content-Type', 'image/svg+xml; charset=utf-8'); | 
					
						
						|  | return res.send(invalidDataSvg); | 
					
						
						|  | } else { | 
					
						
						|  | const errorPage = generateErrorPage('Invalid PPT Data', 'PPT slides data not found or corrupted.'); | 
					
						
						|  | res.setHeader('Content-Type', 'text/html; charset=utf-8'); | 
					
						
						|  | return res.status(400).send(errorPage); | 
					
						
						|  | } | 
					
						
						|  | } | 
					
						
						|  |  | 
					
						
						|  | if (slideIdx >= pptData.slides.length || slideIdx < 0) { | 
					
						
						|  | return res.status(404).json({ error: 'Slide not found' }); | 
					
						
						|  | } | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  | res.json({ | 
					
						
						|  | id: pptData.id, | 
					
						
						|  | title: pptData.title, | 
					
						
						|  | theme: pptData.theme, | 
					
						
						|  | currentSlide: pptData.slides[slideIdx], | 
					
						
						|  | slideIndex: slideIdx, | 
					
						
						|  | totalSlides: pptData.slides.length, | 
					
						
						|  | isPublicView: true | 
					
						
						|  | }); | 
					
						
						|  | } catch (error) { | 
					
						
						|  | next(error); | 
					
						
						|  | } | 
					
						
						|  | }); | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  | router.get('/ppt/:userId/:pptId', async (req, res, next) => { | 
					
						
						|  | try { | 
					
						
						|  | const { userId, pptId } = req.params; | 
					
						
						|  | const fileName = `${pptId}.json`; | 
					
						
						|  |  | 
					
						
						|  | let pptData = null; | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  | for (let i = 0; i < githubService.repositories.length; i++) { | 
					
						
						|  | try { | 
					
						
						|  | const result = await githubService.getFile(userId, fileName, i); | 
					
						
						|  | if (result) { | 
					
						
						|  | pptData = result.content; | 
					
						
						|  | break; | 
					
						
						|  | } | 
					
						
						|  | } catch (error) { | 
					
						
						|  | continue; | 
					
						
						|  | } | 
					
						
						|  | } | 
					
						
						|  |  | 
					
						
						|  | if (!pptData) { | 
					
						
						|  | return res.status(404).json({ error: 'PPT not found' }); | 
					
						
						|  | } | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  | res.json({ | 
					
						
						|  | ...pptData, | 
					
						
						|  | isPublicView: true, | 
					
						
						|  | readOnly: true | 
					
						
						|  | }); | 
					
						
						|  | } catch (error) { | 
					
						
						|  | next(error); | 
					
						
						|  | } | 
					
						
						|  | }); | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  | router.post('/generate-share-link', async (req, res, next) => { | 
					
						
						|  | try { | 
					
						
						|  | const { userId, pptId, slideIndex = 0 } = req.body; | 
					
						
						|  |  | 
					
						
						|  | if (!userId || !pptId) { | 
					
						
						|  | return res.status(400).json({ error: 'User ID and PPT ID are required' }); | 
					
						
						|  | } | 
					
						
						|  |  | 
					
						
						|  | console.log(`Generating share link for PPT: ${pptId}, User: ${userId}`); | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  | const fileName = `${pptId}.json`; | 
					
						
						|  | let pptExists = false; | 
					
						
						|  | let pptData = null; | 
					
						
						|  |  | 
					
						
						|  | console.log(`Checking ${githubService.repositories.length} GitHub repositories...`); | 
					
						
						|  |  | 
					
						
						|  | for (let i = 0; i < githubService.repositories.length; i++) { | 
					
						
						|  | try { | 
					
						
						|  | console.log(`Checking repository ${i}: ${githubService.repositories[i]}`); | 
					
						
						|  | const result = await githubService.getFile(userId, fileName, i); | 
					
						
						|  | if (result) { | 
					
						
						|  | pptExists = true; | 
					
						
						|  | pptData = result.content; | 
					
						
						|  | console.log(`PPT found in repository ${i}`); | 
					
						
						|  | break; | 
					
						
						|  | } | 
					
						
						|  | } catch (error) { | 
					
						
						|  | console.log(`PPT not found in repository ${i}: ${error.message}`); | 
					
						
						|  | continue; | 
					
						
						|  | } | 
					
						
						|  | } | 
					
						
						|  |  | 
					
						
						|  | if (!pptExists) { | 
					
						
						|  | return res.status(404).json({ | 
					
						
						|  | error: 'PPT not found', | 
					
						
						|  | details: `PPT ${pptId} not found for user ${userId}`, | 
					
						
						|  | searchedLocations: 'GitHub repositories' | 
					
						
						|  | }); | 
					
						
						|  | } | 
					
						
						|  |  | 
					
						
						|  | const baseUrl = process.env.PUBLIC_URL || req.get('host'); | 
					
						
						|  |  | 
					
						
						|  | const protocol = baseUrl.includes('localhost') ? 'http' : 'https'; | 
					
						
						|  |  | 
					
						
						|  | const shareLinks = { | 
					
						
						|  |  | 
					
						
						|  | slideUrl: `${protocol}://${baseUrl}/api/public/view/${userId}/${pptId}/${slideIndex}`, | 
					
						
						|  |  | 
					
						
						|  | pptUrl: `${protocol}://${baseUrl}/api/public/ppt/${userId}/${pptId}`, | 
					
						
						|  |  | 
					
						
						|  | viewUrl: `${protocol}://${baseUrl}/public/${userId}/${pptId}/${slideIndex}`, | 
					
						
						|  |  | 
					
						
						|  | screenshotUrl: `${protocol}://${baseUrl}/api/public/image/${userId}/${pptId}/${slideIndex}?format=jpg&quality=90`, | 
					
						
						|  |  | 
					
						
						|  | directImageUrl: `${protocol}://${baseUrl}/api/public/direct-image/${userId}/${pptId}/${slideIndex}`, | 
					
						
						|  |  | 
					
						
						|  | pptInfo: { | 
					
						
						|  | id: pptId, | 
					
						
						|  | title: pptData?.title || 'Unknown Title', | 
					
						
						|  | slideCount: pptData?.slides?.length || 0 | 
					
						
						|  | } | 
					
						
						|  | }; | 
					
						
						|  |  | 
					
						
						|  | console.log('Share links generated successfully:', shareLinks); | 
					
						
						|  | res.json(shareLinks); | 
					
						
						|  | } catch (error) { | 
					
						
						|  | next(error); | 
					
						
						|  | } | 
					
						
						|  | }); | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  | router.get('/screenshot/:userId/:pptId/:slideIndex?', async (req, res, next) => { | 
					
						
						|  | try { | 
					
						
						|  | const { userId, pptId, slideIndex = 0 } = req.params; | 
					
						
						|  | const { format = 'jpeg', quality = 90, strategy = 'frontend-first', returnHtml = 'true' } = req.query; | 
					
						
						|  |  | 
					
						
						|  | console.log(`Screenshot request: userId=${userId}, pptId=${pptId}, slideIndex=${slideIndex}, strategy=${strategy}, returnHtml=${returnHtml}`); | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  | const fileName = `${pptId}.json`; | 
					
						
						|  | let pptData = null; | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  | for (let i = 0; i < githubService.repositories.length; i++) { | 
					
						
						|  | try { | 
					
						
						|  | const result = await githubService.getFile(userId, fileName, i); | 
					
						
						|  | if (result) { | 
					
						
						|  | pptData = result.content; | 
					
						
						|  | break; | 
					
						
						|  | } | 
					
						
						|  | } catch (error) { | 
					
						
						|  | continue; | 
					
						
						|  | } | 
					
						
						|  | } | 
					
						
						|  |  | 
					
						
						|  | if (!pptData) { | 
					
						
						|  | return res.status(404).json({ error: 'PPT not found' }); | 
					
						
						|  | } | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  | if (!pptData.slides || !Array.isArray(pptData.slides)) { | 
					
						
						|  | return res.status(400).json({ error: 'Invalid PPT data: slides not found' }); | 
					
						
						|  | } | 
					
						
						|  |  | 
					
						
						|  | const slideIdx = parseInt(slideIndex); | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  | if (!pptData.slides || !Array.isArray(pptData.slides)) { | 
					
						
						|  | console.log(`❌ Invalid PPT data: slides array not found`); | 
					
						
						|  |  | 
					
						
						|  | if (format === 'svg') { | 
					
						
						|  | const invalidDataSvg = `<svg width="800" height="450" xmlns="http://www.w3.org/2000/svg"> | 
					
						
						|  | <rect width="100%" height="100%" fill="#f8f9fa"/> | 
					
						
						|  | <text x="400" y="200" text-anchor="middle" font-family="Arial, sans-serif" font-size="24" fill="#6c757d">Invalid PPT Data</text> | 
					
						
						|  | <text x="400" y="250" text-anchor="middle" font-family="Arial, sans-serif" font-size="16" fill="#6c757d">Slides data not found</text> | 
					
						
						|  | <text x="400" y="300" text-anchor="middle" font-family="Arial, sans-serif" font-size="12" fill="#999">${new Date().toISOString()}</text> | 
					
						
						|  | </svg>`; | 
					
						
						|  |  | 
					
						
						|  | res.setHeader('Content-Type', 'image/svg+xml; charset=utf-8'); | 
					
						
						|  | return res.send(invalidDataSvg); | 
					
						
						|  | } else { | 
					
						
						|  | const errorPage = generateErrorPage('Invalid PPT Data', 'PPT slides data not found or corrupted.'); | 
					
						
						|  | res.setHeader('Content-Type', 'text/html; charset=utf-8'); | 
					
						
						|  | return res.status(400).send(errorPage); | 
					
						
						|  | } | 
					
						
						|  | } | 
					
						
						|  |  | 
					
						
						|  | if (slideIdx >= pptData.slides.length || slideIdx < 0) { | 
					
						
						|  | return res.status(404).json({ error: 'Invalid slide index' }); | 
					
						
						|  | } | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  | if (strategy === 'frontend-first' && returnHtml !== 'false') { | 
					
						
						|  | console.log('Using frontend export strategy - returning HTML page'); | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  | const htmlPage = generateExportPage(pptData, slideIdx, { | 
					
						
						|  | format, | 
					
						
						|  | quality: parseInt(quality), | 
					
						
						|  | autoDownload: req.query.autoDownload === 'true' | 
					
						
						|  | }); | 
					
						
						|  |  | 
					
						
						|  | res.setHeader('Content-Type', 'text/html; charset=utf-8'); | 
					
						
						|  | res.setHeader('X-Screenshot-Strategy', 'frontend-export'); | 
					
						
						|  | res.setHeader('X-Generation-Time', '< 100ms'); | 
					
						
						|  | return res.send(htmlPage); | 
					
						
						|  | } | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  | console.log('Using backend screenshot for direct image return...'); | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  | const htmlContent = generateSlideHTML(pptData, slideIdx, { format: 'screenshot' }); | 
					
						
						|  |  | 
					
						
						|  | try { | 
					
						
						|  | const screenshot = await screenshotService.generateScreenshot(htmlContent, { | 
					
						
						|  | format, | 
					
						
						|  | quality: parseInt(quality), | 
					
						
						|  | width: pptData.viewportSize || 1000, | 
					
						
						|  | height: Math.ceil((pptData.viewportSize || 1000) * (pptData.viewportRatio || 0.5625)) | 
					
						
						|  | }); | 
					
						
						|  |  | 
					
						
						|  | res.setHeader('Content-Type', `image/${format}`); | 
					
						
						|  | res.setHeader('X-Screenshot-Type', 'backend-generated'); | 
					
						
						|  | res.setHeader('X-Generation-Time', '2-5s'); | 
					
						
						|  | res.send(screenshot); | 
					
						
						|  |  | 
					
						
						|  | } catch (error) { | 
					
						
						|  | console.error('Backend screenshot failed:', error); | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  | const fallbackImage = screenshotService.generateFallbackImage( | 
					
						
						|  | pptData.viewportSize || 1000, | 
					
						
						|  | Math.ceil((pptData.viewportSize || 1000) * (pptData.viewportRatio || 0.5625)) | 
					
						
						|  | ); | 
					
						
						|  |  | 
					
						
						|  | res.setHeader('Content-Type', `image/${format}`); | 
					
						
						|  | res.setHeader('X-Screenshot-Type', 'fallback-generated'); | 
					
						
						|  | res.setHeader('X-Generation-Time', '< 50ms'); | 
					
						
						|  | res.send(fallbackImage); | 
					
						
						|  | } | 
					
						
						|  |  | 
					
						
						|  | } catch (error) { | 
					
						
						|  | next(error); | 
					
						
						|  | } | 
					
						
						|  | }); | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  | router.get('/image/:userId/:pptId/:slideIndex?', async (req, res, next) => { | 
					
						
						|  | try { | 
					
						
						|  | const { userId, pptId, slideIndex = 0 } = req.params; | 
					
						
						|  | const { format = 'svg', quality = 90, width: requestWidth, height: requestHeight } = req.query; | 
					
						
						|  |  | 
					
						
						|  | console.log(`🔥 Image request: userId=${userId}, pptId=${pptId}, slideIndex=${slideIndex}, format=${format}`); | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  | const fileName = `${pptId}.json`; | 
					
						
						|  | let pptData = null; | 
					
						
						|  |  | 
					
						
						|  | for (let i = 0; i < githubService.repositories.length; i++) { | 
					
						
						|  | try { | 
					
						
						|  | const result = await githubService.getFile(userId, fileName, i); | 
					
						
						|  | if (result) { | 
					
						
						|  | pptData = result.content; | 
					
						
						|  | console.log(`✅ PPT data found in repository ${i}`); | 
					
						
						|  | break; | 
					
						
						|  | } | 
					
						
						|  | } catch (error) { | 
					
						
						|  | continue; | 
					
						
						|  | } | 
					
						
						|  | } | 
					
						
						|  |  | 
					
						
						|  | if (!pptData) { | 
					
						
						|  | console.log(`❌ PPT not found: ${pptId}`); | 
					
						
						|  |  | 
					
						
						|  | if (format === 'svg') { | 
					
						
						|  | const notFoundSvg = `<svg width="800" height="450" xmlns="http://www.w3.org/2000/svg"> | 
					
						
						|  | <rect width="100%" height="100%" fill="#f8f9fa"/> | 
					
						
						|  | <text x="400" y="200" text-anchor="middle" font-family="Arial, sans-serif" font-size="24" fill="#6c757d">PPT Not Found</text> | 
					
						
						|  | <text x="400" y="250" text-anchor="middle" font-family="Arial, sans-serif" font-size="16" fill="#6c757d">PPT ${pptId} does not exist</text> | 
					
						
						|  | <text x="400" y="300" text-anchor="middle" font-family="Arial, sans-serif" font-size="12" fill="#999">${new Date().toISOString()}</text> | 
					
						
						|  | </svg>`; | 
					
						
						|  |  | 
					
						
						|  | res.setHeader('Content-Type', 'image/svg+xml; charset=utf-8'); | 
					
						
						|  | return res.send(notFoundSvg); | 
					
						
						|  | } else { | 
					
						
						|  |  | 
					
						
						|  | const errorPage = generateErrorPage('PPT Not Found', `PPT ${pptId} not found. Please check the PPT ID.`); | 
					
						
						|  | res.setHeader('Content-Type', 'text/html; charset=utf-8'); | 
					
						
						|  | return res.status(404).send(errorPage); | 
					
						
						|  | } | 
					
						
						|  | } | 
					
						
						|  |  | 
					
						
						|  | const slideIdx = parseInt(slideIndex); | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  | if (!pptData.slides || !Array.isArray(pptData.slides)) { | 
					
						
						|  | console.log(`❌ Invalid PPT data: slides array not found`); | 
					
						
						|  |  | 
					
						
						|  | if (format === 'svg') { | 
					
						
						|  | const invalidDataSvg = `<svg width="800" height="450" xmlns="http://www.w3.org/2000/svg"> | 
					
						
						|  | <rect width="100%" height="100%" fill="#f8f9fa"/> | 
					
						
						|  | <text x="400" y="200" text-anchor="middle" font-family="Arial, sans-serif" font-size="24" fill="#6c757d">Invalid PPT Data</text> | 
					
						
						|  | <text x="400" y="250" text-anchor="middle" font-family="Arial, sans-serif" font-size="16" fill="#6c757d">Slides data not found</text> | 
					
						
						|  | <text x="400" y="300" text-anchor="middle" font-family="Arial, sans-serif" font-size="12" fill="#999">${new Date().toISOString()}</text> | 
					
						
						|  | </svg>`; | 
					
						
						|  |  | 
					
						
						|  | res.setHeader('Content-Type', 'image/svg+xml; charset=utf-8'); | 
					
						
						|  | return res.send(invalidDataSvg); | 
					
						
						|  | } else { | 
					
						
						|  | const errorPage = generateErrorPage('Invalid PPT Data', 'PPT slides data not found or corrupted.'); | 
					
						
						|  | res.setHeader('Content-Type', 'text/html; charset=utf-8'); | 
					
						
						|  | return res.status(400).send(errorPage); | 
					
						
						|  | } | 
					
						
						|  | } | 
					
						
						|  |  | 
					
						
						|  | if (slideIdx >= pptData.slides.length || slideIdx < 0) { | 
					
						
						|  | console.log(`❌ Invalid slide index: ${slideIndex}`); | 
					
						
						|  |  | 
					
						
						|  | if (format === 'svg') { | 
					
						
						|  | const invalidSlideSvg = `<svg width="800" height="450" xmlns="http://www.w3.org/2000/svg"> | 
					
						
						|  | <rect width="100%" height="100%" fill="#f8f9fa"/> | 
					
						
						|  | <text x="400" y="200" text-anchor="middle" font-family="Arial, sans-serif" font-size="24" fill="#6c757d">Invalid Slide</text> | 
					
						
						|  | <text x="400" y="250" text-anchor="middle" font-family="Arial, sans-serif" font-size="16" fill="#6c757d">Slide ${slideIndex} not found</text> | 
					
						
						|  | <text x="400" y="300" text-anchor="middle" font-family="Arial, sans-serif" font-size="12" fill="#999">${new Date().toISOString()}</text> | 
					
						
						|  | </svg>`; | 
					
						
						|  |  | 
					
						
						|  | res.setHeader('Content-Type', 'image/svg+xml; charset=utf-8'); | 
					
						
						|  | return res.send(invalidSlideSvg); | 
					
						
						|  | } else { | 
					
						
						|  | const errorPage = generateErrorPage('Invalid Slide', `Slide ${slideIndex} not found in this PPT.`); | 
					
						
						|  | res.setHeader('Content-Type', 'text/html; charset=utf-8'); | 
					
						
						|  | return res.status(404).send(errorPage); | 
					
						
						|  | } | 
					
						
						|  | } | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  | if (format === 'svg') { | 
					
						
						|  |  | 
					
						
						|  | console.log(`✅ Generating SVG for slide ${slideIdx}`); | 
					
						
						|  |  | 
					
						
						|  | const defaultWidth = pptData.viewportSize || 1000; | 
					
						
						|  | const defaultHeight = Math.ceil(defaultWidth * (pptData.viewportRatio || 0.5625)); | 
					
						
						|  | const finalWidth = requestWidth ? parseInt(requestWidth) : defaultWidth; | 
					
						
						|  | const finalHeight = requestHeight ? parseInt(requestHeight) : defaultHeight; | 
					
						
						|  |  | 
					
						
						|  | const slide = pptData.slides[slideIdx]; | 
					
						
						|  | const svgContent = generateSlideSVG(slide, pptData, { | 
					
						
						|  | width: finalWidth, | 
					
						
						|  | height: finalHeight | 
					
						
						|  | }); | 
					
						
						|  |  | 
					
						
						|  | console.log(`🎉 SVG generated successfully, returning image data`); | 
					
						
						|  |  | 
					
						
						|  | res.setHeader('Content-Type', 'image/svg+xml; charset=utf-8'); | 
					
						
						|  | res.setHeader('Cache-Control', 'no-cache, no-store, must-revalidate'); | 
					
						
						|  | res.setHeader('Access-Control-Allow-Origin', '*'); | 
					
						
						|  | res.setHeader('X-Force-Update', 'true'); | 
					
						
						|  | res.setHeader('X-Generation-Time', '< 10ms'); | 
					
						
						|  | res.send(svgContent); | 
					
						
						|  |  | 
					
						
						|  | } else { | 
					
						
						|  |  | 
					
						
						|  | console.log(`✅ Generating ${format} image directly`); | 
					
						
						|  |  | 
					
						
						|  | try { | 
					
						
						|  |  | 
					
						
						|  | const htmlContent = generateSlideHTML(pptData, slideIdx, { format: 'screenshot' }); | 
					
						
						|  |  | 
					
						
						|  | const screenshot = await screenshotService.generateScreenshot(htmlContent, { | 
					
						
						|  | format: format === 'jpg' ? 'jpeg' : format, | 
					
						
						|  | quality: parseInt(quality), | 
					
						
						|  | width: pptData.viewportSize || 1000, | 
					
						
						|  | height: Math.ceil((pptData.viewportSize || 1000) * (pptData.viewportRatio || 0.5625)) | 
					
						
						|  | }); | 
					
						
						|  |  | 
					
						
						|  | res.setHeader('Content-Type', `image/${format === 'jpg' ? 'jpeg' : format}`); | 
					
						
						|  | res.setHeader('X-Screenshot-Type', 'backend-generated'); | 
					
						
						|  | res.setHeader('X-Generation-Time', '2-5s'); | 
					
						
						|  | res.setHeader('Cache-Control', 'public, max-age=3600'); | 
					
						
						|  | res.send(screenshot); | 
					
						
						|  |  | 
					
						
						|  | } catch (error) { | 
					
						
						|  | console.error(`Backend ${format} generation failed:`, error); | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  | const fallbackImage = screenshotService.generateFallbackImage( | 
					
						
						|  | pptData.viewportSize || 1000, | 
					
						
						|  | Math.ceil((pptData.viewportSize || 1000) * (pptData.viewportRatio || 0.5625)), | 
					
						
						|  | `PPT ${format.toUpperCase()} Image`, | 
					
						
						|  | 'Image generation failed' | 
					
						
						|  | ); | 
					
						
						|  |  | 
					
						
						|  | res.setHeader('Content-Type', 'image/svg+xml; charset=utf-8'); | 
					
						
						|  | res.setHeader('X-Screenshot-Type', 'fallback-generated'); | 
					
						
						|  | res.setHeader('X-Generation-Time', '< 50ms'); | 
					
						
						|  | res.send(fallbackImage); | 
					
						
						|  | } | 
					
						
						|  | } | 
					
						
						|  |  | 
					
						
						|  | } catch (error) { | 
					
						
						|  | console.error('❌ Image generation failed:', error); | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  | if (req.query.format === 'svg') { | 
					
						
						|  | res.setHeader('Content-Type', 'image/svg+xml; charset=utf-8'); | 
					
						
						|  | res.setHeader('Access-Control-Allow-Origin', '*'); | 
					
						
						|  |  | 
					
						
						|  | const errorSvg = `<svg width="800" height="450" xmlns="http://www.w3.org/2000/svg"> | 
					
						
						|  | <rect width="100%" height="100%" fill="#ffe6e6"/> | 
					
						
						|  | <text x="400" y="200" text-anchor="middle" font-family="Arial, sans-serif" font-size="24" fill="#cc0000">Generation Error</text> | 
					
						
						|  | <text x="400" y="250" text-anchor="middle" font-family="Arial, sans-serif" font-size="16" fill="#cc0000">Failed to generate image</text> | 
					
						
						|  | <text x="400" y="300" text-anchor="middle" font-family="Arial, sans-serif" font-size="12" fill="#999">${new Date().toISOString()}</text> | 
					
						
						|  | </svg>`; | 
					
						
						|  |  | 
					
						
						|  | res.send(errorSvg); | 
					
						
						|  | } else { | 
					
						
						|  | const errorPage = generateErrorPage('Generation Error', 'Failed to generate the requested image format.'); | 
					
						
						|  | res.setHeader('Content-Type', 'text/html; charset=utf-8'); | 
					
						
						|  | res.status(500).send(errorPage); | 
					
						
						|  | } | 
					
						
						|  | } | 
					
						
						|  | }); | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  | router.get('/screenshot-data/:userId/:pptId/:slideIndex?', async (req, res, next) => { | 
					
						
						|  | try { | 
					
						
						|  | const { userId, pptId, slideIndex = 0 } = req.params; | 
					
						
						|  | const { format = 'jpeg', quality = 90 } = req.query; | 
					
						
						|  |  | 
					
						
						|  | console.log(`Screenshot data request: userId=${userId}, pptId=${pptId}, slideIndex=${slideIndex}`); | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  | const fileName = `${pptId}.json`; | 
					
						
						|  | let pptData = null; | 
					
						
						|  |  | 
					
						
						|  | for (let i = 0; i < githubService.repositories.length; i++) { | 
					
						
						|  | try { | 
					
						
						|  | const result = await githubService.getFile(userId, fileName, i); | 
					
						
						|  | if (result) { | 
					
						
						|  | pptData = result.content; | 
					
						
						|  | break; | 
					
						
						|  | } | 
					
						
						|  | } catch (error) { | 
					
						
						|  | continue; | 
					
						
						|  | } | 
					
						
						|  | } | 
					
						
						|  |  | 
					
						
						|  | if (!pptData) { | 
					
						
						|  | return res.status(404).json({ error: 'PPT not found' }); | 
					
						
						|  | } | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  | if (!pptData.slides || !Array.isArray(pptData.slides)) { | 
					
						
						|  | return res.status(400).json({ error: 'Invalid PPT data: slides not found' }); | 
					
						
						|  | } | 
					
						
						|  |  | 
					
						
						|  | const slideIdx = parseInt(slideIndex); | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  | if (!pptData.slides || !Array.isArray(pptData.slides)) { | 
					
						
						|  | console.log(`❌ Invalid PPT data: slides array not found`); | 
					
						
						|  |  | 
					
						
						|  | if (format === 'svg') { | 
					
						
						|  | const invalidDataSvg = `<svg width="800" height="450" xmlns="http://www.w3.org/2000/svg"> | 
					
						
						|  | <rect width="100%" height="100%" fill="#f8f9fa"/> | 
					
						
						|  | <text x="400" y="200" text-anchor="middle" font-family="Arial, sans-serif" font-size="24" fill="#6c757d">Invalid PPT Data</text> | 
					
						
						|  | <text x="400" y="250" text-anchor="middle" font-family="Arial, sans-serif" font-size="16" fill="#6c757d">Slides data not found</text> | 
					
						
						|  | <text x="400" y="300" text-anchor="middle" font-family="Arial, sans-serif" font-size="12" fill="#999">${new Date().toISOString()}</text> | 
					
						
						|  | </svg>`; | 
					
						
						|  |  | 
					
						
						|  | res.setHeader('Content-Type', 'image/svg+xml; charset=utf-8'); | 
					
						
						|  | return res.send(invalidDataSvg); | 
					
						
						|  | } else { | 
					
						
						|  | const errorPage = generateErrorPage('Invalid PPT Data', 'PPT slides data not found or corrupted.'); | 
					
						
						|  | res.setHeader('Content-Type', 'text/html; charset=utf-8'); | 
					
						
						|  | return res.status(400).send(errorPage); | 
					
						
						|  | } | 
					
						
						|  | } | 
					
						
						|  |  | 
					
						
						|  | if (slideIdx >= pptData.slides.length || slideIdx < 0) { | 
					
						
						|  | return res.status(404).json({ error: 'Invalid slide index' }); | 
					
						
						|  | } | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  | const responseData = { | 
					
						
						|  | pptData: { | 
					
						
						|  | id: pptData.id, | 
					
						
						|  | title: pptData.title, | 
					
						
						|  | theme: pptData.theme, | 
					
						
						|  | viewportSize: pptData.viewportSize, | 
					
						
						|  | viewportRatio: pptData.viewportRatio, | 
					
						
						|  | slide: pptData.slides[slideIdx] | 
					
						
						|  | }, | 
					
						
						|  | slideIndex: slideIdx, | 
					
						
						|  | totalSlides: pptData.slides.length, | 
					
						
						|  | exportConfig: { | 
					
						
						|  | format, | 
					
						
						|  | quality: parseInt(quality), | 
					
						
						|  | width: pptData.viewportSize || 1000, | 
					
						
						|  | height: Math.ceil((pptData.viewportSize || 1000) * (pptData.viewportRatio || 0.5625)) | 
					
						
						|  | }, | 
					
						
						|  | strategy: 'frontend-direct-rendering', | 
					
						
						|  | timestamp: new Date().toISOString() | 
					
						
						|  | }; | 
					
						
						|  |  | 
					
						
						|  | res.setHeader('X-Screenshot-Strategy', 'frontend-data-api'); | 
					
						
						|  | res.json(responseData); | 
					
						
						|  |  | 
					
						
						|  | } catch (error) { | 
					
						
						|  | next(error); | 
					
						
						|  | } | 
					
						
						|  | }); | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  | router.get('/screenshot-health', async (req, res, next) => { | 
					
						
						|  | try { | 
					
						
						|  | const status = { | 
					
						
						|  | status: 'healthy', | 
					
						
						|  | timestamp: new Date().toISOString(), | 
					
						
						|  | environment: { | 
					
						
						|  | nodeEnv: process.env.NODE_ENV, | 
					
						
						|  | isHuggingFace: process.env.SPACE_ID ? true : false, | 
					
						
						|  | platform: process.platform | 
					
						
						|  | }, | 
					
						
						|  | screenshotService: { | 
					
						
						|  | available: true, | 
					
						
						|  | strategy: 'frontend-first', | 
					
						
						|  | backendFallback: false, | 
					
						
						|  | screenshotSize: 0 | 
					
						
						|  | } | 
					
						
						|  | }; | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  | try { | 
					
						
						|  | const testImage = screenshotService.generateFallbackImage(100, 100); | 
					
						
						|  | status.screenshotService.screenshotSize = testImage.length; | 
					
						
						|  | status.screenshotService.fallbackAvailable = true; | 
					
						
						|  | } catch (error) { | 
					
						
						|  | status.screenshotService.fallbackAvailable = false; | 
					
						
						|  | status.screenshotService.fallbackError = error.message; | 
					
						
						|  | } | 
					
						
						|  |  | 
					
						
						|  | res.json(status); | 
					
						
						|  | } catch (error) { | 
					
						
						|  | res.status(500).json({ | 
					
						
						|  | status: 'error', | 
					
						
						|  | timestamp: new Date().toISOString(), | 
					
						
						|  | error: error.message | 
					
						
						|  | }); | 
					
						
						|  | } | 
					
						
						|  | }); | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  | router.get('/screenshot-tool/:userId/:pptId/:slideIndex?', async (req, res, next) => { | 
					
						
						|  | try { | 
					
						
						|  | const { userId, pptId, slideIndex = 0 } = req.params; | 
					
						
						|  | const { format = 'jpeg', quality = 90 } = req.query; | 
					
						
						|  |  | 
					
						
						|  | console.log(`Screenshot tool request: userId=${userId}, pptId=${pptId}, slideIndex=${slideIndex}`); | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  | const fileName = `${pptId}.json`; | 
					
						
						|  | let pptData = null; | 
					
						
						|  |  | 
					
						
						|  | for (let i = 0; i < githubService.repositories.length; i++) { | 
					
						
						|  | try { | 
					
						
						|  | const result = await githubService.getFile(userId, fileName, i); | 
					
						
						|  | if (result) { | 
					
						
						|  | pptData = result.content; | 
					
						
						|  | console.log(`✅ PPT data found in repository ${i}`); | 
					
						
						|  | break; | 
					
						
						|  | } | 
					
						
						|  | } catch (error) { | 
					
						
						|  | continue; | 
					
						
						|  | } | 
					
						
						|  | } | 
					
						
						|  |  | 
					
						
						|  | if (!pptData) { | 
					
						
						|  | const errorPage = generateErrorPage('PPT Not Found', `PPT ${pptId} not found for user ${userId}`); | 
					
						
						|  | res.setHeader('Content-Type', 'text/html; charset=utf-8'); | 
					
						
						|  | return res.status(404).send(errorPage); | 
					
						
						|  | } | 
					
						
						|  |  | 
					
						
						|  | const slideIdx = parseInt(slideIndex); | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  | if (!pptData.slides || !Array.isArray(pptData.slides)) { | 
					
						
						|  | console.log(`❌ Invalid PPT data: slides array not found`); | 
					
						
						|  |  | 
					
						
						|  | if (format === 'svg') { | 
					
						
						|  | const invalidDataSvg = `<svg width="800" height="450" xmlns="http://www.w3.org/2000/svg"> | 
					
						
						|  | <rect width="100%" height="100%" fill="#f8f9fa"/> | 
					
						
						|  | <text x="400" y="200" text-anchor="middle" font-family="Arial, sans-serif" font-size="24" fill="#6c757d">Invalid PPT Data</text> | 
					
						
						|  | <text x="400" y="250" text-anchor="middle" font-family="Arial, sans-serif" font-size="16" fill="#6c757d">Slides data not found</text> | 
					
						
						|  | <text x="400" y="300" text-anchor="middle" font-family="Arial, sans-serif" font-size="12" fill="#999">${new Date().toISOString()}</text> | 
					
						
						|  | </svg>`; | 
					
						
						|  |  | 
					
						
						|  | res.setHeader('Content-Type', 'image/svg+xml; charset=utf-8'); | 
					
						
						|  | return res.send(invalidDataSvg); | 
					
						
						|  | } else { | 
					
						
						|  | const errorPage = generateErrorPage('Invalid PPT Data', 'PPT slides data not found or corrupted.'); | 
					
						
						|  | res.setHeader('Content-Type', 'text/html; charset=utf-8'); | 
					
						
						|  | return res.status(400).send(errorPage); | 
					
						
						|  | } | 
					
						
						|  | } | 
					
						
						|  |  | 
					
						
						|  | if (slideIdx >= pptData.slides.length || slideIdx < 0) { | 
					
						
						|  | const errorPage = generateErrorPage('Invalid Slide', `Slide ${slideIndex} not found in PPT`); | 
					
						
						|  | res.setHeader('Content-Type', 'text/html; charset=utf-8'); | 
					
						
						|  | return res.status(404).send(errorPage); | 
					
						
						|  | } | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  | const htmlPage = generateExportPage(pptData, slideIdx, { | 
					
						
						|  | format, | 
					
						
						|  | quality: parseInt(quality), | 
					
						
						|  | autoDownload: false | 
					
						
						|  | }); | 
					
						
						|  |  | 
					
						
						|  | res.setHeader('Content-Type', 'text/html; charset=utf-8'); | 
					
						
						|  | res.setHeader('X-Screenshot-Strategy', 'frontend-screenshot-tool'); | 
					
						
						|  | res.setHeader('X-Generation-Time', '< 50ms'); | 
					
						
						|  | res.send(htmlPage); | 
					
						
						|  |  | 
					
						
						|  | } catch (error) { | 
					
						
						|  | next(error); | 
					
						
						|  | } | 
					
						
						|  | }); | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  | router.get('/export-json/:userId/:pptId', async (req, res, next) => { | 
					
						
						|  | try { | 
					
						
						|  | const { userId, pptId } = req.params; | 
					
						
						|  | const fileName = `${pptId}.json`; | 
					
						
						|  |  | 
					
						
						|  | let pptData = null; | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  | for (let i = 0; i < githubService.repositories.length; i++) { | 
					
						
						|  | try { | 
					
						
						|  | const result = await githubService.getFile(userId, fileName, i); | 
					
						
						|  | if (result) { | 
					
						
						|  | pptData = result.content; | 
					
						
						|  | break; | 
					
						
						|  | } | 
					
						
						|  | } catch (error) { | 
					
						
						|  | continue; | 
					
						
						|  | } | 
					
						
						|  | } | 
					
						
						|  |  | 
					
						
						|  | if (!pptData) { | 
					
						
						|  | return res.status(404).json({ error: 'PPT not found' }); | 
					
						
						|  | } | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  | const jsonData = exportPPTToJSON(pptData); | 
					
						
						|  |  | 
					
						
						|  | res.setHeader('Content-Type', 'application/json; charset=utf-8'); | 
					
						
						|  | res.setHeader('Content-Disposition', `attachment; filename="${pptData.title || 'presentation'}.json"`); | 
					
						
						|  | res.json(jsonData); | 
					
						
						|  | } catch (error) { | 
					
						
						|  | next(error); | 
					
						
						|  | } | 
					
						
						|  | }); | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  | router.get('/export-html/:userId/:pptId', async (req, res, next) => { | 
					
						
						|  | try { | 
					
						
						|  | const { userId, pptId } = req.params; | 
					
						
						|  | const { interactive = 'true', standalone = 'true', css = 'true' } = req.query; | 
					
						
						|  | const fileName = `${pptId}.json`; | 
					
						
						|  |  | 
					
						
						|  | let pptData = null; | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  | for (let i = 0; i < githubService.repositories.length; i++) { | 
					
						
						|  | try { | 
					
						
						|  | const result = await githubService.getFile(userId, fileName, i); | 
					
						
						|  | if (result) { | 
					
						
						|  | pptData = result.content; | 
					
						
						|  | break; | 
					
						
						|  | } | 
					
						
						|  | } catch (error) { | 
					
						
						|  | continue; | 
					
						
						|  | } | 
					
						
						|  | } | 
					
						
						|  |  | 
					
						
						|  | if (!pptData) { | 
					
						
						|  | return res.status(404).json({ error: 'PPT not found' }); | 
					
						
						|  | } | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  | const htmlContent = generateHTMLPresentation(pptData, { | 
					
						
						|  | includeInteractivity: interactive === 'true', | 
					
						
						|  | standalone: standalone === 'true', | 
					
						
						|  | includeCSS: css === 'true' | 
					
						
						|  | }); | 
					
						
						|  |  | 
					
						
						|  | res.setHeader('Content-Type', 'text/html; charset=utf-8'); | 
					
						
						|  | res.setHeader('Content-Disposition', `attachment; filename="${pptData.title || 'presentation'}.html"`); | 
					
						
						|  | res.send(htmlContent); | 
					
						
						|  | } catch (error) { | 
					
						
						|  | next(error); | 
					
						
						|  | } | 
					
						
						|  | }); | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  | router.get('/presentation/:userId/:pptId', async (req, res, next) => { | 
					
						
						|  | try { | 
					
						
						|  | const { userId, pptId } = req.params; | 
					
						
						|  | const fileName = `${pptId}.json`; | 
					
						
						|  |  | 
					
						
						|  | let pptData = null; | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  | for (let i = 0; i < githubService.repositories.length; i++) { | 
					
						
						|  | try { | 
					
						
						|  | const result = await githubService.getFile(userId, fileName, i); | 
					
						
						|  | if (result) { | 
					
						
						|  | pptData = result.content; | 
					
						
						|  | break; | 
					
						
						|  | } | 
					
						
						|  | } catch (error) { | 
					
						
						|  | continue; | 
					
						
						|  | } | 
					
						
						|  | } | 
					
						
						|  |  | 
					
						
						|  | if (!pptData) { | 
					
						
						|  | return res.status(404).send(generateErrorPage('PPT Not Found', 'The presentation you are looking for does not exist.')); | 
					
						
						|  | } | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  | const htmlContent = generateHTMLPresentation(pptData, { | 
					
						
						|  | includeInteractivity: true, | 
					
						
						|  | standalone: true, | 
					
						
						|  | includeCSS: true | 
					
						
						|  | }); | 
					
						
						|  |  | 
					
						
						|  | res.setHeader('Content-Type', 'text/html; charset=utf-8'); | 
					
						
						|  | res.send(htmlContent); | 
					
						
						|  | } catch (error) { | 
					
						
						|  | next(error); | 
					
						
						|  | } | 
					
						
						|  | }); | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  | router.get('/direct-image/:userId/:pptId/:slideIndex?', async (req, res, next) => { | 
					
						
						|  | try { | 
					
						
						|  | const { userId, pptId, slideIndex = 0 } = req.params; | 
					
						
						|  | const { format = 'svg', quality = 90, width: requestWidth, height: requestHeight } = req.query; | 
					
						
						|  |  | 
					
						
						|  | console.log(`🖼️ Direct image request: userId=${userId}, pptId=${pptId}, slideIndex=${slideIndex}, format=${format}`); | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  | const fileName = `${pptId}.json`; | 
					
						
						|  | let pptData = null; | 
					
						
						|  |  | 
					
						
						|  | for (let i = 0; i < githubService.repositories.length; i++) { | 
					
						
						|  | try { | 
					
						
						|  | const result = await githubService.getFile(userId, fileName, i); | 
					
						
						|  | if (result) { | 
					
						
						|  | pptData = result.content; | 
					
						
						|  | console.log(`✅ PPT data found in repository ${i}`); | 
					
						
						|  | break; | 
					
						
						|  | } | 
					
						
						|  | } catch (error) { | 
					
						
						|  | continue; | 
					
						
						|  | } | 
					
						
						|  | } | 
					
						
						|  |  | 
					
						
						|  | if (!pptData) { | 
					
						
						|  | console.log(`❌ PPT not found: ${pptId}`); | 
					
						
						|  |  | 
					
						
						|  | const notFoundSvg = `<svg width="800" height="450" xmlns="http://www.w3.org/2000/svg"> | 
					
						
						|  | <rect width="100%" height="100%" fill="#f8f9fa"/> | 
					
						
						|  | <text x="400" y="200" text-anchor="middle" font-family="Arial, sans-serif" font-size="24" fill="#6c757d">PPT Not Found</text> | 
					
						
						|  | <text x="400" y="250" text-anchor="middle" font-family="Arial, sans-serif" font-size="16" fill="#6c757d">PPT ${pptId} does not exist</text> | 
					
						
						|  | </svg>`; | 
					
						
						|  |  | 
					
						
						|  | res.setHeader('Content-Type', 'image/svg+xml; charset=utf-8'); | 
					
						
						|  | res.setHeader('Cache-Control', 'no-cache'); | 
					
						
						|  | res.setHeader('Access-Control-Allow-Origin', '*'); | 
					
						
						|  | return res.send(notFoundSvg); | 
					
						
						|  | } | 
					
						
						|  |  | 
					
						
						|  | const slideIdx = parseInt(slideIndex); | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  | if (!pptData.slides || !Array.isArray(pptData.slides)) { | 
					
						
						|  | console.log(`❌ Invalid PPT data: slides array not found`); | 
					
						
						|  |  | 
					
						
						|  | if (format === 'svg') { | 
					
						
						|  | const invalidDataSvg = `<svg width="800" height="450" xmlns="http://www.w3.org/2000/svg"> | 
					
						
						|  | <rect width="100%" height="100%" fill="#f8f9fa"/> | 
					
						
						|  | <text x="400" y="200" text-anchor="middle" font-family="Arial, sans-serif" font-size="24" fill="#6c757d">Invalid PPT Data</text> | 
					
						
						|  | <text x="400" y="250" text-anchor="middle" font-family="Arial, sans-serif" font-size="16" fill="#6c757d">Slides data not found</text> | 
					
						
						|  | <text x="400" y="300" text-anchor="middle" font-family="Arial, sans-serif" font-size="12" fill="#999">${new Date().toISOString()}</text> | 
					
						
						|  | </svg>`; | 
					
						
						|  |  | 
					
						
						|  | res.setHeader('Content-Type', 'image/svg+xml; charset=utf-8'); | 
					
						
						|  | return res.send(invalidDataSvg); | 
					
						
						|  | } else { | 
					
						
						|  | const errorPage = generateErrorPage('Invalid PPT Data', 'PPT slides data not found or corrupted.'); | 
					
						
						|  | res.setHeader('Content-Type', 'text/html; charset=utf-8'); | 
					
						
						|  | return res.status(400).send(errorPage); | 
					
						
						|  | } | 
					
						
						|  | } | 
					
						
						|  |  | 
					
						
						|  | if (slideIdx >= pptData.slides.length || slideIdx < 0) { | 
					
						
						|  | console.log(`❌ Invalid slide index: ${slideIndex}`); | 
					
						
						|  |  | 
					
						
						|  | const invalidSlideSvg = `<svg width="800" height="450" xmlns="http://www.w3.org/2000/svg"> | 
					
						
						|  | <rect width="100%" height="100%" fill="#f8f9fa"/> | 
					
						
						|  | <text x="400" y="200" text-anchor="middle" font-family="Arial, sans-serif" font-size="24" fill="#6c757d">Invalid Slide</text> | 
					
						
						|  | <text x="400" y="250" text-anchor="middle" font-family="Arial, sans-serif" font-size="16" fill="#6c757d">Slide ${slideIndex} not found</text> | 
					
						
						|  | </svg>`; | 
					
						
						|  |  | 
					
						
						|  | res.setHeader('Content-Type', 'image/svg+xml; charset=utf-8'); | 
					
						
						|  | res.setHeader('Cache-Control', 'no-cache'); | 
					
						
						|  | res.setHeader('Access-Control-Allow-Origin', '*'); | 
					
						
						|  | return res.send(invalidSlideSvg); | 
					
						
						|  | } | 
					
						
						|  |  | 
					
						
						|  | console.log(`✅ Generating SVG for slide ${slideIdx}`); | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  | const defaultWidth = pptData.viewportSize || 1000; | 
					
						
						|  | const defaultHeight = Math.ceil(defaultWidth * (pptData.viewportRatio || 0.5625)); | 
					
						
						|  | const finalWidth = requestWidth ? parseInt(requestWidth) : defaultWidth; | 
					
						
						|  | const finalHeight = requestHeight ? parseInt(requestHeight) : defaultHeight; | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  | const slide = pptData.slides[slideIdx]; | 
					
						
						|  | const svgContent = generateSlideSVG(slide, pptData, { | 
					
						
						|  | width: finalWidth, | 
					
						
						|  | height: finalHeight | 
					
						
						|  | }); | 
					
						
						|  |  | 
					
						
						|  | console.log(`✅ SVG generated successfully, size: ${svgContent.length} characters`); | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  | res.setHeader('Content-Type', 'image/svg+xml; charset=utf-8'); | 
					
						
						|  | res.setHeader('Cache-Control', 'public, max-age=3600'); | 
					
						
						|  | res.setHeader('Access-Control-Allow-Origin', '*'); | 
					
						
						|  | res.setHeader('X-Generation-Time', '< 10ms'); | 
					
						
						|  | res.setHeader('X-Content-Source', 'direct-svg-generation'); | 
					
						
						|  | res.setHeader('Content-Disposition', 'inline'); | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  | res.send(svgContent); | 
					
						
						|  |  | 
					
						
						|  | } catch (error) { | 
					
						
						|  | console.error('❌ Direct image generation failed:', error); | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  | const errorSvg = `<svg width="800" height="450" xmlns="http://www.w3.org/2000/svg"> | 
					
						
						|  | <rect width="100%" height="100%" fill="#ffe6e6"/> | 
					
						
						|  | <text x="400" y="200" text-anchor="middle" font-family="Arial, sans-serif" font-size="24" fill="#cc0000">Generation Error</text> | 
					
						
						|  | <text x="400" y="250" text-anchor="middle" font-family="Arial, sans-serif" font-size="16" fill="#cc0000">Failed to generate image: ${error.message}</text> | 
					
						
						|  | </svg>`; | 
					
						
						|  |  | 
					
						
						|  | res.setHeader('Content-Type', 'image/svg+xml; charset=utf-8'); | 
					
						
						|  | res.setHeader('Cache-Control', 'no-cache'); | 
					
						
						|  | res.setHeader('Access-Control-Allow-Origin', '*'); | 
					
						
						|  | res.send(errorSvg); | 
					
						
						|  | } | 
					
						
						|  | }); | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  | const imageCache = new Map(); | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  | router.post('/save-image/:userId/:pptId/:slideIndex', express.json({ limit: '10mb' }), async (req, res, next) => { | 
					
						
						|  | try { | 
					
						
						|  | const { userId, pptId, slideIndex } = req.params; | 
					
						
						|  | const { imageData, format = 'jpeg', quality = 90 } = req.body; | 
					
						
						|  |  | 
					
						
						|  | if (!imageData) { | 
					
						
						|  | return res.status(400).json({ error: 'Image data is required' }); | 
					
						
						|  | } | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  | const imageId = `${userId}-${pptId}-${slideIndex}-${Date.now()}`; | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  | imageCache.set(imageId, { | 
					
						
						|  | data: imageData, | 
					
						
						|  | format, | 
					
						
						|  | quality, | 
					
						
						|  | userId, | 
					
						
						|  | pptId, | 
					
						
						|  | slideIndex, | 
					
						
						|  | timestamp: new Date().toISOString(), | 
					
						
						|  | contentType: `image/${format === 'jpg' ? 'jpeg' : format}` | 
					
						
						|  | }); | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  | setTimeout(() => { | 
					
						
						|  | imageCache.delete(imageId); | 
					
						
						|  | }, 3600000); | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  | const baseUrl = req.protocol + '://' + req.get('host'); | 
					
						
						|  | const imageUrl = `${baseUrl}/api/public/cached-image/${imageId}`; | 
					
						
						|  |  | 
					
						
						|  | console.log(`✅ Image saved with ID: ${imageId}`); | 
					
						
						|  |  | 
					
						
						|  | res.json({ | 
					
						
						|  | success: true, | 
					
						
						|  | imageId, | 
					
						
						|  | imageUrl, | 
					
						
						|  | expiresAt: new Date(Date.now() + 3600000).toISOString() | 
					
						
						|  | }); | 
					
						
						|  |  | 
					
						
						|  | } catch (error) { | 
					
						
						|  | console.error('❌ Failed to save image:', error); | 
					
						
						|  | next(error); | 
					
						
						|  | } | 
					
						
						|  | }); | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  | router.get('/cached-image/:imageId', async (req, res, next) => { | 
					
						
						|  | try { | 
					
						
						|  | const { imageId } = req.params; | 
					
						
						|  |  | 
					
						
						|  | const cachedImage = imageCache.get(imageId); | 
					
						
						|  | if (!cachedImage) { | 
					
						
						|  | return res.status(404).json({ error: 'Image not found or expired' }); | 
					
						
						|  | } | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  | res.setHeader('Content-Type', cachedImage.contentType); | 
					
						
						|  | res.setHeader('Cache-Control', 'public, max-age=3600'); | 
					
						
						|  | res.setHeader('Access-Control-Allow-Origin', '*'); | 
					
						
						|  | res.setHeader('X-Image-Source', 'frontend-generated'); | 
					
						
						|  | res.setHeader('X-Generation-Time', cachedImage.timestamp); | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  | if (cachedImage.data.startsWith('data:')) { | 
					
						
						|  | const base64Data = cachedImage.data.split(',')[1]; | 
					
						
						|  | const buffer = Buffer.from(base64Data, 'base64'); | 
					
						
						|  | res.send(buffer); | 
					
						
						|  | } else { | 
					
						
						|  | res.send(cachedImage.data); | 
					
						
						|  | } | 
					
						
						|  |  | 
					
						
						|  | } catch (error) { | 
					
						
						|  | console.error('❌ Failed to serve cached image:', error); | 
					
						
						|  | next(error); | 
					
						
						|  | } | 
					
						
						|  | }); | 
					
						
						|  |  | 
					
						
						|  | export default router; |