# 🗃️ 查詢歷史快取系統 - 功能規格計劃

**專案**: DramaLing 英語學習平台
**功能**: 查詢歷史記錄與智能快取系統
**文檔版本**: v1.0
**建立日期**: 2025-01-18
**核心概念**: 將技術快取包裝為用戶查詢歷史，提升體驗透明度

---

## 🎯 **核心設計理念**

### **從「快取機制」到「查詢歷史」**

| 技術實現 | 用戶概念 | 實際意義 |
|----------|----------|----------|
| Cache Hit | 查詢過的句子 | "您之前查詢過這個句子" |
| Cache Miss | 新句子查詢 | "正在為您分析新句子..." |
| Word Cache | 查詢過的詞彙 | "您之前查詢過這個詞彙" |
| API Call | 即時查詢 | "正在為您查詢詞彙資訊..." |

### **使用者場景**
```
場景1: 句子查詢
用戶輸入: "Hello world"
第1次: "正在分析..." (3-5秒) → 存入查詢歷史
第2次: "您之前查詢過，立即顯示" (<200ms)

場景2: 詞彙查詢
句子: "The apple"
點擊 "The": "正在查詢..." → 存入詞彙查詢歷史
新句子: "The orange"
點擊 "The": "您之前查詢過，立即顯示" → 從歷史載入
```

---

## 📋 **技術規格設計**

## 🎯 **A. 句子查詢歷史系統**

### **A1. 當前實現改造**
**現有**: `SentenceAnalysisCache` (技術導向命名)
**改為**: 保持技術實現，改變用戶訊息

#### **API 回應訊息改造**
**檔案**: `/backend/DramaLing.Api/Controllers/AIController.cs:547`

```csharp
// 當前 (技術導向)
return Ok(new {
    Success = true,
    Data = cachedResult,
    Message = "句子分析完成（快取）",  // ❌ 技術術語
    Cached = true,
    CacheHit = true
});

// 改為 (用戶導向)
return Ok(new {
    Success = true,
    Data = cachedResult,
    Message = "您之前查詢過這個句子，立即為您顯示結果",  // ✅ 用戶友善
    FromHistory = true,          // ✅ 更直觀的欄位名
    QueryDate = cachedAnalysis.CreatedAt,
    TimesQueried = cachedAnalysis.AccessCount
});
```

### **A2. 前端顯示改造**
**檔案**: `/frontend/app/generate/page.tsx`

```typescript
// 查詢歷史狀態顯示
{queryStatus && (
  <div className={`inline-flex items-center px-4 py-2 rounded-lg text-sm font-medium ${
    queryStatus.fromHistory
      ? 'bg-purple-100 text-purple-800'
      : 'bg-blue-100 text-blue-800'
  }`}>
    {queryStatus.fromHistory ? (
      <>
        <span className="mr-2">🗃️</span>
        <span>查詢歷史 (第{queryStatus.timesQueried}次)</span>
        <span className="ml-2 text-xs text-purple-600">
          首次查詢: {formatDate(queryStatus.queryDate)}
        </span>
      </>
    ) : (
      <>
        <span className="mr-2">🔍</span>
        <span>新句子分析中...</span>
      </>
    )}
  </div>
)}
```

---

## 🎯 **B. 詞彙查詢歷史系統**

### **B1. 新增詞彙查詢快取表**
```sql
-- 用戶詞彙查詢歷史表
CREATE TABLE UserVocabularyQueryHistory (
    Id UNIQUEIDENTIFIER PRIMARY KEY,
    UserId UNIQUEIDENTIFIER NOT NULL,             -- 用戶ID (未來用戶系統)
    Word NVARCHAR(100) NOT NULL,                  -- 查詢的詞彙
    WordLowercase NVARCHAR(100) NOT NULL,         -- 小寫版本 (查詢鍵)

    -- 查詢結果快取
    AnalysisResult NVARCHAR(MAX) NOT NULL,        -- JSON 格式的分析結果
    Translation NVARCHAR(200) NOT NULL,           -- 快速存取的翻譯
    Definition NVARCHAR(500) NOT NULL,            -- 快速存取的定義

    -- 查詢上下文
    FirstQueriedInSentence NVARCHAR(1000),        -- 首次查詢時的句子語境
    LastQueriedInSentence NVARCHAR(1000),         -- 最後查詢時的句子語境

    -- 查詢歷史統計
    FirstQueriedAt DATETIME2 NOT NULL,            -- 首次查詢時間
    LastQueriedAt DATETIME2 NOT NULL,             -- 最後查詢時間
    QueryCount INT DEFAULT 1,                     -- 查詢次數

    -- 系統欄位
    CreatedAt DATETIME2 NOT NULL,
    UpdatedAt DATETIME2 NOT NULL,

    -- 索引優化
    INDEX IX_UserVocabularyQueryHistory_UserId_Word (UserId, WordLowercase),
    INDEX IX_UserVocabularyQueryHistory_LastQueriedAt (LastQueriedAt),

    -- 暫時不設定外鍵，因為用戶系統還未完全實現
    -- FOREIGN KEY (UserId) REFERENCES Users(Id)
);
```

