import { NextRequest, NextResponse } from 'next/server'; import { GeminiService } from '@/lib/services/gemini'; import { ProjectService, ProjectFileService, EvaluationService, EvaluationScoreService, EvaluationFeedbackService, CriteriaItemService } from '@/lib/services/database'; // 用於防止重複請求的簡單緩存 const processingRequests = new Set(); export async function POST(request: NextRequest) { let requestKey = ''; try { const formData = await request.formData(); const projectTitle = formData.get('projectTitle') as string; const projectDescription = formData.get('projectDescription') as string; const file = formData.get('file') as File; const websiteUrl = formData.get('websiteUrl') as string; // 創建請求的唯一標識符 requestKey = `${projectTitle}_${file?.name || websiteUrl}_${Date.now()}`; // 檢查是否正在處理相同的請求 if (processingRequests.has(requestKey)) { console.log('⚠️ 檢測到重複請求,跳過處理'); return NextResponse.json( { success: false, error: '請求正在處理中,請勿重複提交' }, { status: 429 } ); } // 標記請求為處理中 processingRequests.add(requestKey); console.log('🚀 開始處理評審請求...'); console.log('📝 專案標題:', projectTitle); console.log('📋 專案描述:', projectDescription); console.log('📁 上傳文件:', file ? file.name : '無'); console.log('🌐 網站連結:', websiteUrl || '無'); console.log('🔑 請求標識符:', requestKey); // 驗證必填欄位 if (!projectTitle?.trim()) { return NextResponse.json( { success: false, error: '請填寫專案標題' }, { status: 400 } ); } if (!file && !websiteUrl?.trim()) { return NextResponse.json( { success: false, error: '請上傳文件或提供網站連結' }, { status: 400 } ); } // 獲取評分標準 console.log('📊 載入評分標準...'); const templates = await CriteriaItemService.getAllTemplates(); console.log('📊 載入的模板:', templates); if (!templates || templates.length === 0) { return NextResponse.json( { success: false, error: '未找到評分標準,請先設定評分標準' }, { status: 400 } ); } const templateId = templates[0].id; const criteria = templates[0].items || []; console.log('📊 評分項目:', criteria); // 1. 檢查是否已有專案記錄(從前端傳入) let projectId: number | null = null; const projectIdStr = formData.get('projectId') as string; if (projectIdStr) { projectId = parseInt(projectIdStr); } if (!projectId) { // 如果沒有傳入專案 ID,則創建新的專案記錄 console.log('💾 創建專案記錄...'); const projectData = { user_id: 1, // 暫時使用固定用戶 ID template_id: templateId, title: projectTitle, description: projectDescription || undefined, status: 'analyzing' as const, // 狀態設為分析中 analysis_started_at: new Date(), analysis_completed_at: undefined }; const projectResult = await ProjectService.create(projectData); projectId = (projectResult as any).insertId; console.log('✅ 專案記錄創建成功,ID:', projectId); } else { console.log('✅ 使用現有專案記錄,ID:', projectId); // 更新專案狀態為分析中 await ProjectService.update(projectId, { status: 'analyzing', analysis_started_at: new Date() }); } // 2. 處理文件上傳(如果有文件) let content = ''; let projectFileId: number | null = null; const projectFileIdStr = formData.get('projectFileId') as string; if (projectFileIdStr) { projectFileId = parseInt(projectFileIdStr); } if (file) { // 檢查是否已有文件記錄(從前端傳入) if (!projectFileId) { // 如果沒有傳入文件 ID,則創建新的文件記錄 console.log('📄 開始處理上傳文件...'); // 檢查文件類型 const allowedTypes = [ 'application/vnd.ms-powerpoint', 'application/vnd.openxmlformats-officedocument.presentationml.presentation', 'video/mp4', 'video/avi', 'video/quicktime', 'application/pdf', 'text/plain' // 添加純文字格式用於測試 ]; if (!allowedTypes.includes(file.type)) { console.log('❌ 不支援的文件格式:', file.type); return NextResponse.json( { success: false, error: `不支援的文件格式: ${file.type}` }, { status: 400 } ); } // 檢查文件大小 (100MB) const maxSize = 100 * 1024 * 1024; if (file.size > maxSize) { return NextResponse.json( { success: false, error: '文件大小超過 100MB 限制' }, { status: 400 } ); } // 創建文件記錄 const fileData = { project_id: projectId!, original_name: file.name, file_name: file.name, // 簡化處理,實際應該生成唯一檔名 file_path: `/uploads/${projectId}/${file.name}`, // 簡化路徑 file_size: file.size, file_type: file.name.split('.').pop() || '', mime_type: file.type, upload_status: 'completed' as const, upload_progress: 100 }; const fileResult = await ProjectFileService.create(fileData); projectFileId = (fileResult as any).insertId; console.log('✅ 文件記錄創建成功,ID:', projectFileId); } else { console.log('✅ 使用現有文件記錄,ID:', projectFileId); } // 提取內容 content = await GeminiService.extractPPTContent(file); } else if (websiteUrl) { // 處理網站連結 console.log('🌐 開始處理網站連結...'); content = `網站內容分析: ${websiteUrl} 由於目前是簡化版本,無法直接抓取網站內容。 實際應用中應該使用網頁抓取技術來獲取網站內容進行分析。 專案描述: ${projectDescription || '無'}`; } // 3. 使用 Gemini AI 進行評分 console.log('🤖 開始 AI 評分...'); const startTime = Date.now(); // 轉換 criteria 格式以符合 GeminiService 的期望 const geminiCriteria = criteria.map(item => ({ id: item.id.toString(), name: item.name, description: item.description || '', weight: item.weight, maxScore: item.max_score })); const evaluation = await GeminiService.analyzePresentation( content, projectTitle, projectDescription || '', geminiCriteria ); const analysisDuration = Math.round((Date.now() - startTime) / 1000); console.log(`⏱️ AI 分析耗時: ${analysisDuration} 秒`); // 4. 上傳評審結果到資料庫 console.log('💾 開始上傳評審結果到資料庫...'); try { // 創建 evaluations 記錄 const evaluationData = { project_id: projectId!, overall_score: evaluation.totalScore, max_possible_score: evaluation.maxTotalScore, grade: evaluation.fullData?.grade || 'N/A', performance_status: evaluation.fullData?.performanceStatus || 'N/A', recommended_stars: evaluation.fullData?.recommendedStars || 0, excellent_items: evaluation.fullData?.overview?.excellentItems || 0, improvement_items: evaluation.fullData?.overview?.improvementItems || 0, analysis_duration: analysisDuration, ai_model_version: 'gemini-1.5-flash', status: 'completed' as const, error_message: undefined }; const evaluationResult = await EvaluationService.create(evaluationData); const evaluationId = (evaluationResult as any).insertId; console.log('✅ Evaluations 記錄創建成功,ID:', evaluationId); // 創建 evaluation_scores 記錄 // 確保為所有 criteria 創建記錄,即使 AI 沒有返回對應的結果 for (const criteriaItem of criteria) { // 從 evaluation.results 中尋找對應的評分結果 console.log(`🔍 尋找評分標準: "${criteriaItem.name}"`); console.log(`📋 可用的 AI 結果:`, evaluation.results.map(r => `"${r.criteriaName}"`)); // 使用更寬鬆的匹配方式,去除前後空格和不可見字符 const cleanCriteriaName = criteriaItem.name.trim().replace(/[\u200B-\u200D\uFEFF]/g, ''); console.log(`🧹 清理後的資料庫名稱: "${cleanCriteriaName}"`); const result = evaluation.results.find(r => { const cleanAiName = r.criteriaName.trim().replace(/[\u200B-\u200D\uFEFF]/g, ''); console.log(`🧹 清理後的 AI 名稱: "${cleanAiName}"`); const isMatch = cleanAiName === cleanCriteriaName; console.log(`🔍 匹配結果: ${isMatch}`); return isMatch; }); let score, maxScore, feedback, details; if (result) { // 如果有 AI 評分結果,使用 AI 的評分 score = result.score; maxScore = result.maxScore; feedback = result.feedback; details = result.details; } else { // 如果沒有 AI 評分結果,使用預設值 console.warn(`⚠️ 找不到評分標準 "${criteriaItem.name}" 的 AI 評分結果,使用預設值`); score = Math.floor(criteriaItem.max_score * 0.7); // 預設 70% 分數 maxScore = criteriaItem.max_score; feedback = '基於資料庫評分標準的預設評語'; details = '基於資料庫評分標準的預設說明'; } const scoreData = { evaluation_id: evaluationId, criteria_item_id: criteriaItem.id, score: score, max_score: maxScore, weight: criteriaItem.weight, weighted_score: (score / maxScore) * criteriaItem.weight, percentage: (score / maxScore) * 100 }; // 調試日誌:檢查是否有 undefined 值 console.log(`🔍 檢查評分數據: ${criteriaItem.name}`, { evaluation_id: evaluationId, criteria_item_id: criteriaItem.id, score: score, max_score: maxScore, weight: criteriaItem.weight, weighted_score: (score / maxScore) * criteriaItem.weight, percentage: (score / maxScore) * 100 }); // 驗證所有必要的值都存在 if (evaluationId === undefined || criteriaItem.id === undefined || score === undefined || maxScore === undefined || criteriaItem.weight === undefined) { console.error(`❌ 評分數據驗證失敗: ${criteriaItem.name}`, scoreData); throw new Error(`評分數據驗證失敗: ${criteriaItem.name} - 存在 undefined 值`); } await EvaluationScoreService.create(scoreData); console.log(`✅ 創建評分記錄: ${criteriaItem.name} (ID: ${criteriaItem.id}) - ${score}/${maxScore}`); } console.log('✅ Evaluation Scores 記錄創建成功'); // 創建 evaluation_feedback 記錄 let sortOrder = 1; // 整體反饋 await EvaluationFeedbackService.create({ evaluation_id: evaluationId, criteria_item_id: undefined, feedback_type: 'overall', content: evaluation.overallFeedback, sort_order: sortOrder++ }); // 各項標準的反饋 for (const result of evaluation.results) { const criteriaItem = criteria.find(c => c.name === result.criteriaName); if (!criteriaItem) continue; // 標準反饋 await EvaluationFeedbackService.create({ evaluation_id: evaluationId, criteria_item_id: criteriaItem.id, feedback_type: 'criteria', content: result.feedback, sort_order: sortOrder++ }); // 詳細反饋 await EvaluationFeedbackService.create({ evaluation_id: evaluationId, criteria_item_id: criteriaItem.id, feedback_type: 'criteria', content: result.details, sort_order: sortOrder++ }); } // 如果有 fullData,添加 strengths 和 improvements 反饋 if (evaluation.fullData) { const fullData = evaluation.fullData; // 為每個 criteria 添加 strengths 和 improvements for (const criteriaData of fullData.criteria) { const criteriaItem = criteria.find(c => c.name === criteriaData.name); if (!criteriaItem) continue; // 添加 strengths if (criteriaData.strengths && criteriaData.strengths.length > 0) { for (const strength of criteriaData.strengths) { await EvaluationFeedbackService.create({ evaluation_id: evaluationId, criteria_item_id: criteriaItem.id, feedback_type: 'strength', content: strength, sort_order: sortOrder++ }); } } // 添加 improvements if (criteriaData.improvements && criteriaData.improvements.length > 0) { for (const improvement of criteriaData.improvements) { await EvaluationFeedbackService.create({ evaluation_id: evaluationId, criteria_item_id: criteriaItem.id, feedback_type: 'improvement', content: improvement, sort_order: sortOrder++ }); } } } } // 如果有 fullData,添加額外的反饋 if (evaluation.fullData) { const fullData = evaluation.fullData; // 詳細分析摘要 if (fullData.detailedAnalysis?.summary) { await EvaluationFeedbackService.create({ evaluation_id: evaluationId, criteria_item_id: undefined, feedback_type: 'overall', content: fullData.detailedAnalysis.summary, sort_order: sortOrder++ }); } // 關鍵發現 if (fullData.detailedAnalysis?.keyFindings) { for (const finding of fullData.detailedAnalysis.keyFindings) { await EvaluationFeedbackService.create({ evaluation_id: evaluationId, criteria_item_id: undefined, feedback_type: 'overall', content: finding, sort_order: sortOrder++ }); } } // 改進建議 if (fullData.improvementSuggestions?.overallSuggestions) { await EvaluationFeedbackService.create({ evaluation_id: evaluationId, criteria_item_id: undefined, feedback_type: 'improvement', content: fullData.improvementSuggestions.overallSuggestions, sort_order: sortOrder++ }); } // 保持優勢 (maintainStrengths) if (fullData.improvementSuggestions?.maintainStrengths) { for (const strength of fullData.improvementSuggestions.maintainStrengths) { await EvaluationFeedbackService.create({ evaluation_id: evaluationId, criteria_item_id: undefined, feedback_type: 'strength', content: `${strength.title}: ${strength.description}`, sort_order: sortOrder++ }); } } // 關鍵改進建議 (keyImprovements) if (fullData.improvementSuggestions?.keyImprovements) { for (const improvement of fullData.improvementSuggestions.keyImprovements) { // 主要改進建議 await EvaluationFeedbackService.create({ evaluation_id: evaluationId, criteria_item_id: undefined, feedback_type: 'improvement', content: `${improvement.title}: ${improvement.description}`, sort_order: sortOrder++ }); // 具體建議 (suggestions) if (improvement.suggestions && improvement.suggestions.length > 0) { for (const suggestion of improvement.suggestions) { await EvaluationFeedbackService.create({ evaluation_id: evaluationId, criteria_item_id: undefined, feedback_type: 'improvement', content: `• ${suggestion}`, sort_order: sortOrder++ }); } } } } // 行動計劃 (actionPlan) if (fullData.improvementSuggestions?.actionPlan) { for (const plan of fullData.improvementSuggestions.actionPlan) { await EvaluationFeedbackService.create({ evaluation_id: evaluationId, criteria_item_id: undefined, feedback_type: 'improvement', content: `${plan.phase}: ${plan.description}`, sort_order: sortOrder++ }); } } } console.log('✅ Evaluation Feedback 記錄創建成功'); // 5. 更新專案狀態為分析完成 await ProjectService.update(projectId!, { status: 'completed', analysis_completed_at: new Date() }); console.log('✅ 專案狀態更新為分析完成'); } catch (dbError) { console.error('❌ 資料庫上傳失敗:', dbError); // 即使資料庫上傳失敗,也返回 AI 分析結果 // 但記錄錯誤以便後續處理 } console.log('🎉 評審完成!'); console.log('📊 最終評分結果:'); console.log(' 總分:', evaluation.totalScore, '/', evaluation.maxTotalScore); console.log(' 各項目評分:'); evaluation.results.forEach(result => { console.log(` - ${result.criteriaName}: ${result.score}/${result.maxScore}`); }); // 清理請求標識符 processingRequests.delete(requestKey); return NextResponse.json({ success: true, data: { evaluation, projectId: projectId, projectFileId: projectFileId, projectTitle, projectDescription, fileInfo: file ? { name: file.name, size: file.size, type: file.type } : null, websiteUrl: websiteUrl || null } }); } catch (error) { console.error('❌ 評審處理失敗:', error); // 清理請求標識符(如果存在) if (requestKey) { processingRequests.delete(requestKey); } return NextResponse.json( { success: false, error: '評審處理失敗,請稍後再試' }, { status: 500 } ); } }