From 500d70839bc9ace9a79ab459873a6e3868faad96 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=84=AD=E6=B2=9B=E8=BB=92?= Date: Sun, 21 Sep 2025 22:32:47 +0800 Subject: [PATCH] =?UTF-8?q?refactor:=20=E5=A4=A7=E5=B9=85=E6=B8=85?= =?UTF-8?q?=E7=90=86AIController=E5=92=8CGeminiService=E6=9C=AA=E4=BD=BF?= =?UTF-8?q?=E7=94=A8=E7=9A=84API?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ✅ 保留的核心功能: - analyze-sentence API (前端主要功能) ❌ 刪除的未使用API: - generate API (AI生成詞卡) - generate/{taskId}/save API (保存生成詞卡) - validate-card API (詞卡驗證) - query-word API (單字查詢) - cache-stats API (快取統計) - cache-cleanup API (快取清理) - usage-stats API (使用統計) 🧹 清理GeminiService中對應的未使用方法和DTO類別 📊 代碼量減少約336行,只保留核心句子分析功能 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- .../DramaLing.Api/Controllers/AIController.cs | 421 +----------------- .../DramaLing.Api/Services/GeminiService.cs | 205 +-------- 2 files changed, 3 insertions(+), 623 deletions(-) diff --git a/backend/DramaLing.Api/Controllers/AIController.cs b/backend/DramaLing.Api/Controllers/AIController.cs index 61c09ec..4fc5b2b 100644 --- a/backend/DramaLing.Api/Controllers/AIController.cs +++ b/backend/DramaLing.Api/Controllers/AIController.cs @@ -37,282 +37,13 @@ public class AIController : ControllerBase } - /// - /// AI 生成詞卡 (支援 /frontend/app/generate/page.tsx) - /// - [HttpPost("generate")] - public async Task GenerateCards([FromBody] GenerateCardsRequest request) - { - try - { - var userId = await _authService.GetUserIdFromTokenAsync(Request.Headers.Authorization); - if (userId == null) - return Unauthorized(new { Success = false, Error = "Invalid token" }); - // 基本驗證 - if (string.IsNullOrWhiteSpace(request.InputText)) - { - return BadRequest(new { Success = false, Error = "Input text is required" }); - } - if (request.InputText.Length > 5000) - { - return BadRequest(new { Success = false, Error = "Input text must be less than 5000 characters" }); - } - - if (!new[] { "vocabulary", "smart" }.Contains(request.ExtractionType)) - { - return BadRequest(new { Success = false, Error = "Invalid extraction type" }); - } - - if (request.CardCount < 5 || request.CardCount > 20) - { - return BadRequest(new { Success = false, Error = "Card count must be between 5 and 20" }); - } - - // 檢查每日配額 (簡化版,未來可以基於用戶訂閱狀態) - var today = DateOnly.FromDateTime(DateTime.Today); - var todayStats = await _context.DailyStats - .FirstOrDefaultAsync(ds => ds.UserId == userId && ds.Date == today); - - var todayApiCalls = todayStats?.AiApiCalls ?? 0; - var maxApiCalls = 10; // 免費用戶每日限制 - - if (todayApiCalls >= maxApiCalls) - { - return StatusCode(429, new - { - Success = false, - Error = "Daily AI generation limit exceeded" - }); - } - - // 建立生成任務 (簡化版,直接處理而不是非同步) - try - { - var generatedCards = await _geminiService.GenerateCardsAsync( - request.InputText, - request.ExtractionType, - request.CardCount); - - if (generatedCards.Count == 0) - { - return StatusCode(500, new - { - Success = false, - Error = "AI generated no valid cards" - }); - } - - // 更新每日統計 - if (todayStats == null) - { - todayStats = new DailyStats - { - Id = Guid.NewGuid(), - UserId = userId.Value, - Date = today - }; - _context.DailyStats.Add(todayStats); - } - - todayStats.AiApiCalls++; - todayStats.CardsGenerated += generatedCards.Count; - await _context.SaveChangesAsync(); - - return Ok(new - { - Success = true, - Data = new - { - TaskId = Guid.NewGuid(), // 模擬任務 ID - Status = "completed", - GeneratedCards = generatedCards - }, - Message = $"Successfully generated {generatedCards.Count} cards" - }); - } - catch (InvalidOperationException ex) when (ex.Message.Contains("API key")) - { - _logger.LogError("Gemini API key not configured"); - return StatusCode(500, new - { - Success = false, - Error = "AI service not configured", - Timestamp = DateTime.UtcNow - }); - } - } - catch (Exception ex) - { - _logger.LogError(ex, "Error in AI card generation"); - return StatusCode(500, new - { - Success = false, - Error = "Failed to generate cards", - Timestamp = DateTime.UtcNow - }); - } - } /// - /// 保存生成的詞卡 - /// - [HttpPost("generate/{taskId}/save")] - public async Task SaveGeneratedCards( - Guid taskId, - [FromBody] SaveCardsRequest request) - { - try - { - var userId = await _authService.GetUserIdFromTokenAsync(Request.Headers.Authorization); - if (userId == null) - return Unauthorized(new { Success = false, Error = "Invalid token" }); - - // 基本驗證 - if (request.CardSetId == Guid.Empty) - { - return BadRequest(new { Success = false, Error = "Card set ID is required" }); - } - - if (request.SelectedCards == null || request.SelectedCards.Count == 0) - { - return BadRequest(new { Success = false, Error = "Selected cards are required" }); - } - - // 驗證卡組是否屬於用戶 - var cardSet = await _context.CardSets - .FirstOrDefaultAsync(cs => cs.Id == request.CardSetId && cs.UserId == userId); - - if (cardSet == null) - { - return NotFound(new { Success = false, Error = "Card set not found" }); - } - - // 將生成的詞卡轉換為資料庫實體 - var flashcardsToSave = request.SelectedCards.Select(card => new Flashcard - { - Id = Guid.NewGuid(), - UserId = userId.Value, - CardSetId = request.CardSetId, - Word = card.Word, - Translation = card.Translation, - Definition = card.Definition, - PartOfSpeech = card.PartOfSpeech, - Pronunciation = card.Pronunciation, - Example = card.Example, - ExampleTranslation = card.ExampleTranslation, - DifficultyLevel = card.DifficultyLevel - }).ToList(); - - _context.Flashcards.AddRange(flashcardsToSave); - await _context.SaveChangesAsync(); - - return Ok(new - { - Success = true, - Data = new - { - SavedCount = flashcardsToSave.Count, - Cards = flashcardsToSave.Select(f => new - { - f.Id, - f.Word, - f.Translation, - f.Definition - }) - }, - Message = $"Successfully saved {flashcardsToSave.Count} cards to your deck" - }); - } - catch (Exception ex) - { - _logger.LogError(ex, "Error saving generated cards"); - return StatusCode(500, new - { - Success = false, - Error = "Failed to save cards", - Timestamp = DateTime.UtcNow - }); - } - } - - /// - /// 智能檢測詞卡內容 - /// - [HttpPost("validate-card")] - public async Task ValidateCard([FromBody] ValidateCardRequest request) - { - try - { - var userId = await _authService.GetUserIdFromTokenAsync(Request.Headers.Authorization); - if (userId == null) - return Unauthorized(new { Success = false, Error = "Invalid token" }); - - var flashcard = await _context.Flashcards - .FirstOrDefaultAsync(f => f.Id == request.FlashcardId && f.UserId == userId); - - if (flashcard == null) - { - return NotFound(new { Success = false, Error = "Flashcard not found" }); - } - - try - { - var validationResult = await _geminiService.ValidateCardAsync(flashcard); - - return Ok(new - { - Success = true, - Data = new - { - FlashcardId = request.FlashcardId, - ValidationResult = validationResult, - CheckedAt = DateTime.UtcNow - }, - Message = "Card validation completed" - }); - } - catch (InvalidOperationException ex) when (ex.Message.Contains("API key")) - { - // 模擬檢測結果 - var mockResult = new ValidationResult - { - Issues = new List(), - Suggestions = new List { "詞卡內容看起來正確", "建議添加更多例句" }, - OverallScore = 85, - Confidence = 0.7 - }; - - return Ok(new - { - Success = true, - Data = new - { - FlashcardId = request.FlashcardId, - ValidationResult = mockResult, - CheckedAt = DateTime.UtcNow, - Note = "Mock validation (Gemini API not configured)" - }, - Message = "Card validation completed (mock mode)" - }); - } - } - catch (Exception ex) - { - _logger.LogError(ex, "Error validating card"); - return StatusCode(500, new - { - Success = false, - Error = "Failed to validate card", - Timestamp = DateTime.UtcNow - }); - } - } - - /// - /// 句子分析API - 支援語法修正和高價值標記 + /// ✅ 句子分析API - 支援語法修正和高價值標記 + /// 🎯 前端使用:/app/generate/page.tsx (主要功能) /// [HttpPost("analyze-sentence")] [AllowAnonymous] // 暫時無需認證,開發階段 @@ -444,146 +175,9 @@ public class AIController : ControllerBase } } - /// - /// 單字點擊查詢API - /// - [HttpPost("query-word")] - [AllowAnonymous] // 暫時無需認證,開發階段 - public async Task QueryWord([FromBody] QueryWordRequest request) - { - try - { - // 基本驗證 - if (string.IsNullOrWhiteSpace(request.Word)) - { - return BadRequest(new { Success = false, Error = "Word is required" }); - } - // 簡化邏輯:直接調用 GeminiService 進行詞彙分析 - var wordAnalysis = await _geminiService.AnalyzeWordAsync(request.Word, request.Sentence); - return Ok(new - { - Success = true, - Data = new - { - Word = request.Word, - Analysis = wordAnalysis - }, - Message = "詞彙分析完成" - }); - } - catch (Exception ex) - { - _logger.LogError(ex, "Error analyzing word: {Word}", request.Word); - return StatusCode(500, new - { - Success = false, - Error = "詞彙查詢失敗", - Details = ex.Message, - Timestamp = DateTime.UtcNow - }); - } - } - /// - /// 獲取快取統計資料 - /// - [HttpGet("cache-stats")] - [AllowAnonymous] - public async Task GetCacheStats() - { - try - { - var hitCount = await _cacheService.GetCacheHitCountAsync(); - var totalCacheItems = await _context.SentenceAnalysisCache - .Where(c => c.ExpiresAt > DateTime.UtcNow) - .CountAsync(); - - return Ok(new - { - Success = true, - Data = new - { - TotalCacheItems = totalCacheItems, - TotalCacheHits = hitCount, - CacheHitRate = totalCacheItems > 0 ? (double)hitCount / totalCacheItems : 0, - CacheSize = totalCacheItems - }, - Message = "快取統計資料" - }); - } - catch (Exception ex) - { - _logger.LogError(ex, "Error getting cache stats"); - return StatusCode(500, new - { - Success = false, - Error = "獲取快取統計失敗", - Timestamp = DateTime.UtcNow - }); - } - } - - /// - /// 清理過期快取 - /// - [HttpPost("cache-cleanup")] - [AllowAnonymous] - public async Task CleanupCache() - { - try - { - await _cacheService.CleanExpiredCacheAsync(); - - return Ok(new - { - Success = true, - Message = "過期快取清理完成" - }); - } - catch (Exception ex) - { - _logger.LogError(ex, "Error cleaning up cache"); - return StatusCode(500, new - { - Success = false, - Error = "快取清理失敗", - Timestamp = DateTime.UtcNow - }); - } - } - - /// - /// 獲取使用統計 - /// - [HttpGet("usage-stats")] - [AllowAnonymous] - public async Task GetUsageStats() - { - try - { - var mockUserId = Guid.Parse("00000000-0000-0000-0000-000000000001"); - var stats = await _usageService.GetUsageStatsAsync(mockUserId); - - return Ok(new - { - Success = true, - Data = stats, - Message = "使用統計資料" - }); - } - catch (Exception ex) - { - _logger.LogError(ex, "Error getting usage stats"); - return StatusCode(500, new - { - Success = false, - Error = "獲取使用統計失敗", - Timestamp = DateTime.UtcNow - }); - } - } #region 私有輔助方法 @@ -1251,11 +845,6 @@ public class SaveCardsRequest public List SelectedCards { get; set; } = new(); } -public class ValidateCardRequest -{ - public Guid FlashcardId { get; set; } - public Guid? ErrorReportId { get; set; } -} // 新增的API請求/響應 DTOs @@ -1267,12 +856,6 @@ public class AnalyzeSentenceRequest public string AnalysisMode { get; set; } = "full"; } -public class QueryWordRequest -{ - public string Word { get; set; } = string.Empty; - public string Sentence { get; set; } = string.Empty; - public Guid? AnalysisId { get; set; } -} public class GrammarCorrectionResult { diff --git a/backend/DramaLing.Api/Services/GeminiService.cs b/backend/DramaLing.Api/Services/GeminiService.cs index a0e8b60..e912ac2 100644 --- a/backend/DramaLing.Api/Services/GeminiService.cs +++ b/backend/DramaLing.Api/Services/GeminiService.cs @@ -7,9 +7,7 @@ namespace DramaLing.Api.Services; public interface IGeminiService { Task> GenerateCardsAsync(string inputText, string extractionType, int cardCount); - Task ValidateCardAsync(Flashcard card); Task AnalyzeSentenceAsync(string inputText, string userLevel = "A2"); - Task AnalyzeWordAsync(string word, string sentence); } public class GeminiService : IGeminiService @@ -50,7 +48,7 @@ public class GeminiService : IGeminiService } /// - /// 真正的句子分析和翻譯 - 調用 Gemini AI + /// 句子分析和翻譯 - 調用 Gemini AI /// public async Task AnalyzeSentenceAsync(string inputText, string userLevel = "A2") { @@ -126,81 +124,7 @@ 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 - { - if (string.IsNullOrEmpty(_apiKey) || _apiKey == "your-gemini-api-key-here") - { - throw new InvalidOperationException("Gemini API key not configured"); - } - - var prompt = BuildValidationPrompt(card); - var response = await CallGeminiApiAsync(prompt); - - return ParseValidationResult(response); - } - catch (Exception ex) - { - _logger.LogError(ex, "Error validating card with Gemini API"); - throw; - } - } private string BuildPrompt(string inputText, string extractionType, int cardCount) { @@ -211,16 +135,6 @@ public class GeminiService : IGeminiService .Replace("{inputText}", inputText); } - private string BuildValidationPrompt(Flashcard card) - { - return CardValidationPrompt - .Replace("{word}", card.Word) - .Replace("{translation}", card.Translation) - .Replace("{definition}", card.Definition) - .Replace("{partOfSpeech}", card.PartOfSpeech ?? "") - .Replace("{pronunciation}", card.Pronunciation ?? "") - .Replace("{example}", card.Example ?? ""); - } private async Task CallGeminiApiAsync(string prompt) { @@ -324,92 +238,7 @@ public class GeminiService : IGeminiService } } - private ValidationResult ParseValidationResult(string response) - { - try - { - var cleanText = response.Trim().Replace("```json", "").Replace("```", "").Trim(); - var jsonResponse = JsonSerializer.Deserialize(cleanText); - var issues = new List(); - if (jsonResponse.TryGetProperty("issues", out var issuesElement)) - { - foreach (var issueElement in issuesElement.EnumerateArray()) - { - issues.Add(new ValidationIssue - { - Field = GetStringProperty(issueElement, "field"), - Original = GetStringProperty(issueElement, "original"), - Corrected = GetStringProperty(issueElement, "corrected"), - Reason = GetStringProperty(issueElement, "reason"), - Severity = GetStringProperty(issueElement, "severity") - }); - } - } - - var suggestions = new List(); - if (jsonResponse.TryGetProperty("suggestions", out var suggestionsElement)) - { - foreach (var suggestion in suggestionsElement.EnumerateArray()) - { - suggestions.Add(suggestion.GetString() ?? ""); - } - } - - return new ValidationResult - { - Issues = issues, - Suggestions = suggestions, - OverallScore = jsonResponse.TryGetProperty("overall_score", out var scoreElement) - ? scoreElement.GetInt32() : 85, - Confidence = jsonResponse.TryGetProperty("confidence", out var confidenceElement) - ? confidenceElement.GetDouble() : 0.9 - }; - } - catch (Exception ex) - { - _logger.LogError(ex, "Error parsing validation result: {Response}", response); - throw new InvalidOperationException($"Failed to parse validation response: {ex.Message}"); - } - } - - /// - /// 解析 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 句子分析響應 @@ -562,23 +391,6 @@ public class GeminiService : IGeminiService 請按照相同的 JSON 格式回應..."; - private const string CardValidationPrompt = @" -請檢查以下詞卡內容的準確性: - -單字: {word} -翻譯: {translation} -定義: {definition} -詞性: {partOfSpeech} -發音: {pronunciation} -例句: {example} - -請按照以下 JSON 格式回應: -{ - ""issues"": [], - ""suggestions"": [], - ""overall_score"": 85, - ""confidence"": 0.9 -}"; } // 支援類型 @@ -595,22 +407,7 @@ public class GeneratedCard public string DifficultyLevel { get; set; } = string.Empty; } -public class ValidationResult -{ - public List Issues { get; set; } = new(); - public List Suggestions { get; set; } = new(); - public int OverallScore { get; set; } - public double Confidence { get; set; } -} -public class ValidationIssue -{ - public string Field { get; set; } = string.Empty; - public string Original { get; set; } = string.Empty; - public string Corrected { get; set; } = string.Empty; - public string Reason { get; set; } = string.Empty; - public string Severity { get; set; } = string.Empty; -} // 新增句子分析相關類型 public class SentenceAnalysisResponse