using DramaLing.Api.Data; using DramaLing.Api.Models.DTOs.SpacedRepetition; using DramaLing.Api.Models.Entities; using Microsoft.EntityFrameworkCore; namespace DramaLing.Api.Services; /// /// 題目生成服務介面 /// public interface IQuestionGeneratorService { Task GenerateQuestionAsync(Guid flashcardId, string questionType); } /// /// 題目生成服務實現 /// public class QuestionGeneratorService : IQuestionGeneratorService { private readonly DramaLingDbContext _context; private readonly IOptionsVocabularyService _optionsVocabularyService; private readonly ILogger _logger; public QuestionGeneratorService( DramaLingDbContext context, IOptionsVocabularyService optionsVocabularyService, ILogger logger) { _context = context; _optionsVocabularyService = optionsVocabularyService; _logger = logger; } /// /// 根據題型生成對應的題目數據 /// public async Task GenerateQuestionAsync(Guid flashcardId, string questionType) { var flashcard = await _context.Flashcards.FindAsync(flashcardId); if (flashcard == null) throw new ArgumentException($"Flashcard {flashcardId} not found"); _logger.LogInformation("Generating {QuestionType} question for flashcard {FlashcardId}, word: {Word}", questionType, flashcardId, flashcard.Word); return questionType switch { "vocab-choice" => await GenerateVocabChoiceAsync(flashcard), "sentence-fill" => GenerateFillBlankQuestion(flashcard), "sentence-reorder" => GenerateReorderQuestion(flashcard), "sentence-listening" => await GenerateSentenceListeningAsync(flashcard), _ => new QuestionData { QuestionType = questionType, CorrectAnswer = flashcard.Word } }; } /// /// 生成詞彙選擇題選項 /// private async Task GenerateVocabChoiceAsync(Flashcard flashcard) { var distractors = new List(); // 🆕 優先嘗試使用智能詞彙庫生成選項 try { // 直接使用 Flashcard 的屬性 var cefrLevel = flashcard.DifficultyLevel ?? "B1"; // 預設為 B1 var partOfSpeech = flashcard.PartOfSpeech ?? "noun"; // 預設為 noun _logger.LogDebug("Attempting to generate smart distractors for '{Word}' (CEFR: {CEFR}, PartOfSpeech: {PartOfSpeech})", flashcard.Word, cefrLevel, partOfSpeech); // 檢查詞彙庫是否有足夠詞彙 var hasSufficientVocab = await _optionsVocabularyService.HasSufficientVocabularyAsync(cefrLevel, partOfSpeech); if (hasSufficientVocab) { var smartDistractors = await _optionsVocabularyService.GenerateDistractorsAsync( flashcard.Word, cefrLevel, partOfSpeech, 3); if (smartDistractors.Any()) { distractors.AddRange(smartDistractors); _logger.LogInformation("Successfully generated {Count} smart distractors for '{Word}'", smartDistractors.Count, flashcard.Word); } } } catch (Exception ex) { _logger.LogWarning(ex, "Failed to generate smart distractors for '{Word}', falling back to user vocabulary", flashcard.Word); } // 🔄 回退機制:如果智能詞彙庫無法提供足夠選項,使用原有邏輯 if (distractors.Count < 3) { _logger.LogInformation("Using fallback method for '{Word}' (current distractors: {Count})", flashcard.Word, distractors.Count); var userDistractors = await _context.Flashcards .Where(f => f.UserId == flashcard.UserId && f.Id != flashcard.Id && !f.IsArchived && !distractors.Contains(f.Word)) // 避免重複 .OrderBy(x => Guid.NewGuid()) .Take(3 - distractors.Count) .Select(f => f.Word) .ToListAsync(); distractors.AddRange(userDistractors); // 如果還是不夠,使用預設選項 while (distractors.Count < 3) { var defaultOptions = new[] { "example", "sample", "test", "word", "basic", "simple", "common", "usual" }; var availableDefaults = defaultOptions .Where(opt => opt != flashcard.Word && !distractors.Contains(opt)); var neededCount = 3 - distractors.Count; distractors.AddRange(availableDefaults.Take(neededCount)); // 防止無限循環 if (!availableDefaults.Any()) break; } } var options = new List { flashcard.Word }; options.AddRange(distractors.Take(3)); // 隨機打亂選項順序 var shuffledOptions = options.OrderBy(x => Guid.NewGuid()).ToArray(); return new QuestionData { QuestionType = "vocab-choice", Options = shuffledOptions, CorrectAnswer = flashcard.Word }; } /// /// 生成填空題 /// private QuestionData GenerateFillBlankQuestion(Flashcard flashcard) { if (string.IsNullOrEmpty(flashcard.Example)) { throw new ArgumentException($"Flashcard {flashcard.Id} has no example sentence for fill-blank question"); } // 在例句中將目標詞彙替換為空白 var blankedSentence = flashcard.Example.Replace(flashcard.Word, "______", StringComparison.OrdinalIgnoreCase); // 如果沒有替換成功,嘗試其他變化形式 if (blankedSentence == flashcard.Example) { // TODO: 未來可以實現更智能的詞形變化識別 blankedSentence = flashcard.Example + " (請填入: " + flashcard.Word + ")"; } return new QuestionData { QuestionType = "sentence-fill", BlankedSentence = blankedSentence, CorrectAnswer = flashcard.Word, Sentence = flashcard.Example }; } /// /// 生成例句重組題 /// private QuestionData GenerateReorderQuestion(Flashcard flashcard) { if (string.IsNullOrEmpty(flashcard.Example)) { throw new ArgumentException($"Flashcard {flashcard.Id} has no example sentence for reorder question"); } // 將例句拆分為單字並打亂順序 var words = flashcard.Example .Split(new[] { ' ', '\t', '\n', '\r' }, StringSplitOptions.RemoveEmptyEntries) .Select(word => word.Trim('.',',','!','?',';',':')) // 移除標點符號 .Where(word => !string.IsNullOrEmpty(word)) .ToArray(); // 隨機打亂順序 var scrambledWords = words.OrderBy(x => Guid.NewGuid()).ToArray(); return new QuestionData { QuestionType = "sentence-reorder", ScrambledWords = scrambledWords, CorrectAnswer = flashcard.Example, Sentence = flashcard.Example }; } /// /// 生成例句聽力題選項 /// private async Task GenerateSentenceListeningAsync(Flashcard flashcard) { if (string.IsNullOrEmpty(flashcard.Example)) { throw new ArgumentException($"Flashcard {flashcard.Id} has no example sentence for listening question"); } // 從其他詞卡中選擇3個例句作為干擾選項 var distractorSentences = await _context.Flashcards .Where(f => f.UserId == flashcard.UserId && f.Id != flashcard.Id && !f.IsArchived && !string.IsNullOrEmpty(f.Example)) .OrderBy(x => Guid.NewGuid()) .Take(3) .Select(f => f.Example!) .ToListAsync(); // 如果沒有足夠的例句,添加預設選項 while (distractorSentences.Count < 3) { var defaultSentences = new[] { "This is a simple example sentence.", "I think this is a good opportunity.", "She decided to take a different approach.", "They managed to solve the problem quickly." }; var availableDefaults = defaultSentences .Where(sent => sent != flashcard.Example && !distractorSentences.Contains(sent)); distractorSentences.AddRange(availableDefaults.Take(3 - distractorSentences.Count)); } var options = new List { flashcard.Example }; options.AddRange(distractorSentences.Take(3)); // 隨機打亂選項順序 var shuffledOptions = options.OrderBy(x => Guid.NewGuid()).ToArray(); return new QuestionData { QuestionType = "sentence-listening", Options = shuffledOptions, CorrectAnswer = flashcard.Example, Sentence = flashcard.Example, AudioUrl = $"/audio/sentences/{flashcard.Id}.mp3" // 未來音頻服務用 }; } /// /// 判斷是否為A1學習者 /// public bool IsA1Learner(int userLevel) => userLevel <= 20; // 固定A1門檻 /// /// 獲取適配情境描述 /// public string GetAdaptationContext(int userLevel, int wordLevel) { var difficulty = wordLevel - userLevel; if (userLevel <= 20) // 固定A1門檻 return "A1學習者"; if (difficulty < -10) return "簡單詞彙"; if (difficulty >= -10 && difficulty <= 10) return "適中詞彙"; return "困難詞彙"; } }