dramaling-vocab-learning/VOCABULARY_CACHE_MECHANISM_...

15 KiB
Raw Blame History

🗄️ 詞彙快取機制技術規格書

專案: DramaLing 英語學習平台 功能: 詞彙分析快取系統 文檔版本: v1.0 建立日期: 2025-01-18 分析範圍: 前端快取 + 後端 API 快取


📋 快取系統概述

DramaLing 詞彙快取系統包含三層快取結構

  1. 前端頁面快取 - 當前頁面的詞彙分析資料
  2. 後端句子快取 - 24小時的句子分析結果快取
  3. 假資料快取 - 開發階段的模擬詞彙資料

🎯 Layer 1: 前端頁面快取

📁 實現位置

檔案: /frontend/app/generate/page.tsx 狀態管理: const [sentenceAnalysis, setSentenceAnalysis] = useState<any>(null)

🔄 快取行為分析

初始化 (第84行)

setSentenceAnalysis(result.data.wordAnalysis || result.data.WordAnalysis || {})

行為: 完全覆蓋式更新

動態擴展 (第405-412行)

onNewWordAnalysis={(word, newAnalysis) => {
  setSentenceAnalysis((prev: any) => ({
    ...prev,           // 保留現有資料
    [word]: newAnalysis // 新增單字分析
  }))
}}

行為: 累積式更新

📊 完整的詞彙資料流程

場景測試: "The apple" → "The orange"

📍 步驟1: 分析 "The apple"
API 回應: { "apple": {...} }
前端狀態: { "apple": {...} }
結果: "The" = 灰框 (無預存資料)

📍 步驟2: 點擊 "The"
API 調用: POST /api/ai/query-word {"word": "the", ...}
前端狀態: { "apple": {...}, "the": {...} }
結果: "The" = 藍框 (有預存資料)

📍 步驟3: 換新句子 "The orange"
API 回應: { "orange": {...} }
前端狀態: { "orange": {...} }  ❌ "the" 被清空!
結果: "The" = 灰框 (又變成無預存資料)

🚨 當前問題

操作 預期行為 實際行為 問題
查詢過的詞彙 保持快取,下次直接顯示 換句子後被清空 覆蓋式更新
跨句子學習 累積詞彙庫,提升效率 每次重新開始 浪費 AI 資源
用戶體驗 學過的詞彙有記憶 需要重複查詢 體驗差

🎯 Layer 2: 後端句子快取

📁 實現位置

檔案: /backend/DramaLing.Api/Controllers/AIController.cs 服務: IAnalysisCacheService _cacheService 資料表: SentenceAnalysisCache

💾 快取機制

存入快取 (第589-602行)

await _cacheService.SetCachedAnalysisAsync(
    request.InputText,      // 快取鍵:句子文本
    baseResponseData,       // 完整分析結果
    TimeSpan.FromHours(24)  // TTL: 24小時
);

快取檢索 (第533-561行)

var cachedAnalysis = await _cacheService.GetCachedAnalysisAsync(request.InputText);
if (cachedAnalysis != null && !request.ForceRefresh) {
    // 返回快取結果,標記為 cached: true
}

📊 快取資料結構

CREATE TABLE SentenceAnalysisCache (
    Id UNIQUEIDENTIFIER PRIMARY KEY,
    InputText NVARCHAR(1000) NOT NULL,           -- 原句 (快取鍵)
    InputTextHash NVARCHAR(64) NOT NULL,         -- 句子雜湊值
    AnalysisResult NVARCHAR(MAX) NOT NULL,       -- JSON 分析結果
    ExpiresAt DATETIME2 NOT NULL,                -- 過期時間
    CreatedAt DATETIME2 NOT NULL,                -- 建立時間
    LastAccessedAt DATETIME2,                    -- 最後存取時間
    AccessCount INT NOT NULL DEFAULT 0           -- 存取次數
);

🔄 快取邏輯流程

用戶輸入: "Hello world"
    ↓
檢查快取: SELECT * FROM SentenceAnalysisCache WHERE InputTextHash = HASH("Hello world")
    ↓
