Spaces:
Paused
Paused
| import express from 'express'; | |
| import { v4 as uuidv4 } from 'uuid'; | |
| import githubService from '../services/githubService.js'; | |
| const router = express.Router(); | |
| // 添加路由级别的日志中间件 | |
| router.use((req, res, next) => { | |
| console.log(`PPT Router - ${req.method} ${req.path} - Body:`, Object.keys(req.body || {})); | |
| next(); | |
| }); | |
| // 获取用户的PPT列表 - 优先从Huggingface硬盘读取 | |
| router.get('/list', async (req, res, next) => { | |
| try { | |
| const userId = req.user.userId; | |
| // 优先从Huggingface存储服务获取PPT列表 | |
| const { default: huggingfaceStorageService } = await import('../services/huggingfaceStorageService.js'); | |
| let pptList = []; | |
| try { | |
| // 尝试从Huggingface硬盘读取PPT列表 | |
| pptList = await huggingfaceStorageService.getUserPPTList(userId); | |
| console.log(`📁 Found ${pptList.length} PPTs from Huggingface storage for user ${userId}`); | |
| } catch (hfError) { | |
| console.warn('⚠️ Huggingface storage unavailable, falling back to GitHub:', hfError.message); | |
| // 回退到GitHub存储 | |
| pptList = await githubService.getUserPPTList(userId); | |
| } | |
| res.json(pptList); | |
| } catch (error) { | |
| next(error); | |
| } | |
| }); | |
| // 获取指定PPT数据 - 优先从Huggingface硬盘读取 | |
| router.get('/:pptId', async (req, res, next) => { | |
| try { | |
| const userId = req.user.userId; | |
| const { pptId } = req.params; | |
| console.log(`🔍 Fetching PPT: ${pptId} for user: ${userId}`); | |
| let pptData = null; | |
| let foundInRepo = -1; | |
| // 优先从Huggingface存储服务获取PPT数据 | |
| const { default: huggingfaceStorageService } = await import('../services/huggingfaceStorageService.js'); | |
| try { | |
| // 尝试从Huggingface硬盘读取PPT数据 | |
| pptData = await huggingfaceStorageService.getPPTData(userId, pptId); | |
| foundInRepo = 0; | |
| console.log(`✅ PPT found in Huggingface storage`); | |
| } catch (hfError) { | |
| console.warn('⚠️ PPT not found in Huggingface storage, trying other sources:', hfError.message); | |
| if (githubService.useMemoryStorage) { | |
| // 内存存储模式 | |
| const result = await githubService.getPPTFromMemory(userId, pptId); | |
| if (result && result.content) { | |
| pptData = result.content; | |
| foundInRepo = 0; | |
| console.log(`✅ PPT found in memory storage`); | |
| } | |
| } else { | |
| // GitHub存储模式 - 尝试所有仓库 | |
| console.log(`Available repositories: ${githubService.repositories.length}`); | |
| for (let i = 0; i < githubService.repositories.length; i++) { | |
| try { | |
| console.log(`📂 Checking repository ${i}: ${githubService.repositories[i]}`); | |
| const result = await githubService.getPPT(userId, pptId, i); | |
| if (result && result.content) { | |
| pptData = result.content; | |
| foundInRepo = i; | |
| console.log(`✅ PPT found in repository ${i}${result.isReassembled ? ' (reassembled from chunks)' : ''}`); | |
| break; | |
| } | |
| } catch (error) { | |
| console.log(`❌ PPT not found in repository ${i}: ${error.message}`); | |
| continue; | |
| } | |
| } | |
| } | |
| } | |
| if (!pptData) { | |
| console.log(`❌ PPT ${pptId} not found for user ${userId}`); | |
| return res.status(404).json({ | |
| error: 'PPT not found', | |
| pptId: pptId, | |
| userId: userId, | |
| storageMode: githubService.useMemoryStorage ? 'memory' : 'github' | |
| }); | |
| } | |
| // 🔧 修复:标准化PPT数据格式,确保前端兼容性 | |
| const standardizedPptData = { | |
| // 确保基本字段存在 | |
| id: pptData.id || pptData.pptId || pptId, | |
| pptId: pptData.pptId || pptData.id || pptId, | |
| title: pptData.title || '未命名演示文稿', | |
| // 标准化slides数组 | |
| slides: Array.isArray(pptData.slides) ? pptData.slides.map((slide, index) => ({ | |
| id: slide.id || `slide-${index}`, | |
| elements: Array.isArray(slide.elements) ? slide.elements : [], | |
| background: slide.background || { type: 'solid', color: '#ffffff' }, | |
| ...slide | |
| })) : [], | |
| // 标准化主题 | |
| theme: pptData.theme || { | |
| backgroundColor: '#ffffff', | |
| themeColor: '#d14424', | |
| fontColor: '#333333', | |
| fontName: 'Microsoft YaHei' | |
| }, | |
| // 🔧 关键修复:确保视口信息正确传递 | |
| viewportSize: pptData.viewportSize || 1000, | |
| viewportRatio: pptData.viewportRatio || 0.5625, | |
| // 时间戳 | |
| createdAt: pptData.createdAt || new Date().toISOString(), | |
| updatedAt: pptData.updatedAt || new Date().toISOString(), | |
| // 保留其他可能的属性 | |
| ...pptData | |
| }; | |
| // 🔧 新增:数据验证和修复 | |
| if (standardizedPptData.slides.length === 0) { | |
| console.log(`⚠️ PPT ${pptId} has no slides, creating default slide`); | |
| standardizedPptData.slides = [{ | |
| id: 'default-slide', | |
| elements: [], | |
| background: { type: 'solid', color: '#ffffff' } | |
| }]; | |
| } | |
| // 验证视口比例的合理性 | |
| if (standardizedPptData.viewportRatio <= 0 || standardizedPptData.viewportRatio > 2) { | |
| console.log(`⚠️ Invalid viewportRatio ${standardizedPptData.viewportRatio}, resetting to 0.5625`); | |
| standardizedPptData.viewportRatio = 0.5625; | |
| } | |
| // 验证视口尺寸的合理性 | |
| if (standardizedPptData.viewportSize <= 0 || standardizedPptData.viewportSize > 2000) { | |
| console.log(`⚠️ Invalid viewportSize ${standardizedPptData.viewportSize}, resetting to 1000`); | |
| standardizedPptData.viewportSize = 1000; | |
| } | |
| console.log(`✅ Successfully found and standardized PPT ${pptId}:`, { | |
| slidesCount: standardizedPptData.slides.length, | |
| viewportSize: standardizedPptData.viewportSize, | |
| viewportRatio: standardizedPptData.viewportRatio, | |
| storageMode: githubService.useMemoryStorage ? 'memory' : `repository ${foundInRepo}` | |
| }); | |
| res.json(standardizedPptData); | |
| } catch (error) { | |
| console.error(`❌ Error fetching PPT ${req.params.pptId}:`, error); | |
| next(error); | |
| } | |
| }); | |
| // 保存PPT数据 - 新架构版本 | |
| router.post('/save', async (req, res, next) => { | |
| try { | |
| const userId = req.user.userId; | |
| const { pptId, title, slides, theme, viewportSize, viewportRatio } = req.body; | |
| console.log(`🔄 Starting PPT save for user ${userId}, PPT ID: ${pptId}`); | |
| console.log(`📊 Request body analysis:`, { | |
| pptId: !!pptId, | |
| title: title?.length || 0, | |
| slidesCount: Array.isArray(slides) ? slides.length : 'not array', | |
| theme: !!theme, | |
| requestSize: req.get('content-length'), | |
| storageMode: githubService.useMemoryStorage ? 'memory' : 'github' | |
| }); | |
| if (!pptId || !slides) { | |
| console.log(`❌ Missing required fields - pptId: ${!!pptId}, slides: ${!!slides}`); | |
| return res.status(400).json({ error: 'PPT ID and slides are required' }); | |
| } | |
| // 验证slides数组 | |
| if (!Array.isArray(slides) || slides.length === 0) { | |
| console.log(`❌ Invalid slides array - isArray: ${Array.isArray(slides)}, length: ${slides?.length || 0}`); | |
| return res.status(400).json({ error: 'Slides must be a non-empty array' }); | |
| } | |
| // 构建标准化的PPT数据结构 | |
| const pptData = { | |
| id: pptId, | |
| title: title || '未命名演示文稿', | |
| slides: slides.map((slide, index) => ({ | |
| id: slide.id || `slide-${index}`, | |
| elements: Array.isArray(slide.elements) ? slide.elements : [], | |
| background: slide.background || { type: 'solid', color: '#ffffff' }, | |
| ...slide | |
| })), | |
| theme: theme || { | |
| backgroundColor: '#ffffff', | |
| themeColor: '#d14424', | |
| fontColor: '#333333', | |
| fontName: 'Microsoft YaHei' | |
| }, | |
| viewportSize: viewportSize || 1000, | |
| viewportRatio: viewportRatio || 0.5625, | |
| createdAt: new Date().toISOString(), | |
| updatedAt: new Date().toISOString() | |
| }; | |
| // 计算文件大小分析 | |
| const jsonData = JSON.stringify(pptData); | |
| const totalDataSize = Buffer.byteLength(jsonData, 'utf8'); | |
| console.log(`📊 PPT data analysis:`); | |
| console.log(` - Total slides: ${slides.length}`); | |
| console.log(` - Total data size: ${totalDataSize} bytes (${(totalDataSize / 1024).toFixed(2)} KB)`); | |
| console.log(` - Average slide size: ${(totalDataSize / slides.length / 1024).toFixed(2)} KB`); | |
| // 分析每个slide的大小 | |
| const slideAnalysis = slides.map((slide, index) => { | |
| const slideJson = JSON.stringify(slide); | |
| const slideSize = Buffer.byteLength(slideJson, 'utf8'); | |
| return { | |
| index, | |
| size: slideSize, | |
| sizeKB: (slideSize / 1024).toFixed(2), | |
| elementsCount: slide.elements?.length || 0, | |
| hasLargeContent: slideSize > 800 * 1024 // 800KB+ | |
| }; | |
| }); | |
| const largeSlides = slideAnalysis.filter(s => s.hasLargeContent); | |
| const maxSlideSize = Math.max(...slideAnalysis.map(s => s.size)); | |
| console.log(`📋 Slide size analysis:`); | |
| console.log(` - Largest slide: ${(maxSlideSize / 1024).toFixed(2)} KB`); | |
| console.log(` - Large slides (>800KB): ${largeSlides.length}`); | |
| if (largeSlides.length > 0) { | |
| console.log(` - Large slide indices: ${largeSlides.map(s => s.index).join(', ')}`); | |
| } | |
| try { | |
| let result; | |
| // 优先保存到Huggingface存储服务 | |
| const { default: huggingfaceStorageService } = await import('../services/huggingfaceStorageService.js'); | |
| try { | |
| // 尝试保存到Huggingface硬盘 | |
| result = await huggingfaceStorageService.storePPTData(userId, pptId, pptData); | |
| console.log(`✅ PPT saved to Huggingface storage:`, result); | |
| } catch (hfError) { | |
| console.warn('⚠️ Failed to save to Huggingface storage, falling back to other storage:', hfError.message); | |
| console.log(`💾 Using fallback storage mode: ${githubService.useMemoryStorage ? 'memory' : 'GitHub folder architecture'}`); | |
| if (githubService.useMemoryStorage) { | |
| // 内存存储模式 | |
| result = await githubService.savePPTToMemory(userId, pptId, pptData); | |
| console.log(`✅ PPT saved to memory storage:`, result); | |
| } else { | |
| // GitHub存储模式 - 使用新的文件夹架构 | |
| console.log(`🐙 Using GitHub folder storage for ${pptId}`); | |
| console.log(`📂 Available repositories: ${githubService.repositories?.length || 0}`); | |
| if (!githubService.repositories || githubService.repositories.length === 0) { | |
| throw new Error('No GitHub repositories configured'); | |
| } | |
| console.log(`🔍 Pre-save validation:`); | |
| console.log(` - Repository 0: ${githubService.repositories[0]}`); | |
| console.log(` - Token configured: ${!!githubService.token}`); | |
| console.log(` - API URL: ${githubService.apiUrl}`); | |
| result = await githubService.savePPT(userId, pptId, pptData, 0); | |
| console.log(`✅ PPT saved to GitHub folder storage:`, result); | |
| } | |
| } | |
| console.log(`✅ PPT save completed successfully:`, { | |
| pptId, | |
| userId, | |
| slideCount: slides.length, | |
| totalDataSize, | |
| storageType: result.storage || 'unknown', | |
| compressionApplied: result.compressionSummary?.compressedSlides > 0, | |
| storageMode: githubService.useMemoryStorage ? 'memory' : 'github' | |
| }); | |
| const response = { | |
| message: 'PPT saved successfully', | |
| pptId, | |
| slidesCount: slides.length, | |
| savedAt: new Date().toISOString(), | |
| totalFileSize: `${(totalDataSize / 1024).toFixed(2)} KB`, | |
| storageType: result.storage || 'unknown', | |
| storageMode: githubService.useMemoryStorage ? 'memory' : 'github', | |
| architecture: 'folder-based', | |
| slideAnalysis: { | |
| totalSlides: slides.length, | |
| largestSlideSize: `${(maxSlideSize / 1024).toFixed(2)} KB`, | |
| largeSlides: largeSlides.length, | |
| averageSlideSize: `${(totalDataSize / slides.length / 1024).toFixed(2)} KB` | |
| } | |
| }; | |
| // 添加压缩信息 | |
| if (result.compressionSummary) { | |
| response.compression = { | |
| applied: result.compressionSummary.compressedSlides > 0, | |
| compressedSlides: result.compressionSummary.compressedSlides, | |
| totalSlides: result.compressionSummary.totalSlides, | |
| savedSlides: result.compressionSummary.savedSlides, | |
| failedSlides: result.compressionSummary.failedSlides, | |
| originalSize: `${(result.compressionSummary.totalOriginalSize / 1024).toFixed(2)} KB`, | |
| finalSize: `${(result.compressionSummary.totalFinalSize / 1024).toFixed(2)} KB`, | |
| savedSpace: `${((result.compressionSummary.totalOriginalSize - result.compressionSummary.totalFinalSize) / 1024).toFixed(2)} KB`, | |
| compressionRatio: `${(result.compressionSummary.compressionRatio * 100).toFixed(1)}%` | |
| }; | |
| if (result.compressionSummary.compressedSlides > 0) { | |
| response.message = `PPT saved successfully with ${result.compressionSummary.compressedSlides} slides optimized for storage`; | |
| response.optimization = `Automatic compression applied to ${result.compressionSummary.compressedSlides} large slides, saving ${response.compression.savedSpace}`; | |
| } | |
| // 添加保存警告信息 | |
| if (result.compressionSummary.failedSlides > 0) { | |
| response.warning = `${result.compressionSummary.failedSlides} slides failed to save`; | |
| response.partialSave = true; | |
| response.savedSlides = result.compressionSummary.savedSlides; | |
| response.failedSlides = result.compressionSummary.failedSlides; | |
| if (result.compressionSummary.errors) { | |
| response.slideErrors = result.compressionSummary.errors; | |
| } | |
| } | |
| } | |
| // 添加存储路径信息 | |
| if (result.folderPath) { | |
| response.storagePath = result.folderPath; | |
| response.architecture = 'folder-based (meta.json + individual slide files)'; | |
| } | |
| // 添加警告信息 | |
| if (result.warnings) { | |
| response.warnings = result.warnings; | |
| } | |
| // 记录PPT修改,用于自动备份 | |
| try { | |
| const { default: autoBackupService } = await import('../services/autoBackupService.js'); | |
| autoBackupService.recordPPTModification(userId, pptId); | |
| } catch (backupError) { | |
| console.warn('⚠️ Failed to record PPT modification for auto backup:', backupError.message); | |
| } | |
| // 🔄 新增:自动更新持久化图片链接 | |
| try { | |
| const { default: persistentImageLinkService } = await import('../services/persistentImageLinkService.js'); | |
| // 异步更新所有页面的持久化链接(不阻塞响应) | |
| setImmediate(async () => { | |
| try { | |
| console.log(`🔄 Starting persistent image links update for PPT ${pptId}`); | |
| const updateResults = await persistentImageLinkService.updateAllPersistentLinksWithFrontendExport( | |
| userId, | |
| pptId, | |
| slides, | |
| { | |
| format: 'jpg', | |
| quality: 0.9, | |
| viewportSize: viewportSize || 1000, | |
| viewportRatio: viewportRatio || 0.5625 | |
| } | |
| ); | |
| const successCount = updateResults.filter(r => r.success).length; | |
| const failCount = updateResults.filter(r => !r.success).length; | |
| console.log(`✅ Persistent image links update completed: ${successCount} success, ${failCount} failed`); | |
| if (failCount > 0) { | |
| console.warn(`⚠️ Some persistent image links failed to update:`, | |
| updateResults.filter(r => !r.success).map(r => `Page ${r.pageIndex}: ${r.error}`) | |
| ); | |
| } | |
| } catch (updateError) { | |
| console.error('❌ Failed to update persistent image links:', updateError.message); | |
| } | |
| }); | |
| // 添加持久化链接信息到响应 | |
| response.persistentLinks = { | |
| enabled: true, | |
| updateTriggered: true, | |
| message: 'Persistent image links update started in background' | |
| }; | |
| } catch (persistentError) { | |
| console.warn('⚠️ Failed to trigger persistent image links update:', persistentError.message); | |
| response.persistentLinks = { | |
| enabled: false, | |
| error: persistentError.message | |
| }; | |
| } | |
| res.json(response); | |
| } catch (saveError) { | |
| console.error(`❌ Save operation failed:`, { | |
| error: saveError.message, | |
| stack: saveError.stack, | |
| userId, | |
| pptId, | |
| slidesCount: slides.length, | |
| totalDataSize, | |
| errorName: saveError.name, | |
| errorCode: saveError.code | |
| }); | |
| let errorResponse = { | |
| error: 'PPT save failed', | |
| details: saveError.message, | |
| pptId: pptId, | |
| slidesCount: slides.length, | |
| totalFileSize: `${(totalDataSize / 1024).toFixed(2)} KB`, | |
| timestamp: new Date().toISOString(), | |
| slideAnalysis: { | |
| largestSlideSize: `${(maxSlideSize / 1024).toFixed(2)} KB`, | |
| largeSlides: largeSlides.length | |
| } | |
| }; | |
| // 根据错误类型提供具体建议 | |
| if (saveError.message.includes('File too large') || saveError.message.includes('exceeds')) { | |
| errorResponse.error = 'Slide file too large for storage'; | |
| errorResponse.suggestion = 'Some slides are too large even after compression. Try reducing image sizes or slide complexity.'; | |
| errorResponse.limits = { | |
| maxSlideSize: '999 KB per slide', | |
| largestSlideSize: `${(maxSlideSize / 1024).toFixed(2)} KB`, | |
| oversizeSlides: largeSlides.length | |
| }; | |
| return res.status(413).json(errorResponse); | |
| } | |
| if (saveError.message.includes('Failed to compress slide')) { | |
| errorResponse.error = 'Slide compression failed'; | |
| errorResponse.suggestion = 'Unable to compress slides to acceptable size. Try manually reducing content complexity.'; | |
| errorResponse.compressionError = true; | |
| return res.status(500).json(errorResponse); | |
| } | |
| if (saveError.message.includes('GitHub API') || saveError.message.includes('GitHub')) { | |
| errorResponse.error = 'GitHub storage service error'; | |
| errorResponse.suggestion = 'Temporary GitHub service issue. Please try again in a few minutes.'; | |
| errorResponse.githubError = true; | |
| return res.status(502).json(errorResponse); | |
| } | |
| if (saveError.message.includes('No GitHub repositories')) { | |
| errorResponse.error = 'Storage configuration error'; | |
| errorResponse.suggestion = 'GitHub repositories not properly configured. Please contact support.'; | |
| errorResponse.configError = true; | |
| return res.status(500).json(errorResponse); | |
| } | |
| // 网络相关错误 | |
| if (saveError.code === 'ETIMEDOUT' || saveError.message.includes('timeout')) { | |
| errorResponse.error = 'Storage operation timeout'; | |
| errorResponse.suggestion = 'The save operation took too long. Try reducing file size or try again later.'; | |
| errorResponse.timeoutError = true; | |
| return res.status(504).json(errorResponse); | |
| } | |
| // 默认错误 | |
| errorResponse.suggestion = 'Unknown save error. Please try again or contact support if the problem persists.'; | |
| errorResponse.unknownError = true; | |
| return res.status(500).json(errorResponse); | |
| } | |
| } catch (error) { | |
| console.error(`❌ PPT save failed for user ${req.user?.userId}, PPT ID: ${req.body?.pptId}:`, { | |
| error: error.message, | |
| stack: error.stack, | |
| userId: req.user?.userId, | |
| pptId: req.body?.pptId, | |
| requestSize: req.get('content-length'), | |
| slidesCount: req.body?.slides?.length, | |
| errorName: error.name, | |
| errorCode: error.code | |
| }); | |
| // 提供更详细的错误处理 | |
| let errorResponse = { | |
| error: 'PPT save processing failed', | |
| details: error.message, | |
| timestamp: new Date().toISOString(), | |
| userId: req.user?.userId, | |
| pptId: req.body?.pptId, | |
| architecture: 'folder-based' | |
| }; | |
| if (error.message.includes('Invalid slide data')) { | |
| errorResponse.error = 'Invalid slide data detected'; | |
| errorResponse.suggestion = 'One or more slides contain invalid or corrupted data'; | |
| return res.status(400).json(errorResponse); | |
| } | |
| if (error.message.includes('JSON')) { | |
| errorResponse.error = 'Invalid data format'; | |
| errorResponse.suggestion = 'Check slide data structure and content'; | |
| return res.status(400).json(errorResponse); | |
| } | |
| errorResponse.suggestion = 'Unknown processing error. Please try again or contact support if the problem persists.'; | |
| next(error); | |
| } | |
| }); | |
| // 创建新PPT | |
| router.post('/create', async (req, res, next) => { | |
| try { | |
| const userId = req.user.userId; | |
| const { title } = req.body; | |
| if (!title) { | |
| return res.status(400).json({ error: 'Title is required' }); | |
| } | |
| const pptId = uuidv4(); | |
| const now = new Date().toISOString(); | |
| // 确保数据格式与前端store一致 | |
| const pptData = { | |
| id: pptId, | |
| title, | |
| theme: { | |
| backgroundColor: '#ffffff', | |
| themeColor: '#d14424', | |
| fontColor: '#333333', | |
| fontName: 'Microsoft YaHei' | |
| }, | |
| slides: [ | |
| { | |
| id: uuidv4(), | |
| elements: [ | |
| { | |
| type: 'text', | |
| id: uuidv4(), | |
| left: 150, | |
| top: 200, | |
| width: 600, | |
| height: 100, | |
| content: title, | |
| fontSize: 32, | |
| fontName: 'Microsoft YaHei', | |
| defaultColor: '#333333', | |
| bold: true, | |
| align: 'center' | |
| } | |
| ], | |
| background: { | |
| type: 'solid', | |
| color: '#ffffff' | |
| } | |
| } | |
| ], | |
| // 确保视口信息与前端一致 | |
| viewportSize: 1000, | |
| viewportRatio: 0.5625, | |
| createdAt: now, | |
| updatedAt: now | |
| }; | |
| const fileName = `${pptId}.json`; | |
| // 优先使用Huggingface存储服务保存PPT数据 | |
| const { default: huggingfaceStorageService } = await import('../services/huggingfaceStorageService.js'); | |
| try { | |
| // 尝试保存到Huggingface存储 | |
| await huggingfaceStorageService.storePPTData(userId, pptId, pptData); | |
| console.log(`✅ PPT created and saved to Huggingface storage: ${pptId}`); | |
| } catch (hfError) { | |
| console.warn('⚠️ Huggingface storage unavailable, falling back to GitHub:', hfError.message); | |
| // 回退到GitHub存储 | |
| console.log(`Creating PPT for user ${userId}, using ${githubService.useMemoryStorage ? 'memory' : 'GitHub'} storage`); | |
| if (githubService.useMemoryStorage) { | |
| await githubService.saveToMemory(userId, fileName, pptData); | |
| } else { | |
| await githubService.saveFile(userId, fileName, pptData); | |
| } | |
| } | |
| console.log(`PPT created successfully: ${pptId}`); | |
| res.json({ success: true, pptId, pptData }); | |
| } catch (error) { | |
| console.error('PPT creation error:', error); | |
| next(error); | |
| } | |
| }); | |
| // 删除PPT | |
| router.delete('/:pptId', async (req, res, next) => { | |
| try { | |
| const userId = req.user.userId; | |
| const { pptId } = req.params; | |
| const fileName = `${pptId}.json`; | |
| let deleted = false; | |
| // 优先从Huggingface存储删除 | |
| const { default: huggingfaceStorageService } = await import('../services/huggingfaceStorageService.js'); | |
| try { | |
| await huggingfaceStorageService.deletePPTData(userId, pptId); | |
| deleted = true; | |
| console.log(`✅ PPT deleted from Huggingface storage: ${pptId}`); | |
| } catch (hfError) { | |
| console.warn('⚠️ Failed to delete from Huggingface storage, trying other storage:', hfError.message); | |
| if (githubService.useMemoryStorage) { | |
| // 内存存储模式 | |
| deleted = githubService.memoryStorage.delete(`users/${userId}/${fileName}`); | |
| if (deleted) { | |
| console.log(`✅ PPT deleted from memory storage: ${pptId}`); | |
| } | |
| } else { | |
| // GitHub存储模式 - 从所有仓库中删除 | |
| if (!githubService.repositories || githubService.repositories.length === 0) { | |
| console.warn('⚠️ GitHub repositories not configured, cannot delete from GitHub storage'); | |
| } else { | |
| for (let i = 0; i < githubService.repositories.length; i++) { | |
| try { | |
| await githubService.deleteFile(userId, fileName, i); | |
| deleted = true; | |
| console.log(`✅ PPT deleted from GitHub storage: ${pptId}`); | |
| break; // 只需要从一个仓库删除成功即可 | |
| } catch (error) { | |
| // 继续尝试其他仓库 | |
| continue; | |
| } | |
| } | |
| } | |
| } | |
| } | |
| if (!deleted) { | |
| return res.status(404).json({ error: 'PPT not found in any storage' }); | |
| } | |
| res.json({ message: 'PPT deleted successfully' }); | |
| } catch (error) { | |
| next(error); | |
| } | |
| }); | |
| // 复制PPT | |
| router.post('/:pptId/copy', async (req, res, next) => { | |
| try { | |
| const userId = req.user.userId; | |
| const { pptId } = req.params; | |
| const { title } = req.body; | |
| const sourceFileName = `${pptId}.json`; | |
| // 获取源PPT数据 - 优先从Huggingface存储读取 | |
| let sourcePPT = null; | |
| const { default: huggingfaceStorageService } = await import('../services/huggingfaceStorageService.js'); | |
| try { | |
| // 尝试从Huggingface存储读取 | |
| sourcePPT = await huggingfaceStorageService.getPPTData(userId, pptId); | |
| console.log('✅ Source PPT loaded from Huggingface storage for copying'); | |
| } catch (hfError) { | |
| console.warn('⚠️ Failed to load source PPT from Huggingface storage, trying other storage:', hfError.message); | |
| if (githubService.useMemoryStorage) { | |
| // 内存存储模式 | |
| sourcePPT = await githubService.getFromMemory(userId, sourceFileName); | |
| if (sourcePPT) { | |
| console.log('✅ Source PPT loaded from memory storage for copying'); | |
| } | |
| } else { | |
| // GitHub存储模式 | |
| if (!githubService.repositories || githubService.repositories.length === 0) { | |
| console.warn('⚠️ GitHub repositories not configured, cannot load from GitHub storage'); | |
| } else { | |
| for (let i = 0; i < githubService.repositories.length; i++) { | |
| try { | |
| const result = await githubService.getFile(userId, sourceFileName, i); | |
| if (result) { | |
| sourcePPT = result; | |
| console.log('✅ Source PPT loaded from GitHub storage for copying'); | |
| break; | |
| } | |
| } catch (error) { | |
| continue; | |
| } | |
| } | |
| } | |
| } | |
| } | |
| if (!sourcePPT) { | |
| return res.status(404).json({ error: 'Source PPT not found' }); | |
| } | |
| // 创建新的PPT ID和数据 | |
| const newPptId = uuidv4(); | |
| const newFileName = `${newPptId}.json`; | |
| const newPPTData = { | |
| ...sourcePPT, | |
| id: newPptId, | |
| title: title || `${sourcePPT.title} - 副本`, | |
| createdAt: new Date().toISOString(), | |
| updatedAt: new Date().toISOString() | |
| }; | |
| // 保存复制的PPT - 优先保存到Huggingface存储 | |
| try { | |
| await huggingfaceStorageService.storePPTData(userId, newPptId, newPPTData); | |
| console.log(`✅ Copied PPT saved to Huggingface storage: ${newPptId}`); | |
| } catch (hfError) { | |
| console.warn('⚠️ Failed to save copied PPT to Huggingface storage, using fallback:', hfError.message); | |
| if (githubService.useMemoryStorage) { | |
| await githubService.saveToMemory(userId, newFileName, newPPTData); | |
| console.log(`✅ Copied PPT saved to memory storage: ${newPptId}`); | |
| } else { | |
| await githubService.saveFile(userId, newFileName, newPPTData, 0); | |
| console.log(`✅ Copied PPT saved to GitHub storage: ${newPptId}`); | |
| } | |
| } | |
| res.json({ | |
| message: 'PPT copied successfully', | |
| pptId: newPptId, | |
| ppt: newPPTData | |
| }); | |
| } catch (error) { | |
| next(error); | |
| } | |
| }); | |
| export default router; |