using DramaLing.Api.Models.DTOs; using DramaLing.Api.Utils; using System.Text.Json; namespace DramaLing.Api.Services.AI.Gemini; /// /// 句子分析服務實作 /// public class SentenceAnalyzer : ISentenceAnalyzer { private readonly IGeminiClient _geminiClient; private readonly ILogger _logger; public SentenceAnalyzer( IGeminiClient geminiClient, ILogger logger) { _geminiClient = geminiClient ?? throw new ArgumentNullException(nameof(geminiClient)); _logger = logger ?? throw new ArgumentNullException(nameof(logger)); } public async Task AnalyzeSentenceAsync(string inputText, AnalysisOptions options) { var startTime = DateTime.UtcNow; try { _logger.LogInformation("Starting sentence analysis for text: {Text}", inputText.Substring(0, Math.Min(50, inputText.Length))); var prompt = BuildAnalysisPrompt(inputText); var aiResponse = await _geminiClient.CallGeminiAPIAsync(prompt); _logger.LogInformation("Gemini AI response received: {ResponseLength} characters", aiResponse?.Length ?? 0); if (string.IsNullOrWhiteSpace(aiResponse)) { throw new InvalidOperationException("Gemini API returned empty response"); } // 檢查是否是安全過濾的回退訊息 if (aiResponse.Contains("temporarily unavailable due to safety filtering")) { _logger.LogWarning("Using safety filtering fallback response"); } var analysisData = CreateAnalysisFromAIResponse(inputText, aiResponse); var processingTime = (DateTime.UtcNow - startTime).TotalSeconds; _logger.LogInformation("Sentence analysis completed in {ProcessingTime}s", processingTime); return analysisData; } catch (Exception ex) { _logger.LogError(ex, "Error analyzing sentence: {Text}", inputText); throw; } } private string BuildAnalysisPrompt(string inputText) { return $@"You are an English learning assistant. Analyze this sentence and return ONLY a valid JSON response. **Input Sentence**: ""{inputText}"" **Required JSON Structure:** {{ ""sentenceTranslation"": ""Traditional Chinese translation of the entire sentence"", ""hasGrammarErrors"": true/false, ""grammarCorrections"": [ {{ ""original"": ""incorrect text"", ""corrected"": ""correct text"", ""type"": ""error type (tense/subject-verb/preposition/word-order)"", ""explanation"": ""brief explanation in Traditional Chinese"" }} ], ""vocabularyAnalysis"": {{ ""word1"": {{ ""word"": ""the word"", ""translation"": ""Traditional Chinese translation"", ""definition"": ""English definition"", ""partOfSpeech"": ""noun/verb/adjective/adverb/pronoun/preposition/conjunction/interjection"", ""pronunciation"": ""/phonetic/"", ""CEFR"": ""A1/A2/B1/B2/C1/C2"", ""frequency"": ""high/medium/low"", ""synonyms"": [""synonym1"", ""synonym2""], ""example"": ""example sentence"", ""exampleTranslation"": ""Traditional Chinese example translation"" }} }}, ""idioms"": [ {{ ""idiom"": ""idiomatic expression"", ""translation"": ""Traditional Chinese meaning"", ""definition"": ""English explanation"", ""pronunciation"": ""/phonetic notation/"", ""CEFR"": ""A1/A2/B1/B2/C1/C2"", ""frequency"": ""high/medium/low"", ""synonyms"": [""synonym1"", ""synonym2""], ""example"": ""usage example"", ""exampleTranslation"": ""Traditional Chinese example"" }} ] }} **Analysis Guidelines:** 1. **Grammar Check**: Detect tense errors, subject-verb agreement, preposition usage, word order 2. **Vocabulary Analysis**: Include ALL significant words (exclude articles: a, an, the) 3. **CEFR Levels**: Assign accurate A1-C2 levels for each word 4. **Idioms**: Identify any idiomatic expressions or phrasal verbs 5. **Translations**: Use Traditional Chinese (Taiwan standard) **IMPORTANT**: Return ONLY the JSON object, no additional text or explanation."; } private SentenceAnalysisData CreateAnalysisFromAIResponse(string inputText, string aiResponse) { _logger.LogInformation("Creating analysis from AI response: {ResponsePreview}...", aiResponse.Substring(0, Math.Min(100, aiResponse.Length))); try { // 清理 AI 回應以確保是純 JSON var cleanJson = aiResponse.Trim(); if (cleanJson.StartsWith("```json")) { cleanJson = cleanJson.Substring(7); } if (cleanJson.EndsWith("```")) { cleanJson = cleanJson.Substring(0, cleanJson.Length - 3); } // 解析 AI 回應的 JSON var aiAnalysis = JsonSerializer.Deserialize(cleanJson, new JsonSerializerOptions { PropertyNameCaseInsensitive = true }); if (aiAnalysis == null) { throw new InvalidOperationException("Failed to parse AI response JSON"); } // 轉換為 DTO 結構 var analysisData = new SentenceAnalysisData { OriginalText = inputText, SentenceMeaning = aiAnalysis.SentenceTranslation ?? "", VocabularyAnalysis = ConvertVocabularyAnalysis(aiAnalysis.VocabularyAnalysis ?? new()), Idioms = ConvertIdioms(aiAnalysis.Idioms ?? new()), GrammarCorrection = ConvertGrammarCorrection(aiAnalysis), Metadata = new AnalysisMetadata { ProcessingDate = DateTime.UtcNow, AnalysisModel = "gemini-1.5-flash", AnalysisVersion = "2.0" } }; return analysisData; } catch (JsonException ex) { _logger.LogError(ex, "Failed to parse AI response as JSON: {Response}", aiResponse); return CreateFallbackAnalysis(inputText, aiResponse); } } private Dictionary ConvertVocabularyAnalysis( Dictionary aiVocab) { var result = new Dictionary(); foreach (var kvp in aiVocab) { var aiWord = kvp.Value; result[kvp.Key] = new VocabularyAnalysisDto { Word = aiWord.Word ?? kvp.Key, Translation = aiWord.Translation ?? "", Definition = aiWord.Definition ?? "", PartOfSpeech = aiWord.PartOfSpeech ?? "unknown", Pronunciation = aiWord.Pronunciation ?? $"/{kvp.Key}/", DifficultyLevelNumeric = CEFRHelper.ToNumeric(aiWord.CEFR ?? "A0"), CEFR = aiWord.CEFR ?? "A0", Frequency = aiWord.Frequency ?? "medium", Synonyms = aiWord.Synonyms ?? new List(), Example = aiWord.Example, ExampleTranslation = aiWord.ExampleTranslation, }; } return result; } private List ConvertIdioms(List aiIdioms) { var result = new List(); foreach (var aiIdiom in aiIdioms) { result.Add(new IdiomDto { Idiom = aiIdiom.Idiom ?? "", Translation = aiIdiom.Translation ?? "", Definition = aiIdiom.Definition ?? "", Pronunciation = aiIdiom.Pronunciation ?? "", DifficultyLevelNumeric = CEFRHelper.ToNumeric(aiIdiom.CEFR ?? "A0"), CEFR = aiIdiom.CEFR ?? "A0", Frequency = aiIdiom.Frequency ?? "medium", Synonyms = aiIdiom.Synonyms ?? new List(), Example = aiIdiom.Example, ExampleTranslation = aiIdiom.ExampleTranslation }); } return result; } private GrammarCorrectionDto? ConvertGrammarCorrection(AiAnalysisResponse aiAnalysis) { if (!aiAnalysis.HasGrammarErrors || aiAnalysis.GrammarCorrections == null || !aiAnalysis.GrammarCorrections.Any()) { return null; } var corrections = aiAnalysis.GrammarCorrections.Select(gc => new GrammarErrorDto { Error = gc.Original ?? "", Correction = gc.Corrected ?? "", Type = gc.Type ?? "grammar", Explanation = gc.Explanation ?? "", Severity = "medium", Position = new ErrorPosition { Start = 0, End = 0 } }).ToList(); return new GrammarCorrectionDto { HasErrors = true, CorrectedText = string.Join(" ", corrections.Select(c => c.Correction)), Corrections = corrections }; } private SentenceAnalysisData CreateFallbackAnalysis(string inputText, string aiResponse) { _logger.LogWarning("Using fallback analysis due to JSON parsing failure"); return new SentenceAnalysisData { OriginalText = inputText, SentenceMeaning = aiResponse, VocabularyAnalysis = CreateBasicVocabularyFromText(inputText, aiResponse), Metadata = new AnalysisMetadata { ProcessingDate = DateTime.UtcNow, AnalysisModel = "gemini-1.5-flash-fallback", AnalysisVersion = "2.0" }, }; } private Dictionary CreateBasicVocabularyFromText( string inputText, string aiResponse) { var words = inputText.Split(' ', StringSplitOptions.RemoveEmptyEntries); var result = new Dictionary(); foreach (var word in words.Take(15)) { var cleanWord = word.ToLower().Trim('.', ',', '!', '?', ';', ':', '"', '\''); if (string.IsNullOrEmpty(cleanWord) || cleanWord.Length < 2) continue; result[cleanWord] = new VocabularyAnalysisDto { Word = cleanWord, Translation = ExtractTranslationFromAI(cleanWord, aiResponse), Definition = $"Please refer to the AI analysis above for detailed definition.", PartOfSpeech = "unknown", Pronunciation = $"/{cleanWord}/", DifficultyLevelNumeric = EstimateBasicDifficultyNumeric(cleanWord), Frequency = "medium", Synonyms = new List(), Example = null, ExampleTranslation = null, }; } return result; } private string ExtractTranslationFromAI(string word, string aiResponse) { if (aiResponse.Contains(word, StringComparison.OrdinalIgnoreCase)) { return $"{word} translation from AI"; } return $"{word} - 請查看完整分析"; } private int EstimateBasicDifficultyNumeric(string word) { var a1Words = new[] { "i", "you", "he", "she", "it", "we", "they", "the", "a", "an", "is", "are", "was", "were", "have", "do", "go", "get", "see", "know" }; var a2Words = new[] { "make", "think", "come", "take", "use", "work", "call", "try", "ask", "need", "feel", "become", "leave", "put", "say", "tell", "turn", "move" }; var lowerWord = word.ToLower(); if (a1Words.Contains(lowerWord)) return 1; // A1 if (a2Words.Contains(lowerWord)) return 2; // A2 if (word.Length <= 4) return 2; // A2 if (word.Length <= 6) return 3; // B1 return 4; // B2 } } // AI Response JSON Models internal class AiAnalysisResponse { public string? SentenceTranslation { get; set; } public bool HasGrammarErrors { get; set; } public List? GrammarCorrections { get; set; } public Dictionary? VocabularyAnalysis { get; set; } public List? Idioms { get; set; } } internal class AiGrammarCorrection { public string? Original { get; set; } public string? Corrected { get; set; } public string? Type { get; set; } public string? Explanation { get; set; } } internal class AiVocabularyAnalysis { public string? Word { get; set; } public string? Translation { get; set; } public string? Definition { get; set; } public string? PartOfSpeech { get; set; } public string? Pronunciation { get; set; } public string? CEFR { get; set; } public string? Frequency { get; set; } public List? Synonyms { get; set; } public string? Example { get; set; } public string? ExampleTranslation { get; set; } } internal class AiIdiom { public string? Idiom { get; set; } public string? Translation { get; set; } public string? Definition { get; set; } public string? Pronunciation { get; set; } public string? CEFR { get; set; } public string? Frequency { get; set; } public List? Synonyms { get; set; } public string? Example { get; set; } public string? ExampleTranslation { get; set; } }