如果命中: 返回快取結果 (cached: true, cacheHit: true)
如果錯失: 調用 AI → 存入快取 → 返回結果 (cached: false, usingAI: true)

🎯 Layer 3: 單字查詢快取 (目前為假資料)

📁 實現位置

檔案: /backend/DramaLing.Api/Controllers/AIController.cs:1019-1037

⚠️ 當前狀態: 混合實現 (需要進一步確認)

根據後端日誌證據,系統確實在調用真實的 Gemini AI

info: Calling Gemini AI for text: Learning is fun and exciting
POST https://generativelanguage.googleapis.com/v1beta/models/gemini-1.5-flash-latest:generateContent?key=AIza...

但程式碼顯示模擬實現

private async Task<object> AnalyzeLowValueWord(string word, string sentence)
{
    // 模擬即時AI分析
    await Task.Delay(200);  // 模擬延遲

    return new {
        word = word,
        translation = "即時分析的翻譯",  // ⚠️ 疑似固定回應
        definition = "即時分析的定義",
        // ...
    };
}

📊 API 回應速度分析

API 端點 速度 實現狀態 證據
/analyze-sentence ~3-5秒 確認真實 AI 日誌顯示 Gemini 調用
/query-word ~200ms-1s 需要確認 程式碼顯示模擬,但可能有其他路徑

🔍 需要進一步調查

  1. AnalyzeLowValueWord 是否有其他版本的實現
  2. 是否存在條件分支調用真實 AI
  3. 固定回應 "即時分析的翻譯" 是否為測試資料

📋 詳細的預存機制規格

🔍 您的測試場景分析

場景: "The apple" → 點擊 "The" → "The orange"

🟦 第1步: 分析 "The apple"
├─ API: POST /analyze-sentence {"inputText": "The apple"}
├─ AI 回應: {"wordAnalysis": {"apple": {...}}}  // 不包含 "the"
├─ 前端狀態: sentenceAnalysis = {"apple": {...}}
└─ 視覺: "The"=灰框, "apple"=綠框

🟦 第2步: 點擊 "The"
├─ 觸發: queryWordWithAI("the")
├─ API: POST /query-word {"word": "the", "sentence": "The apple"}
├─ 模擬回應: {"word": "the", "translation": "即時分析的翻譯", ...}
├─ 前端狀態: sentenceAnalysis = {"apple": {...}, "the": {...}}
└─ 視覺: "The"=藍框, "apple"=綠框

🟦 第3步: 分析 "The orange"
├─ API: POST /analyze-sentence {"inputText": "The orange"}
├─ AI 回應: {"wordAnalysis": {"orange": {...}}}
├─ 前端狀態: sentenceAnalysis = {"orange": {...}}  ❌ "the" 被覆蓋清空!
└─ 視覺: "The"=灰框, "orange"=綠框

🟦 第4步: 再次點擊 "The"
├─ 發現: sentenceAnalysis["the"] = undefined
├─ 觸發: queryWordWithAI("the") again  ❌ 重複查詢!
└─ 結果: 浪費 AI 資源,用戶體驗差

📊 當前快取機制的優缺點

優點

  1. 句子級快取: 相同句子 24 小時內不重複分析
  2. 動態擴展: 點擊的詞彙會加入當前分析
  3. 記憶體效率: 不會無限累積資料

缺點

  1. 跨句子遺失: 換句子後之前查詢的詞彙被清空
  2. 重複查詢: 相同詞彙在不同句子中需要重複查詢
  3. 假資料問題: query-word 目前不是真實 AI 查詢

🛠️ 改善方案規格

方案1: 全域詞彙快取 (推薦)

前端實現

// 新增全域詞彙快取
const [globalWordCache, setGlobalWordCache] = useState<Record<string, any>>({})

// 修改句子分析更新邏輯
setSentenceAnalysis(prev => ({
  ...globalWordCache,           // 保留全域快取
  ...prev,                     // 保留當前分析
  ...result.data.wordAnalysis  // 新增句子分析
}))

