# 🗃️ 查詢歷史快取系統 - 功能規格計劃 **專案**: 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 && (
{queryStatus.fromHistory ? ( <> 🗃️ 查詢歷史 (第{queryStatus.timesQueried}次) 首次查詢: {formatDate(queryStatus.queryDate)} ) : ( <> 🔍 新句子分析中... )}
)} ``` --- ## 🎯 **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 QueryWordAsync(string word, string sentence, Guid? userId = null); Task> GetUserQueryHistoryAsync(Guid userId, int limit = 50); } public class VocabularyQueryService : IVocabularyQueryService { private readonly DramaLingDbContext _context; private readonly IGeminiService _geminiService; private readonly ILogger _logger; public async Task 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(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 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 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 (
{/* 詞彙基本資訊 */}

{word}

{analysis.pronunciation}

{analysis.translation}

{analysis.definition}

{/* 查詢歷史資訊 */} {queryHistory && (

🗃️ 查詢歷史

{queryHistory.isFromHistory ? (
查詢次數: {queryHistory.queryCount} 次
首次查詢: {formatDate(queryHistory.firstQueriedAt)}
{queryHistory.firstContext !== queryHistory.currentContext && (

首次語境: {queryHistory.firstContext}

當前語境: {queryHistory.currentContext}

)}
) : (
✨ 首次查詢,已加入您的查詢歷史
)}
)}
); } ``` --- ## 🎯 **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\ 然後再存到紀錄中\\ \ \ 這不是學習歷史\ 使用者也沒有儲存詞彙\ 那只是查詢的歷史而已\ \ 請你設計這個功能\ 寫成功能規格計劃再根目錄