# 🗄️ 詞彙快取機制技術規格書 **專案**: DramaLing 英語學習平台 **功能**: 詞彙分析快取系統 **文檔版本**: v1.0 **建立日期**: 2025-01-18 **分析範圍**: 前端快取 + 後端 API 快取 --- ## 📋 **快取系統概述** DramaLing 詞彙快取系統包含**三層快取結構**: 1. **前端頁面快取** - 當前頁面的詞彙分析資料 2. **後端句子快取** - 24小時的句子分析結果快取 3. **假資料快取** - 開發階段的模擬詞彙資料 --- ## 🎯 **Layer 1: 前端頁面快取** ### **📁 實現位置** **檔案**: `/frontend/app/generate/page.tsx` **狀態管理**: `const [sentenceAnalysis, setSentenceAnalysis] = useState(null)` ### **🔄 快取行為分析** #### **初始化** (第84行) ```typescript setSentenceAnalysis(result.data.wordAnalysis || result.data.WordAnalysis || {}) ``` **行為**: **完全覆蓋式更新** #### **動態擴展** (第405-412行) ```typescript 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行) ```csharp await _cacheService.SetCachedAnalysisAsync( request.InputText, // 快取鍵:句子文本 baseResponseData, // 完整分析結果 TimeSpan.FromHours(24) // TTL: 24小時 ); ``` #### **快取檢索** (第533-561行) ```csharp var cachedAnalysis = await _cacheService.GetCachedAnalysisAsync(request.InputText); if (cachedAnalysis != null && !request.ForceRefresh) { // 返回快取結果,標記為 cached: true } ``` ### **📊 快取資料結構** ```sql 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... ``` **但程式碼顯示模擬實現**: ```csharp private async Task 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: 全域詞彙快取 (推薦)** #### **前端實現** ```typescript // 新增全域詞彙快取 const [globalWordCache, setGlobalWordCache] = useState>({}) // 修改句子分析更新邏輯 setSentenceAnalysis(prev => ({ ...globalWordCache, // 保留全域快取 ...prev, // 保留當前分析 ...result.data.wordAnalysis // 新增句子分析 })) // 修改詞彙查詢邏輯 onNewWordAnalysis={(word, newAnalysis) => { // 同時更新兩個快取 setGlobalWordCache(prev => ({ ...prev, [word]: newAnalysis })) setSentenceAnalysis(prev => ({ ...prev, [word]: newAnalysis })) }} ``` #### **本地存儲持久化** ```typescript // 保存到 localStorage useEffect(() => { const cached = localStorage.getItem('dramalingWordCache') if (cached) { setGlobalWordCache(JSON.parse(cached)) } }, []) useEffect(() => { localStorage.setItem('dramalingWordCache', JSON.stringify(globalWordCache)) }, [globalWordCache]) ``` ### **方案2: 真實 AI 查詢實現** #### **後端修改** ```csharp private async Task 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: 後端詞彙快取資料表** #### **新資料表設計** ```sql 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) ); ``` #### **後端查詢邏輯** ```csharp public async Task 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` ```csharp private async Task AnalyzeLowValueWord(string word, string sentence) { // 🚨 這只是模擬實現! await Task.Delay(200); // 假延遲 return new { word = word, translation = "即時分析的翻譯", // 🚨 所有詞彙都一樣 definition = "即時分析的定義", // 🚨 所有詞彙都一樣 partOfSpeech = "noun", // 🚨 所有詞彙都一樣 pronunciation = "/example/", // 🚨 所有詞彙都一樣 // ... }; } ``` ### **🧪 驗證測試** #### **測試1: 查詢不同詞彙** ```bash # 查詢 "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** ```bash # 查看後端日誌 grep -i "gemini\|ai\|query" backend_logs.txt # 結果: 沒有真實的 AI API 調用記錄 ``` --- ## 📋 **建議的改善優先級** ### **🔥 高優先級 (立即修復)** 1. **實現真實的詞彙 AI 查詢** - 替換假資料為真實 Gemini API 調用 - 提供準確的詞彙分析 2. **前端全域詞彙快取** - 避免重複查詢相同詞彙 - 提升用戶體驗 ### **⚡ 中優先級 (2週內)** 3. **後端詞彙快取資料表** - 永久保存查詢過的詞彙 - 跨用戶共享常用詞彙分析 4. **智能快取策略** - 基於詞彙頻率的快取優先級 - 自動清理低價值快取項目 ### **💡 低優先級 (未來功能)** 5. **跨設備同步** - 用戶詞彙學習記錄雲端同步 - 個人化詞彙掌握程度追蹤 --- ## 🎯 **回答您的問題** ### **當前實際行為**: **場景**: "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