478 lines
15 KiB
Markdown
478 lines
15 KiB
Markdown
# 🗄️ 詞彙快取機制技術規格書
|
||
|
||
**專案**: 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行)
|
||
```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<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: 全域詞彙快取 (推薦)**
|
||
|
||
#### **前端實現**
|
||
```typescript
|
||
// 新增全域詞彙快取
|
||
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 }))
|
||
}}
|
||
```
|
||
|
||
#### **本地存儲持久化**
|
||
```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<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: 後端詞彙快取資料表**
|
||
|
||
#### **新資料表設計**
|
||
```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<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`
|
||
|
||
```csharp
|
||
private async Task<object> 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 |