32 KiB
🎯 個人化高價值詞彙判定系統 - 詳細開發計劃
專案: DramaLing 英語學習平台 功能: 個人化高價值詞彙智能判定 計劃版本: v1.0 建立日期: 2025-01-18 預計開發時程: 2週
📋 問題分析與目標
🚨 現有問題
當前實現位置: /backend/DramaLing.Api/Services/GeminiService.cs:95
// 目前的固定判定邏輯
"3. 標記B1以上詞彙為高價值"
問題分析:
- A1 學習者: 看不到 A2 詞彙的學習價值(應該對他們很重要)
- C1 學習者: 被 B1 詞彙干擾(對他們太簡單)
- 缺乏個人化: 一刀切設計不符合個別學習需求
- 學習效果差: 無法提供適當的挑戰程度
🎯 設計目標
新邏輯: 高價值詞彙 = 學習者當前程度 + 1~2 階級
預期效果:
| 學習者程度 | 當前標記為高價值 | 新標記為高價值 | 改善效果 |
|---|---|---|---|
| A1 | B1, B2, C1, C2 | A2, B1 | 更實用的學習目標 |
| A2 | B1, B2, C1, C2 | B1, B2 | 適當的進階挑戰 |
| B1 | B1, B2, C1, C2 | B2, C1 | 避免重複簡單詞彙 |
| B2 | B1, B2, C1, C2 | C1 | 避免重複簡單詞彙 |
| C1 | B1, B2, C1, C2 | C1, C2 | 專注高階詞彙精進 |
| C2 | B1, B2, C1, C2 | C1, C2 | 專注高階詞彙精進 |
🛠️ 詳細開發計劃
📋 Phase 1: 資料模型擴充 (第1-2天)
1.1 User 實體擴充
檔案: /backend/DramaLing.Api/Models/Entities/User.cs
當前行數: 30行 (Preferences 欄位後)
新增欄位:
// 在第30行 public Dictionary<string, object> Preferences { get; set; } = new(); 後新增
[MaxLength(10)]
public string EnglishLevel { get; set; } = "A2"; // A1, A2, B1, B2, C1, C2
public DateTime LevelUpdatedAt { get; set; } = DateTime.UtcNow;
public bool IsLevelVerified { get; set; } = false; // 是否通過測試驗證
[MaxLength(500)]
public string? LevelNotes { get; set; } // 程度設定備註
1.2 資料庫遷移
執行指令:
# 在 /backend/DramaLing.Api/ 目錄下執行
dotnet ef migrations add AddUserEnglishLevel
dotnet ef database update
生成的遷移檔案: /backend/DramaLing.Api/Migrations/{timestamp}_AddUserEnglishLevel.cs
1.3 API 請求模型更新
檔案: /backend/DramaLing.Api/Controllers/AIController.cs
位置: 第1433行附近的 AnalyzeSentenceRequest 類別
當前結構:
public class AnalyzeSentenceRequest
{
public string InputText { get; set; } = string.Empty;
public bool ForceRefresh { get; set; } = false;
public string AnalysisMode { get; set; } = "full";
}
修改後:
public class AnalyzeSentenceRequest
{
public string InputText { get; set; } = string.Empty;
public string UserLevel { get; set; } = "A2"; // 新增:用戶英語程度
public bool ForceRefresh { get; set; } = false;
public string AnalysisMode { get; set; } = "full";
}
📋 Phase 2: 智能判定邏輯建立 (第3-5天)
2.1 建立 CEFR 等級服務
新檔案: /backend/DramaLing.Api/Services/CEFRLevelService.cs
using System;
namespace DramaLing.Api.Services;
public static class CEFRLevelService
{
private static readonly string[] Levels = { "A1", "A2", "B1", "B2", "C1", "C2" };
/// <summary>
/// 取得 CEFR 等級的數字索引
/// </summary>
public static int GetLevelIndex(string level)
{
if (string.IsNullOrEmpty(level)) return 1; // 預設 A2
return Array.IndexOf(Levels, level.ToUpper());
}
/// <summary>
/// 判定詞彙對特定用戶是否為高價值
/// 規則:比用戶程度高 1-2 級的詞彙為高價值
/// </summary>
public static bool IsHighValueForUser(string wordLevel, string userLevel)
{
var userIndex = GetLevelIndex(userLevel);
var wordIndex = GetLevelIndex(wordLevel);
// 無效等級處理
if (userIndex == -1 || wordIndex == -1) return false;
// 高價值 = 比用戶程度高 1-2 級
return wordIndex >= userIndex + 1 && wordIndex <= userIndex + 2;
}
/// <summary>
/// 取得用戶的目標學習等級範圍
/// </summary>
public static string GetTargetLevelRange(string userLevel)
{
var userIndex = GetLevelIndex(userLevel);
if (userIndex == -1) return "B1-B2";
var targetMin = Levels[Math.Min(userIndex + 1, Levels.Length - 1)];
var targetMax = Levels[Math.Min(userIndex + 2, Levels.Length - 1)];
return targetMin == targetMax ? targetMin : $"{targetMin}-{targetMax}";
}
/// <summary>
/// 取得下一個等級
/// </summary>
public static string GetNextLevel(string currentLevel, int steps = 1)
{
var currentIndex = GetLevelIndex(currentLevel);
if (currentIndex == -1) return "B1";
var nextIndex = Math.Min(currentIndex + steps, Levels.Length - 1);
return Levels[nextIndex];
}
}
2.2 更新 GeminiService
檔案: /backend/DramaLing.Api/Services/GeminiService.cs
修改位置: 第54行的 AnalyzeSentenceAsync 方法
當前方法簽名:
public async Task<SentenceAnalysisResponse> AnalyzeSentenceAsync(string inputText)
修改後簽名:
public async Task<SentenceAnalysisResponse> AnalyzeSentenceAsync(
string inputText,
string userLevel = "A2")
Prompt 動態化 (第63-98行):
// 替換現有的固定 prompt
private string BuildSentenceAnalysisPrompt(string inputText, string userLevel)
{
var targetRange = CEFRLevelService.GetTargetLevelRange(userLevel);
return $@"
請分析以下英文句子,提供完整的分析:
句子:{inputText}
學習者程度:{userLevel}
請按照以下JSON格式回應,不要包含任何其他文字:
{{
""translation"": ""自然流暢的繁體中文翻譯"",
""explanation"": ""詳細解釋句子的語法結構、詞彙特點、使用場景和學習要點"",
""grammarCorrection"": {{
""hasErrors"": false,
""originalText"": ""{inputText}"",
""correctedText"": null,
""corrections"": []
}},
""highValueWords"": [""重要詞彙1"", ""重要詞彙2""],
""wordAnalysis"": {{
""單字"": {{
""translation"": ""中文翻譯"",
""definition"": ""英文定義"",
""partOfSpeech"": ""詞性"",
""pronunciation"": ""音標"",
""isHighValue"": true,
""difficultyLevel"": ""CEFR等級""
}}
}}
}}
要求:
1. 翻譯要自然流暢,符合中文語法
2. 解釋要具體有用,不要空泛
3. **基於學習者程度({userLevel}),標記 {targetRange} 等級的詞彙為高價值**
4. 如有語法錯誤請指出並修正
5. 確保JSON格式正確
高價值判定邏輯:
- 學習者程度: {userLevel}
- 高價值範圍: {targetRange}
- 太簡單的詞彙(≤{userLevel})不要標記為高價值
- 太難的詞彙(>{targetRange})謹慎標記
- 重點關注 {targetRange} 範圍內的詞彙
";
}
2.3 後處理邏輯新增
// 在 AnalyzeSentenceAsync 方法中新增後處理
private SentenceAnalysisResponse PostProcessHighValueWords(
SentenceAnalysisResponse result,
string userLevel)
{
// 二次驗證高價值詞彙判定,確保 AI 判定正確
foreach (var wordPair in result.WordAnalysis)
{
var word = wordPair.Value;
word.IsHighValue = CEFRLevelService.IsHighValueForUser(
word.DifficultyLevel,
userLevel);
}
// 更新高價值詞彙列表
result.HighValueWords = result.WordAnalysis
.Where(w => w.Value.IsHighValue)
.Select(w => w.Key)
.ToList();
return result;
}
📋 Phase 3: Controller 層整合 (第6-7天)
3.1 更新 AnalyzeSentence API
檔案: /backend/DramaLing.Api/Controllers/AIController.cs
位置: 第501行的 AnalyzeSentence 方法
在第566行前新增用戶程度取得邏輯:
// 第560行後,第566行 aiAnalysis 調用前新增:
// 取得用戶英語程度
string userLevel = request.UserLevel;
if (string.IsNullOrEmpty(userLevel))
{
// 如果 API 請求未提供程度,嘗試從用戶資料取得
var userId = await _authService.GetUserIdFromTokenAsync(Request.Headers.Authorization);
if (userId.HasValue)
{
var user = await _context.Users.FindAsync(userId.Value);
userLevel = user?.EnglishLevel ?? "A2";
_logger.LogInformation("Retrieved user level from database: {UserLevel}", userLevel);
}
else
{
userLevel = "A2"; // 未登入用戶預設程度
_logger.LogInformation("Using default level for anonymous user: A2");
}
}
_logger.LogInformation("Using user level for analysis: {UserLevel}", userLevel);
修改第566行的 AI 調用:
// 原本:
// var aiAnalysis = await _geminiService.AnalyzeSentenceAsync(request.InputText);
// 修改為:
var aiAnalysis = await _geminiService.AnalyzeSentenceAsync(request.InputText, userLevel);
3.2 回應資料增強
位置: 第573行的 baseResponseData 物件
新增用戶程度相關資訊:
var baseResponseData = new
{
AnalysisId = Guid.NewGuid(),
InputText = request.InputText,
UserLevel = userLevel, // 新增:顯示使用的程度
HighValueCriteria = CEFRLevelService.GetTargetLevelRange(userLevel), // 新增:顯示高價值判定範圍
GrammarCorrection = aiAnalysis.GrammarCorrection,
SentenceMeaning = new
{
Translation = aiAnalysis.Translation,
Explanation = aiAnalysis.Explanation
},
FinalAnalysisText = finalText ?? request.InputText,
WordAnalysis = aiAnalysis.WordAnalysis,
HighValueWords = aiAnalysis.HighValueWords,
PhrasesDetected = new object[0]
};
📋 Phase 4: 前端個人化體驗 (第8-11天)
4.1 建立用戶程度設定頁面
新檔案: /frontend/app/settings/page.tsx
'use client'
import { useState, useEffect } from 'react'
interface LanguageLevel {
value: string;
label: string;
description: string;
examples: string[];
}
export default function SettingsPage() {
const [userLevel, setUserLevel] = useState('A2');
const [isLoading, setIsLoading] = useState(false);
const levels: LanguageLevel[] = [
{
value: 'A1',
label: 'A1 - 初學者',
description: '能理解基本詞彙和簡單句子',
examples: ['hello', 'good', 'house', 'eat', 'happy']
},
{
value: 'A2',
label: 'A2 - 基礎',
description: '能處理日常對話和常見主題',
examples: ['important', 'difficult', 'interesting', 'beautiful', 'understand']
},
{
value: 'B1',
label: 'B1 - 中級',
description: '能理解清楚標準語言的要點',
examples: ['analyze', 'opportunity', 'environment', 'responsibility', 'development']
},
{
value: 'B2',
label: 'B2 - 中高級',
description: '能理解複雜文本的主要內容',
examples: ['sophisticated', 'implications', 'comprehensive', 'substantial', 'methodology']
},
{
value: 'C1',
label: 'C1 - 高級',
description: '能流利表達,理解含蓄意思',
examples: ['meticulous', 'predominantly', 'intricate', 'corroborate', 'paradigm']
},
{
value: 'C2',
label: 'C2 - 精通',
description: '接近母語水平',
examples: ['ubiquitous', 'ephemeral', 'perspicacious', 'multifarious', 'idiosyncratic']
}
];
const saveUserLevel = async () => {
setIsLoading(true);
try {
// 保存到本地存儲
localStorage.setItem('userEnglishLevel', userLevel);
// 如果用戶已登入,也保存到伺服器
const token = localStorage.getItem('authToken');
if (token) {
await fetch('/api/user/update-level', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${token}`
},
body: JSON.stringify({ englishLevel: userLevel })
});
}
alert('✅ 程度設定已保存!系統將為您提供個人化的詞彙標記。');
} catch (error) {
console.error('Error saving user level:', error);
alert('❌ 保存失敗,請稍後再試');
} finally {
setIsLoading(false);
}
};
const getHighValueRange = (level: string) => {
const ranges = {
'A1': 'A2-B1',
'A2': 'B1-B2',
'B1': 'B2-C1',
'B2': 'C1-C2',
'C1': 'C1-C2',
'C2': 'C1-C2'
};
return ranges[level as keyof typeof ranges] || 'B1-B2';
};
return (
<div className="max-w-4xl mx-auto p-6">
<div className="mb-8">
<h1 className="text-3xl font-bold mb-4">🎯 英語程度設定</h1>
<p className="text-gray-600">
設定您的英語程度,系統將為您提供個人化的詞彙學習建議。
設定後,我們會重點標記比您目前程度高1-2級的詞彙。
</p>
</div>
<div className="grid gap-4 mb-8">
{levels.map(level => (
<label
key={level.value}
className={`
p-6 border-2 rounded-xl cursor-pointer transition-all hover:shadow-md
${userLevel === level.value
? 'border-blue-500 bg-blue-50 shadow-md'
: 'border-gray-200 hover:border-gray-300'
}
`}
>
<input
type="radio"
name="level"
value={level.value}
checked={userLevel === level.value}
onChange={(e) => setUserLevel(e.target.value)}
className="sr-only"
/>
<div className="flex items-start justify-between">
<div className="flex-1">
<div className="font-bold text-xl text-gray-800 mb-2">
{level.label}
</div>
<div className="text-gray-600 mb-3">
{level.description}
</div>
<div className="flex flex-wrap gap-2">
{level.examples.map(example => (
<span
key={example}
className="px-3 py-1 bg-gray-100 text-gray-700 rounded-full text-sm"
>
{example}
</span>
))}
</div>
</div>
{userLevel === level.value && (
<div className="ml-4">
<div className="w-6 h-6 bg-blue-500 rounded-full flex items-center justify-center">
<svg className="w-4 h-4 text-white" fill="currentColor" viewBox="0 0 20 20">
<path fillRule="evenodd" d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z" clipRule="evenodd" />
</svg>
</div>
</div>
)}
</div>
</label>
))}
</div>
{/* 個人化效果預覽 */}
<div className="bg-gradient-to-r from-blue-50 to-indigo-50 p-6 rounded-xl mb-8">
<h3 className="font-bold text-lg text-blue-800 mb-3">
💡 您的個人化學習效果預覽
</h3>
<div className="grid md:grid-cols-2 gap-4">
<div>
<h4 className="font-semibold text-blue-700 mb-2">高價值詞彙範圍</h4>
<p className="text-blue-600">
系統將重點標記 <span className="font-bold">{getHighValueRange(userLevel)}</span> 等級的詞彙
</p>
</div>
<div>
<h4 className="font-semibold text-blue-700 mb-2">學習建議</h4>
<p className="text-blue-600 text-sm">
專注於比您目前程度({userLevel})高1-2級的詞彙,既有挑戰性又不會太困難
</p>
</div>
</div>
</div>
<button
onClick={saveUserLevel}
disabled={isLoading}
className={`
w-full py-4 rounded-xl font-semibold text-lg transition-all
${isLoading
? 'bg-gray-300 text-gray-500 cursor-not-allowed'
: 'bg-blue-500 text-white hover:bg-blue-600 shadow-lg hover:shadow-xl'
}
`}
>
{isLoading ? '⏳ 保存中...' : '✅ 保存程度設定'}
</button>
<div className="mt-4 text-center">
<p className="text-sm text-gray-500">
💡 提示: 您隨時可以回到這裡調整程度設定
</p>
</div>
</div>
);
}
4.2 更新導航選單
檔案: /frontend/components/Navigation.tsx
新增設定頁面連結:
// 在現有導航項目中新增
<Link
href="/settings"
className={`px-3 py-2 rounded-md text-sm font-medium ${
pathname === '/settings'
? 'bg-blue-100 text-blue-700'
: 'text-gray-600 hover:text-gray-900'
}`}
>
⚙️ 設定
</Link>
4.3 修改句子分析頁面
檔案: /frontend/app/generate/page.tsx
修改位置: 第39行的 handleAnalyzeSentence 函數
新增用戶程度取得邏輯:
const handleAnalyzeSentence = async () => {
if (!textInput?.trim()) {
alert('🔍 請輸入要分析的句子')
return
}
// 取得用戶設定的程度
const userLevel = localStorage.getItem('userEnglishLevel') || 'A2';
console.log('🎯 使用用戶程度:', userLevel);
console.log('✅ 開始分析,設定 loading 狀態')
setIsAnalyzing(true)
try {
console.log('🌐 發送API請求到:', 'http://localhost:5000/api/ai/analyze-sentence')
const response = await fetch('http://localhost:5000/api/ai/analyze-sentence', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
inputText: textInput,
userLevel: userLevel, // 傳遞用戶程度
analysisMode: 'full'
})
})
// ... 其餘現有邏輯保持不變
}
// ... catch 和 finally 保持不變
};
4.4 個人化詞彙標記顯示
檔案: /frontend/app/generate/page.tsx
位置: 詞彙顯示區域 (分析結果視圖中)
新增個人化詞彙標記組件:
// 新增輔助函數
const getLevelIndex = (level: string): number => {
const levels = ['A1', 'A2', 'B1', 'B2', 'C1', 'C2'];
return levels.indexOf(level.toUpperCase());
};
const getWordValueStyle = (wordLevel: string, userLevel: string) => {
const userIndex = getLevelIndex(userLevel);
const wordIndex = getLevelIndex(wordLevel);
if (wordIndex <= userIndex) {
return {
bg: 'bg-green-100 border-green-300',
text: 'text-green-800',
label: '已掌握',
icon: '✅'
};
} else if (wordIndex === userIndex + 1) {
return {
bg: 'bg-yellow-100 border-yellow-300',
text: 'text-yellow-800',
label: '適中挑戰',
icon: '🎯'
};
} else if (wordIndex === userIndex + 2) {
return {
bg: 'bg-orange-100 border-orange-300',
text: 'text-orange-800',
label: '高挑戰',
icon: '🚀'
};
} else {
return {
bg: 'bg-red-100 border-red-300',
text: 'text-red-800',
label: '太難',
icon: '⚠️'
};
}
};
// 詞彙顯示組件
function WordAnalysisCard({ word, analysis, userLevel }: Props) {
const style = getWordValueStyle(analysis.difficultyLevel, userLevel);
return (
<div className={`p-4 rounded-lg border-2 ${style.bg} ${style.text}`}>
<div className="flex items-center justify-between mb-2">
<span className="font-bold text-lg">{word}</span>
<div className="flex items-center gap-2">
<span className="text-sm">{analysis.difficultyLevel}</span>
<span className="text-xs px-2 py-1 rounded-full bg-white bg-opacity-50">
{style.icon} {style.label}
</span>
</div>
</div>
<div className="text-sm space-y-1">
<p><strong>翻譯:</strong> {analysis.translation}</p>
<p><strong>發音:</strong> {analysis.pronunciation}</p>
<p><strong>定義:</strong> {analysis.definition}</p>
</div>
{analysis.isHighValue && (
<div className="mt-3 p-2 bg-white bg-opacity-30 rounded text-xs">
💎 <strong>高價值詞彙</strong> - 適合您目前程度的學習重點
</div>
)}
</div>
);
}
4.5 新增程度指示器
檔案: /frontend/app/generate/page.tsx
位置: 使用次數顯示區域後 (第306行後)
{/* 個人化程度指示器 */}
<div className="text-center text-sm text-gray-600 mt-2">
{(() => {
const userLevel = localStorage.getItem('userEnglishLevel') || 'A2';
const targetRange = getTargetLevelRange(userLevel);
return (
<div className="flex items-center justify-center gap-2">
<span>🎯 您的程度: {userLevel}</span>
<span className="text-gray-400">|</span>
<span>💎 高價值範圍: {targetRange}</span>
<Link
href="/settings"
className="text-blue-500 hover:text-blue-700 ml-2"
>
調整 ⚙️
</Link>
</div>
);
})()}
</div>
📋 Phase 5: 進階功能 (第12-14天)
5.1 建立用戶程度更新 API
檔案: /backend/DramaLing.Api/Controllers/UsersController.cs (可能需要新建)
[ApiController]
[Route("api/[controller]")]
[Authorize]
public class UsersController : ControllerBase
{
private readonly DramaLingDbContext _context;
private readonly IAuthService _authService;
[HttpPost("update-level")]
public async Task<ActionResult> UpdateUserLevel([FromBody] UpdateLevelRequest request)
{
var userId = await _authService.GetUserIdFromTokenAsync(Request.Headers.Authorization);
if (userId == null) return Unauthorized();
var user = await _context.Users.FindAsync(userId.Value);
if (user == null) return NotFound();
// 驗證等級有效性
var validLevels = new[] { "A1", "A2", "B1", "B2", "C1", "C2" };
if (!validLevels.Contains(request.EnglishLevel.ToUpper()))
{
return BadRequest("Invalid English level");
}
user.EnglishLevel = request.EnglishLevel.ToUpper();
user.LevelUpdatedAt = DateTime.UtcNow;
user.UpdatedAt = DateTime.UtcNow;
await _context.SaveChangesAsync();
return Ok(new {
Success = true,
Data = new {
UserId = user.Id,
EnglishLevel = user.EnglishLevel,
UpdatedAt = user.LevelUpdatedAt
},
Message = "User level updated successfully"
});
}
}
public class UpdateLevelRequest
{
public string EnglishLevel { get; set; } = string.Empty;
}
5.2 程度建議系統
檔案: /frontend/components/LevelRecommendation.tsx (新建)
interface LevelRecommendationProps {
userLevel: string;
analysisResult: any;
}
export function LevelRecommendation({ userLevel, analysisResult }: LevelRecommendationProps) {
const recommendations = {
A1: {
focus: "先掌握基礎詞彙,建立信心",
nextGoal: "目標掌握所有 A2 詞彙",
tips: ["多看簡單對話", "重複練習基礎詞彙", "先不要急於挑戰難詞"]
},
A2: {
focus: "挑戰 B1 詞彙,擴展詞彙量",
nextGoal: "準備進入中級程度",
tips: ["開始接觸新聞", "學習片語搭配", "練習複雜句型"]
},
B1: {
focus: "精進 B2 詞彙,提升表達精準度",
nextGoal: "達到中高級水平",
tips: ["閱讀專業文章", "學習學術詞彙", "練習論述表達"]
},
B2: {
focus: "挑戰 C1 詞彙,培養高階表達",
nextGoal: "邁向高級程度",
tips: ["深度閱讀", "學習抽象概念詞彙", "練習批判性思考表達"]
},
C1: {
focus: "精進 C2 詞彙,達到接近母語水平",
nextGoal: "達到精通程度",
tips: ["文學作品閱讀", "學習專業術語", "練習修辭技巧"]
},
C2: {
focus: "保持語言敏感度,精進表達細節",
nextGoal: "維持高水準",
tips: ["關注語言變化", "學習方言俚語", "練習創意表達"]
}
};
const rec = recommendations[userLevel as keyof typeof recommendations] || recommendations.A2;
return (
<div className="bg-gradient-to-r from-purple-50 to-pink-50 p-6 rounded-xl">
<h3 className="font-bold text-lg text-purple-800 mb-4">
🎯 基於您程度({userLevel})的個人化建議
</h3>
<div className="grid md:grid-cols-2 gap-4">
<div>
<h4 className="font-semibold text-purple-700 mb-2">當前重點</h4>
<p className="text-purple-600">{rec.focus}</p>
</div>
<div>
<h4 className="font-semibold text-purple-700 mb-2">下階段目標</h4>
<p className="text-purple-600">{rec.nextGoal}</p>
</div>
</div>
<div className="mt-4">
<h4 className="font-semibold text-purple-700 mb-2">學習技巧</h4>
<ul className="text-purple-600 text-sm space-y-1">
{rec.tips.map((tip, index) => (
<li key={index} className="flex items-center gap-2">
<span className="text-purple-400">•</span>
{tip}
</li>
))}
</ul>
</div>
</div>
);
}
📋 Phase 6: 測試與驗證 (第13-14天)
6.1 單元測試
新檔案: /backend/DramaLing.Api.Tests/Services/CEFRLevelServiceTests.cs
using Xunit;
using DramaLing.Api.Services;
public class CEFRLevelServiceTests
{
[Theory]
[InlineData("A2", "A1", false)] // 太簡單
[InlineData("A2", "A2", false)] // 同等級
[InlineData("A2", "B1", true)] // +1級,高價值
[InlineData("A2", "B2", true)] // +2級,高價值
[InlineData("A2", "C1", false)] // 太難
public void IsHighValueForUser_ShouldReturnExpectedResult(
string userLevel, string wordLevel, bool expected)
{
var result = CEFRLevelService.IsHighValueForUser(wordLevel, userLevel);
Assert.Equal(expected, result);
}
[Theory]
[InlineData("A1", "A2-B1")]
[InlineData("A2", "B1-B2")]
[InlineData("B1", "B2-C1")]
[InlineData("C1", "C2")]
public void GetTargetLevelRange_ShouldReturnCorrectRange(
string userLevel, string expectedRange)
{
var result = CEFRLevelService.GetTargetLevelRange(userLevel);
Assert.Equal(expectedRange, result);
}
}
6.2 整合測試腳本
新檔案: /test-personalized-analysis.sh
#!/bin/bash
echo "🧪 測試個人化高價值詞彙判定"
# 測試句子
TEST_SENTENCE="The sophisticated analysis reveals important implications for understanding complex linguistic patterns."
echo "📝 測試句子: $TEST_SENTENCE"
echo ""
# 測試不同程度用戶
for level in A1 A2 B1 B2 C1; do
echo "🎯 測試 $level 程度用戶:"
response=$(curl -s -X POST http://localhost:5000/api/ai/analyze-sentence \
-H "Content-Type: application/json" \
-d "{\"inputText\": \"$TEST_SENTENCE\", \"userLevel\": \"$level\"}")
# 提取高價值詞彙和判定範圍
highValueWords=$(echo $response | jq -r '.data.highValueWords[]?' 2>/dev/null | tr '\n' ',' | sed 's/,$//')
criteria=$(echo $response | jq -r '.data.highValueCriteria?' 2>/dev/null)
echo " 高價值範圍: $criteria"
echo " 標記詞彙: $highValueWords"
echo ""
done
echo "✅ 測試完成!檢查每個程度用戶的高價值詞彙是否不同"
⏰ 開發時程表
| 天數 | 階段 | 主要任務 | 預計工時 | 檔案涉及 |
|---|---|---|---|---|
| Day 1-2 | 資料模型 | User 實體擴充、API 擴充、資料庫遷移 | 12h | User.cs, AIController.cs |
| Day 3-5 | 判定邏輯 | CEFRLevelService、GeminiService 重構 | 16h | CEFRLevelService.cs, GeminiService.cs |
| Day 6-7 | Controller 整合 | API 邏輯整合、錯誤處理 | 10h | AIController.cs |
| Day 8-10 | 前端設定頁 | 程度設定介面、導航整合 | 18h | settings/page.tsx, Navigation.tsx |
| Day 11 | 前端分析整合 | generate 頁面修改、個人化顯示 | 8h | generate/page.tsx |
| Day 12-13 | 測試開發 | 單元測試、整合測試、腳本 | 12h | 測試檔案 |
| Day 14 | 優化除錯 | 性能調整、UI 優化、文檔更新 | 6h | 各檔案 |
總計: 82 工時 (約2週全職開發)
🎯 驗收標準
功能需求驗收
- 個人化判定: A2 用戶看到 B1-B2 詞彙為高價值,C1 用戶只看到 C2 為高價值
- 設定頁面: 用戶可以選擇並保存 A1-C2 程度
- 即時生效: 程度修改後,下次分析立即使用新程度
- 回退機制: 未設定程度的用戶預設為 A2
- 本地存儲: 程度設定保存在 localStorage,未登入用戶也能使用
技術需求驗收
- API 相容性: 現有前端不傳 userLevel 時仍正常運作
- 效能基準: API 回應時間增加 < 200ms
- 錯誤處理: 無效程度參數時優雅降級
- 資料完整性: 用戶程度資料正確遷移和驗證
- 測試覆蓋: 單元測試覆蓋率 > 80%
用戶體驗驗收
- 視覺清晰: 不同價值詞彙有明顯的視覺區分
- 操作直觀: 程度設定流程簡單明確
- 回饋及時: 程度變更後有明確的確認訊息
- 學習指導: 提供基於程度的學習建議
🚀 部署與上線
上線前檢查清單
- 所有測試通過
- 資料庫遷移在生產環境執行
- 現有用戶資料正確設定預設程度 (A2)
- API 文檔更新完成
- 監控系統配置完成
上線後監控指標
- 功能使用率: 多少用戶設定了程度
- 學習效果: 個人化後的詞彙點擊率變化
- 錯誤率: 新功能相關的錯誤追蹤
- 效能影響: API 回應時間變化
📞 風險評估與應對
技術風險
-
AI Prompt 複雜化: 可能影響回應品質
- 應對: 充分測試,準備回退機制
-
資料庫效能: 新增欄位可能影響查詢效能
- 應對: 建立適當索引,監控查詢時間
-
前端相容性: 新功能可能影響現有使用者體驗
- 應對: 向下相容設計,預設值保持原有行為
產品風險
-
用戶理解難度: CEFR 程度對一般用戶可能太專業
- 應對: 提供簡單的描述和範例詞彙
-
設定複雜性: 增加設定可能讓產品變複雜
- 應對: 智能預設值,讓設定變為可選功能
📋 成功指標
定量指標 (3個月後評估)
- 程度設定率: > 60% 的活躍用戶設定了程度
- 詞彙點擊提升: 高價值詞彙點擊率提升 > 30%
- 學習時長: 平均學習時長提升 > 20%
- 用戶滿意度: 功能滿意度 > 4.0/5.0
定性指標
- 學習體驗: 用戶反應詞彙推薦更精準
- 挑戰適中: 減少「太難」或「太簡單」的反饋
- 進步感知: 用戶感受到明確的學習進階路徑
© 2025 DramaLing Development Team 文檔建立: 2025-01-18 計劃核准: 待用戶確認