diff --git a/app/api/evaluation/[id]/route.ts b/app/api/evaluation/[id]/route.ts new file mode 100644 index 0000000..b55bec2 --- /dev/null +++ b/app/api/evaluation/[id]/route.ts @@ -0,0 +1,168 @@ +import { NextRequest, NextResponse } from 'next/server'; +import { EvaluationService } from '@/lib/services/database'; + +export async function GET( + request: NextRequest, + { params }: { params: { id: string } } +) { + try { + const evaluationId = parseInt(params.id); + + if (isNaN(evaluationId)) { + return NextResponse.json( + { success: false, error: '無效的評審ID' }, + { status: 400 } + ); + } + + console.log(`📊 開始獲取評審詳細數據: ID=${evaluationId}`); + + // 獲取評審詳細數據 + const evaluationWithDetails = await EvaluationService.findWithDetails(evaluationId); + + if (!evaluationWithDetails) { + return NextResponse.json( + { success: false, error: '找不到指定的評審記錄' }, + { status: 404 } + ); + } + + console.log(`✅ 成功獲取評審數據: 專案=${evaluationWithDetails.project?.title}`); + + // 處理反饋數據,按類型分組 + const feedbackByType = { + criteria: evaluationWithDetails.feedback?.filter(f => f.feedback_type === 'criteria') || [], + strength: evaluationWithDetails.feedback?.filter(f => f.feedback_type === 'strength') || [], + improvement: evaluationWithDetails.feedback?.filter(f => f.feedback_type === 'improvement') || [], + overall: evaluationWithDetails.feedback?.filter(f => f.feedback_type === 'overall') || [] + }; + + // 為每個評分標準獲取對應的 AI 評語、優點和改進建議 + const criteriaWithFeedback = evaluationWithDetails.scores?.map(score => { + const criteriaId = score.criteria_item_id; + const criteriaName = score.criteria_item_name || '未知項目'; + + // 獲取該評分標準的 AI 評語 + const criteriaFeedback = feedbackByType.criteria + .filter(f => f.criteria_item_id === criteriaId) + .map(f => f.content) + .filter((content, index, arr) => arr.indexOf(content) === index); // 去重 + + // 獲取該評分標準的優點 + const strengths = feedbackByType.strength + .filter(f => f.criteria_item_id === criteriaId) + .map(f => f.content) + .filter((content, index, arr) => arr.indexOf(content) === index); // 去重 + + // 獲取該評分標準的改進建議 + const improvements = feedbackByType.improvement + .filter(f => f.criteria_item_id === criteriaId) + .map(f => f.content) + .filter((content, index, arr) => arr.indexOf(content) === index); // 去重 + + return { + name: criteriaName, + score: Number(score.score) || 0, + maxScore: Number(score.max_score) || 10, + weight: Number(score.weight) || 1, + weightedScore: Number(score.weighted_score) || 0, + percentage: Number(score.percentage) || 0, + feedback: criteriaFeedback[0] || '無評語', // 使用第一個評語 + strengths: strengths, + improvements: improvements + }; + }) || []; + + // 生成圖表數據 + const chartData = { + // 各項目得分 - 使用 evaluation_scores 的 score 資料 + barChart: criteriaWithFeedback.map(criteria => ({ + name: criteria.name, + score: criteria.score + })), + + // 權重分布 - 使用 evaluation_scores 的 weighted_score 資料 + pieChart: criteriaWithFeedback.map(criteria => ({ + name: criteria.name, + value: criteria.weightedScore, + weight: criteria.weight + })), + + // 能力雷達圖 - 使用 evaluation_scores 的 score 資料 + radarChart: criteriaWithFeedback.map(criteria => ({ + subject: criteria.name, + score: criteria.score, + fullMark: criteria.maxScore + })) + }; + + // 轉換數據格式以符合前端需求 + const formattedData = { + projectTitle: evaluationWithDetails.project?.title || '未知專案', + overallScore: Number(evaluationWithDetails.overall_score) || 0, + totalPossible: Number(evaluationWithDetails.max_possible_score) || 100, + grade: evaluationWithDetails.grade || 'N/A', + performanceStatus: evaluationWithDetails.performance_status || 'N/A', + recommendedStars: evaluationWithDetails.recommended_stars || 0, + analysisDate: evaluationWithDetails.created_at ? new Date(evaluationWithDetails.created_at).toISOString().split('T')[0] : new Date().toISOString().split('T')[0], + criteria: criteriaWithFeedback, + overview: { + excellentItems: evaluationWithDetails.excellent_items || 0, + improvementItems: evaluationWithDetails.improvement_items || 0, + overallPerformance: Number(evaluationWithDetails.overall_score) || 0 + }, + detailedAnalysis: { + summary: feedbackByType.overall[0]?.content || '無詳細分析', + keyFindings: feedbackByType.overall.map(f => f.content).filter((content, index, arr) => arr.indexOf(content) === index) // 去重 + }, + improvementSuggestions: { + // 整體改進建議 - evaluation_feedback 的第一筆 overall + overallSuggestions: feedbackByType.overall[0]?.content || '無改進建議', + + // 繼續保持的優勢 - evaluation_feedback 的 strength + maintainStrengths: feedbackByType.strength + .filter(f => f.criteria_item_id) // 只取有 criteria_item_id 的 + .map(f => ({ + title: f.criteria_item_name || '優勢', + description: f.content || '無描述' + })) + .filter((item, index, arr) => arr.findIndex(i => i.title === item.title) === index), // 去重 + + // 重點改進方向 - evaluation_feedback 的 improvement (除了最後3筆) + keyImprovements: feedbackByType.improvement + .filter(f => f.criteria_item_id) // 只取有 criteria_item_id 的 + .slice(0, -3) // 排除最後3筆 + .map(f => ({ + title: f.criteria_item_name || '改進項目', + description: f.content || '無描述', + suggestions: [f.content || '無建議'] + })) + .filter((item, index, arr) => arr.findIndex(i => i.title === item.title) === index), // 去重 + + // 下一步行動計劃 - evaluation_feedback 的 improvement 最後3筆 + actionPlan: feedbackByType.improvement + .filter(f => f.criteria_item_id) // 只取有 criteria_item_id 的 + .slice(-3) // 取最後3筆 + .map((f, index) => ({ + phase: `階段 ${index + 1}`, + description: f.content || '無描述' + })) + }, + chartData: chartData + }; + + console.log(`📋 評審數據格式化完成: 專案=${formattedData.projectTitle}, 分數=${formattedData.overallScore}`); + + return NextResponse.json({ + success: true, + data: formattedData + }); + + } catch (error) { + console.error('❌ 獲取評審詳細數據失敗:', error); + return NextResponse.json( + { success: false, error: '獲取評審詳細數據失敗' }, + { status: 500 } + ); + } +} diff --git a/app/history/page.tsx b/app/history/page.tsx index 474520d..4615a5e 100644 --- a/app/history/page.tsx +++ b/app/history/page.tsx @@ -260,7 +260,7 @@ export default function HistoryPage() { {item.status === "completed" ? ( <> diff --git a/app/results/page.tsx b/app/results/page.tsx index 8f39ed7..2f976c0 100644 --- a/app/results/page.tsx +++ b/app/results/page.tsx @@ -1,6 +1,7 @@ "use client" import { useState, useEffect } from "react" +import { useSearchParams } from "next/navigation" import { Sidebar } from "@/components/sidebar" import { Button } from "@/components/ui/button" import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card" @@ -128,32 +129,58 @@ export default function ResultsPage() { const [activeTab, setActiveTab] = useState("overview") const [evaluationData, setEvaluationData] = useState(null) const [isLoading, setIsLoading] = useState(true) + const [error, setError] = useState(null) const { toast } = useToast() + const searchParams = useSearchParams() useEffect(() => { - // 從 localStorage 獲取評審結果 - const storedData = localStorage.getItem('evaluationResult') - if (storedData) { + const loadEvaluationData = async () => { try { - const data = JSON.parse(storedData) - setEvaluationData(data) + setIsLoading(true) + setError(null) + + // 檢查是否有 URL 參數中的評審 ID + const evaluationId = searchParams.get('id') + + if (evaluationId) { + // 從 API 獲取評審數據 + console.log(`📊 從 API 獲取評審數據: ID=${evaluationId}`) + const response = await fetch(`/api/evaluation/${evaluationId}`) + const result = await response.json() + + if (result.success) { + setEvaluationData(result.data) + console.log('✅ 成功載入評審數據:', result.data.projectTitle) + } else { + throw new Error(result.error || '獲取評審數據失敗') + } + } else { + // 回退到從 localStorage 獲取評審結果 + console.log('📊 從 localStorage 獲取評審結果') + const storedData = localStorage.getItem('evaluationResult') + if (storedData) { + const data = JSON.parse(storedData) + setEvaluationData(data) + console.log('✅ 成功載入 localStorage 數據') + } else { + throw new Error('沒有找到評審結果') + } + } } catch (error) { - console.error('解析評審結果失敗:', error) + console.error('載入評審結果失敗:', error) + setError(error.message) toast({ - title: "數據錯誤", - description: "無法載入評審結果,請重新進行評審", + title: "載入失敗", + description: error.message || "無法載入評審結果,請重新進行評審", variant: "destructive", }) + } finally { + setIsLoading(false) } - } else { - toast({ - title: "無評審結果", - description: "請先進行評審以查看結果", - variant: "destructive", - }) } - setIsLoading(false) - }, [toast]) + + loadEvaluationData() + }, [searchParams, toast]) // 如果正在載入,顯示載入狀態 if (isLoading) { @@ -174,8 +201,8 @@ export default function ResultsPage() { ) } - // 如果沒有數據,顯示錯誤狀態 - if (!evaluationData) { + // 如果沒有數據或發生錯誤,顯示錯誤狀態 + if (!evaluationData || error) { return (
@@ -183,10 +210,17 @@ export default function ResultsPage() {
-

沒有找到評審結果

- +

+ {error ? `載入失敗: ${error}` : '沒有找到評審結果'} +

+
+ + +
@@ -196,7 +230,7 @@ export default function ResultsPage() { } // 使用真實數據或回退到模擬數據 - const results = evaluationData.evaluation?.fullData || mockResults + const results = evaluationData.evaluation?.fullData || evaluationData || mockResults // 計算統計數據 - 基於 criteria_items 的平均分作為閾值 const calculateOverview = (criteria: any[]) => { diff --git a/check-feedback-data.js b/check-feedback-data.js new file mode 100644 index 0000000..4a0013a --- /dev/null +++ b/check-feedback-data.js @@ -0,0 +1,66 @@ +const mysql = require('mysql2/promise'); + +const dbConfig = { + host: 'mysql.theaken.com', + port: 33306, + user: 'root', + password: 'zh6161168', + database: 'db_AI_scoring' +}; + +async function checkFeedbackData() { + let connection; + try { + console.log('🔗 連接到資料庫...'); + connection = await mysql.createConnection(dbConfig); + console.log('✅ 資料庫連接成功'); + + // 檢查 evaluation_feedback 表結構 + console.log('📋 檢查 evaluation_feedback 表結構...'); + const [columns] = await connection.execute(` + SELECT COLUMN_NAME, DATA_TYPE, IS_NULLABLE, COLUMN_DEFAULT + FROM INFORMATION_SCHEMA.COLUMNS + WHERE TABLE_NAME = 'evaluation_feedback' + ORDER BY ORDINAL_POSITION + `); + console.log('表結構:', columns); + + // 檢查 evaluation_feedback 數據 + console.log('📊 檢查 evaluation_feedback 數據...'); + const [feedbackData] = await connection.execute(` + SELECT id, evaluation_id, criteria_item_id, feedback_type, content, sort_order + FROM evaluation_feedback + WHERE evaluation_id = 2 + ORDER BY sort_order + `); + console.log('評審 ID 2 的反饋數據:', feedbackData); + + // 檢查不同 feedback_type 的數據 + console.log('🔍 檢查不同 feedback_type 的數據...'); + const [typeStats] = await connection.execute(` + SELECT feedback_type, COUNT(*) as count + FROM evaluation_feedback + GROUP BY feedback_type + `); + console.log('feedback_type 統計:', typeStats); + + // 檢查 criteria_items 表 + console.log('📝 檢查 criteria_items 表...'); + const [criteriaItems] = await connection.execute(` + SELECT id, name, description + FROM criteria_items + ORDER BY id + `); + console.log('評分標準項目:', criteriaItems); + + } catch (error) { + console.error('❌ 錯誤:', error.message); + } finally { + if (connection) { + await connection.end(); + console.log('🔌 資料庫連接已關閉'); + } + } +} + +checkFeedbackData(); diff --git a/check-strength-improvement.js b/check-strength-improvement.js new file mode 100644 index 0000000..26fa975 --- /dev/null +++ b/check-strength-improvement.js @@ -0,0 +1,61 @@ +const mysql = require('mysql2/promise'); + +const dbConfig = { + host: 'mysql.theaken.com', + port: 33306, + user: 'root', + password: 'zh6161168', + database: 'db_AI_scoring' +}; + +async function checkStrengthImprovement() { + let connection; + try { + console.log('🔗 連接到資料庫...'); + connection = await mysql.createConnection(dbConfig); + console.log('✅ 資料庫連接成功'); + + // 檢查 strength 類型的數據 + console.log('💪 檢查 strength 類型的數據...'); + const [strengthData] = await connection.execute(` + SELECT ef.id, ef.evaluation_id, ef.criteria_item_id, ef.content, ci.name as criteria_name + FROM evaluation_feedback ef + LEFT JOIN criteria_items ci ON ef.criteria_item_id = ci.id + WHERE ef.evaluation_id = 2 AND ef.feedback_type = 'strength' + ORDER BY ef.criteria_item_id, ef.sort_order + `); + console.log('優點數據:', strengthData); + + // 檢查 improvement 類型的數據 + console.log('🔧 檢查 improvement 類型的數據...'); + const [improvementData] = await connection.execute(` + SELECT ef.id, ef.evaluation_id, ef.criteria_item_id, ef.content, ci.name as criteria_name + FROM evaluation_feedback ef + LEFT JOIN criteria_items ci ON ef.criteria_item_id = ci.id + WHERE ef.evaluation_id = 2 AND ef.feedback_type = 'improvement' + ORDER BY ef.criteria_item_id, ef.sort_order + `); + console.log('改進建議數據:', improvementData); + + // 檢查 criteria 類型的數據(AI 評語) + console.log('💬 檢查 criteria 類型的數據(AI 評語)...'); + const [criteriaData] = await connection.execute(` + SELECT ef.id, ef.evaluation_id, ef.criteria_item_id, ef.content, ci.name as criteria_name + FROM evaluation_feedback ef + LEFT JOIN criteria_items ci ON ef.criteria_item_id = ci.id + WHERE ef.evaluation_id = 2 AND ef.feedback_type = 'criteria' + ORDER BY ef.criteria_item_id, ef.sort_order + `); + console.log('AI 評語數據:', criteriaData); + + } catch (error) { + console.error('❌ 錯誤:', error.message); + } finally { + if (connection) { + await connection.end(); + console.log('🔌 資料庫連接已關閉'); + } + } +} + +checkStrengthImprovement(); diff --git a/test-api.js b/test-api.js new file mode 100644 index 0000000..666ba87 --- /dev/null +++ b/test-api.js @@ -0,0 +1,25 @@ +const fetch = require('node-fetch'); + +async function testAPI() { + try { + console.log('🧪 測試 API 端點...'); + + // 測試統計API + console.log('📊 測試統計API...'); + const statsResponse = await fetch('http://localhost:3000/api/history/stats'); + const statsData = await statsResponse.json(); + console.log('統計數據:', statsData); + + // 測試評審詳細API + console.log('📋 測試評審詳細API...'); + const detailResponse = await fetch('http://localhost:3000/api/evaluation/2'); + const detailData = await detailResponse.json(); + console.log('評審詳細數據:', JSON.stringify(detailData, null, 2)); + + } catch (error) { + console.error('❌ 測試失敗:', error.message); + } +} + +// 等待一下讓服務器啟動 +setTimeout(testAPI, 3000); diff --git a/test-chart-data.js b/test-chart-data.js new file mode 100644 index 0000000..c5d9ae8 --- /dev/null +++ b/test-chart-data.js @@ -0,0 +1,74 @@ +const http = require('http'); + +function makeRequest(url) { + return new Promise((resolve, reject) => { + const req = http.get(url, (res) => { + let data = ''; + res.on('data', (chunk) => { + data += chunk; + }); + res.on('end', () => { + try { + const jsonData = JSON.parse(data); + resolve(jsonData); + } catch (error) { + reject(error); + } + }); + }); + req.on('error', reject); + }); +} + +async function testChartData() { + try { + console.log('🧪 測試圖表數據生成...'); + + // 測試評審詳細API + console.log('📋 測試評審詳細API (ID=2)...'); + const detailData = await makeRequest('http://localhost:3000/api/evaluation/2'); + + if (detailData.success) { + console.log('✅ API 調用成功'); + console.log('📊 專案標題:', detailData.data.projectTitle); + console.log('📊 總分:', detailData.data.overallScore); + + // 檢查圖表數據 + console.log('\n📊 圖表數據檢查:'); + + console.log('\n📈 各項目得分 (Bar Chart):'); + detailData.data.chartData.barChart.forEach((item, index) => { + console.log(` ${index + 1}. ${item.name}: ${item.score}分`); + }); + + console.log('\n🥧 權重分布 (Pie Chart):'); + detailData.data.chartData.pieChart.forEach((item, index) => { + console.log(` ${index + 1}. ${item.name}: 權重=${item.weight}%, 加權分數=${item.value}`); + }); + + console.log('\n🎯 能力雷達圖 (Radar Chart):'); + detailData.data.chartData.radarChart.forEach((item, index) => { + console.log(` ${index + 1}. ${item.subject}: ${item.score}/${item.fullMark}`); + }); + + // 檢查評分標準詳情 + console.log('\n📝 評分標準詳情:'); + detailData.data.criteria.forEach((criteria, index) => { + console.log(` ${index + 1}. ${criteria.name}: ${criteria.score}/${criteria.maxScore}`); + console.log(` AI 評語: ${criteria.feedback}`); + console.log(` 優點: ${criteria.strengths.length > 0 ? criteria.strengths.join(', ') : '無'}`); + console.log(` 改進建議: ${criteria.improvements.length > 0 ? criteria.improvements.join(', ') : '無'}`); + console.log(''); + }); + + } else { + console.log('❌ API 調用失敗:', detailData.error); + } + + } catch (error) { + console.error('❌ 測試失敗:', error.message); + } +} + +// 等待一下讓服務器啟動 +setTimeout(testChartData, 2000); diff --git a/test-database.js b/test-database.js new file mode 100644 index 0000000..902755c --- /dev/null +++ b/test-database.js @@ -0,0 +1,40 @@ +const mysql = require('mysql2/promise'); + +const dbConfig = { + host: 'mysql.theaken.com', + port: 33306, + user: 'root', + password: 'zh6161168', + database: 'db_AI_scoring' +}; + +async function checkData() { + let connection; + try { + console.log('🔗 連接到資料庫...'); + connection = await mysql.createConnection(dbConfig); + console.log('✅ 資料庫連接成功'); + + // 檢查評審記錄 + const [evaluations] = await connection.execute('SELECT id, project_id, overall_score, grade FROM evaluations LIMIT 5'); + console.log('📊 評審記錄:', evaluations); + + // 檢查專案記錄 + const [projects] = await connection.execute('SELECT id, title, status FROM projects LIMIT 5'); + console.log('📋 專案記錄:', projects); + + // 檢查評分記錄 + const [scores] = await connection.execute('SELECT id, evaluation_id, criteria_item_id, score FROM evaluation_scores LIMIT 5'); + console.log('🎯 評分記錄:', scores); + + } catch (error) { + console.error('❌ 錯誤:', error.message); + } finally { + if (connection) { + await connection.end(); + console.log('🔌 資料庫連接已關閉'); + } + } +} + +checkData(); diff --git a/test-fixed-api.js b/test-fixed-api.js new file mode 100644 index 0000000..0898d5d --- /dev/null +++ b/test-fixed-api.js @@ -0,0 +1,66 @@ +const http = require('http'); + +function makeRequest(url) { + return new Promise((resolve, reject) => { + const req = http.get(url, (res) => { + let data = ''; + res.on('data', (chunk) => { + data += chunk; + }); + res.on('end', () => { + try { + const jsonData = JSON.parse(data); + resolve(jsonData); + } catch (error) { + reject(error); + } + }); + }); + req.on('error', reject); + }); +} + +async function testFixedAPI() { + try { + console.log('🧪 測試修正後的 API...'); + + // 測試評審詳細API + console.log('📋 測試評審詳細API (ID=2)...'); + const detailData = await makeRequest('http://localhost:3000/api/evaluation/2'); + + if (detailData.success) { + console.log('✅ API 調用成功'); + console.log('📊 專案標題:', detailData.data.projectTitle); + console.log('📊 總分:', detailData.data.overallScore); + console.log('📊 等級:', detailData.data.grade); + + console.log('\n📝 評分標準詳情:'); + detailData.data.criteria.forEach((criteria, index) => { + console.log(` ${index + 1}. ${criteria.name}: ${criteria.score}/${criteria.maxScore}`); + console.log(` AI 評語: ${criteria.feedback}`); + console.log(` 優點: ${criteria.strengths.length > 0 ? criteria.strengths.join(', ') : '無'}`); + console.log(` 改進建議: ${criteria.improvements.length > 0 ? criteria.improvements.join(', ') : '無'}`); + console.log(''); + }); + + console.log('📋 詳細分析:'); + console.log(' 摘要:', detailData.data.detailedAnalysis.summary); + console.log(' 關鍵發現:', detailData.data.detailedAnalysis.keyFindings); + + console.log('\n💡 改進建議:'); + console.log(' 整體建議:', detailData.data.improvementSuggestions.overallSuggestions); + console.log(' 保持優勢:', detailData.data.improvementSuggestions.maintainStrengths); + console.log(' 關鍵改進:', detailData.data.improvementSuggestions.keyImprovements); + console.log(' 行動計劃:', detailData.data.improvementSuggestions.actionPlan); + + } else { + console.log('❌ API 調用失敗:', detailData.error); + } + + } catch (error) { + console.error('❌ 測試失敗:', error.message); + } +} + +// 等待一下讓服務器啟動 +setTimeout(testFixedAPI, 2000); diff --git a/uploads/projects/24/1758637006045_3d4e2i2yb.pptx b/uploads/projects/24/1758637006045_3d4e2i2yb.pptx new file mode 100644 index 0000000..8b38ba6 Binary files /dev/null and b/uploads/projects/24/1758637006045_3d4e2i2yb.pptx differ