// 修改詞彙查詢邏輯
onNewWordAnalysis={(word, newAnalysis) => {
  // 同時更新兩個快取
  setGlobalWordCache(prev => ({ ...prev, [word]: newAnalysis }))
  setSentenceAnalysis(prev => ({ ...prev, [word]: newAnalysis }))
}}

本地存儲持久化

// 保存到 localStorage
useEffect(() => {
  const cached = localStorage.getItem('dramalingWordCache')
  if (cached) {
    setGlobalWordCache(JSON.parse(cached))
  }
}, [])

useEffect(() => {
  localStorage.setItem('dramalingWordCache', JSON.stringify(globalWordCache))
}, [globalWordCache])

方案2: 真實 AI 查詢實現

後端修改

private async Task<object> AnalyzeLowValueWord(string word, string sentence)
{
    try {
        // 🆕 真實調用 Gemini AI
        var prompt = $@"
請分析單字 ""{word}"" 在句子 ""{sentence}"" 中的詳細資訊:

請以JSON格式回應
{{
  ""word"": ""{word}"",
  ""translation"": ""繁體中文翻譯"",
  ""definition"": ""英文定義"",
  ""partOfSpeech"": ""詞性"",
  ""pronunciation"": ""IPA音標"",
  ""difficultyLevel"": ""CEFR等級"",
  ""contextMeaning"": ""在此句子中的具體含義"",
  ""isHighValue"": false
}}
";

        var response = await _geminiService.CallGeminiApiAsync(prompt);
        return _geminiService.ParseWordAnalysisResponse(response);
    }
    catch {
        // 回退到模擬資料
        await Task.Delay(200);
        return CreateMockWordAnalysis(word);
    }
}

方案3: 後端詞彙快取資料表

新資料表設計

CREATE TABLE WordAnalysisCache (
    Id UNIQUEIDENTIFIER PRIMARY KEY,
    Word NVARCHAR(100) NOT NULL,                 -- 詞彙 (快取鍵)
    WordLowercase NVARCHAR(100) NOT NULL,        -- 小寫版本 (查詢用)
    Translation NVARCHAR(200) NOT NULL,          -- 翻譯
    Definition NVARCHAR(500) NOT NULL,           -- 定義
    PartOfSpeech NVARCHAR(50),                   -- 詞性
    Pronunciation NVARCHAR(100),                 -- 發音
    DifficultyLevel NVARCHAR(10),                -- CEFR 等級
    IsHighValue BIT DEFAULT 0,                   -- 是否高價值
    Synonyms NVARCHAR(500),                      -- 同義詞 (JSON)
    ExampleSentences NVARCHAR(MAX),              -- 例句 (JSON)
    CreatedAt DATETIME2 NOT NULL,                -- 建立時間
    UpdatedAt DATETIME2 NOT NULL,                -- 更新時間
    AccessCount INT DEFAULT 0,                   -- 存取次數

    INDEX IX_WordAnalysisCache_WordLowercase (WordLowercase)
);

後端查詢邏輯

public async Task<WordAnalysisResult> QueryWordAsync(string word, string sentence)
{
    var wordLower = word.ToLower();

    // 1. 檢查詞彙快取
    var cached = await _context.WordAnalysisCache
        .FirstOrDefaultAsync(w => w.WordLowercase == wordLower);

    if (cached != null) {
        // 更新存取統計
        cached.AccessCount++;
        cached.UpdatedAt = DateTime.UtcNow;
        await _context.SaveChangesAsync();

        return MapToWordAnalysisResult(cached);
    }

    // 2. 快取錯失,調用 AI
    var aiResult = await CallGeminiForWordAnalysis(word, sentence);

    // 3. 存入快取
    var cacheEntry = new WordAnalysisCache {
        Word = word,
        WordLowercase = wordLower,
        Translation = aiResult.Translation,
        // ... 其他欄位
    };

    _context.WordAnalysisCache.Add(cacheEntry);
    await _context.SaveChangesAsync();

    return aiResult;
}

