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 ILogger _logger; public QuestionGeneratorService( DramaLingDbContext context, ILogger logger) { _context = context; _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) { // 從相同用戶的其他詞卡中選擇3個干擾選項 var distractors = await _context.Flashcards .Where(f => f.UserId == flashcard.UserId && f.Id != flashcard.Id && !f.IsArchived) .OrderBy(x => Guid.NewGuid()) // 隨機排序 .Take(3) .Select(f => f.Word) .ToListAsync(); // 如果沒有足夠的詞卡,添加一些預設選項 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)); distractors.AddRange(availableDefaults.Take(3 - distractors.Count)); } 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 "困難詞彙"; } /// /// 獲取選擇原因說明 /// private string GetSelectionReason(string selectedMode, int userLevel, int wordLevel) { var context = GetAdaptationContext(userLevel, wordLevel); return context switch { "A1學習者" => "A1學習者使用基礎題型建立信心", "簡單詞彙" => "簡單詞彙重點練習應用和拼寫", "適中詞彙" => "適中詞彙進行全方位練習,包括口說", "困難詞彙" => "困難詞彙回歸基礎重建記憶", _ => "系統智能選擇最適合的複習方式" }; } }