using System.Text.RegularExpressions; namespace DramaLing.Api.Services; public interface IBlankGenerationService { Task GenerateBlankQuestionAsync(string word, string example); string? TryProgrammaticBlank(string word, string example); Task GenerateAIBlankAsync(string word, string example); bool HasValidBlank(string blankQuestion); } public class BlankGenerationService : IBlankGenerationService { private readonly IWordVariationService _wordVariationService; private readonly IGeminiService _geminiService; private readonly ILogger _logger; public BlankGenerationService( IWordVariationService wordVariationService, IGeminiService geminiService, ILogger logger) { _wordVariationService = wordVariationService; _geminiService = geminiService; _logger = logger; } public async Task GenerateBlankQuestionAsync(string word, string example) { if (string.IsNullOrEmpty(word) || string.IsNullOrEmpty(example)) { _logger.LogWarning("Invalid input - word or example is null/empty"); return null; } _logger.LogInformation("Generating blank question for word: {Word}, example: {Example}", word, example); // Step 1: 嘗試程式碼挖空 var programmaticResult = TryProgrammaticBlank(word, example); if (!string.IsNullOrEmpty(programmaticResult)) { _logger.LogInformation("Successfully generated programmatic blank for word: {Word}", word); return programmaticResult; } // Step 2: 程式碼挖空失敗,嘗試 AI 挖空 _logger.LogInformation("Programmatic blank failed for word: {Word}, trying AI blank", word); var aiResult = await GenerateAIBlankAsync(word, example); if (!string.IsNullOrEmpty(aiResult)) { _logger.LogInformation("Successfully generated AI blank for word: {Word}", word); return aiResult; } _logger.LogWarning("Both programmatic and AI blank generation failed for word: {Word}", word); return null; } public string? TryProgrammaticBlank(string word, string example) { try { _logger.LogDebug("Attempting programmatic blank for word: {Word}", word); // 1. 完全匹配 (不區分大小寫) var exactMatch = Regex.Replace(example, $@"\b{Regex.Escape(word)}\b", "____", RegexOptions.IgnoreCase); if (exactMatch != example) { _logger.LogDebug("Exact match blank successful for word: {Word}", word); return exactMatch; } // 2. 常見變形處理 var variations = _wordVariationService.GetCommonVariations(word); foreach(var variation in variations) { var variantMatch = Regex.Replace(example, $@"\b{Regex.Escape(variation)}\b", "____", RegexOptions.IgnoreCase); if (variantMatch != example) { _logger.LogDebug("Variation match blank successful for word: {Word}, variation: {Variation}", word, variation); return variantMatch; } } _logger.LogDebug("Programmatic blank failed for word: {Word}", word); return null; // 挖空失敗 } catch (Exception ex) { _logger.LogError(ex, "Error in programmatic blank for word: {Word}", word); return null; } } public async Task GenerateAIBlankAsync(string word, string example) { try { var prompt = $@" 請將以下例句中與詞彙「{word}」相關的詞挖空,用____替代: 詞彙: {word} 例句: {example} 規則: 1. 只挖空與目標詞彙相關的詞(包含變形、時態、複數等) 2. 用____替代被挖空的詞 3. 保持句子其他部分不變 4. 直接返回挖空後的句子,不要額外說明 挖空後的句子:"; _logger.LogInformation("Generating AI blank for word: {Word}, example: {Example}", word, example); // 暫時使用程式碼邏輯,AI 功能將在後續版本實現 // TODO: 整合 Gemini API 進行智能挖空 _logger.LogInformation("AI blank generation not yet implemented, returning null"); return null; } catch (Exception ex) { _logger.LogError(ex, "Error generating AI blank for word: {Word}", word); return null; } } public bool HasValidBlank(string blankQuestion) { var isValid = !string.IsNullOrEmpty(blankQuestion) && blankQuestion.Contains("____"); _logger.LogDebug("Validating blank question: {IsValid}", isValid); return isValid; } }