using System.Text.Json; using System.Text; using DramaLing.Api.Models.Entities; namespace DramaLing.Api.Services; public interface IGeminiService { Task> GenerateCardsAsync(string inputText, string extractionType, int cardCount); Task ValidateCardAsync(Flashcard card); Task AnalyzeSentenceAsync(string inputText); } public class GeminiService : IGeminiService { private readonly HttpClient _httpClient; private readonly IConfiguration _configuration; private readonly ILogger _logger; private readonly string _apiKey; public GeminiService(HttpClient httpClient, IConfiguration configuration, ILogger logger) { _httpClient = httpClient; _configuration = configuration; _logger = logger; _apiKey = Environment.GetEnvironmentVariable("DRAMALING_GEMINI_API_KEY") ?? _configuration["AI:GeminiApiKey"] ?? ""; } public async Task> GenerateCardsAsync(string inputText, string extractionType, int cardCount) { try { if (string.IsNullOrEmpty(_apiKey) || _apiKey == "your-gemini-api-key-here") { throw new InvalidOperationException("Gemini API key not configured"); } var prompt = BuildPrompt(inputText, extractionType, cardCount); var response = await CallGeminiApiAsync(prompt); return ParseGeneratedCards(response); } catch (Exception ex) { _logger.LogError(ex, "Error generating cards with Gemini API"); throw; } } /// /// 真正的句子分析和翻譯 - 調用 Gemini AI /// public async Task AnalyzeSentenceAsync(string inputText) { try { if (string.IsNullOrEmpty(_apiKey) || _apiKey == "your-gemini-api-key-here") { throw new InvalidOperationException("Gemini API key not configured"); } var prompt = $@" 請分析以下英文句子,提供完整的分析: 句子:{inputText} 請按照以下JSON格式回應,不要包含任何其他文字: {{ ""translation"": ""自然流暢的繁體中文翻譯"", ""explanation"": ""詳細解釋句子的語法結構、詞彙特點、使用場景和學習要點"", ""grammarCorrection"": {{ ""hasErrors"": false, ""originalText"": ""{inputText}"", ""correctedText"": null, ""corrections"": [] }}, ""highValueWords"": [""重要詞彙1"", ""重要詞彙2""], ""wordAnalysis"": {{ ""單字"": {{ ""translation"": ""中文翻譯"", ""definition"": ""英文定義"", ""partOfSpeech"": ""詞性"", ""pronunciation"": ""音標"", ""isHighValue"": true, ""difficultyLevel"": ""CEFR等級"" }} }} }} 要求: 1. 翻譯要自然流暢,符合中文語法 2. 解釋要具體有用,不要空泛 3. 標記B1以上詞彙為高價值 4. 如有語法錯誤請指出並修正 5. 確保JSON格式正確 "; var response = await CallGeminiApiAsync(prompt); return ParseSentenceAnalysisResponse(response); } catch (Exception ex) { _logger.LogError(ex, "Error analyzing sentence with Gemini API"); throw; } } 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) { var template = extractionType == "vocabulary" ? VocabularyExtractionPrompt : SmartExtractionPrompt; return template .Replace("{cardCount}", cardCount.ToString()) .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) { var requestBody = new { contents = new[] { new { parts = new[] { new { text = prompt } } } } }; var json = JsonSerializer.Serialize(requestBody); var content = new StringContent(json, Encoding.UTF8, "application/json"); var response = await _httpClient.PostAsync( $"https://generativelanguage.googleapis.com/v1beta/models/gemini-1.5-flash-latest:generateContent?key={_apiKey}", content); if (!response.IsSuccessStatusCode) { var errorContent = await response.Content.ReadAsStringAsync(); _logger.LogError("Gemini API error: {StatusCode} - {Content}", response.StatusCode, errorContent); throw new HttpRequestException($"Gemini API request failed: {response.StatusCode}"); } var responseContent = await response.Content.ReadAsStringAsync(); var geminiResponse = JsonSerializer.Deserialize(responseContent); if (geminiResponse.TryGetProperty("candidates", out var candidates) && candidates.GetArrayLength() > 0 && candidates[0].TryGetProperty("content", out var contentElement) && contentElement.TryGetProperty("parts", out var parts) && parts.GetArrayLength() > 0 && parts[0].TryGetProperty("text", out var textElement)) { return textElement.GetString() ?? ""; } throw new InvalidOperationException("Invalid response format from Gemini API"); } private List ParseGeneratedCards(string response) { try { // 清理回應文本 var cleanText = response.Trim(); cleanText = cleanText.Replace("```json", "").Replace("```", "").Trim(); // 如果不是以 { 開始,嘗試找到 JSON 部分 if (!cleanText.StartsWith("{")) { var jsonStart = cleanText.IndexOf("{"); if (jsonStart >= 0) { cleanText = cleanText[jsonStart..]; } } var jsonResponse = JsonSerializer.Deserialize(cleanText); if (!jsonResponse.TryGetProperty("cards", out var cardsElement) || cardsElement.ValueKind != JsonValueKind.Array) { throw new InvalidOperationException("Response does not contain cards array"); } var cards = new List(); foreach (var cardElement in cardsElement.EnumerateArray()) { var card = new GeneratedCard { Word = GetStringProperty(cardElement, "word"), PartOfSpeech = GetStringProperty(cardElement, "part_of_speech"), Pronunciation = GetStringProperty(cardElement, "pronunciation"), Translation = GetStringProperty(cardElement, "translation"), Definition = GetStringProperty(cardElement, "definition"), Synonyms = GetArrayProperty(cardElement, "synonyms"), Example = GetStringProperty(cardElement, "example"), ExampleTranslation = GetStringProperty(cardElement, "example_translation"), DifficultyLevel = GetStringProperty(cardElement, "difficulty_level") }; if (!string.IsNullOrEmpty(card.Word) && !string.IsNullOrEmpty(card.Translation)) { cards.Add(card); } } return cards; } catch (Exception ex) { _logger.LogError(ex, "Error parsing generated cards response: {Response}", response); throw new InvalidOperationException($"Failed to parse AI response: {ex.Message}"); } } 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 SentenceAnalysisResponse ParseSentenceAnalysisResponse(string response) { try { var cleanText = response.Trim().Replace("```json", "").Replace("```", "").Trim(); if (!cleanText.StartsWith("{")) { var jsonStart = cleanText.IndexOf("{"); if (jsonStart >= 0) { cleanText = cleanText[jsonStart..]; } } var jsonResponse = JsonSerializer.Deserialize(cleanText); return new SentenceAnalysisResponse { Translation = GetStringProperty(jsonResponse, "translation"), Explanation = GetStringProperty(jsonResponse, "explanation"), HighValueWords = GetArrayProperty(jsonResponse, "highValueWords"), WordAnalysis = ParseWordAnalysisFromJson(jsonResponse), GrammarCorrection = ParseGrammarCorrectionFromJson(jsonResponse) }; } catch (Exception ex) { _logger.LogError(ex, "Error parsing sentence analysis response: {Response}", response); throw new InvalidOperationException($"Failed to parse AI sentence analysis: {ex.Message}"); } } private Dictionary ParseWordAnalysisFromJson(JsonElement jsonResponse) { var result = new Dictionary(); if (jsonResponse.TryGetProperty("wordAnalysis", out var wordAnalysisElement)) { foreach (var property in wordAnalysisElement.EnumerateObject()) { var word = property.Name; var analysis = property.Value; result[word] = new WordAnalysisResult { Word = word, Translation = GetStringProperty(analysis, "translation"), Definition = GetStringProperty(analysis, "definition"), PartOfSpeech = GetStringProperty(analysis, "partOfSpeech"), Pronunciation = GetStringProperty(analysis, "pronunciation"), IsHighValue = analysis.TryGetProperty("isHighValue", out var isHighValueElement) && isHighValueElement.GetBoolean(), DifficultyLevel = GetStringProperty(analysis, "difficultyLevel") }; } } return result; } private GrammarCorrectionResult ParseGrammarCorrectionFromJson(JsonElement jsonResponse) { if (jsonResponse.TryGetProperty("grammarCorrection", out var grammarElement)) { return new GrammarCorrectionResult { HasErrors = grammarElement.TryGetProperty("hasErrors", out var hasErrorsElement) && hasErrorsElement.GetBoolean(), OriginalText = GetStringProperty(grammarElement, "originalText"), CorrectedText = GetStringProperty(grammarElement, "correctedText"), Corrections = new List() // 簡化 }; } return new GrammarCorrectionResult { HasErrors = false, OriginalText = "", CorrectedText = null, Corrections = new List() }; } private static string GetStringProperty(JsonElement element, string propertyName) { return element.TryGetProperty(propertyName, out var prop) ? prop.GetString() ?? "" : ""; } private static List GetArrayProperty(JsonElement element, string propertyName) { var result = new List(); if (element.TryGetProperty(propertyName, out var arrayElement) && arrayElement.ValueKind == JsonValueKind.Array) { foreach (var item in arrayElement.EnumerateArray()) { result.Add(item.GetString() ?? ""); } } return result; } // Prompt 模板 private const string VocabularyExtractionPrompt = @" 從以下英文文本中萃取 {cardCount} 個最重要的詞彙,為每個詞彙生成詞卡資料。 輸入文本: {inputText} 請按照以下 JSON 格式回應,不要包含任何其他文字或代碼塊標記: { ""cards"": [ { ""word"": ""單字原型"", ""part_of_speech"": ""詞性(n./v./adj./adv.等)"", ""pronunciation"": ""IPA音標"", ""translation"": ""繁體中文翻譯"", ""definition"": ""英文定義(保持A1-A2程度)"", ""synonyms"": [""同義詞1"", ""同義詞2""], ""example"": ""例句(使用原文中的句子或生成新句子)"", ""example_translation"": ""例句中文翻譯"", ""difficulty_level"": ""CEFR等級(A1/A2/B1/B2/C1/C2)"" } ] } 要求: 1. 選擇最有學習價值的詞彙 2. 定義要簡單易懂,適合英語學習者 3. 例句要實用且符合語境 4. 確保 JSON 格式正確 5. 同義詞最多2個,選擇常用的"; private const string SmartExtractionPrompt = @" 分析以下英文文本,識別片語、俚語和常用表達,生成 {cardCount} 個學習卡片: 輸入文本: {inputText} 重點關注: 1. 片語和俚語 2. 文化相關表達 3. 語境特定用法 4. 慣用語和搭配 請按照相同的 JSON 格式回應..."; private const string CardValidationPrompt = @" 請檢查以下詞卡內容的準確性: 單字: {word} 翻譯: {translation} 定義: {definition} 詞性: {partOfSpeech} 發音: {pronunciation} 例句: {example} 請按照以下 JSON 格式回應: { ""issues"": [], ""suggestions"": [], ""overall_score"": 85, ""confidence"": 0.9 }"; } // 支援類型 public class GeneratedCard { public string Word { get; set; } = string.Empty; public string PartOfSpeech { get; set; } = string.Empty; public string Pronunciation { get; set; } = string.Empty; public string Translation { get; set; } = string.Empty; public string Definition { get; set; } = string.Empty; public List Synonyms { get; set; } = new(); public string Example { get; set; } = string.Empty; public string ExampleTranslation { get; set; } = string.Empty; 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 { public string Translation { get; set; } = string.Empty; public string Explanation { get; set; } = string.Empty; public List HighValueWords { get; set; } = new(); public Dictionary WordAnalysis { get; set; } = new(); public GrammarCorrectionResult GrammarCorrection { get; set; } = new(); } public class WordAnalysisResult { public string Word { get; set; } = string.Empty; public string Translation { get; set; } = string.Empty; public string Definition { get; set; } = string.Empty; public string PartOfSpeech { get; set; } = string.Empty; public string Pronunciation { get; set; } = string.Empty; public bool IsHighValue { get; set; } public string DifficultyLevel { get; set; } = string.Empty; } public class GrammarCorrectionResult { public bool HasErrors { get; set; } public string OriginalText { get; set; } = string.Empty; public string? CorrectedText { get; set; } public List Corrections { get; set; } = new(); } public class GrammarCorrection { public string ErrorType { 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; }