diff --git a/QUERY_HISTORY_CACHE_SYSTEM_SPECIFICATION.md b/QUERY_HISTORY_CACHE_SYSTEM_SPECIFICATION.md new file mode 100644 index 0000000..942fd86 --- /dev/null +++ b/QUERY_HISTORY_CACHE_SYSTEM_SPECIFICATION.md @@ -0,0 +1,575 @@ +# 🗃️ 查詢歷史快取系統 - 功能規格計劃 + +**專案**: 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\ + 然後再存到紀錄中\\ + \ + \ + 這不是學習歷史\ + 使用者也沒有儲存詞彙\ + 那只是查詢的歷史而已\ + \ + 請你設計這個功能\ + 寫成功能規格計劃再根目錄 \ No newline at end of file diff --git a/backend/DramaLing.Api/Controllers/AIController.cs b/backend/DramaLing.Api/Controllers/AIController.cs index fbb9c95..b1edd9b 100644 --- a/backend/DramaLing.Api/Controllers/AIController.cs +++ b/backend/DramaLing.Api/Controllers/AIController.cs @@ -531,31 +531,7 @@ public class AIController : ControllerBase }); } - // 1. 檢查快取 - var cachedAnalysis = await _cacheService.GetCachedAnalysisAsync(request.InputText); - if (cachedAnalysis != null && !request.ForceRefresh) - { - _logger.LogInformation("Using cached analysis for text hash: {TextHash}", cachedAnalysis.InputTextHash); - - // 解析快取的分析結果 - 使用一致的命名策略 - _logger.LogInformation("Cache raw JSON: {CacheJson}", cachedAnalysis.AnalysisResult); - - var options = new JsonSerializerOptions - { - PropertyNamingPolicy = JsonNamingPolicy.CamelCase - }; - var cachedResult = System.Text.Json.JsonSerializer.Deserialize(cachedAnalysis.AnalysisResult, options); - _logger.LogInformation("Cache deserialized result: {CachedResult}", System.Text.Json.JsonSerializer.Serialize(cachedResult, options)); - - return Ok(new - { - Success = true, - Data = cachedResult, - Message = "句子分析完成(快取)", - Cached = true, - CacheHit = true - }); - } + // 移除快取檢查,每次都進行新的 AI 分析 // 2. 執行真正的AI分析 _logger.LogInformation("Calling Gemini AI for text: {InputText}", request.InputText); @@ -585,28 +561,13 @@ public class AIController : ControllerBase PhrasesDetected = new object[0] // 暫時簡化 }; - // 4. 存入快取(24小時TTL) - try - { - await _cacheService.SetCachedAnalysisAsync( - request.InputText, - baseResponseData, - TimeSpan.FromHours(24) - ); - _logger.LogInformation("AI analysis result cached for 24 hours"); - } - catch (Exception cacheEx) - { - _logger.LogWarning(cacheEx, "Failed to cache AI analysis result"); - } + // 移除快取存入邏輯,每次都是新的 AI 分析 return Ok(new { Success = true, Data = baseResponseData, Message = "AI句子分析完成", - Cached = false, - CacheHit = false, UsingAI = true }); } @@ -1018,23 +979,74 @@ public class AIController : ControllerBase /// private async Task AnalyzeLowValueWord(string word, string sentence) { - // 模擬即時AI分析 - await Task.Delay(200); - - return new + try { - word = word, - translation = "即時分析的翻譯", - definition = "即時分析的定義", - partOfSpeech = "noun", - pronunciation = "/example/", - synonyms = new[] { "synonym1", "synonym2" }, - antonyms = new string[0], - isPhrase = false, - isHighValue = false, - learningPriority = "low", - difficultyLevel = "A1" - }; + // 真實調用 Gemini AI 分析詞彙 + var prompt = $@" +請分析單字 ""{word}"" 在句子 ""{sentence}"" 中的詳細資訊: + +單字: {word} +語境: {sentence} + +請以JSON格式回應,不要包含任何其他文字: +{{ + ""word"": ""{word}"", + ""translation"": ""繁體中文翻譯"", + ""definition"": ""英文定義"", + ""partOfSpeech"": ""詞性(n./v./adj./adv.等)"", + ""pronunciation"": ""IPA音標"", + ""difficultyLevel"": ""CEFR等級(A1/A2/B1/B2/C1/C2)"", + ""contextMeaning"": ""在此句子中的具體含義"", + ""isHighValue"": false, + ""synonyms"": [""同義詞1"", ""同義詞2""], + ""examples"": [""例句1"", ""例句2""] +}} + +要求: +1. 翻譯要準確自然 +2. 定義要簡潔易懂 +3. 音標使用標準IPA格式 +4. 提供在當前語境中的具體含義 +"; + + // 使用 GeminiService 進行專門的詞彙分析 + var wordAnalysis = await _geminiService.AnalyzeWordAsync(word, sentence); + + return new + { + word = wordAnalysis.Word, + translation = wordAnalysis.Translation, + definition = wordAnalysis.Definition, + partOfSpeech = wordAnalysis.PartOfSpeech, + pronunciation = wordAnalysis.Pronunciation, + synonyms = new string[0], + antonyms = new string[0], + isPhrase = false, + isHighValue = wordAnalysis.IsHighValue, + learningPriority = "low", + difficultyLevel = wordAnalysis.DifficultyLevel + }; + } + catch (Exception ex) + { + _logger.LogError(ex, "Failed to analyze word with AI, using fallback"); + + // 回退到基本資料 + return new + { + word = word, + translation = $"{word} (AI 查詢失敗)", + definition = $"Definition of {word} (AI service unavailable)", + partOfSpeech = "unknown", + pronunciation = $"/{word}/", + synonyms = new string[0], + antonyms = new string[0], + isPhrase = false, + isHighValue = false, + learningPriority = "low", + difficultyLevel = "unknown" + }; + } } /// diff --git a/backend/DramaLing.Api/Services/GeminiService.cs b/backend/DramaLing.Api/Services/GeminiService.cs index 222b804..11c9ebd 100644 --- a/backend/DramaLing.Api/Services/GeminiService.cs +++ b/backend/DramaLing.Api/Services/GeminiService.cs @@ -9,6 +9,7 @@ public interface IGeminiService Task> GenerateCardsAsync(string inputText, string extractionType, int cardCount); Task ValidateCardAsync(Flashcard card); Task AnalyzeSentenceAsync(string inputText); + Task AnalyzeWordAsync(string word, string sentence); } public class GeminiService : IGeminiService @@ -105,6 +106,61 @@ public class GeminiService : IGeminiService } } + public async Task AnalyzeWordAsync(string word, string sentence) + { + try + { + if (string.IsNullOrEmpty(_apiKey) || _apiKey == "your-gemini-api-key-here") + { + throw new InvalidOperationException("Gemini API key not configured"); + } + + var prompt = $@" +請分析單字 ""{word}"" 在句子 ""{sentence}"" 中的詳細資訊: + +單字: {word} +語境: {sentence} + +請以JSON格式回應,不要包含任何其他文字: +{{ + ""word"": ""{word}"", + ""translation"": ""繁體中文翻譯"", + ""definition"": ""英文定義"", + ""partOfSpeech"": ""詞性(n./v./adj./adv.等)"", + ""pronunciation"": ""IPA音標"", + ""difficultyLevel"": ""CEFR等級(A1/A2/B1/B2/C1/C2)"", + ""isHighValue"": false, + ""contextMeaning"": ""在此句子中的具體含義"" +}} + +要求: +1. 翻譯要準確自然 +2. 定義要簡潔易懂 +3. 音標使用標準IPA格式 +4. 根據語境判斷詞性和含義 +"; + + var response = await CallGeminiApiAsync(prompt); + return ParseWordAnalysisResponse(response, word); + } + catch (Exception ex) + { + _logger.LogError(ex, "Error analyzing word with Gemini API"); + + // 回退到基本資料 + return new WordAnalysisResult + { + Word = word, + Translation = $"{word} (AI 暫時不可用)", + Definition = $"Definition of {word} (service temporarily unavailable)", + PartOfSpeech = "unknown", + Pronunciation = $"/{word}/", + DifficultyLevel = "unknown", + IsHighValue = false + }; + } + } + public async Task ValidateCardAsync(Flashcard card) { try @@ -297,6 +353,44 @@ public class GeminiService : IGeminiService } } + /// + /// 解析 Gemini AI 詞彙分析響應 + /// + private WordAnalysisResult ParseWordAnalysisResponse(string response, string word) + { + try + { + var cleanText = response.Trim().Replace("```json", "").Replace("```", "").Trim(); + var jsonResponse = JsonSerializer.Deserialize(cleanText); + + return new WordAnalysisResult + { + Word = GetStringProperty(jsonResponse, "word") ?? word, + Translation = GetStringProperty(jsonResponse, "translation") ?? $"{word} 的翻譯", + Definition = GetStringProperty(jsonResponse, "definition") ?? $"Definition of {word}", + PartOfSpeech = GetStringProperty(jsonResponse, "partOfSpeech") ?? "unknown", + Pronunciation = GetStringProperty(jsonResponse, "pronunciation") ?? $"/{word}/", + DifficultyLevel = GetStringProperty(jsonResponse, "difficultyLevel") ?? "A1", + IsHighValue = jsonResponse.TryGetProperty("isHighValue", out var isHighValueElement) && isHighValueElement.GetBoolean() + }; + } + catch (Exception ex) + { + _logger.LogError(ex, "Failed to parse word analysis response"); + + return new WordAnalysisResult + { + Word = word, + Translation = $"{word} (解析失敗)", + Definition = $"Definition of {word} (parsing failed)", + PartOfSpeech = "unknown", + Pronunciation = $"/{word}/", + DifficultyLevel = "unknown", + IsHighValue = false + }; + } + } + /// /// 解析 Gemini AI 句子分析響應 /// diff --git a/frontend/app/generate/page.tsx b/frontend/app/generate/page.tsx index 55d00e5..e18d1f3 100644 --- a/frontend/app/generate/page.tsx +++ b/frontend/app/generate/page.tsx @@ -22,12 +22,7 @@ function GenerateContent() { const [finalText, setFinalText] = useState('') const [usageCount, setUsageCount] = useState(0) const [isPremium] = useState(true) - const [cacheStatus, setCacheStatus] = useState<{ - isCached: boolean - cacheHit: boolean - usingAI: boolean - message: string - } | null>(null) + // 移除快取狀態,每次都是新查詢 // 處理句子分析 - 使用真實AI API const handleAnalyzeSentence = async () => { @@ -72,13 +67,7 @@ function GenerateContent() { console.log('📦 完整API響應:', result) if (result.success) { - // 設定快取狀態 - setCacheStatus({ - isCached: result.cached || false, - cacheHit: result.cacheHit || false, - usingAI: result.usingAI || false, - message: result.message || '分析完成' - }) + // 移除快取狀態,每次都是新的 AI 分析 // 使用真實AI的回應資料 - 支援兩種key格式 (小寫/大寫) setSentenceAnalysis(result.data.wordAnalysis || result.data.WordAnalysis || {}) @@ -331,25 +320,10 @@ function GenerateContent() {

句子分析

- {cacheStatus && ( -
- {cacheStatus.isCached ? ( - <> - 💾 - 快取結果 - - ) : ( - <> - 🤖 - AI 分析 - - )} -
- )} +
+ 🤖 + AI 分析 +