import express from 'express'; // import screenshotService from '../services/screenshotService.js'; // 已禁用 import githubService from '../services/githubService.js'; import { generateSlideHTML } from '../utils/htmlGenerator.js'; const router = express.Router(); // SVG功能已被完全删除 // Generate error page for frontend display function generateErrorPage(title, message) { return ` ${title} - PPT导出
${title}
${message}
回到首页
`; } // Publicly access PPT page - return HTML page 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); let pptData = null; try { // 尝试从Huggingface存储读取 const { default: huggingfaceStorageService } = await import('../services/huggingfaceStorageService.js'); pptData = await huggingfaceStorageService.getPPTData(userId, pptId); console.log('✅ PPT data loaded from Huggingface storage for view'); } catch (hfError) { console.warn('⚠️ Failed to load from Huggingface storage, trying GitHub:', hfError.message); // 回退到GitHub存储 const fileName = `${pptId}.json`; // Check if GitHub repositories are configured if (!githubService.repositories || githubService.repositories.length === 0) { console.warn('⚠️ GitHub repositories not configured, cannot fallback to GitHub storage'); } else { // Try all GitHub repositories 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 loaded from GitHub repository ${i} for view`); 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) { console.log(`❌ Invalid slide index: ${slideIndex} (parsed: ${slideIdx}), PPT has ${pptData.slides.length} slides (valid range: 0-${pptData.slides.length - 1})`); return res.status(404).send(generateErrorPage('Slide Not Found', `Slide ${slideIndex} not found. PPT has ${pptData.slides.length} slides (valid range: 0-${pptData.slides.length - 1}).`)); } // 使用共享模块生成HTML const htmlContent = generateSlideHTML(pptData, slideIdx, { format: 'view' }); res.setHeader('Content-Type', 'text/html; charset=utf-8'); res.send(htmlContent); } catch (error) { next(error); } }); // API endpoint: Get PPT data and specified slide (JSON format) router.get('/api-view/:userId/:pptId/:slideIndex?', async (req, res, next) => { try { const { userId, pptId, slideIndex = 0 } = req.params; let pptData = null; try { // 尝试从Huggingface存储读取 const { default: huggingfaceStorageService } = await import('../services/huggingfaceStorageService.js'); pptData = await huggingfaceStorageService.getPPTData(userId, pptId); console.log('✅ PPT data loaded from Huggingface storage for api-view'); } catch (hfError) { console.warn('⚠️ Failed to load from Huggingface storage, trying GitHub:', hfError.message); // 回退到GitHub存储 const fileName = `${pptId}.json`; // Check if GitHub repositories are configured if (!githubService.repositories || githubService.repositories.length === 0) { console.warn('⚠️ GitHub repositories not configured, cannot fallback to GitHub storage'); } else { // Try all GitHub repositories 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 loaded from GitHub repository ${i} for api-view`); break; } } catch (error) { continue; } } } } if (!pptData) { return res.status(404).json({ error: 'PPT not found' }); } const slideIdx = parseInt(slideIndex); // 验证 slideIndex 是否为有效数字 if (isNaN(slideIdx)) { console.log(`❌ Invalid slide index format: ${slideIndex}`); const errorPage = generateErrorPage('Invalid Slide Index', `Slide index "${slideIndex}" is not a valid number.`); res.setHeader('Content-Type', 'text/html; charset=utf-8'); return res.status(400).send(errorPage); } // 安全检查:确保 slides 数组存在 if (!pptData.slides || !Array.isArray(pptData.slides)) { console.log(`❌ Invalid PPT data: slides array not found`); 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} (parsed: ${slideIdx}), PPT has ${pptData.slides.length} slides (valid range: 0-${pptData.slides.length - 1})`); return res.status(404).json({ error: 'Slide not found', details: `Slide ${slideIndex} not found. PPT has ${pptData.slides.length} slides (valid range: 0-${pptData.slides.length - 1}).`, slideIndex: slideIdx, totalSlides: pptData.slides.length, validRange: `0-${pptData.slides.length - 1}` }); } // Return PPT data and specified slide 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); } }); // Get complete PPT data (read-only mode) router.get('/ppt/:userId/:pptId', async (req, res, next) => { try { const { userId, pptId } = req.params; let pptData = null; try { // 尝试从Huggingface存储读取 const { default: huggingfaceStorageService } = await import('../services/huggingfaceStorageService.js'); pptData = await huggingfaceStorageService.getPPTData(userId, pptId); console.log('✅ PPT data loaded from Huggingface storage for ppt endpoint'); } catch (hfError) { console.warn('⚠️ Failed to load from Huggingface storage, trying GitHub:', hfError.message); // 回退到GitHub存储 const fileName = `${pptId}.json`; // Check if GitHub repositories are configured if (!githubService.repositories || githubService.repositories.length === 0) { console.warn('⚠️ GitHub repositories not configured, cannot fallback to GitHub storage'); } else { // Try all GitHub repositories 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 loaded from GitHub repository ${i} for ppt endpoint`); break; } } catch (error) { continue; } } } } if (!pptData) { return res.status(404).json({ error: 'PPT not found' }); } // Return read-only version of PPT data res.json({ ...pptData, isPublicView: true, readOnly: true }); } catch (error) { next(error); } }); // Generate PPT share link 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}`); // Verify if PPT exists - 优先从Huggingface存储读取 let pptData = null; try { // 尝试从Huggingface存储读取 const { default: huggingfaceStorageService } = await import('../services/huggingfaceStorageService.js'); pptData = await huggingfaceStorageService.getPPTData(userId, pptId); console.log('✅ PPT data loaded from Huggingface storage for share link generation'); } catch (hfError) { console.warn('⚠️ Failed to load from Huggingface storage, trying GitHub:', hfError.message); // 回退到GitHub存储 const fileName = `${pptId}.json`; // Check if GitHub repositories are configured if (!githubService.repositories || githubService.repositories.length === 0) { console.warn('⚠️ GitHub repositories not configured, cannot fallback to GitHub storage'); } else { 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) { pptData = result.content; console.log(`✅ PPT data loaded from GitHub repository ${i}`); break; } } catch (error) { console.log(`PPT not found in repository ${i}: ${error.message}`); continue; } } } } if (!pptData) { return res.status(404).json({ error: 'PPT not found', details: `PPT ${pptId} not found for user ${userId}`, searchedLocations: 'Huggingface storage and GitHub repositories' }); } const baseUrl = process.env.PUBLIC_URL || req.get('host'); // 修复协议判断:localhost 始终使用 http,生产环境使用 https const protocol = baseUrl.includes('localhost') ? 'http' : 'https'; const shareLinks = { // Single page share link slideUrl: `${protocol}://${baseUrl}/api/public/view/${userId}/${pptId}/${slideIndex}`, // Complete PPT share link pptUrl: `${protocol}://${baseUrl}/api/public/ppt/${userId}/${pptId}`, // Frontend view link viewUrl: `${protocol}://${baseUrl}/public/${userId}/${pptId}/${slideIndex}`, // 图片链接 - 支持内存图片访问 screenshotUrl: `${protocol}://${baseUrl}/api/public/image/${userId}/${pptId}/${slideIndex}`, directImageUrl: `${protocol}://${baseUrl}/api/public/direct-image/${userId}/${pptId}/${slideIndex}`, metadata: { title: pptData.title || 'Untitled PPT', slideCount: pptData.slides?.length || 0, currentSlide: slideIndex, generatedAt: new Date().toISOString(), note: 'Image links support memory-based image access with fallback to error SVG' } }; console.log('✅ Share links generated successfully'); res.json(shareLinks); } catch (error) { console.error('❌ Error generating share link:', error); next(error); } }); // 公开图片访问路由 - 处理 /image/:userId/:linkId/:pageIndex 格式 router.get('/image/:userId/:linkId/:pageIndex', async (req, res, next) => { try { const { userId, linkId, pageIndex } = req.params; const pageIdx = parseInt(pageIndex); console.log(`📷 Public image request: userId=${userId}, linkId=${linkId}, pageIndex=${pageIndex}`); // 首先尝试从内存存储中获取图片 try { const { default: huggingfaceStorageService } = await import('../services/huggingfaceStorageService.js'); // 检查内存中是否有该图片 if (huggingfaceStorageService.hasPublicImage(userId, linkId, pageIdx)) { console.log(`✅ Found image in memory storage: ${userId}/${linkId}/${pageIdx}`); const imageResult = await huggingfaceStorageService.getPublicImage(userId, linkId, pageIdx); // 设置响应头 const format = imageResult.metadata.format || 'png'; res.setHeader('Content-Type', `image/${format}`); res.setHeader('Cache-Control', 'public, max-age=3600'); // 缓存1小时 res.setHeader('Content-Length', imageResult.data.length); // 返回图片数据 return res.send(imageResult.data); } } catch (memoryError) { console.log(`⚠️ Memory storage access failed: ${memoryError.message}`); } // 检查linkId是否为UUID格式(持久化链接) const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i; if (uuidRegex.test(linkId)) { // 检查这是否真的是一个持久化链接 try { const { default: persistentImageLinkService } = await import('../services/persistentImageLinkService.js'); // 确保服务已初始化 if (!persistentImageLinkService.initialized) { await persistentImageLinkService.initialize(); } // 检查持久化链接是否存在 const linkExists = persistentImageLinkService.persistentLinks.has(linkId); if (linkExists) { // 这是真正的持久化图片链接,重定向到正确的持久化图片API console.log(`🔗 Redirecting persistent image request: ${linkId}`); return res.redirect(`/api/persistent-images/${linkId}`); } else { // UUID格式但不是持久化链接,当作PPT ID处理 console.log(`⚠️ UUID format but not a persistent link, treating as PPT ID: ${linkId}`); } } catch (error) { console.log(`⚠️ Error checking persistent link, treating as PPT ID: ${error.message}`); } } // 内存中没有找到图片,且不是持久化链接,生成错误SVG图片 console.log(`⚠️ Image not found in memory storage, generating error SVG for pptId: ${linkId}`); // 生成错误提示SVG const errorSvg = ` 图片未找到 PPT ID: ${linkId} 页面: ${pageIndex} 请先生成图片或使用HTML预览 `; res.setHeader('Content-Type', 'image/svg+xml'); res.setHeader('Cache-Control', 'no-cache'); return res.send(errorSvg); } catch (error) { next(error); } }); // 传统图片路由(3个参数)- 已禁用 router.get('/image/:userId/:pptId', async (req, res, next) => { try { console.log('❌ Image generation feature has been disabled'); // 对于图片请求,返回适当的错误响应而不是HTML页面 res.status(410).json({ error: 'Feature Disabled', message: 'Image generation functionality has been disabled. Please use the HTML view instead.', note: 'This endpoint no longer generates images. Use /api/public/view/:userId/:pptId/:slideIndex for HTML view.' }); } catch (error) { next(error); } }); // 公开访问:获取用户的PPT列表(仅包含内存中有图片的PPT) router.get('/ppt-list/:userId', async (req, res, next) => { try { const { userId } = req.params; console.log(`📋 Public PPT list request for user: ${userId}`); try { const { default: huggingfaceStorageService } = await import('../services/huggingfaceStorageService.js'); // 获取用户的PPT列表(仅包含内存中有图片的PPT) const pptList = huggingfaceStorageService.getPublicPPTList(userId); console.log(`✅ Found ${pptList.length} PPTs with images for user ${userId}`); res.json({ success: true, userId, pptCount: pptList.length, ppts: pptList, note: 'Only PPTs with images stored in memory are listed' }); } catch (error) { console.error(`❌ Failed to get PPT list for user ${userId}:`, error); res.status(500).json({ success: false, error: 'Failed to retrieve PPT list', message: error.message }); } } catch (error) { next(error); } }); // 直接持久化图片链接路由 - 处理 /direct-image/:userId/:linkId/:pageIndex 格式 router.get('/direct-image/:userId/:linkId/:pageIndex', async (req, res, next) => { try { const { userId, linkId, pageIndex } = req.params; const pageIdx = parseInt(pageIndex); // 首先尝试从内存存储中获取图片 try { const { default: huggingfaceStorageService } = await import('../services/huggingfaceStorageService.js'); // 检查内存中是否有该图片 if (huggingfaceStorageService.hasPublicImage(userId, linkId, pageIdx)) { console.log(`✅ Found direct image in memory storage: ${userId}/${linkId}/${pageIdx}`); const imageResult = await huggingfaceStorageService.getPublicImage(userId, linkId, pageIdx); // 设置响应头 const format = imageResult.metadata.format || 'png'; res.setHeader('Content-Type', `image/${format}`); res.setHeader('Cache-Control', 'public, max-age=3600'); // 缓存1小时 res.setHeader('Content-Length', imageResult.data.length); // 返回图片数据 return res.send(imageResult.data); } } catch (memoryError) { console.log(`⚠️ Memory storage access failed: ${memoryError.message}`); } // 检查linkId是否为UUID格式(持久化链接) const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i; if (uuidRegex.test(linkId)) { // 这是持久化图片链接,重定向到正确的持久化图片API console.log(`🔗 Redirecting direct persistent image request: ${linkId}`); return res.redirect(`/api/persistent-images/${linkId}`); } else { // 内存中没有找到图片,且不是持久化链接 console.log('❌ Direct image not found in memory storage'); // 对于图片请求,返回适当的错误响应而不是HTML页面 res.status(404).json({ error: 'Image Not Found', message: 'Image not found in memory storage. Please generate the image first or use the HTML view instead.', note: 'Use /api/public/view/:userId/:pptId/:slideIndex for HTML view.' }); } } catch (error) { next(error); } }); // 传统直接图片路由(3个参数)- 已禁用 router.get('/direct-image/:userId/:pptId', async (req, res, next) => { try { console.log('❌ Direct image generation feature has been disabled'); // 对于图片请求,返回适当的错误响应而不是HTML页面 res.status(410).json({ error: 'Feature Disabled', message: 'Direct image generation functionality has been disabled. Please use the HTML view instead.', note: 'This endpoint no longer generates images. Use /api/public/view/:userId/:pptId/:slideIndex for HTML view.' }); } catch (error) { next(error); } }); export default router;