15 KiB
15 KiB
🗄️ 詞彙快取機制技術規格書
專案: DramaLing 英語學習平台 功能: 詞彙分析快取系統 文檔版本: v1.0 建立日期: 2025-01-18 分析範圍: 前端快取 + 後端 API 快取
📋 快取系統概述
DramaLing 詞彙快取系統包含三層快取結構:
- 前端頁面快取 - 當前頁面的詞彙分析資料
- 後端句子快取 - 24小時的句子分析結果快取
- 假資料快取 - 開發階段的模擬詞彙資料
🎯 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 | ❓ 需要確認 | 程式碼顯示模擬,但可能有其他路徑 |
🔍 需要進一步調查
AnalyzeLowValueWord是否有其他版本的實現- 是否存在條件分支調用真實 AI
- 固定回應 "即時分析的翻譯" 是否為測試資料
📋 詳細的預存機制規格
🔍 您的測試場景分析
場景: "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 資源,用戶體驗差
📊 當前快取機制的優缺點
✅ 優點
- 句子級快取: 相同句子 24 小時內不重複分析
- 動態擴展: 點擊的詞彙會加入當前分析
- 記憶體效率: 不會無限累積資料
❌ 缺點
- 跨句子遺失: 換句子後之前查詢的詞彙被清空
- 重複查詢: 相同詞彙在不同句子中需要重複查詢
- 假資料問題: 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 調用記錄
📋 建議的改善優先級
🔥 高優先級 (立即修復)
-
實現真實的詞彙 AI 查詢
- 替換假資料為真實 Gemini API 調用
- 提供準確的詞彙分析
-
前端全域詞彙快取
- 避免重複查詢相同詞彙
- 提升用戶體驗
⚡ 中優先級 (2週內)
-
後端詞彙快取資料表
- 永久保存查詢過的詞彙
- 跨用戶共享常用詞彙分析
-
智能快取策略
- 基於詞彙頻率的快取優先級
- 自動清理低價值快取項目
💡 低優先級 (未來功能)
- 跨設備同步
- 用戶詞彙學習記錄雲端同步
- 個人化詞彙掌握程度追蹤
🎯 回答您的問題
當前實際行為:
場景: "The apple" → 點擊 "The" → "The orange"
1. 分析 "The apple" → "The" 無預存資料 (灰框)
2. 點擊 "The" → 假 AI 查詢 → 加入前端快取 → "The" 變藍框
3. 分析 "The orange" → 前端快取被覆蓋清空 → "The" 又變灰框 ❌
4. 點擊 "The" → 重新假 AI 查詢 → 重複步驟2 ❌
問題總結:
- ❌ 不會在預存裡: 換句子後快取被清空
- ❌ 重複假查詢: 每次都返回相同的假資料
- ❌ 浪費資源: 用戶以為是真實 AI 查詢
建議修復:
- 立即: 修改前端為累積式快取
- 短期: 實現真實的詞彙 AI 查詢
- 長期: 建立後端詞彙快取資料表
📞 技術支援
相關檔案:
- 前端快取:
/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