### **B2. 詞彙查詢服務重構**
**檔案**: `/backend/DramaLing.Api/Services/VocabularyQueryService.cs`

```csharp
public interface IVocabularyQueryService
{
    Task<VocabularyQueryResponse> QueryWordAsync(string word, string sentence, Guid? userId = null);
    Task<List<UserVocabularyQueryHistory>> GetUserQueryHistoryAsync(Guid userId, int limit = 50);
}

public class VocabularyQueryService : IVocabularyQueryService
{
    private readonly DramaLingDbContext _context;
    private readonly IGeminiService _geminiService;
    private readonly ILogger<VocabularyQueryService> _logger;

    public async Task<VocabularyQueryResponse> QueryWordAsync(string word, string sentence, Guid? userId = null)
    {
        var wordLower = word.ToLower();
        var mockUserId = userId ?? Guid.Parse("00000000-0000-0000-0000-000000000001"); // 模擬用戶

        // 1. 檢查用戶的詞彙查詢歷史
        var queryHistory = await _context.UserVocabularyQueryHistory
            .FirstOrDefaultAsync(h => h.UserId == mockUserId && h.WordLowercase == wordLower);

        if (queryHistory != null)
        {
            // 更新查詢統計
            queryHistory.LastQueriedAt = DateTime.UtcNow;
            queryHistory.LastQueriedInSentence = sentence;
            queryHistory.QueryCount++;
            await _context.SaveChangesAsync();

            // 返回歷史查詢結果
            var historicalAnalysis = JsonSerializer.Deserialize<object>(queryHistory.AnalysisResult);

            return new VocabularyQueryResponse
            {
                Success = true,
                Data = new
                {
                    Word = word,
                    Analysis = historicalAnalysis,
                    QueryHistory = new
                    {
                        IsFromHistory = true,
                        FirstQueriedAt = queryHistory.FirstQueriedAt,
                        QueryCount = queryHistory.QueryCount,
                        DaysSinceFirstQuery = (DateTime.UtcNow - queryHistory.FirstQueriedAt).Days,
                        FirstContext = queryHistory.FirstQueriedInSentence,
                        CurrentContext = sentence
                    }
                },
                Message = $"您之前查詢過 \"{word}\"，這是第{queryHistory.QueryCount}次查詢"
            };
        }

        // 2. 新詞彙查詢 - 調用 AI
        var aiAnalysis = await AnalyzeWordWithAI(word, sentence);

        // 3. 存入查詢歷史
        var newHistory = new UserVocabularyQueryHistory
        {
            Id = Guid.NewGuid(),
            UserId = mockUserId,
            Word = word,
            WordLowercase = wordLower,
            AnalysisResult = JsonSerializer.Serialize(aiAnalysis),
            Translation = aiAnalysis.Translation,
            Definition = aiAnalysis.Definition,
            FirstQueriedInSentence = sentence,
            LastQueriedInSentence = sentence,
            FirstQueriedAt = DateTime.UtcNow,
            LastQueriedAt = DateTime.UtcNow,
            QueryCount = 1,
            CreatedAt = DateTime.UtcNow,
            UpdatedAt = DateTime.UtcNow
        };

        _context.UserVocabularyQueryHistory.Add(newHistory);
        await _context.SaveChangesAsync();

        return new VocabularyQueryResponse
        {
            Success = true,
            Data = new
            {
                Word = word,
                Analysis = aiAnalysis,
                QueryHistory = new
                {
                    IsFromHistory = false,
                    IsNewQuery = true,
                    FirstQueriedAt = DateTime.UtcNow,
                    QueryCount = 1,
                    Context = sentence
                }
            },
            Message = $"首次查詢 \"{word}\"，已加入您的查詢歷史"
        };
    }

    private async Task<object> AnalyzeWordWithAI(string word, string sentence)
    {
        try
        {
            // 🚀 這裡應該是真實的 AI 調用，不是模擬
            var prompt = $@"
請分析單字 ""{word}"" 在句子 ""{sentence}"" 中的詳細資訊：

單字: {word}
語境: {sentence}

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

            var response = await _geminiService.CallGeminiApiAsync(prompt);
            return ParseVocabularyAnalysisResponse(response);
        }
        catch (Exception ex)
        {
            _logger.LogWarning(ex, "AI vocabulary analysis failed, using fallback data");

            // 回退到基本資料
            return new
            {
                word = word,
                translation = $"{word} 的翻譯",
                definition = $"Definition of {word}",
                partOfSpeech = "unknown",
                pronunciation = $"/{word}/",
                difficultyLevel = "unknown",
                contextMeaning = $"在句子 \"{sentence}\" 中的含義",
                isHighValue = false,
                examples = new string[0]
            };
        }
    }
}
```

