648 lines
26 KiB
C#
648 lines
26 KiB
C#
using DramaLing.Api.Models.DTOs;
|
||
using System.Text.Json;
|
||
using System.Text.RegularExpressions;
|
||
using System.Text;
|
||
|
||
namespace DramaLing.Api.Services;
|
||
|
||
public interface IGeminiService
|
||
{
|
||
Task<SentenceAnalysisData> AnalyzeSentenceAsync(string inputText, string userLevel, AnalysisOptions options);
|
||
}
|
||
|
||
public class GeminiService : IGeminiService
|
||
{
|
||
private readonly HttpClient _httpClient;
|
||
private readonly ILogger<GeminiService> _logger;
|
||
private readonly string[] _cefrLevels = { "A1", "A2", "B1", "B2", "C1", "C2" };
|
||
private readonly string _apiKey;
|
||
|
||
public GeminiService(HttpClient httpClient, IConfiguration configuration, ILogger<GeminiService> logger)
|
||
{
|
||
_httpClient = httpClient;
|
||
_logger = logger;
|
||
|
||
_apiKey = Environment.GetEnvironmentVariable("GEMINI_API_KEY")
|
||
?? configuration["AI:GeminiApiKey"]
|
||
?? configuration["Gemini:ApiKey"]
|
||
?? "mock-api-key"; // For development without Gemini
|
||
|
||
_logger.LogInformation("GeminiService initialized with API key: {ApiKeyStart}...",
|
||
_apiKey.Length > 10 ? _apiKey.Substring(0, 10) : "mock");
|
||
|
||
_httpClient.DefaultRequestHeaders.Add("User-Agent", "DramaLing/1.0");
|
||
}
|
||
|
||
public async Task<SentenceAnalysisData> AnalyzeSentenceAsync(string inputText, string userLevel, AnalysisOptions options)
|
||
{
|
||
var startTime = DateTime.UtcNow;
|
||
|
||
try
|
||
{
|
||
_logger.LogInformation("Starting sentence analysis for text: {Text}, UserLevel: {UserLevel}",
|
||
inputText.Substring(0, Math.Min(50, inputText.Length)), userLevel);
|
||
|
||
var prompt = BuildAnalysisPrompt(inputText, userLevel, options);
|
||
var response = await CallGeminiAPI(prompt);
|
||
var analysisData = ParseGeminiResponse(response, inputText, userLevel);
|
||
|
||
var processingTime = (DateTime.UtcNow - startTime).TotalSeconds;
|
||
analysisData.Metadata.ProcessingDate = DateTime.UtcNow;
|
||
|
||
_logger.LogInformation("Sentence analysis completed in {ProcessingTime}s", processingTime);
|
||
|
||
return analysisData;
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
_logger.LogError(ex, "Error analyzing sentence: {Text}", inputText);
|
||
throw;
|
||
}
|
||
}
|
||
|
||
private string BuildAnalysisPrompt(string inputText, string userLevel, AnalysisOptions options)
|
||
{
|
||
var userIndex = Array.IndexOf(_cefrLevels, userLevel);
|
||
var targetLevels = GetTargetLevels(userIndex);
|
||
|
||
return $@"
|
||
請分析以下英文句子並以JSON格式回應:
|
||
句子: ""{inputText}""
|
||
學習者程度: {userLevel}
|
||
|
||
請提供完整的分析,包含:
|
||
|
||
1. 語法檢查:檢查是否有語法錯誤,如有則提供修正建議
|
||
2. 詞彙分析:分析句子中每個有意義的詞彙
|
||
3. 中文翻譯:提供自然流暢的繁體中文翻譯
|
||
4. 慣用語識別:識別句子中的慣用語和片語
|
||
|
||
詞彙分析要求:
|
||
- 為每個詞彙標註CEFR等級 (A1-C2)
|
||
- 如果是慣用語,設置 isPhrase: true
|
||
- 提供IPA發音標記
|
||
- 包含同義詞
|
||
- 提供適當的例句和翻譯
|
||
|
||
回應格式要求:
|
||
{{
|
||
""grammarCorrection"": {{
|
||
""hasErrors"": boolean,
|
||
""correctedText"": ""修正後的句子"",
|
||
""corrections"": [
|
||
{{
|
||
""error"": ""錯誤詞彙"",
|
||
""correction"": ""正確詞彙"",
|
||
""type"": ""錯誤類型"",
|
||
""explanation"": ""解釋""
|
||
}}
|
||
]
|
||
}},
|
||
""sentenceMeaning"": ""繁體中文翻譯"",
|
||
""vocabularyAnalysis"": {{
|
||
""詞彙"": {{
|
||
""word"": ""詞彙"",
|
||
""translation"": ""中文翻譯"",
|
||
""definition"": ""英文定義"",
|
||
""partOfSpeech"": ""詞性"",
|
||
""pronunciation"": ""IPA發音"",
|
||
""difficultyLevel"": ""CEFR等級"",
|
||
""isPhrase"": false,
|
||
""frequency"": ""使用頻率"",
|
||
""synonyms"": [""同義詞""],
|
||
""example"": ""例句"",
|
||
""exampleTranslation"": ""例句翻譯"",
|
||
""tags"": [""標籤""]
|
||
}}
|
||
}}
|
||
}}
|
||
|
||
重要:回應必須是有效的JSON格式,不要包含任何其他文字。";
|
||
}
|
||
|
||
private string[] GetTargetLevels(int userIndex)
|
||
{
|
||
var targets = new List<string>();
|
||
|
||
if (userIndex + 1 < _cefrLevels.Length)
|
||
targets.Add(_cefrLevels[userIndex + 1]);
|
||
|
||
if (userIndex + 2 < _cefrLevels.Length)
|
||
targets.Add(_cefrLevels[userIndex + 2]);
|
||
|
||
return targets.ToArray();
|
||
}
|
||
|
||
private async Task<string> CallGeminiAPI(string prompt)
|
||
{
|
||
// 暫時使用模擬數據,稍後可替換為真實Gemini調用
|
||
if (_apiKey == "mock-api-key")
|
||
{
|
||
_logger.LogInformation("Using mock AI response for development");
|
||
await Task.Delay(1000); // 模擬API延遲
|
||
return GetMockResponse();
|
||
}
|
||
|
||
try
|
||
{
|
||
var requestBody = new
|
||
{
|
||
contents = new[]
|
||
{
|
||
new
|
||
{
|
||
parts = new[]
|
||
{
|
||
new { text = prompt }
|
||
}
|
||
}
|
||
},
|
||
generationConfig = new
|
||
{
|
||
temperature = 0.3,
|
||
topK = 1,
|
||
topP = 1,
|
||
maxOutputTokens = 2000
|
||
}
|
||
};
|
||
|
||
var json = JsonSerializer.Serialize(requestBody);
|
||
var content = new StringContent(json, Encoding.UTF8, "application/json");
|
||
|
||
var response = await _httpClient.PostAsync($"https://generativelanguage.googleapis.com/v1beta/models/gemini-pro:generateContent?key={_apiKey}", content);
|
||
response.EnsureSuccessStatusCode();
|
||
|
||
var responseJson = await response.Content.ReadAsStringAsync();
|
||
var geminiResponse = JsonSerializer.Deserialize<GeminiApiResponse>(responseJson);
|
||
|
||
return geminiResponse?.Candidates?.FirstOrDefault()?.Content?.Parts?.FirstOrDefault()?.Text ?? string.Empty;
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
_logger.LogError(ex, "Gemini API call failed, falling back to mock response");
|
||
return GetMockResponse();
|
||
}
|
||
}
|
||
|
||
private string GetMockResponse()
|
||
{
|
||
return @"{
|
||
""grammarCorrection"": {
|
||
""hasErrors"": true,
|
||
""correctedText"": ""She just joined the team, so let's cut her some slack until she gets used to the workflow."",
|
||
""corrections"": [
|
||
{
|
||
""error"": ""join"",
|
||
""correction"": ""joined"",
|
||
""type"": ""時態錯誤"",
|
||
""explanation"": ""第三人稱單數過去式應使用 'joined'""
|
||
},
|
||
{
|
||
""error"": ""get"",
|
||
""correction"": ""gets"",
|
||
""type"": ""時態錯誤"",
|
||
""explanation"": ""第三人稱單數現在式應使用 'gets'""
|
||
}
|
||
]
|
||
},
|
||
""sentenceMeaning"": ""她剛加入團隊,所以讓我們對她寬容一點,直到她習慣工作流程。"",
|
||
""vocabularyAnalysis"": {
|
||
""she"": {
|
||
""word"": ""she"",
|
||
""translation"": ""她"",
|
||
""definition"": ""female person pronoun"",
|
||
""partOfSpeech"": ""pronoun"",
|
||
""pronunciation"": ""/ʃiː/"",
|
||
""difficultyLevel"": ""A1"",
|
||
""isPhrase"": false,
|
||
""frequency"": ""very_high"",
|
||
""synonyms"": [""her""],
|
||
""example"": ""She is a teacher."",
|
||
""exampleTranslation"": ""她是一名老師。"",
|
||
""tags"": [""basic"", ""pronoun""]
|
||
},
|
||
""just"": {
|
||
""word"": ""just"",
|
||
""translation"": ""剛剛;僅僅"",
|
||
""definition"": ""recently; only"",
|
||
""partOfSpeech"": ""adverb"",
|
||
""pronunciation"": ""/dʒʌst/"",
|
||
""difficultyLevel"": ""A2"",
|
||
""isPhrase"": false,
|
||
""frequency"": ""high"",
|
||
""synonyms"": [""recently"", ""only"", ""merely""],
|
||
""example"": ""I just arrived."",
|
||
""exampleTranslation"": ""我剛到。"",
|
||
""tags"": [""time"", ""adverb""]
|
||
},
|
||
""joined"": {
|
||
""word"": ""joined"",
|
||
""translation"": ""加入"",
|
||
""definition"": ""became a member of (past tense of join)"",
|
||
""partOfSpeech"": ""verb"",
|
||
""pronunciation"": ""/dʒɔɪnd/"",
|
||
""difficultyLevel"": ""B1"",
|
||
""isPhrase"": false,
|
||
""frequency"": ""medium"",
|
||
""synonyms"": [""entered"", ""became part of""],
|
||
""example"": ""He joined the company last year."",
|
||
""exampleTranslation"": ""他去年加入了這家公司。"",
|
||
""tags"": [""work"", ""action""]
|
||
},
|
||
""the"": {
|
||
""word"": ""the"",
|
||
""translation"": ""定冠詞"",
|
||
""definition"": ""definite article"",
|
||
""partOfSpeech"": ""article"",
|
||
""pronunciation"": ""/ðə/"",
|
||
""difficultyLevel"": ""A1"",
|
||
""isPhrase"": false,
|
||
""frequency"": ""very_high"",
|
||
""synonyms"": [],
|
||
""example"": ""The cat is sleeping."",
|
||
""exampleTranslation"": ""貓在睡覺。"",
|
||
""tags"": [""basic""]
|
||
},
|
||
""team"": {
|
||
""word"": ""team"",
|
||
""translation"": ""團隊"",
|
||
""definition"": ""a group of people working together"",
|
||
""partOfSpeech"": ""noun"",
|
||
""pronunciation"": ""/tiːm/"",
|
||
""difficultyLevel"": ""A2"",
|
||
""isPhrase"": false,
|
||
""frequency"": ""high"",
|
||
""synonyms"": [""group"", ""crew""],
|
||
""example"": ""Our team works well together."",
|
||
""exampleTranslation"": ""我們的團隊合作得很好。"",
|
||
""tags"": [""work"", ""group""]
|
||
},
|
||
""so"": {
|
||
""word"": ""so"",
|
||
""translation"": ""所以;如此"",
|
||
""definition"": ""therefore; to such a degree"",
|
||
""partOfSpeech"": ""adverb"",
|
||
""pronunciation"": ""/soʊ/"",
|
||
""difficultyLevel"": ""A1"",
|
||
""isPhrase"": false,
|
||
""frequency"": ""very_high"",
|
||
""synonyms"": [""therefore"", ""thus""],
|
||
""example"": ""It was raining, so I stayed home."",
|
||
""exampleTranslation"": ""下雨了,所以我待在家裡。"",
|
||
""tags"": [""basic""]
|
||
},
|
||
""let's"": {
|
||
""word"": ""let's"",
|
||
""translation"": ""讓我們"",
|
||
""definition"": ""let us (contraction)"",
|
||
""partOfSpeech"": ""contraction"",
|
||
""pronunciation"": ""/lets/"",
|
||
""difficultyLevel"": ""A1"",
|
||
""isPhrase"": false,
|
||
""frequency"": ""high"",
|
||
""synonyms"": [""let us""],
|
||
""example"": ""Let's go to the park."",
|
||
""exampleTranslation"": ""我們去公園吧。"",
|
||
""tags"": [""basic""]
|
||
},
|
||
""cut"": {
|
||
""word"": ""cut"",
|
||
""translation"": ""切;削減"",
|
||
""definition"": ""to use a knife or other sharp tool to divide something"",
|
||
""partOfSpeech"": ""verb"",
|
||
""pronunciation"": ""/kʌt/"",
|
||
""difficultyLevel"": ""A2"",
|
||
""isPhrase"": false,
|
||
""frequency"": ""high"",
|
||
""synonyms"": [""slice"", ""chop"", ""reduce""],
|
||
""example"": ""Please cut the apple."",
|
||
""exampleTranslation"": ""請切蘋果。"",
|
||
""tags"": [""action""]
|
||
},
|
||
""her"": {
|
||
""word"": ""her"",
|
||
""translation"": ""她的;她"",
|
||
""definition"": ""belonging to or associated with a female"",
|
||
""partOfSpeech"": ""pronoun"",
|
||
""pronunciation"": ""/hər/"",
|
||
""difficultyLevel"": ""A1"",
|
||
""isPhrase"": false,
|
||
""frequency"": ""very_high"",
|
||
""synonyms"": [""hers""],
|
||
""example"": ""This is her book."",
|
||
""exampleTranslation"": ""這是她的書。"",
|
||
""tags"": [""basic"", ""pronoun""]
|
||
},
|
||
""some"": {
|
||
""word"": ""some"",
|
||
""translation"": ""一些"",
|
||
""definition"": ""an unspecified amount or number of"",
|
||
""partOfSpeech"": ""determiner"",
|
||
""pronunciation"": ""/sʌm/"",
|
||
""difficultyLevel"": ""A1"",
|
||
""isPhrase"": false,
|
||
""frequency"": ""very_high"",
|
||
""synonyms"": [""several"", ""a few""],
|
||
""example"": ""I need some help."",
|
||
""exampleTranslation"": ""我需要一些幫助。"",
|
||
""tags"": [""basic""]
|
||
},
|
||
""slack"": {
|
||
""word"": ""slack"",
|
||
""translation"": ""寬鬆;懈怠"",
|
||
""definition"": ""looseness; lack of tension"",
|
||
""partOfSpeech"": ""noun"",
|
||
""pronunciation"": ""/slæk/"",
|
||
""difficultyLevel"": ""B1"",
|
||
""isPhrase"": false,
|
||
""frequency"": ""medium"",
|
||
""synonyms"": [""looseness"", ""leeway""],
|
||
""example"": ""There's too much slack in this rope."",
|
||
""exampleTranslation"": ""這條繩子太鬆了。"",
|
||
""tags"": [""physical""]
|
||
},
|
||
""until"": {
|
||
""word"": ""until"",
|
||
""translation"": ""直到"",
|
||
""definition"": ""up to a particular time"",
|
||
""partOfSpeech"": ""preposition"",
|
||
""pronunciation"": ""/ʌnˈtɪl/"",
|
||
""difficultyLevel"": ""A2"",
|
||
""isPhrase"": false,
|
||
""frequency"": ""high"",
|
||
""synonyms"": [""till"", ""up to""],
|
||
""example"": ""Wait until tomorrow."",
|
||
""exampleTranslation"": ""等到明天。"",
|
||
""tags"": [""time""]
|
||
},
|
||
""gets"": {
|
||
""word"": ""gets"",
|
||
""translation"": ""變得;獲得"",
|
||
""definition"": ""becomes or obtains (third person singular)"",
|
||
""partOfSpeech"": ""verb"",
|
||
""pronunciation"": ""/ɡets/"",
|
||
""difficultyLevel"": ""A1"",
|
||
""isPhrase"": false,
|
||
""frequency"": ""very_high"",
|
||
""synonyms"": [""becomes"", ""obtains""],
|
||
""example"": ""It gets cold at night."",
|
||
""exampleTranslation"": ""晚上會變冷。"",
|
||
""tags"": [""basic""]
|
||
},
|
||
""used"": {
|
||
""word"": ""used"",
|
||
""translation"": ""習慣的"",
|
||
""definition"": ""familiar with something (used to)"",
|
||
""partOfSpeech"": ""adjective"",
|
||
""pronunciation"": ""/juːzd/"",
|
||
""difficultyLevel"": ""A2"",
|
||
""isPhrase"": false,
|
||
""frequency"": ""high"",
|
||
""synonyms"": [""accustomed"", ""familiar""],
|
||
""example"": ""I'm not used to this weather."",
|
||
""exampleTranslation"": ""我不習慣這種天氣。"",
|
||
""tags"": [""state""]
|
||
},
|
||
""to"": {
|
||
""word"": ""to"",
|
||
""translation"": ""到;向"",
|
||
""definition"": ""preposition expressing direction"",
|
||
""partOfSpeech"": ""preposition"",
|
||
""pronunciation"": ""/tu/"",
|
||
""difficultyLevel"": ""A1"",
|
||
""isPhrase"": false,
|
||
""frequency"": ""very_high"",
|
||
""synonyms"": [],
|
||
""example"": ""I'm going to school."",
|
||
""exampleTranslation"": ""我要去學校。"",
|
||
""tags"": [""basic""]
|
||
},
|
||
""workflow"": {
|
||
""word"": ""workflow"",
|
||
""translation"": ""工作流程"",
|
||
""definition"": ""the sequence of processes through which work passes"",
|
||
""partOfSpeech"": ""noun"",
|
||
""pronunciation"": ""/ˈwɜːrkfloʊ/"",
|
||
""difficultyLevel"": ""B2"",
|
||
""isPhrase"": false,
|
||
""frequency"": ""medium"",
|
||
""synonyms"": [""process"", ""procedure"", ""system""],
|
||
""example"": ""We need to improve our workflow."",
|
||
""exampleTranslation"": ""我們需要改善工作流程。"",
|
||
""tags"": [""work"", ""process""]
|
||
},
|
||
""cut someone some slack"": {
|
||
""word"": ""cut someone some slack"",
|
||
""translation"": ""對某人寬容一點"",
|
||
""definition"": ""to be more lenient or forgiving with someone"",
|
||
""partOfSpeech"": ""idiom"",
|
||
""pronunciation"": ""/kʌt ˈsʌmwʌn sʌm slæk/"",
|
||
""difficultyLevel"": ""B2"",
|
||
""isPhrase"": true,
|
||
""frequency"": ""medium"",
|
||
""synonyms"": [""be lenient"", ""be forgiving"", ""give leeway""],
|
||
""example"": ""Cut him some slack, he's new here."",
|
||
""exampleTranslation"": ""對他寬容一點,他是新來的。"",
|
||
""tags"": [""idiom"", ""workplace"", ""tolerance""]
|
||
}
|
||
}
|
||
}";
|
||
}
|
||
|
||
private SentenceAnalysisData ParseGeminiResponse(string response, string originalText, string userLevel)
|
||
{
|
||
try
|
||
{
|
||
// Clean the response to extract JSON
|
||
var jsonMatch = Regex.Match(response, @"\{.*\}", RegexOptions.Singleline);
|
||
if (!jsonMatch.Success)
|
||
{
|
||
throw new InvalidOperationException("Invalid JSON response from Gemini");
|
||
}
|
||
|
||
var jsonResponse = jsonMatch.Value;
|
||
var options = new JsonSerializerOptions
|
||
{
|
||
PropertyNameCaseInsensitive = true,
|
||
PropertyNamingPolicy = JsonNamingPolicy.CamelCase
|
||
};
|
||
|
||
var parsedResponse = JsonSerializer.Deserialize<GeminiAnalysisResponse>(jsonResponse, options)
|
||
?? throw new InvalidOperationException("Failed to parse Gemini response");
|
||
|
||
return ConvertToAnalysisData(parsedResponse, originalText, userLevel);
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
_logger.LogError(ex, "Error parsing Gemini response: {Response}", response);
|
||
return CreateFallbackResponse(originalText, userLevel);
|
||
}
|
||
}
|
||
|
||
private SentenceAnalysisData ConvertToAnalysisData(GeminiAnalysisResponse response, string originalText, string userLevel)
|
||
{
|
||
var analysisData = new SentenceAnalysisData
|
||
{
|
||
OriginalText = originalText,
|
||
SentenceMeaning = response.SentenceMeaning ?? string.Empty,
|
||
GrammarCorrection = response.GrammarCorrection != null ? new GrammarCorrectionDto
|
||
{
|
||
HasErrors = response.GrammarCorrection.HasErrors,
|
||
CorrectedText = response.GrammarCorrection.CorrectedText ?? originalText,
|
||
Corrections = response.GrammarCorrection.Corrections?.Select(c => new GrammarErrorDto
|
||
{
|
||
Error = c.Error ?? string.Empty,
|
||
Correction = c.Correction ?? string.Empty,
|
||
Type = c.Type ?? string.Empty,
|
||
Explanation = c.Explanation ?? string.Empty,
|
||
Severity = "medium"
|
||
}).ToList() ?? new()
|
||
} : null,
|
||
Metadata = new AnalysisMetadata
|
||
{
|
||
UserLevel = userLevel,
|
||
ProcessingDate = DateTime.UtcNow,
|
||
AnalysisModel = "gemini-pro"
|
||
}
|
||
};
|
||
|
||
// Process vocabulary analysis
|
||
if (response.VocabularyAnalysis != null)
|
||
{
|
||
foreach (var (word, analysis) in response.VocabularyAnalysis)
|
||
{
|
||
analysisData.VocabularyAnalysis[word] = new VocabularyAnalysisDto
|
||
{
|
||
Word = analysis.Word ?? word,
|
||
Translation = analysis.Translation ?? string.Empty,
|
||
Definition = analysis.Definition ?? string.Empty,
|
||
PartOfSpeech = analysis.PartOfSpeech ?? string.Empty,
|
||
Pronunciation = analysis.Pronunciation ?? $"/{word}/",
|
||
DifficultyLevel = analysis.DifficultyLevel ?? "A1",
|
||
IsPhrase = analysis.IsPhrase,
|
||
Frequency = analysis.Frequency ?? "medium",
|
||
Synonyms = analysis.Synonyms ?? new List<string>(),
|
||
Example = analysis.Example,
|
||
ExampleTranslation = analysis.ExampleTranslation,
|
||
Tags = analysis.Tags ?? new List<string>()
|
||
};
|
||
}
|
||
}
|
||
|
||
// Calculate statistics
|
||
analysisData.Statistics = CalculateStatistics(analysisData.VocabularyAnalysis, userLevel);
|
||
|
||
return analysisData;
|
||
}
|
||
|
||
private AnalysisStatistics CalculateStatistics(Dictionary<string, VocabularyAnalysisDto> vocabulary, string userLevel)
|
||
{
|
||
var userIndex = Array.IndexOf(_cefrLevels, userLevel);
|
||
var stats = new AnalysisStatistics();
|
||
|
||
foreach (var word in vocabulary.Values)
|
||
{
|
||
var wordIndex = Array.IndexOf(_cefrLevels, word.DifficultyLevel);
|
||
|
||
if (word.IsPhrase)
|
||
{
|
||
stats.Phrases++;
|
||
}
|
||
else if (wordIndex < userIndex)
|
||
{
|
||
stats.SimpleWords++;
|
||
}
|
||
else if (wordIndex == userIndex)
|
||
{
|
||
stats.ModerateWords++;
|
||
}
|
||
else
|
||
{
|
||
stats.DifficultWords++;
|
||
}
|
||
}
|
||
|
||
stats.TotalWords = vocabulary.Count;
|
||
stats.UniqueWords = vocabulary.Count;
|
||
stats.AverageDifficulty = userLevel; // Simplified calculation
|
||
|
||
return stats;
|
||
}
|
||
|
||
private SentenceAnalysisData CreateFallbackResponse(string originalText, string userLevel)
|
||
{
|
||
_logger.LogWarning("Using fallback response for text: {Text}", originalText);
|
||
|
||
return new SentenceAnalysisData
|
||
{
|
||
OriginalText = originalText,
|
||
SentenceMeaning = "分析過程中發生錯誤,請稍後再試。",
|
||
Metadata = new AnalysisMetadata
|
||
{
|
||
UserLevel = userLevel,
|
||
ProcessingDate = DateTime.UtcNow,
|
||
AnalysisModel = "fallback"
|
||
}
|
||
};
|
||
}
|
||
}
|
||
|
||
// Gemini API response models
|
||
internal class GeminiApiResponse
|
||
{
|
||
public List<GeminiCandidate>? Candidates { get; set; }
|
||
}
|
||
|
||
internal class GeminiCandidate
|
||
{
|
||
public GeminiContent? Content { get; set; }
|
||
}
|
||
|
||
internal class GeminiContent
|
||
{
|
||
public List<GeminiPart>? Parts { get; set; }
|
||
}
|
||
|
||
internal class GeminiPart
|
||
{
|
||
public string? Text { get; set; }
|
||
}
|
||
|
||
// Internal models for Gemini response parsing
|
||
internal class GeminiAnalysisResponse
|
||
{
|
||
public GeminiGrammarCorrection? GrammarCorrection { get; set; }
|
||
public string? SentenceMeaning { get; set; }
|
||
public Dictionary<string, GeminiVocabularyAnalysis>? VocabularyAnalysis { get; set; }
|
||
}
|
||
|
||
internal class GeminiGrammarCorrection
|
||
{
|
||
public bool HasErrors { get; set; }
|
||
public string? CorrectedText { get; set; }
|
||
public List<GeminiGrammarError>? Corrections { get; set; }
|
||
}
|
||
|
||
internal class GeminiGrammarError
|
||
{
|
||
public string? Error { get; set; }
|
||
public string? Correction { get; set; }
|
||
public string? Type { get; set; }
|
||
public string? Explanation { get; set; }
|
||
}
|
||
|
||
internal class GeminiVocabularyAnalysis
|
||
{
|
||
public string? Word { get; set; }
|
||
public string? Translation { get; set; }
|
||
public string? Definition { get; set; }
|
||
public string? PartOfSpeech { get; set; }
|
||
public string? Pronunciation { get; set; }
|
||
public string? DifficultyLevel { get; set; }
|
||
public bool IsPhrase { get; set; }
|
||
public string? Frequency { get; set; }
|
||
public List<string>? Synonyms { get; set; }
|
||
public string? Example { get; set; }
|
||
public string? ExampleTranslation { get; set; }
|
||
public List<string>? Tags { get; set; }
|
||
} |