dramaling-vocab-learning/backend/DramaLing.Api/Services/BlankGenerationService.cs

138 lines
5.0 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

using System.Text.RegularExpressions;
namespace DramaLing.Api.Services;
public interface IBlankGenerationService
{
Task<string?> GenerateBlankQuestionAsync(string word, string example);
string? TryProgrammaticBlank(string word, string example);
Task<string?> GenerateAIBlankAsync(string word, string example);
bool HasValidBlank(string blankQuestion);
}
public class BlankGenerationService : IBlankGenerationService
{
private readonly IWordVariationService _wordVariationService;
private readonly IGeminiService _geminiService;
private readonly ILogger<BlankGenerationService> _logger;
public BlankGenerationService(
IWordVariationService wordVariationService,
IGeminiService geminiService,
ILogger<BlankGenerationService> logger)
{
_wordVariationService = wordVariationService;
_geminiService = geminiService;
_logger = logger;
}
public async Task<string?> 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<string?> 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;
}
}