---

## 🎯 **C. API 端點重構**

### **C1. 更新現有端點**
**檔案**: `/backend/DramaLing.Api/Controllers/AIController.cs`

#### **句子分析端點保持不變**
```http
POST /api/ai/analyze-sentence
```
**只修改回應訊息，讓用戶理解是查詢歷史**

#### **詞彙查詢端點整合歷史服務**
```csharp
[HttpPost("query-word")]
[AllowAnonymous]
public async Task<ActionResult> QueryWord([FromBody] QueryWordRequest request)
{
    try
    {
        // 使用新的查詢歷史服務
        var result = await _vocabularyQueryService.QueryWordAsync(
            request.Word,
            request.Sentence,
            userId: null // 暫時使用模擬用戶
        );

        return Ok(result);
    }
    catch (Exception ex)
    {
        _logger.LogError(ex, "Error in vocabulary query");
        return StatusCode(500, new
        {
            Success = false,
            Error = "詞彙查詢失敗",
            Details = ex.Message
        });
    }
}
```

---

## 🎯 **D. 前端查詢歷史整合**

### **D1. ClickableTextV2 組件改造**
**檔案**: `/frontend/components/ClickableTextV2.tsx`

```typescript
// 修改詞彙查詢成功的處理
if (result.success && result.data?.analysis) {
  // 顯示查詢歷史資訊
  const queryHistory = result.data.queryHistory;

  if (queryHistory.isFromHistory) {
    console.log(`📚 從查詢歷史載入: ${word} (第${queryHistory.queryCount}次查詢)`);
  } else {
    console.log(`🔍 新詞彙查詢: ${word} (已加入查詢歷史)`);
  }

  // 將新的分析資料通知父組件
  onNewWordAnalysis?.(word, {
    ...result.data.analysis,
    queryHistory: queryHistory  // 附帶查詢歷史資訊
  });

  // 顯示分析結果
  setPopupPosition(position);
  setSelectedWord(word);
  onWordClick?.(word, result.data.analysis);
}
```

### **D2. 詞彙彈窗增加歷史資訊**
```typescript
// 在詞彙彈窗中顯示查詢歷史
function VocabularyPopup({ word, analysis, queryHistory }: Props) {
  return (
    <div className="vocabulary-popup bg-white border rounded-lg shadow-lg p-4 w-80">
      {/* 詞彙基本資訊 */}
      <div className="word-basic-info mb-3">
        <h3 className="text-lg font-bold">{word}</h3>
        <p className="text-gray-600">{analysis.pronunciation}</p>
        <p className="text-blue-600 font-medium">{analysis.translation}</p>
        <p className="text-gray-700 text-sm mt-1">{analysis.definition}</p>
      </div>

      {/* 查詢歷史資訊 */}
      {queryHistory && (
        <div className="query-history bg-gray-50 p-3 rounded-lg">
          <h4 className="font-semibold text-xs text-gray-700 mb-2 flex items-center">
            <span className="mr-1">🗃️</span>
            查詢歷史
          </h4>

          {queryHistory.isFromHistory ? (
            <div className="text-xs text-gray-600 space-y-1">
              <div className="flex justify-between">
                <span>查詢次數:</span>
                <span className="font-medium">{queryHistory.queryCount} 次</span>
              </div>
              <div className="flex justify-between">
                <span>首次查詢:</span>
                <span className="font-medium">{formatDate(queryHistory.firstQueriedAt)}</span>
              </div>
              {queryHistory.firstContext !== queryHistory.currentContext && (
                <div className="mt-2 p-2 bg-blue-50 rounded text-xs">
                  <p className="text-blue-700">
                    <strong>首次語境:</strong> {queryHistory.firstContext}
                  </p>
                  <p className="text-blue-700 mt-1">
                    <strong>當前語境:</strong> {queryHistory.currentContext}
                  </p>
                </div>
              )}
            </div>
          ) : (
            <div className="text-xs text-green-600">
              ✨ 首次查詢，已加入您的查詢歷史
            </div>
          )}
        </div>
      )}
    </div>
  );
}
```

---

## 🎯 **E. 用戶介面語言優化**

### **E1. 訊息文案改造**

| 情況 | 技術訊息 | 用戶友善訊息 |
|------|----------|--------------|
| 快取命中 | "句子分析完成（快取）" | "您之前查詢過這個句子，立即為您顯示結果" |
| 新查詢 | "AI句子分析完成" | "新句子分析完成，已加入您的查詢歷史" |
| 詞彙快取 | "高價值詞彙查詢完成（免費）" | "您之前查詢過這個詞彙 (第N次查詢)" |
| 詞彙新查詢 | "低價值詞彙查詢完成" | "首次查詢此詞彙，已加入查詢歷史" |

