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

**專案**: 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