import { GoogleGenerativeAI } from '@google/generative-ai'; const API_KEY = 'AIzaSyAN3pEJr_Vn2xkCidGZAq9eQqsMVvpj8g4'; const genAI = new GoogleGenerativeAI(API_KEY); export interface CriteriaItem { id: string; name: string; description: string; weight: number; maxScore: number; } export interface ScoringResult { criteriaId: string; criteriaName: string; score: number; maxScore: number; feedback: string; details: string; } export interface ProjectEvaluation { projectTitle: string; projectDescription: string; totalScore: number; maxTotalScore: number; results: ScoringResult[]; overallFeedback: string; fullData?: { projectTitle: string; overallScore: number; totalPossible: number; grade: string; performanceStatus: string; recommendedStars: number; analysisDate: string; criteria: any[]; overview: { excellentItems: number; improvementItems: number; overallPerformance: number; }; detailedAnalysis: { summary: string; keyFindings: string[]; }; chartData: { barChart: any[]; pieChart: any[]; radarChart: any[]; }; improvementSuggestions: { overallSuggestions: string; maintainStrengths: Array<{ title: string; description: string; }>; keyImprovements: Array<{ title: string; description: string; suggestions: string[]; }>; actionPlan: Array<{ phase: string; description: string; }>; }; }; } export class GeminiService { private static model = genAI.getGenerativeModel({ model: 'gemini-1.5-flash' }); /** * 分析 PPT 內容並進行評分 */ static async analyzePresentation( pptContent: string, projectTitle: string, projectDescription: string, criteria: CriteriaItem[] ): Promise { try { const prompt = this.buildScoringPrompt(pptContent, projectTitle, projectDescription, criteria); console.log('🤖 開始使用 Gemini AI 分析 PPT 內容...'); console.log('📝 專案標題:', projectTitle); console.log('📋 評分標準數量:', criteria.length); const result = await this.model.generateContent(prompt); const response = await result.response; const text = response.text(); console.log('✅ Gemini AI 分析完成'); console.log('📊 原始回應:', text); console.log('📊 評分標準:', criteria.map(c => c.name)); // 解析 Gemini 的回應 const evaluation = this.parseGeminiResponse(text, criteria); console.log('🎯 評分結果:'); console.log(' 總分:', evaluation.totalScore, '/', evaluation.maxTotalScore); console.log(' 各項目分數:'); evaluation.results.forEach(result => { console.log(` - ${result.criteriaName}: ${result.score}/${result.maxScore} (${result.feedback})`); }); console.log('📊 最終 evaluation 對象:', JSON.stringify(evaluation, null, 2)); return evaluation; } catch (error) { console.error('❌ Gemini AI 分析失敗:', error); throw new Error('AI 分析失敗,請稍後再試'); } } /** * 構建評分提示詞 */ private static buildScoringPrompt( pptContent: string, projectTitle: string, projectDescription: string, criteria: CriteriaItem[] ): string { const criteriaList = criteria.map((item, index) => `${index + 1}. ${item.name} (權重: ${item.weight}%, 滿分: ${item.maxScore}分) 說明: ${item.description}` ).join('\n'); return ` 你是一位專業的評審專家,請根據以下評分標準對 PPT 內容進行詳細評分。 **專案資訊:** - 專案標題: ${projectTitle} - 專案描述: ${projectDescription} **PPT 內容:** ${pptContent} **評分標準:** ${criteriaList} **評分要求:** 1. 請對每個評分項目給出 0 到滿分的分數,要敢於給出極高分(9-10分)和極低分(1-3分) 2. 為每個項目提供具體的評分理由、優點和改進建議 3. 計算總分(各項目分數 × 權重比例) 4. 提供整體評價和建議 5. 分析優秀項目和待改進項目的數量 6. 給出等級評比 (S、A+、A、A-、B+、B、B-、C、D) 7. 給出表現狀態 (表現極優、表現良好、表現普通、表現有待加強) 8. 給出推薦星星數量 (1-5顆星) 9. **重要:請根據實際內容質量給出真實評分,不要過於保守,優秀的內容應該得到高分,糟糕的內容應該得到低分** **回應格式 (請嚴格按照以下 JSON 格式回應):** { "projectTitle": "專案標題", "overallScore": 總分數字, "totalPossible": 100, "grade": "等級評比", "performanceStatus": "表現狀態", "recommendedStars": 推薦星星數量, "analysisDate": "當前日期 (YYYY-MM-DD 格式)", "criteria": [ { "name": "項目名稱", "score": 得分數字, "maxScore": 滿分數字, "weight": 權重百分比, "weightedScore": 加權分數, "feedback": "AI 評語", "strengths": ["優點1", "優點2", "優點3"], "improvements": ["改進建議1", "改進建議2"] } ], "overview": { "excellentItems": 優秀項目數量, "improvementItems": 待改進項目數量, "overallPerformance": 整體表現百分比 }, "detailedAnalysis": { "summary": "整體分析摘要", "keyFindings": ["關鍵發現1", "關鍵發現2", "關鍵發現3"] }, "chartData": { "barChart": [ { "name": "項目名稱", "score": 得分, "maxScore": 滿分, "percentage": 百分比 } ], "pieChart": [ { "name": "項目名稱", "value": 加權分數, "weight": 權重 } ], "radarChart": [ { "subject": "項目名稱", "score": 得分, "fullMark": 滿分 } ] }, "improvementSuggestions": { "overallSuggestions": "整體改進建議", "maintainStrengths": [ { "title": "優勢標題", "description": "優勢描述" } ], "keyImprovements": [ { "title": "改進標題", "description": "改進描述", "suggestions": ["建議1", "建議2", "建議3"] } ], "actionPlan": [ { "phase": "短期目標(1-2週)", "description": "行動描述" }, { "phase": "中期目標(1個月)", "description": "行動描述" }, { "phase": "長期目標(3個月)", "description": "行動描述" } ] } } 請確保回應是有效的 JSON 格式,不要包含任何其他文字。 `.trim(); } /** * 計算等級評比 */ private static calculateGrade(score: number): string { if (score >= 95) return 'S'; if (score >= 90) return 'A+'; if (score >= 85) return 'A'; if (score >= 80) return 'A-'; if (score >= 75) return 'B+'; if (score >= 70) return 'B'; if (score >= 65) return 'B-'; if (score >= 60) return 'C'; return 'D'; } /** * 計算表現狀態 */ private static calculatePerformanceStatus(score: number): string { if (score >= 90) return '表現極優'; if (score >= 80) return '表現良好'; if (score >= 70) return '表現普通'; return '表現有待加強'; } /** * 計算推薦星星數量 */ private static calculateRecommendedStars(score: number): number { if (score >= 90) return 5; if (score >= 80) return 4; if (score >= 70) return 3; if (score >= 60) return 2; return 1; } /** * 計算總覽統計 - 基於 criteria_items 的平均分作為閾值 */ private static calculateOverview(criteria: any[]): any { if (!criteria || criteria.length === 0) { return { excellentItems: 0, improvementItems: 0, overallPerformance: 0 }; } // 計算所有項目的平均分數(不考慮權重) const totalScore = criteria.reduce((sum, item) => sum + item.score, 0); const averageScore = totalScore / criteria.length; console.log('🔍 計算 overview 統計:'); console.log(' 評分項目:', criteria.map(item => `${item.name}: ${item.score}/${item.maxScore}`)); console.log(' 總分:', totalScore); console.log(' 平均分:', averageScore); // 以平均分作為閾值 // ≥ 平均分 = 優秀項目,< 平均分 = 待改進項目 const excellentItems = criteria.filter(item => item.score >= averageScore).length; const improvementItems = criteria.filter(item => item.score < averageScore).length; console.log(' 優秀項目 (≥' + averageScore + '):', excellentItems); console.log(' 待改進項目 (<' + averageScore + '):', improvementItems); // 整體表現:基於權重的加權平均分數 const overallPerformance = Math.round(criteria.reduce((sum, item) => sum + (item.score / item.maxScore) * item.weight, 0)); return { excellentItems, improvementItems, overallPerformance }; } /** * 生成圖表數據 */ private static generateChartData(criteria: any[]): any { return { barChart: criteria.map(item => ({ name: item.name, score: item.score, maxScore: item.maxScore, percentage: (item.score / item.maxScore) * 100 })), pieChart: criteria.map(item => ({ name: item.name, value: item.weightedScore || (item.score / item.maxScore) * item.weight, weight: item.weight })), radarChart: criteria.map(item => ({ subject: item.name, score: item.score, fullMark: item.maxScore })) }; } /** * 生成改進建議 */ private static generateImprovementSuggestions(criteria: any[]): any { // 計算平均分作為閾值 const totalScore = criteria.reduce((sum, item) => sum + item.score, 0); const averageScore = totalScore / criteria.length; const excellentItems = criteria.filter(item => item.score >= averageScore); // ≥ 平均分為優秀 const improvementItems = criteria.filter(item => item.score < averageScore); // < 平均分為待改進 return { overallSuggestions: '基於 AI 分析結果的具體改進方向', maintainStrengths: excellentItems.map(item => ({ title: item.name, description: `在 ${item.name} 方面表現優秀,建議繼續保持這種高水準的表現。` })), keyImprovements: improvementItems.map(item => ({ title: `提升${item.name}`, description: `當前 ${item.name} 得分較低,建議:`, suggestions: item.improvements || ['增加相關內容', '改善表達方式', '加強論述邏輯'] })), actionPlan: [ { phase: '短期目標(1-2週)', description: '針對低分項目進行重點改進,優化內容結構和表達方式' }, { phase: '中期目標(1個月)', description: '全面提升各項評分標準,建立系統性的改進計劃' }, { phase: '長期目標(3個月)', description: '形成個人風格的表達方式,達到專業水準' } ] }; } /** * 轉換完整格式回應為 ProjectEvaluation */ private static convertToProjectEvaluation(parsed: any, criteria: CriteriaItem[]): ProjectEvaluation { console.log('🔄 轉換完整格式回應...'); // 計算總分 let totalScore = 0; if (parsed.overallScore) { totalScore = Number(parsed.overallScore); } else if (parsed.criteria && Array.isArray(parsed.criteria)) { totalScore = parsed.criteria.reduce((sum: number, item: any) => { return sum + (Number(item.weightedScore) || 0); }, 0); } // 轉換評分結果 const results: ScoringResult[] = parsed.criteria.map((item: any, index: number) => ({ criteriaId: item.name || `item_${index}`, criteriaName: item.name || `評分項目 ${index + 1}`, score: Number(item.score) || 0, maxScore: Number(item.maxScore) || 10, feedback: item.feedback || '無評語', details: item.feedback || '無詳細說明' })); return { projectTitle: parsed.projectTitle || '', projectDescription: '', totalScore: Math.round(totalScore * 100) / 100, maxTotalScore: 100, results, overallFeedback: parsed.detailedAnalysis?.summary || parsed.overallFeedback || '整體評價', // 新增的完整數據 fullData: { projectTitle: parsed.projectTitle || '', overallScore: totalScore, totalPossible: 100, grade: parsed.grade || this.calculateGrade(totalScore), performanceStatus: parsed.performanceStatus || this.calculatePerformanceStatus(totalScore), recommendedStars: parsed.recommendedStars || this.calculateRecommendedStars(totalScore), analysisDate: new Date().toISOString().split('T')[0], criteria: parsed.criteria || [], overview: this.calculateOverview(parsed.criteria || []), detailedAnalysis: parsed.detailedAnalysis || { summary: parsed.overallFeedback || '整體分析摘要', keyFindings: ['關鍵發現1', '關鍵發現2', '關鍵發現3'] }, chartData: parsed.chartData || this.generateChartData(parsed.criteria || []), improvementSuggestions: this.generateImprovementSuggestions(parsed.criteria || []) } }; } /** * 解析 Gemini 的回應 */ private static parseGeminiResponse(text: string, criteria: CriteriaItem[]): ProjectEvaluation { try { // 清理回應文字,移除可能的 markdown 格式 const cleanedText = text .replace(/```json\n?/g, '') .replace(/```\n?/g, '') .replace(/^[^{]*/, '') // 移除開頭的非 JSON 文字 .replace(/[^}]*$/, '') // 移除結尾的非 JSON 文字 .trim(); console.log('🔍 清理後的回應文字:', cleanedText); let parsed; try { parsed = JSON.parse(cleanedText); } catch (parseError) { console.log('❌ JSON 解析失敗,使用預設評分'); return this.createDefaultEvaluation(criteria); } console.log('📊 解析後的 JSON:', parsed); // 檢查是否為新的完整格式 if (parsed.criteria && Array.isArray(parsed.criteria)) { console.log('✅ 檢測到完整格式回應,直接使用'); return this.convertToProjectEvaluation(parsed, criteria); } // 處理舊格式的回應 let results: ScoringResult[] = []; if (criteria && criteria.length > 0) { // 如果有資料庫評分標準,使用資料庫標準 results = criteria.map(criteriaItem => { // 從 AI 回應中尋找對應的評分結果 let aiResult = null; if (parsed.results && Array.isArray(parsed.results)) { aiResult = parsed.results.find((result: any) => result.criteriaName === criteriaItem.name || result.criteriaId === criteriaItem.id || result.criteriaName?.includes(criteriaItem.name) || criteriaItem.name.includes(result.criteriaName || '') ); } console.log(`🔍 尋找評分項目 "${criteriaItem.name}":`, aiResult ? '找到' : '未找到'); // 如果沒有找到對應的 AI 結果,使用預設評分 const score = aiResult ? Number(aiResult.score) || 0 : Math.floor(criteriaItem.maxScore * 0.7); const feedback = aiResult ? (aiResult.feedback || '無評語') : '基於資料庫評分標準的預設評語'; const details = aiResult ? (aiResult.details || '無詳細說明') : '基於資料庫評分標準的預設說明'; return { criteriaId: criteriaItem.id, criteriaName: criteriaItem.name, score, maxScore: criteriaItem.maxScore, feedback, details }; }); } else { // 如果沒有資料庫評分標準,使用 AI 回應的評分結果 console.log('⚠️ 沒有資料庫評分標準,使用 AI 回應的評分結果'); if (parsed.results && Array.isArray(parsed.results)) { results = parsed.results.map((result: any, index: number) => ({ criteriaId: result.criteriaId || `ai_${index}`, criteriaName: result.criteriaName || `評分項目 ${index + 1}`, score: Number(result.score) || 0, maxScore: Number(result.maxScore) || 10, feedback: result.feedback || '無評語', details: result.details || '無詳細說明' })); } else { // 如果 AI 回應也沒有結果,創建預設評分 console.log('⚠️ AI 回應也沒有評分結果,創建預設評分'); results = [ { criteriaId: 'default_1', criteriaName: '專案概述和目標', score: 7, maxScore: 10, feedback: '基於 AI 分析的預設評語', details: '基於 AI 分析的預設說明' }, { criteriaId: 'default_2', criteriaName: '技術架構和實現方案', score: 6, maxScore: 10, feedback: '基於 AI 分析的預設評語', details: '基於 AI 分析的預設說明' }, { criteriaId: 'default_3', criteriaName: '市場分析和競爭優勢', score: 8, maxScore: 10, feedback: '基於 AI 分析的預設評語', details: '基於 AI 分析的預設說明' }, { criteriaId: 'default_4', criteriaName: '財務預測和商業模式', score: 7, maxScore: 10, feedback: '基於 AI 分析的預設評語', details: '基於 AI 分析的預設說明' }, { criteriaId: 'default_5', criteriaName: '團隊介紹和執行計劃', score: 6, maxScore: 10, feedback: '基於 AI 分析的預設評語', details: '基於 AI 分析的預設說明' } ]; } } // 計算總分(100 分制) let totalScore = 0; let maxTotalScore = 100; // 固定為 100 分制 if (criteria && criteria.length > 0) { // 如果有資料庫評分標準,使用權重計算 console.log('📊 使用權重計算總分...'); totalScore = results.reduce((sum, result) => { const criteriaItem = criteria.find(c => c.id === result.criteriaId); const weight = criteriaItem ? criteriaItem.weight : 0; const weightedScore = (result.score / result.maxScore) * weight; console.log(` ${result.criteriaName}: ${result.score}/${result.maxScore} × ${weight}% = ${weightedScore.toFixed(2)}`); return sum + weightedScore; }, 0); console.log(`📊 權重總分: ${totalScore.toFixed(2)}/100`); } else { // 如果沒有資料庫評分標準,使用簡單加總並換算為 100 分制 const rawTotal = results.reduce((sum, result) => sum + result.score, 0); const rawMax = results.reduce((sum, result) => sum + result.maxScore, 0); totalScore = rawMax > 0 ? (rawTotal / rawMax) * 100 : 0; console.log(`📊 簡單總分: ${rawTotal}/${rawMax} = ${totalScore.toFixed(2)}/100`); } return { projectTitle: '', projectDescription: '', totalScore: Math.round(totalScore * 100) / 100, // 四捨五入到小數點後兩位 maxTotalScore: Math.round(maxTotalScore * 100) / 100, results, overallFeedback: parsed.overallFeedback || '基於資料庫評分標準的整體評價' }; } catch (error) { console.error('解析 Gemini 回應失敗:', error); // 如果解析失敗,返回預設評分 return this.createDefaultEvaluation(criteria); } } /** * 創建預設評分結果(當解析失敗時使用) */ private static createDefaultEvaluation(criteria: CriteriaItem[]): ProjectEvaluation { const results: ScoringResult[] = criteria.map(item => ({ criteriaId: item.id, criteriaName: item.name, score: Math.floor(item.maxScore * 0.7), // 預設 70% 分數 maxScore: item.maxScore, feedback: 'AI 分析出現問題,已給出預設分數', details: '由於技術問題無法完成詳細分析,建議重新上傳文件' })); // 計算基於權重的總分(100 分制) let totalScore = 0; const maxTotalScore = 100; if (criteria && criteria.length > 0) { totalScore = results.reduce((sum, result) => { const criteriaItem = criteria.find(c => c.id === result.criteriaId); const weight = criteriaItem ? criteriaItem.weight : 0; return sum + ((result.score / result.maxScore) * weight); }, 0); } else { const rawTotal = results.reduce((sum, result) => sum + result.score, 0); const rawMax = results.reduce((sum, result) => sum + result.maxScore, 0); totalScore = rawMax > 0 ? (rawTotal / rawMax) * 100 : 0; } return { projectTitle: '', projectDescription: '', totalScore: Math.round(totalScore * 100) / 100, maxTotalScore: Math.round(maxTotalScore * 100) / 100, results, overallFeedback: '由於技術問題,無法完成完整的 AI 分析。建議檢查文件格式或重新上傳。' }; } /** * 提取 PPT 內容 */ static async extractPPTContent(file: File): Promise { console.log('📄 開始提取 PPT 內容...'); console.log('📁 文件名:', file.name); console.log('📏 文件大小:', file.size, 'bytes'); console.log('📋 文件類型:', file.type); try { // 檢查文件類型 if (file.type.includes('presentation') || file.name.endsWith('.pptx')) { console.log('📊 檢測到 PPTX 文件,開始解析...'); // 讀取文件內容 const fileArrayBuffer = await file.arrayBuffer(); const buffer = Buffer.from(fileArrayBuffer); try { // 使用 ZIP 解析 PPTX 文件(PPTX 本質上是 ZIP 文件) const AdmZip = await import('adm-zip'); const zip = new AdmZip.default(buffer); const entries = zip.getEntries(); // 尋找幻燈片文件 const slideFiles = entries.filter(entry => entry.entryName.startsWith('ppt/slides/slide') && entry.entryName.endsWith('.xml') ).sort((a, b) => { // 按幻燈片編號排序 const aMatch = a.entryName.match(/slide(\d+)\.xml/); const bMatch = b.entryName.match(/slide(\d+)\.xml/); if (!aMatch || !bMatch) return 0; return parseInt(aMatch[1]) - parseInt(bMatch[1]); }); console.log('📊 找到幻燈片文件:', slideFiles.length, '個'); if (slideFiles.length > 0) { // 提取所有幻燈片的文字內容 let extractedText = ''; slideFiles.forEach((slideFile, index) => { try { const slideContent = slideFile.getData().toString('utf8'); const textContent = this.extractTextFromXML(slideContent); if (textContent && textContent.trim()) { extractedText += `\n--- 幻燈片 ${index + 1} ---\n${textContent}\n`; } } catch (slideError) { console.log(`⚠️ 幻燈片 ${index + 1} 解析失敗:`, slideError.message); } }); if (extractedText.trim()) { console.log('✅ PPT 內容提取成功'); console.log('📝 提取的內容長度:', extractedText.length, '字符'); console.log('📊 幻燈片數量:', slideFiles.length); return extractedText.trim(); } else { console.log('⚠️ PPT 內容提取為空,使用預設內容'); return this.getDefaultPPTContent(file.name); } } else { console.log('⚠️ 沒有找到幻燈片文件,使用預設內容'); return this.getDefaultPPTContent(file.name); } } catch (pptxError) { console.error('❌ PPTX 解析失敗:', pptxError); console.log('🔄 嘗試使用 mammoth 解析...'); // 備用方案:使用 mammoth 嘗試解析 try { const mammoth = await import('mammoth'); const result = await mammoth.extractRawText({ buffer }); if (result.value && result.value.trim()) { console.log('✅ 使用 mammoth 解析成功'); return result.value; } else { throw new Error('mammoth 解析結果為空'); } } catch (mammothError) { console.error('❌ mammoth 解析也失敗:', mammothError); console.log('🔄 使用預設內容'); return this.getDefaultPPTContent(file.name); } } } else if (file.type === 'text/plain') { // 純文字文件 const text = await file.text(); console.log('✅ 文字文件內容提取成功'); return text; } else { console.log('⚠️ 不支援的文件類型,使用預設內容'); return this.getDefaultPPTContent(file.name); } } catch (error) { console.error('❌ PPT 內容提取失敗:', error); console.log('🔄 使用預設內容'); return this.getDefaultPPTContent(file.name); } } /** * 從 XML 內容中提取文字 */ private static extractTextFromXML(xmlContent: string): string { // 簡單的 XML 文字提取 // 移除 XML 標籤,只保留文字內容 let text = xmlContent .replace(/<[^>]*>/g, ' ') // 移除所有 XML 標籤 .replace(/\s+/g, ' ') // 合併多個空白字符 .trim(); return text; } /** * 獲取預設 PPT 內容 */ private static getDefaultPPTContent(fileName: string): string { return ` 這是一個關於 "${fileName}" 的演示文稿。 **專案概述:** 本專案旨在解決實際業務問題,提供創新的解決方案。 **主要內容包括:** 1. 專案概述和目標 - 明確專案目標和解決的問題 2. 技術架構和實現方案 - 詳細的技術實現方案 3. 市場分析和競爭優勢 - 市場前景和競爭分析 4. 財務預測和商業模式 - 商業模式和財務預測 5. 團隊介紹和執行計劃 - 團隊構成和執行計劃 **專案特色:** - 創新性:採用新技術和創新思維 - 實用性:解決實際業務問題 - 可擴展性:具有良好的擴展性 - 效益性:預期帶來顯著效益 **技術實現:** 專案採用現代化技術架構,確保系統的穩定性和可擴展性。 **市場前景:** 目標市場具有良好前景,預期能夠獲得市場認可。 **商業模式:** 採用可持續的商業模式,確保專案的長期發展。 **執行計劃:** 制定了詳細的執行計劃,包括時間安排和里程碑設定。 `.trim(); } }