### **E2. 載入狀態文案**
```typescript
// 分析中的狀態提示
const getLoadingMessage = (type: 'sentence' | 'vocabulary', isNew: boolean) => {
  if (type === 'sentence') {
    return isNew
      ? "🔍 正在分析新句子，約需 3-5 秒..."
      : "📚 從查詢歷史載入...";
  } else {
    return isNew
      ? "🤖 正在查詢詞彙資訊..."
      : "🗃️ 從查詢歷史載入...";
  }
};
```

---

## 🛠️ **實施計劃**

### **📋 Phase 1: 後端查詢歷史服務 (1-2天)**

#### **1.1 建立詞彙查詢歷史表**
```bash
# 建立 Entity Framework 遷移
dotnet ef migrations add AddUserVocabularyQueryHistory
dotnet ef database update
```

#### **1.2 建立查詢歷史服務**
- 新增 `VocabularyQueryService.cs`
- 實現真實的 AI 詞彙查詢 (替換模擬)
- 整合查詢歷史記錄功能

#### **1.3 修改現有 API 回應訊息**
- 將技術術語改為用戶友善語言
- 新增查詢歷史相關欄位
- 保持 API 結構相容性

### **📋 Phase 2: 前端查詢歷史整合 (2-3天)**

#### **2.1 更新 ClickableTextV2 組件**
- 整合查詢歷史資訊顯示
- 優化詞彙彈窗包含歷史資訊
- 改善視覺提示系統

#### **2.2 修改 generate 頁面**
- 更新查詢狀態顯示
- 改善載入狀態文案
- 新增查詢歷史統計

#### **2.3 訊息文案全面優化**
- 替換所有技術術語
- 採用用戶友善的描述
- 增加情境化的提示

### **📋 Phase 3: 查詢歷史頁面 (3-4天)**

#### **3.1 建立查詢歷史頁面**
```typescript
// 新頁面: /frontend/app/query-history/page.tsx
- 顯示所有查詢過的句子
- 顯示所有查詢過的詞彙
- 提供搜尋和篩選功能
- 支援重新查詢功能
```

#### **3.2 導航整合**
- 在主導航中新增「查詢歷史」
- 在 generate 頁面新增快速連結
- 在詞彙彈窗中新增「查看完整歷史」

---

## 📊 **與現有快取系統的關係**

### **保持底層技術優勢**
- ✅ **效能優化**: 繼續享受快取帶來的速度提升
- ✅ **成本控制**: 避免重複的 AI API 調用
- ✅ **系統穩定性**: 保持現有的錯誤處理機制

### **改善用戶認知**
- 🔄 **概念轉換**: 從「快取」到「查詢歷史」
- 📊 **透明化**: 讓用戶了解系統行為
- 🎯 **價值感知**: 用戶看到查詢的累積價值

### **技術實現不變，體驗大幅提升**
```
底層: 仍然是高效的快取機制
表層: 包裝為有意義的查詢歷史體驗
結果: 技術效益 + 用戶體驗雙贏
```

---

## 🎯 **預期效果**

### **用戶體驗轉變**
- **舊**: "為什麼這個查詢這麼快？"
- **新**: "我之前查詢過這個詞彙，這是第3次遇到"

### **系統感知轉變**
- **舊**: 神秘的黑盒子系統
- **新**: 透明的查詢歷史助手

### **價值感知轉變**
- **舊**: 一次性工具
- **新**: 個人化查詢資料庫

## 📋 **成功指標**

### **定量指標**
- **歷史查看率**: >60% 用戶注意到查詢歷史資訊
- **重複查詢滿意度**: >80% 用戶對快速載入感到滿意
- **功能理解度**: >90% 用戶理解為什麼有些查詢很快

### **定性指標**
- **透明感**: 用戶明白系統行為邏輯
- **積累感**: 用戶感受到查詢的累積價值
- **信任感**: 用戶信任系統會記住他們的查詢

---

**© 2025 DramaLing Development Team**
**設計理念**: 技術服務於用戶體驗，快取包裝為查詢歷史
**核心價值**: 讓用戶感受到每次查詢的累積意義


> 我覺得快取機制不太貼切，\
  具體應該改成歷史紀錄的概念\
  使用者查完某個原始例句後\
  就會存成紀錄\
  如果在查詢非高價值的詞彙，因為還沒有紀錄所以就會再去問ad\
  然後再存到紀錄中\\
  \
  \
  這不是學習歷史\
  使用者也沒有儲存詞彙\
  那只是查詢的歷史而已\
  \
    請你設計這個功能\
    寫成功能規格計劃再根目錄