dramaling-vocab-learning/note/plan/PERSONALIZED_HIGH_VALUE_WOR...

32 KiB
Raw Blame History

🎯 個人化高價值詞彙判定系統 - 詳細開發計劃

專案: 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 回應時間變化

📞 風險評估與應對

技術風險

  1. AI Prompt 複雜化: 可能影響回應品質

    • 應對: 充分測試,準備回退機制
  2. 資料庫效能: 新增欄位可能影響查詢效能

    • 應對: 建立適當索引,監控查詢時間
  3. 前端相容性: 新功能可能影響現有使用者體驗

    • 應對: 向下相容設計,預設值保持原有行為

產品風險

  1. 用戶理解難度: CEFR 程度對一般用戶可能太專業

    • 應對: 提供簡單的描述和範例詞彙
  2. 設定複雜性: 增加設定可能讓產品變複雜

    • 應對: 智能預設值,讓設定變為可選功能

📋 成功指標

定量指標 (3個月後評估)

  • 程度設定率: > 60% 的活躍用戶設定了程度
  • 詞彙點擊提升: 高價值詞彙點擊率提升 > 30%
  • 學習時長: 平均學習時長提升 > 20%
  • 用戶滿意度: 功能滿意度 > 4.0/5.0

定性指標

  • 學習體驗: 用戶反應詞彙推薦更精準
  • 挑戰適中: 減少「太難」或「太簡單」的反饋
  • 進步感知: 用戶感受到明確的學習進階路徑

© 2025 DramaLing Development Team 文檔建立: 2025-01-18 計劃核准: 待用戶確認