138 lines
5.0 KiB
C#
138 lines
5.0 KiB
C#
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;
|
||
}
|
||
} |