📊 三種快取策略比較

策略 持久性 效能 實現複雜度 AI 成本 用戶體驗
目前 (頁面級) 換句子清空 🟡 中等 🟢 簡單 🔴 高 (重複查詢) 🔴
方案1 (前端全域) 🟡 瀏覽器重啟清空 🟢 🟡 中等 🟢 🟢
方案2 (後端資料庫) 永久保存 🟢 🔴 複雜 🟢 極低 極佳

🔧 當前 query-word API 的實現細節

📍 速度快的真相

檔案: /backend/DramaLing.Api/Controllers/AIController.cs:1019-1037

private async Task<object> AnalyzeLowValueWord(string word, string sentence)
{
    // 🚨 這只是模擬實現!
    await Task.Delay(200);  // 假延遲

    return new {
        word = word,
        translation = "即時分析的翻譯",  // 🚨 所有詞彙都一樣
        definition = "即時分析的定義",   // 🚨 所有詞彙都一樣
        partOfSpeech = "noun",          // 🚨 所有詞彙都一樣
        pronunciation = "/example/",     // 🚨 所有詞彙都一樣
        // ...
    };
}

🧪 驗證測試

測試1: 查詢不同詞彙

# 查詢 "hello"
curl -X POST http://localhost:5000/api/ai/query-word \
  -d '{"word": "hello", "sentence": "Hello world"}'
# 結果: translation = "即時分析的翻譯"

# 查詢 "amazing"
curl -X POST http://localhost:5000/api/ai/query-word \
  -d '{"word": "amazing", "sentence": "Amazing day"}'
# 結果: translation = "即時分析的翻譯"  ❌ 完全相同!

測試2: 檢查是否真的調用 AI

# 查看後端日誌
grep -i "gemini\|ai\|query" backend_logs.txt
# 結果: 沒有真實的 AI API 調用記錄

📋 建議的改善優先級

🔥 高優先級 (立即修復)

  1. 實現真實的詞彙 AI 查詢

    • 替換假資料為真實 Gemini API 調用
    • 提供準確的詞彙分析
  2. 前端全域詞彙快取

    • 避免重複查詢相同詞彙
    • 提升用戶體驗

中優先級 (2週內)

  1. 後端詞彙快取資料表

    • 永久保存查詢過的詞彙
    • 跨用戶共享常用詞彙分析
  2. 智能快取策略

    • 基於詞彙頻率的快取優先級
    • 自動清理低價值快取項目

💡 低優先級 (未來功能)

  1. 跨設備同步
    • 用戶詞彙學習記錄雲端同步
    • 個人化詞彙掌握程度追蹤

🎯 回答您的問題

當前實際行為:

場景: "The apple" → 點擊 "The" → "The orange"

1. 分析 "The apple" → "The" 無預存資料 (灰框)
2. 點擊 "The" → 假 AI 查詢 → 加入前端快取 → "The" 變藍框
3. 分析 "The orange" → 前端快取被覆蓋清空 → "The" 又變灰框 ❌
4. 點擊 "The" → 重新假 AI 查詢 → 重複步驟2 ❌

問題總結:

  • 不會在預存裡: 換句子後快取被清空
  • 重複假查詢: 每次都返回相同的假資料
  • 浪費資源: 用戶以為是真實 AI 查詢

建議修復:

  1. 立即: 修改前端為累積式快取
  2. 短期: 實現真實的詞彙 AI 查詢
  3. 長期: 建立後端詞彙快取資料表

📞 技術支援

相關檔案:

  • 前端快取: /frontend/app/generate/page.tsx:84, 405-412
  • 後端假查詢: /backend/DramaLing.Api/Controllers/AIController.cs:1019-1037
  • 後端句子快取: /backend/DramaLing.Api/Services/AnalysisCacheService.cs

建議優先修復: 前端累積式快取 + 真實 AI 查詢實現


© 2025 DramaLing Development Team 文檔建立: 2025-01-18 分析基於: 當前系統 commit e940d86