1471 lines
53 KiB
C#
1471 lines
53 KiB
C#
using Microsoft.AspNetCore.Mvc;
|
||
using Microsoft.EntityFrameworkCore;
|
||
using DramaLing.Api.Data;
|
||
using DramaLing.Api.Models.Entities;
|
||
using DramaLing.Api.Services;
|
||
using Microsoft.AspNetCore.Authorization;
|
||
|
||
namespace DramaLing.Api.Controllers;
|
||
|
||
[ApiController]
|
||
[Route("api/[controller]")]
|
||
[Authorize]
|
||
public class AIController : ControllerBase
|
||
{
|
||
private readonly DramaLingDbContext _context;
|
||
private readonly IAuthService _authService;
|
||
private readonly IGeminiService _geminiService;
|
||
private readonly IAnalysisCacheService _cacheService;
|
||
private readonly IUsageTrackingService _usageService;
|
||
private readonly ILogger<AIController> _logger;
|
||
|
||
public AIController(
|
||
DramaLingDbContext context,
|
||
IAuthService authService,
|
||
IGeminiService geminiService,
|
||
IAnalysisCacheService cacheService,
|
||
IUsageTrackingService usageService,
|
||
ILogger<AIController> logger)
|
||
{
|
||
_context = context;
|
||
_authService = authService;
|
||
_geminiService = geminiService;
|
||
_cacheService = cacheService;
|
||
_usageService = usageService;
|
||
_logger = logger;
|
||
}
|
||
|
||
/// <summary>
|
||
/// AI 生成詞卡測試端點 (開發用,無需認證)
|
||
/// </summary>
|
||
[HttpPost("test/generate")]
|
||
[AllowAnonymous]
|
||
public async Task<ActionResult> TestGenerateCards([FromBody] GenerateCardsRequest request)
|
||
{
|
||
try
|
||
{
|
||
// 基本驗證
|
||
if (string.IsNullOrWhiteSpace(request.InputText))
|
||
{
|
||
return BadRequest(new { Success = false, Error = "Input text is required" });
|
||
}
|
||
|
||
if (request.InputText.Length > 5000)
|
||
{
|
||
return BadRequest(new { Success = false, Error = "Input text must be less than 5000 characters" });
|
||
}
|
||
|
||
if (!new[] { "vocabulary", "smart" }.Contains(request.ExtractionType))
|
||
{
|
||
return BadRequest(new { Success = false, Error = "Invalid extraction type" });
|
||
}
|
||
|
||
if (request.CardCount < 1 || request.CardCount > 20)
|
||
{
|
||
return BadRequest(new { Success = false, Error = "Card count must be between 1 and 20" });
|
||
}
|
||
|
||
// 測試模式:直接使用模擬資料
|
||
try
|
||
{
|
||
var generatedCards = await _geminiService.GenerateCardsAsync(
|
||
request.InputText,
|
||
request.ExtractionType,
|
||
request.CardCount);
|
||
|
||
return Ok(new
|
||
{
|
||
Success = true,
|
||
Data = new
|
||
{
|
||
TaskId = Guid.NewGuid(),
|
||
Status = "completed",
|
||
GeneratedCards = generatedCards
|
||
},
|
||
Message = $"Successfully generated {generatedCards.Count} cards"
|
||
});
|
||
}
|
||
catch (InvalidOperationException ex) when (ex.Message.Contains("API key"))
|
||
{
|
||
_logger.LogWarning("Gemini API key not configured, using mock data");
|
||
|
||
// 返回模擬資料
|
||
var mockCards = GenerateMockCards(request.CardCount);
|
||
return Ok(new
|
||
{
|
||
Success = true,
|
||
Data = new
|
||
{
|
||
TaskId = Guid.NewGuid(),
|
||
Status = "completed",
|
||
GeneratedCards = mockCards
|
||
},
|
||
Message = $"Generated {mockCards.Count} mock cards (Test mode)"
|
||
});
|
||
}
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
_logger.LogError(ex, "Error in AI card generation test");
|
||
return StatusCode(500, new
|
||
{
|
||
Success = false,
|
||
Error = "Failed to generate cards",
|
||
Details = ex.Message,
|
||
Timestamp = DateTime.UtcNow
|
||
});
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// AI 生成詞卡 (支援 /frontend/app/generate/page.tsx)
|
||
/// </summary>
|
||
[HttpPost("generate")]
|
||
public async Task<ActionResult> GenerateCards([FromBody] GenerateCardsRequest request)
|
||
{
|
||
try
|
||
{
|
||
var userId = await _authService.GetUserIdFromTokenAsync(Request.Headers.Authorization);
|
||
if (userId == null)
|
||
return Unauthorized(new { Success = false, Error = "Invalid token" });
|
||
|
||
// 基本驗證
|
||
if (string.IsNullOrWhiteSpace(request.InputText))
|
||
{
|
||
return BadRequest(new { Success = false, Error = "Input text is required" });
|
||
}
|
||
|
||
if (request.InputText.Length > 5000)
|
||
{
|
||
return BadRequest(new { Success = false, Error = "Input text must be less than 5000 characters" });
|
||
}
|
||
|
||
if (!new[] { "vocabulary", "smart" }.Contains(request.ExtractionType))
|
||
{
|
||
return BadRequest(new { Success = false, Error = "Invalid extraction type" });
|
||
}
|
||
|
||
if (request.CardCount < 5 || request.CardCount > 20)
|
||
{
|
||
return BadRequest(new { Success = false, Error = "Card count must be between 5 and 20" });
|
||
}
|
||
|
||
// 檢查每日配額 (簡化版,未來可以基於用戶訂閱狀態)
|
||
var today = DateOnly.FromDateTime(DateTime.Today);
|
||
var todayStats = await _context.DailyStats
|
||
.FirstOrDefaultAsync(ds => ds.UserId == userId && ds.Date == today);
|
||
|
||
var todayApiCalls = todayStats?.AiApiCalls ?? 0;
|
||
var maxApiCalls = 10; // 免費用戶每日限制
|
||
|
||
if (todayApiCalls >= maxApiCalls)
|
||
{
|
||
return StatusCode(429, new
|
||
{
|
||
Success = false,
|
||
Error = "Daily AI generation limit exceeded"
|
||
});
|
||
}
|
||
|
||
// 建立生成任務 (簡化版,直接處理而不是非同步)
|
||
try
|
||
{
|
||
var generatedCards = await _geminiService.GenerateCardsAsync(
|
||
request.InputText,
|
||
request.ExtractionType,
|
||
request.CardCount);
|
||
|
||
if (generatedCards.Count == 0)
|
||
{
|
||
return StatusCode(500, new
|
||
{
|
||
Success = false,
|
||
Error = "AI generated no valid cards"
|
||
});
|
||
}
|
||
|
||
// 更新每日統計
|
||
if (todayStats == null)
|
||
{
|
||
todayStats = new DailyStats
|
||
{
|
||
Id = Guid.NewGuid(),
|
||
UserId = userId.Value,
|
||
Date = today
|
||
};
|
||
_context.DailyStats.Add(todayStats);
|
||
}
|
||
|
||
todayStats.AiApiCalls++;
|
||
todayStats.CardsGenerated += generatedCards.Count;
|
||
await _context.SaveChangesAsync();
|
||
|
||
return Ok(new
|
||
{
|
||
Success = true,
|
||
Data = new
|
||
{
|
||
TaskId = Guid.NewGuid(), // 模擬任務 ID
|
||
Status = "completed",
|
||
GeneratedCards = generatedCards
|
||
},
|
||
Message = $"Successfully generated {generatedCards.Count} cards"
|
||
});
|
||
}
|
||
catch (InvalidOperationException ex) when (ex.Message.Contains("API key"))
|
||
{
|
||
_logger.LogWarning("Gemini API key not configured, using mock data");
|
||
|
||
// 返回模擬資料(開發階段)
|
||
var mockCards = GenerateMockCards(request.CardCount);
|
||
return Ok(new
|
||
{
|
||
Success = true,
|
||
Data = new
|
||
{
|
||
TaskId = Guid.NewGuid(),
|
||
Status = "completed",
|
||
GeneratedCards = mockCards
|
||
},
|
||
Message = $"Generated {mockCards.Count} mock cards (Gemini API not configured)"
|
||
});
|
||
}
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
_logger.LogError(ex, "Error in AI card generation");
|
||
return StatusCode(500, new
|
||
{
|
||
Success = false,
|
||
Error = "Failed to generate cards",
|
||
Timestamp = DateTime.UtcNow
|
||
});
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 測試版保存生成的詞卡 (無需認證)
|
||
/// </summary>
|
||
[HttpPost("test/save")]
|
||
[AllowAnonymous]
|
||
public async Task<ActionResult> TestSaveCards([FromBody] TestSaveCardsRequest request)
|
||
{
|
||
try
|
||
{
|
||
// 基本驗證
|
||
if (request.SelectedCards == null || request.SelectedCards.Count == 0)
|
||
{
|
||
return BadRequest(new { Success = false, Error = "Selected cards are required" });
|
||
}
|
||
|
||
// 創建或使用預設卡組
|
||
var defaultCardSet = await _context.CardSets
|
||
.FirstOrDefaultAsync(cs => cs.IsDefault);
|
||
|
||
if (defaultCardSet == null)
|
||
{
|
||
// 創建預設卡組
|
||
defaultCardSet = new CardSet
|
||
{
|
||
Id = Guid.NewGuid(),
|
||
UserId = Guid.NewGuid(), // 測試用戶 ID
|
||
Name = "AI 生成詞卡",
|
||
Description = "通過 AI 智能生成的詞卡集合",
|
||
Color = "#3B82F6",
|
||
IsDefault = true,
|
||
CreatedAt = DateTime.UtcNow,
|
||
UpdatedAt = DateTime.UtcNow
|
||
};
|
||
_context.CardSets.Add(defaultCardSet);
|
||
}
|
||
|
||
// 將生成的詞卡轉換為資料庫實體
|
||
var flashcardsToSave = request.SelectedCards.Select(card => new Flashcard
|
||
{
|
||
Id = Guid.NewGuid(),
|
||
UserId = defaultCardSet.UserId,
|
||
CardSetId = defaultCardSet.Id,
|
||
Word = card.Word,
|
||
Translation = card.Translation,
|
||
Definition = card.Definition,
|
||
PartOfSpeech = card.PartOfSpeech,
|
||
Pronunciation = card.Pronunciation,
|
||
Example = card.Example,
|
||
ExampleTranslation = card.ExampleTranslation,
|
||
DifficultyLevel = card.DifficultyLevel,
|
||
CreatedAt = DateTime.UtcNow,
|
||
UpdatedAt = DateTime.UtcNow
|
||
}).ToList();
|
||
|
||
_context.Flashcards.AddRange(flashcardsToSave);
|
||
|
||
// 更新卡組計數
|
||
defaultCardSet.CardCount += flashcardsToSave.Count;
|
||
defaultCardSet.UpdatedAt = DateTime.UtcNow;
|
||
|
||
await _context.SaveChangesAsync();
|
||
|
||
return Ok(new
|
||
{
|
||
Success = true,
|
||
Data = new
|
||
{
|
||
SavedCount = flashcardsToSave.Count,
|
||
CardSetId = defaultCardSet.Id,
|
||
CardSetName = defaultCardSet.Name,
|
||
Cards = flashcardsToSave.Select(f => new
|
||
{
|
||
f.Id,
|
||
f.Word,
|
||
f.Translation,
|
||
f.Definition
|
||
})
|
||
},
|
||
Message = $"Successfully saved {flashcardsToSave.Count} cards to deck '{defaultCardSet.Name}'"
|
||
});
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
_logger.LogError(ex, "Error saving generated cards");
|
||
return StatusCode(500, new
|
||
{
|
||
Success = false,
|
||
Error = "Failed to save cards",
|
||
Details = ex.Message,
|
||
Timestamp = DateTime.UtcNow
|
||
});
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 保存生成的詞卡
|
||
/// </summary>
|
||
[HttpPost("generate/{taskId}/save")]
|
||
public async Task<ActionResult> SaveGeneratedCards(
|
||
Guid taskId,
|
||
[FromBody] SaveCardsRequest request)
|
||
{
|
||
try
|
||
{
|
||
var userId = await _authService.GetUserIdFromTokenAsync(Request.Headers.Authorization);
|
||
if (userId == null)
|
||
return Unauthorized(new { Success = false, Error = "Invalid token" });
|
||
|
||
// 基本驗證
|
||
if (request.CardSetId == Guid.Empty)
|
||
{
|
||
return BadRequest(new { Success = false, Error = "Card set ID is required" });
|
||
}
|
||
|
||
if (request.SelectedCards == null || request.SelectedCards.Count == 0)
|
||
{
|
||
return BadRequest(new { Success = false, Error = "Selected cards are required" });
|
||
}
|
||
|
||
// 驗證卡組是否屬於用戶
|
||
var cardSet = await _context.CardSets
|
||
.FirstOrDefaultAsync(cs => cs.Id == request.CardSetId && cs.UserId == userId);
|
||
|
||
if (cardSet == null)
|
||
{
|
||
return NotFound(new { Success = false, Error = "Card set not found" });
|
||
}
|
||
|
||
// 將生成的詞卡轉換為資料庫實體
|
||
var flashcardsToSave = request.SelectedCards.Select(card => new Flashcard
|
||
{
|
||
Id = Guid.NewGuid(),
|
||
UserId = userId.Value,
|
||
CardSetId = request.CardSetId,
|
||
Word = card.Word,
|
||
Translation = card.Translation,
|
||
Definition = card.Definition,
|
||
PartOfSpeech = card.PartOfSpeech,
|
||
Pronunciation = card.Pronunciation,
|
||
Example = card.Example,
|
||
ExampleTranslation = card.ExampleTranslation,
|
||
DifficultyLevel = card.DifficultyLevel
|
||
}).ToList();
|
||
|
||
_context.Flashcards.AddRange(flashcardsToSave);
|
||
await _context.SaveChangesAsync();
|
||
|
||
return Ok(new
|
||
{
|
||
Success = true,
|
||
Data = new
|
||
{
|
||
SavedCount = flashcardsToSave.Count,
|
||
Cards = flashcardsToSave.Select(f => new
|
||
{
|
||
f.Id,
|
||
f.Word,
|
||
f.Translation,
|
||
f.Definition
|
||
})
|
||
},
|
||
Message = $"Successfully saved {flashcardsToSave.Count} cards to your deck"
|
||
});
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
_logger.LogError(ex, "Error saving generated cards");
|
||
return StatusCode(500, new
|
||
{
|
||
Success = false,
|
||
Error = "Failed to save cards",
|
||
Timestamp = DateTime.UtcNow
|
||
});
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 智能檢測詞卡內容
|
||
/// </summary>
|
||
[HttpPost("validate-card")]
|
||
public async Task<ActionResult> ValidateCard([FromBody] ValidateCardRequest request)
|
||
{
|
||
try
|
||
{
|
||
var userId = await _authService.GetUserIdFromTokenAsync(Request.Headers.Authorization);
|
||
if (userId == null)
|
||
return Unauthorized(new { Success = false, Error = "Invalid token" });
|
||
|
||
var flashcard = await _context.Flashcards
|
||
.FirstOrDefaultAsync(f => f.Id == request.FlashcardId && f.UserId == userId);
|
||
|
||
if (flashcard == null)
|
||
{
|
||
return NotFound(new { Success = false, Error = "Flashcard not found" });
|
||
}
|
||
|
||
try
|
||
{
|
||
var validationResult = await _geminiService.ValidateCardAsync(flashcard);
|
||
|
||
return Ok(new
|
||
{
|
||
Success = true,
|
||
Data = new
|
||
{
|
||
FlashcardId = request.FlashcardId,
|
||
ValidationResult = validationResult,
|
||
CheckedAt = DateTime.UtcNow
|
||
},
|
||
Message = "Card validation completed"
|
||
});
|
||
}
|
||
catch (InvalidOperationException ex) when (ex.Message.Contains("API key"))
|
||
{
|
||
// 模擬檢測結果
|
||
var mockResult = new ValidationResult
|
||
{
|
||
Issues = new List<ValidationIssue>(),
|
||
Suggestions = new List<string> { "詞卡內容看起來正確", "建議添加更多例句" },
|
||
OverallScore = 85,
|
||
Confidence = 0.7
|
||
};
|
||
|
||
return Ok(new
|
||
{
|
||
Success = true,
|
||
Data = new
|
||
{
|
||
FlashcardId = request.FlashcardId,
|
||
ValidationResult = mockResult,
|
||
CheckedAt = DateTime.UtcNow,
|
||
Note = "Mock validation (Gemini API not configured)"
|
||
},
|
||
Message = "Card validation completed (mock mode)"
|
||
});
|
||
}
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
_logger.LogError(ex, "Error validating card");
|
||
return StatusCode(500, new
|
||
{
|
||
Success = false,
|
||
Error = "Failed to validate card",
|
||
Timestamp = DateTime.UtcNow
|
||
});
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 句子分析API - 支援語法修正和高價值標記
|
||
/// </summary>
|
||
[HttpPost("analyze-sentence")]
|
||
[AllowAnonymous] // 暫時無需認證,開發階段
|
||
public async Task<ActionResult> AnalyzeSentence([FromBody] AnalyzeSentenceRequest request)
|
||
{
|
||
try
|
||
{
|
||
// 基本驗證
|
||
if (string.IsNullOrWhiteSpace(request.InputText))
|
||
{
|
||
return BadRequest(new { Success = false, Error = "Input text is required" });
|
||
}
|
||
|
||
if (request.InputText.Length > 300)
|
||
{
|
||
return BadRequest(new { Success = false, Error = "Input text must be less than 300 characters for manual input" });
|
||
}
|
||
|
||
// 0. 檢查使用限制(使用模擬用戶ID)
|
||
var mockUserId = Guid.Parse("00000000-0000-0000-0000-000000000001"); // 模擬用戶ID
|
||
var canUse = await _usageService.CheckUsageLimitAsync(mockUserId, isPremium: false);
|
||
if (!canUse)
|
||
{
|
||
return StatusCode(429, new
|
||
{
|
||
Success = false,
|
||
Error = "免費用戶使用限制已達上限",
|
||
ErrorCode = "USAGE_LIMIT_EXCEEDED",
|
||
ResetInfo = new
|
||
{
|
||
WindowHours = 3,
|
||
Limit = 5
|
||
}
|
||
});
|
||
}
|
||
|
||
// 1. 檢查快取
|
||
var cachedAnalysis = await _cacheService.GetCachedAnalysisAsync(request.InputText);
|
||
if (cachedAnalysis != null && !request.ForceRefresh)
|
||
{
|
||
_logger.LogInformation("Using cached analysis for text hash: {TextHash}", cachedAnalysis.InputTextHash);
|
||
|
||
// 解析快取的分析結果
|
||
var cachedResult = System.Text.Json.JsonSerializer.Deserialize<object>(cachedAnalysis.AnalysisResult);
|
||
|
||
return Ok(new
|
||
{
|
||
Success = true,
|
||
Data = cachedResult,
|
||
Message = "句子分析完成(快取)",
|
||
Cached = true,
|
||
CacheHit = true
|
||
});
|
||
}
|
||
|
||
// 2. 執行真正的AI分析
|
||
_logger.LogInformation("Calling Gemini AI for text: {InputText}", request.InputText);
|
||
|
||
try
|
||
{
|
||
// 真正調用 Gemini AI 進行句子分析
|
||
var aiAnalysis = await _geminiService.AnalyzeSentenceAsync(request.InputText);
|
||
|
||
// 使用AI分析結果
|
||
var finalText = aiAnalysis.GrammarCorrection.HasErrors ?
|
||
aiAnalysis.GrammarCorrection.CorrectedText : request.InputText;
|
||
|
||
// 3. 準備AI分析響應資料
|
||
var baseResponseData = new
|
||
{
|
||
AnalysisId = Guid.NewGuid(),
|
||
InputText = request.InputText,
|
||
GrammarCorrection = aiAnalysis.GrammarCorrection,
|
||
SentenceMeaning = new
|
||
{
|
||
Translation = aiAnalysis.Translation,
|
||
Explanation = aiAnalysis.Explanation
|
||
},
|
||
FinalAnalysisText = finalText ?? request.InputText,
|
||
WordAnalysis = aiAnalysis.WordAnalysis,
|
||
HighValueWords = aiAnalysis.HighValueWords,
|
||
PhrasesDetected = new object[0] // 暫時簡化
|
||
};
|
||
|
||
// 4. 存入快取(24小時TTL)
|
||
try
|
||
{
|
||
await _cacheService.SetCachedAnalysisAsync(
|
||
request.InputText,
|
||
baseResponseData,
|
||
TimeSpan.FromHours(24)
|
||
);
|
||
_logger.LogInformation("AI analysis result cached for 24 hours");
|
||
}
|
||
catch (Exception cacheEx)
|
||
{
|
||
_logger.LogWarning(cacheEx, "Failed to cache AI analysis result");
|
||
}
|
||
|
||
return Ok(new
|
||
{
|
||
Success = true,
|
||
Data = baseResponseData,
|
||
Message = "AI句子分析完成",
|
||
Cached = false,
|
||
CacheHit = false,
|
||
UsingAI = true
|
||
});
|
||
}
|
||
catch (Exception aiEx)
|
||
{
|
||
_logger.LogWarning(aiEx, "Gemini AI failed, falling back to local analysis");
|
||
|
||
// AI 失敗時回退到本地分析
|
||
var grammarCorrection = PerformGrammarCheck(request.InputText);
|
||
var finalText = grammarCorrection.HasErrors ? grammarCorrection.CorrectedText : request.InputText;
|
||
var analysis = await AnalyzeSentenceWithHighValueMarking(finalText ?? request.InputText);
|
||
|
||
var fallbackData = new
|
||
{
|
||
AnalysisId = Guid.NewGuid(),
|
||
InputText = request.InputText,
|
||
GrammarCorrection = grammarCorrection,
|
||
SentenceMeaning = new
|
||
{
|
||
Translation = analysis.Translation,
|
||
Explanation = analysis.Explanation
|
||
},
|
||
FinalAnalysisText = finalText,
|
||
WordAnalysis = analysis.WordAnalysis,
|
||
HighValueWords = analysis.HighValueWords,
|
||
PhrasesDetected = analysis.PhrasesDetected
|
||
};
|
||
|
||
return Ok(new
|
||
{
|
||
Success = true,
|
||
Data = fallbackData,
|
||
Message = "本地分析完成(AI不可用)",
|
||
Cached = false,
|
||
CacheHit = false,
|
||
UsingAI = false
|
||
});
|
||
}
|
||
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
_logger.LogError(ex, "Error in sentence analysis");
|
||
return StatusCode(500, new
|
||
{
|
||
Success = false,
|
||
Error = "句子分析失敗",
|
||
Details = ex.Message,
|
||
Timestamp = DateTime.UtcNow
|
||
});
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 單字點擊查詢API
|
||
/// </summary>
|
||
[HttpPost("query-word")]
|
||
[AllowAnonymous] // 暫時無需認證,開發階段
|
||
public async Task<ActionResult> QueryWord([FromBody] QueryWordRequest request)
|
||
{
|
||
try
|
||
{
|
||
// 基本驗證
|
||
if (string.IsNullOrWhiteSpace(request.Word))
|
||
{
|
||
return BadRequest(new { Success = false, Error = "Word is required" });
|
||
}
|
||
|
||
// 模擬檢查是否為高價值詞彙
|
||
var isHighValue = IsHighValueWord(request.Word, request.Sentence);
|
||
|
||
if (isHighValue)
|
||
{
|
||
return Ok(new
|
||
{
|
||
Success = true,
|
||
Data = new
|
||
{
|
||
Word = request.Word,
|
||
IsHighValue = true,
|
||
WasPreAnalyzed = true,
|
||
CostIncurred = 0,
|
||
Analysis = GetHighValueWordAnalysis(request.Word)
|
||
},
|
||
Message = "高價值詞彙查詢完成(免費)"
|
||
});
|
||
}
|
||
else
|
||
{
|
||
// 低價值詞彙需要即時分析
|
||
var analysis = await AnalyzeLowValueWord(request.Word, request.Sentence);
|
||
|
||
return Ok(new
|
||
{
|
||
Success = true,
|
||
Data = new
|
||
{
|
||
Word = request.Word,
|
||
IsHighValue = false,
|
||
WasPreAnalyzed = false,
|
||
CostIncurred = 1,
|
||
Analysis = analysis,
|
||
UsageStatistics = new
|
||
{
|
||
RemainingAnalyses = 3, // 模擬扣除後剩餘
|
||
CostType = "word_query"
|
||
}
|
||
},
|
||
Message = "低價值詞彙查詢完成"
|
||
});
|
||
}
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
_logger.LogError(ex, "Error in word query");
|
||
return StatusCode(500, new
|
||
{
|
||
Success = false,
|
||
Error = "詞彙查詢失敗",
|
||
Details = ex.Message,
|
||
Timestamp = DateTime.UtcNow
|
||
});
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 獲取快取統計資料
|
||
/// </summary>
|
||
[HttpGet("cache-stats")]
|
||
[AllowAnonymous]
|
||
public async Task<ActionResult> GetCacheStats()
|
||
{
|
||
try
|
||
{
|
||
var hitCount = await _cacheService.GetCacheHitCountAsync();
|
||
var totalCacheItems = await _context.SentenceAnalysisCache
|
||
.Where(c => c.ExpiresAt > DateTime.UtcNow)
|
||
.CountAsync();
|
||
|
||
return Ok(new
|
||
{
|
||
Success = true,
|
||
Data = new
|
||
{
|
||
TotalCacheItems = totalCacheItems,
|
||
TotalCacheHits = hitCount,
|
||
CacheHitRate = totalCacheItems > 0 ? (double)hitCount / totalCacheItems : 0,
|
||
CacheSize = totalCacheItems
|
||
},
|
||
Message = "快取統計資料"
|
||
});
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
_logger.LogError(ex, "Error getting cache stats");
|
||
return StatusCode(500, new
|
||
{
|
||
Success = false,
|
||
Error = "獲取快取統計失敗",
|
||
Timestamp = DateTime.UtcNow
|
||
});
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 清理過期快取
|
||
/// </summary>
|
||
[HttpPost("cache-cleanup")]
|
||
[AllowAnonymous]
|
||
public async Task<ActionResult> CleanupCache()
|
||
{
|
||
try
|
||
{
|
||
await _cacheService.CleanExpiredCacheAsync();
|
||
|
||
return Ok(new
|
||
{
|
||
Success = true,
|
||
Message = "過期快取清理完成"
|
||
});
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
_logger.LogError(ex, "Error cleaning up cache");
|
||
return StatusCode(500, new
|
||
{
|
||
Success = false,
|
||
Error = "快取清理失敗",
|
||
Timestamp = DateTime.UtcNow
|
||
});
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 獲取使用統計
|
||
/// </summary>
|
||
[HttpGet("usage-stats")]
|
||
[AllowAnonymous]
|
||
public async Task<ActionResult> GetUsageStats()
|
||
{
|
||
try
|
||
{
|
||
var mockUserId = Guid.Parse("00000000-0000-0000-0000-000000000001");
|
||
var stats = await _usageService.GetUsageStatsAsync(mockUserId);
|
||
|
||
return Ok(new
|
||
{
|
||
Success = true,
|
||
Data = stats,
|
||
Message = "使用統計資料"
|
||
});
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
_logger.LogError(ex, "Error getting usage stats");
|
||
return StatusCode(500, new
|
||
{
|
||
Success = false,
|
||
Error = "獲取使用統計失敗",
|
||
Timestamp = DateTime.UtcNow
|
||
});
|
||
}
|
||
}
|
||
|
||
#region 私有輔助方法
|
||
|
||
/// <summary>
|
||
/// 執行語法檢查
|
||
/// </summary>
|
||
private GrammarCorrectionResult PerformGrammarCheck(string inputText)
|
||
{
|
||
// 模擬語法檢查邏輯
|
||
if (inputText.ToLower().Contains("go to school yesterday") ||
|
||
inputText.ToLower().Contains("meet my friends"))
|
||
{
|
||
return new GrammarCorrectionResult
|
||
{
|
||
HasErrors = true,
|
||
OriginalText = inputText,
|
||
CorrectedText = inputText.Replace("go to", "went to").Replace("meet my", "met my"),
|
||
Corrections = new List<GrammarCorrection>
|
||
{
|
||
new GrammarCorrection
|
||
{
|
||
Position = new Position { Start = 2, End = 4 },
|
||
ErrorType = "tense_mismatch",
|
||
Original = "go",
|
||
Corrected = "went",
|
||
Reason = "過去式時態修正:句子中有 'yesterday',應使用過去式",
|
||
Severity = "high"
|
||
}
|
||
},
|
||
ConfidenceScore = 0.95
|
||
};
|
||
}
|
||
|
||
return new GrammarCorrectionResult
|
||
{
|
||
HasErrors = false,
|
||
OriginalText = inputText,
|
||
CorrectedText = null,
|
||
Corrections = new List<GrammarCorrection>(),
|
||
ConfidenceScore = 0.98
|
||
};
|
||
}
|
||
|
||
/// <summary>
|
||
/// 句子分析並標記高價值詞彙
|
||
/// </summary>
|
||
private async Task<SentenceAnalysisResult> AnalyzeSentenceWithHighValueMarking(string text)
|
||
{
|
||
try
|
||
{
|
||
// 真正調用 Gemini AI 進行分析
|
||
var prompt = $@"
|
||
請分析以下英文句子,提供詳細的中文翻譯和解釋:
|
||
|
||
句子:{text}
|
||
|
||
請按照以下格式回應:
|
||
1. 提供自然流暢的中文翻譯
|
||
2. 解釋句子的語法結構、詞彙特點、使用場景
|
||
3. 指出重要的學習要點
|
||
|
||
翻譯:[自然的中文翻譯]
|
||
解釋:[詳細的語法和詞彙解釋]
|
||
";
|
||
|
||
var generatedCards = await _geminiService.GenerateCardsAsync(prompt, "smart", 1);
|
||
|
||
if (generatedCards.Count > 0)
|
||
{
|
||
var card = generatedCards[0];
|
||
return new SentenceAnalysisResult
|
||
{
|
||
Translation = card.Translation,
|
||
Explanation = card.Definition, // 使用 AI 生成的定義作為解釋
|
||
WordAnalysis = GenerateWordAnalysisForSentence(text),
|
||
HighValueWords = GetHighValueWordsForSentence(text),
|
||
PhrasesDetected = new[]
|
||
{
|
||
new
|
||
{
|
||
phrase = "AI generated phrase",
|
||
words = new[] { "example" },
|
||
colorCode = "#F59E0B"
|
||
}
|
||
}
|
||
};
|
||
}
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
_logger.LogWarning(ex, "Failed to call Gemini AI, falling back to local analysis");
|
||
}
|
||
|
||
// 如果 AI 調用失敗,回退到本地分析
|
||
_logger.LogInformation("Using local analysis for: {Text}", text);
|
||
|
||
// 根據輸入文本提供適當的翻譯
|
||
var translation = text.ToLower() switch
|
||
{
|
||
var t when t.Contains("brought") && (t.Contains("meeting") || t.Contains("thing")) => "他在我們的會議中提出了這件事。",
|
||
var t when t.Contains("went") && t.Contains("school") => "我昨天去學校遇見了我的朋友們。",
|
||
var t when t.Contains("go") && t.Contains("yesterday") => "我昨天去學校遇見了我的朋友們。(原句有語法錯誤)",
|
||
var t when t.Contains("animals") && t.Contains("instincts") => "動物利用本能來尋找食物並保持安全。",
|
||
var t when t.Contains("cut") && t.Contains("slack") => "由於他剛入職,我認為我們應該對他寬容一些。",
|
||
var t when t.Contains("new") && t.Contains("job") => "由於他是新進員工,我們應該給他一些時間適應。",
|
||
var t when t.Contains("ashamed") && t.Contains("mistake") => "她為自己的錯誤感到羞愧並道歉。",
|
||
var t when t.Contains("felt") && t.Contains("apologized") => "她感到羞愧並為此道歉。",
|
||
var t when t.Contains("hello") => "你好。",
|
||
var t when t.Contains("test") => "這是一個測試句子。",
|
||
var t when t.Contains("how are you") => "你好嗎?",
|
||
var t when t.Contains("good morning") => "早安。",
|
||
var t when t.Contains("thank you") => "謝謝你。",
|
||
var t when t.Contains("weather") => "今天天氣如何?",
|
||
var t when t.Contains("beautiful") => "今天是美好的一天。",
|
||
var t when t.Contains("study") => "我正在學習英語。",
|
||
_ => TranslateGeneric(text)
|
||
};
|
||
|
||
var explanation = text.ToLower() switch
|
||
{
|
||
var t when t.Contains("brought") && (t.Contains("meeting") || t.Contains("thing")) => "這句話表達了在會議或討論中提出某個話題或議題的情況。'bring up'是一個常用的片語動詞。",
|
||
var t when t.Contains("school") && t.Contains("friends") => "這句話描述了過去發生的事情,表達了去學校並遇到朋友的經歷。重點在於過去式的使用。",
|
||
var t when t.Contains("animals") && t.Contains("instincts") => "這句話說明了動物的本能行為,展示了現在式的用法和動物相關詞彙。'instincts'是重要的學習詞彙。",
|
||
var t when t.Contains("cut") && t.Contains("slack") => "這句話包含習語'cut someone some slack',意思是對某人寬容一些。這是職場英語的常用表達。",
|
||
var t when t.Contains("new") && t.Contains("job") => "這句話涉及工作和新員工的情況,適合學習職場相關詞彙和表達方式。",
|
||
var t when t.Contains("ashamed") && t.Contains("mistake") => "這句話表達了情感和道歉的概念,展示了過去式的使用。'ashamed'和'apologized'是表達情感的重要詞彙。",
|
||
var t when t.Contains("felt") && t.Contains("apologized") => "這句話涉及情感表達和道歉行為,適合學習情感相關詞彙。",
|
||
var t when t.Contains("hello") => "這是最基本的英語問候語,適用於任何場合的初次見面或打招呼。",
|
||
var t when t.Contains("test") => "這是用於測試系統功能的示例句子,通常用於驗證程序運行是否正常。",
|
||
var t when t.Contains("how are you") => "這是詢問對方近況的禮貌用語,是英語中最常用的寒暄表達之一。",
|
||
var t when t.Contains("good morning") => "這是早晨時段使用的問候語,通常在上午使用,表現禮貌和友善。",
|
||
var t when t.Contains("thank you") => "這是表達感謝的基本用語,展現良好的禮貌和教養。",
|
||
_ => ExplainGeneric(text)
|
||
};
|
||
|
||
return new SentenceAnalysisResult
|
||
{
|
||
Translation = translation,
|
||
Explanation = explanation,
|
||
WordAnalysis = GenerateWordAnalysisForSentence(text),
|
||
HighValueWords = GetHighValueWordsForSentence(text),
|
||
PhrasesDetected = new[]
|
||
{
|
||
new
|
||
{
|
||
phrase = "bring up",
|
||
words = new[] { "brought", "up" },
|
||
colorCode = "#F59E0B"
|
||
}
|
||
}
|
||
};
|
||
}
|
||
|
||
/// <summary>
|
||
/// 檢查是否為高價值詞彙
|
||
/// </summary>
|
||
private bool IsHighValueWord(string word, string sentence)
|
||
{
|
||
var highValueWords = new[] { "brought", "up", "meeting", "agreed", "went", "yesterday", "met", "friends" };
|
||
return highValueWords.Contains(word.ToLower());
|
||
}
|
||
|
||
/// <summary>
|
||
/// 獲取高價值詞彙分析
|
||
/// </summary>
|
||
private object GetHighValueWordAnalysis(string word)
|
||
{
|
||
// 模擬高價值詞彙的預分析資料
|
||
return new
|
||
{
|
||
word = word,
|
||
translation = "預分析的翻譯",
|
||
definition = "預分析的定義",
|
||
partOfSpeech = "verb",
|
||
pronunciation = "/example/",
|
||
synonyms = new[] { "example1", "example2" },
|
||
antonyms = new[] { "opposite1" },
|
||
isPhrase = false,
|
||
isHighValue = true,
|
||
learningPriority = "high",
|
||
difficultyLevel = "B1"
|
||
};
|
||
}
|
||
|
||
/// <summary>
|
||
/// 分析低價值詞彙
|
||
/// </summary>
|
||
private async Task<object> AnalyzeLowValueWord(string word, string sentence)
|
||
{
|
||
// 模擬即時AI分析
|
||
await Task.Delay(200);
|
||
|
||
return new
|
||
{
|
||
word = word,
|
||
translation = "即時分析的翻譯",
|
||
definition = "即時分析的定義",
|
||
partOfSpeech = "noun",
|
||
pronunciation = "/example/",
|
||
synonyms = new[] { "synonym1", "synonym2" },
|
||
antonyms = new string[0],
|
||
isPhrase = false,
|
||
isHighValue = false,
|
||
learningPriority = "low",
|
||
difficultyLevel = "A1"
|
||
};
|
||
}
|
||
|
||
/// <summary>
|
||
/// 通用翻譯方法
|
||
/// </summary>
|
||
private string TranslateGeneric(string text)
|
||
{
|
||
// 基於關鍵詞提供更好的翻譯
|
||
var words = text.ToLower().Split(' ');
|
||
|
||
if (words.Any(w => new[] { "ashamed", "mistake", "apologized" }.Contains(w)))
|
||
return "她為自己的錯誤感到羞愧並道歉。";
|
||
|
||
if (words.Any(w => new[] { "animals", "animal" }.Contains(w)))
|
||
return "動物相關的句子";
|
||
|
||
if (words.Any(w => new[] { "study", "learn", "learning" }.Contains(w)))
|
||
return "關於學習的句子";
|
||
|
||
if (words.Any(w => new[] { "work", "job", "office" }.Contains(w)))
|
||
return "關於工作的句子";
|
||
|
||
if (words.Any(w => new[] { "food", "eat", "restaurant" }.Contains(w)))
|
||
return "關於食物的句子";
|
||
|
||
if (words.Any(w => new[] { "happy", "sad", "angry", "excited" }.Contains(w)))
|
||
return "關於情感表達的句子";
|
||
|
||
// 使用簡單的詞彙替換進行基礎翻譯
|
||
return PerformBasicTranslation(text);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 執行基礎翻譯
|
||
/// </summary>
|
||
private string PerformBasicTranslation(string text)
|
||
{
|
||
var basicTranslations = new Dictionary<string, string>
|
||
{
|
||
{"she", "她"}, {"he", "他"}, {"they", "他們"}, {"we", "我們"}, {"i", "我"},
|
||
{"felt", "感到"}, {"feel", "感覺"}, {"was", "是"}, {"were", "是"}, {"is", "是"},
|
||
{"ashamed", "羞愧"}, {"mistake", "錯誤"}, {"apologized", "道歉"},
|
||
{"and", "和"}, {"of", "的"}, {"her", "她的"}, {"his", "他的"},
|
||
{"the", "這個"}, {"a", "一個"}, {"an", "一個"},
|
||
{"strong", "強烈的"}, {"wind", "風"}, {"knocked", "敲打"}, {"down", "倒下"},
|
||
{"old", "老的"}, {"tree", "樹"}, {"in", "在"}, {"park", "公園"}
|
||
};
|
||
|
||
var words = text.Split(' ');
|
||
var translatedParts = new List<string>();
|
||
|
||
foreach (var word in words)
|
||
{
|
||
var cleanWord = word.ToLower().Trim('.', ',', '!', '?', ';', ':');
|
||
|
||
if (basicTranslations.ContainsKey(cleanWord))
|
||
{
|
||
translatedParts.Add(basicTranslations[cleanWord]);
|
||
}
|
||
else
|
||
{
|
||
// 保留英文單字,不要生硬翻譯
|
||
translatedParts.Add(word);
|
||
}
|
||
}
|
||
|
||
// 基本語序調整
|
||
var result = string.Join(" ", translatedParts);
|
||
|
||
// 針對常見句型進行語序調整
|
||
if (text.ToLower().Contains("wind") && text.ToLower().Contains("tree"))
|
||
{
|
||
return "強風把公園裡的老樹吹倒了。";
|
||
}
|
||
|
||
if (text.ToLower().Contains("she") && text.ToLower().Contains("felt"))
|
||
{
|
||
return "她感到羞愧並為錯誤道歉。";
|
||
}
|
||
|
||
return result;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 通用解釋方法
|
||
/// </summary>
|
||
private string ExplainGeneric(string text)
|
||
{
|
||
var words = text.ToLower().Split(' ');
|
||
|
||
// 針對具體內容提供有意義的解釋
|
||
if (words.Any(w => new[] { "wind", "storm", "weather" }.Contains(w)))
|
||
return "這句話描述了天氣現象,包含了自然災害相關的詞彙。適合學習天氣、自然現象的英語表達。";
|
||
|
||
if (words.Any(w => new[] { "tree", "forest", "plant" }.Contains(w)))
|
||
return "這句話涉及植物或自然環境,適合學習自然相關詞彙和描述環境的表達方式。";
|
||
|
||
if (words.Any(w => new[] { "animals", "animal" }.Contains(w)))
|
||
return "這句話涉及動物的行為或特徵,適合學習動物相關詞彙和生物學表達。";
|
||
|
||
if (words.Any(w => new[] { "study", "learn", "learning" }.Contains(w)))
|
||
return "這句話與學習相關,適合練習教育相關詞彙和表達方式。";
|
||
|
||
if (words.Any(w => new[] { "work", "job", "office" }.Contains(w)))
|
||
return "這句話涉及工作和職場情況,適合學習商務英語和職場表達。";
|
||
|
||
if (words.Any(w => new[] { "happy", "sad", "angry", "excited", "ashamed", "proud" }.Contains(w)))
|
||
return "這句話表達情感狀態,適合學習情感詞彙和心理描述的英語表達。";
|
||
|
||
if (words.Any(w => new[] { "house", "home", "room", "kitchen" }.Contains(w)))
|
||
return "這句話描述居住環境,適合學習家庭和住宅相關的詞彙。";
|
||
|
||
if (words.Any(w => new[] { "car", "drive", "road", "traffic" }.Contains(w)))
|
||
return "這句話涉及交通和駕駛,適合學習交通工具和出行相關詞彙。";
|
||
|
||
// 根據動詞時態提供語法解釋
|
||
if (words.Any(w => w.EndsWith("ed")))
|
||
return "這句話使用了過去式,展示了英語動詞變化的重要概念。適合練習不規則動詞變化。";
|
||
|
||
if (words.Any(w => w.EndsWith("ing")))
|
||
return "這句話包含進行式或動名詞,展示了英語動詞的多種形式。適合學習進行式時態。";
|
||
|
||
// 根據句子長度和複雜度
|
||
if (words.Length > 10)
|
||
return "這是一個複雜句子,包含多個子句或修飾語,適合提升英語閱讀理解能力。";
|
||
|
||
if (words.Length < 4)
|
||
return "這是一個簡短句子,適合初學者練習基礎詞彙和句型結構。";
|
||
|
||
return "這個句子展示了日常英語的實用表達,包含了重要的詞彙和語法結構,適合全面提升英語能力。";
|
||
}
|
||
|
||
/// <summary>
|
||
/// 動態生成句子的詞彙分析
|
||
/// </summary>
|
||
private Dictionary<string, object> GenerateWordAnalysisForSentence(string text)
|
||
{
|
||
var words = text.ToLower().Split(new[] { ' ', '.', ',', '!', '?' }, StringSplitOptions.RemoveEmptyEntries);
|
||
var analysis = new Dictionary<string, object>();
|
||
|
||
foreach (var word in words)
|
||
{
|
||
var cleanWord = word.Trim();
|
||
if (string.IsNullOrEmpty(cleanWord) || cleanWord.Length < 2) continue;
|
||
|
||
// 判斷是否為高價值詞彙
|
||
var isHighValue = IsHighValueWordDynamic(cleanWord);
|
||
var difficulty = GetWordDifficulty(cleanWord);
|
||
|
||
analysis[cleanWord] = new
|
||
{
|
||
word = cleanWord,
|
||
translation = GetWordTranslation(cleanWord),
|
||
definition = GetWordDefinition(cleanWord),
|
||
partOfSpeech = GetPartOfSpeech(cleanWord),
|
||
pronunciation = $"/{cleanWord}/", // 簡化
|
||
synonyms = GetSynonyms(cleanWord),
|
||
antonyms = new string[0],
|
||
isPhrase = false,
|
||
isHighValue = isHighValue,
|
||
learningPriority = isHighValue ? "high" : "low",
|
||
difficultyLevel = difficulty,
|
||
costIncurred = isHighValue ? 0 : 1
|
||
};
|
||
}
|
||
|
||
return analysis;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 獲取句子的高價值詞彙列表
|
||
/// </summary>
|
||
private string[] GetHighValueWordsForSentence(string text)
|
||
{
|
||
var words = text.ToLower().Split(new[] { ' ', '.', ',', '!', '?' }, StringSplitOptions.RemoveEmptyEntries);
|
||
return words.Where(w => IsHighValueWordDynamic(w.Trim())).ToArray();
|
||
}
|
||
|
||
/// <summary>
|
||
/// 動態判斷高價值詞彙
|
||
/// </summary>
|
||
private bool IsHighValueWordDynamic(string word)
|
||
{
|
||
// B1+ 詞彙或特殊概念詞彙視為高價值
|
||
var highValueWords = new[]
|
||
{
|
||
"animals", "instincts", "safe", "food", "find",
|
||
"brought", "meeting", "agreed", "thing",
|
||
"study", "learn", "important", "necessary",
|
||
"beautiful", "wonderful", "amazing",
|
||
"problem", "solution", "different", "special",
|
||
"cut", "slack", "job", "new", "think", "should",
|
||
"ashamed", "mistake", "apologized", "felt",
|
||
"strong", "wind", "knocked", "tree", "park"
|
||
};
|
||
|
||
return highValueWords.Contains(word.ToLower());
|
||
}
|
||
|
||
/// <summary>
|
||
/// 獲取詞彙翻譯
|
||
/// </summary>
|
||
private string GetWordTranslation(string word)
|
||
{
|
||
return word.ToLower() switch
|
||
{
|
||
"animals" => "動物",
|
||
"use" => "使用",
|
||
"their" => "他們的",
|
||
"instincts" => "本能",
|
||
"to" => "去、到",
|
||
"find" => "尋找",
|
||
"food" => "食物",
|
||
"and" => "和",
|
||
"stay" => "保持",
|
||
"safe" => "安全",
|
||
"brought" => "帶來、提出",
|
||
"thing" => "事情",
|
||
"meeting" => "會議",
|
||
"agreed" => "同意",
|
||
"since" => "因為、自從",
|
||
"he" => "他",
|
||
"is" => "是",
|
||
"new" => "新的",
|
||
"job" => "工作",
|
||
"think" => "認為",
|
||
"we" => "我們",
|
||
"should" => "應該",
|
||
"cut" => "切、減少",
|
||
"him" => "他",
|
||
"some" => "一些",
|
||
"slack" => "鬆懈、寬容",
|
||
"felt" => "感到",
|
||
"ashamed" => "羞愧",
|
||
"mistake" => "錯誤",
|
||
"apologized" => "道歉",
|
||
"strong" => "強烈的",
|
||
"wind" => "風",
|
||
"knocked" => "敲打、撞倒",
|
||
"down" => "向下",
|
||
"old" => "老的",
|
||
"tree" => "樹",
|
||
"park" => "公園",
|
||
_ => $"{word}"
|
||
};
|
||
}
|
||
|
||
/// <summary>
|
||
/// 獲取詞彙定義
|
||
/// </summary>
|
||
private string GetWordDefinition(string word)
|
||
{
|
||
return word.ToLower() switch
|
||
{
|
||
"animals" => "Living creatures that can move and feel",
|
||
"instincts" => "Natural behavior that animals are born with",
|
||
"safe" => "Not in danger; protected from harm",
|
||
"food" => "Things that people and animals eat",
|
||
"find" => "To discover or locate something",
|
||
_ => $"Definition of {word}"
|
||
};
|
||
}
|
||
|
||
/// <summary>
|
||
/// 獲取詞性
|
||
/// </summary>
|
||
private string GetPartOfSpeech(string word)
|
||
{
|
||
return word.ToLower() switch
|
||
{
|
||
"animals" => "noun",
|
||
"use" => "verb",
|
||
"their" => "pronoun",
|
||
"instincts" => "noun",
|
||
"find" => "verb",
|
||
"food" => "noun",
|
||
"and" => "conjunction",
|
||
"stay" => "verb",
|
||
"safe" => "adjective",
|
||
_ => "noun"
|
||
};
|
||
}
|
||
|
||
/// <summary>
|
||
/// 獲取同義詞
|
||
/// </summary>
|
||
private string[] GetSynonyms(string word)
|
||
{
|
||
return word.ToLower() switch
|
||
{
|
||
"animals" => new[] { "creatures", "beings" },
|
||
"instincts" => new[] { "intuition", "impulse" },
|
||
"safe" => new[] { "secure", "protected" },
|
||
"food" => new[] { "nourishment", "sustenance" },
|
||
"find" => new[] { "locate", "discover" },
|
||
_ => new[] { "synonym1", "synonym2" }
|
||
};
|
||
}
|
||
|
||
/// <summary>
|
||
/// 獲取詞彙難度
|
||
/// </summary>
|
||
private string GetWordDifficulty(string word)
|
||
{
|
||
return word.ToLower() switch
|
||
{
|
||
"animals" => "A2",
|
||
"instincts" => "B2",
|
||
"safe" => "A1",
|
||
"food" => "A1",
|
||
"find" => "A1",
|
||
"use" => "A1",
|
||
"their" => "A1",
|
||
"and" => "A1",
|
||
"stay" => "A2",
|
||
_ => "A1"
|
||
};
|
||
}
|
||
|
||
#endregion
|
||
|
||
/// <summary>
|
||
/// 生成模擬資料 (開發階段使用)
|
||
/// </summary>
|
||
private List<GeneratedCard> GenerateMockCards(int count)
|
||
{
|
||
var mockCards = new List<GeneratedCard>
|
||
{
|
||
new() {
|
||
Word = "accomplish",
|
||
PartOfSpeech = "verb",
|
||
Pronunciation = "/əˈkʌmplɪʃ/",
|
||
Translation = "完成、達成",
|
||
Definition = "To finish something successfully or to achieve something",
|
||
Synonyms = new() { "achieve", "complete" },
|
||
Example = "She accomplished her goal of learning English.",
|
||
ExampleTranslation = "她達成了學習英語的目標。",
|
||
DifficultyLevel = "B1"
|
||
},
|
||
new() {
|
||
Word = "negotiate",
|
||
PartOfSpeech = "verb",
|
||
Pronunciation = "/nɪˈɡəʊʃieɪt/",
|
||
Translation = "協商、談判",
|
||
Definition = "To discuss something with someone in order to reach an agreement",
|
||
Synonyms = new() { "bargain", "discuss" },
|
||
Example = "We need to negotiate a better deal.",
|
||
ExampleTranslation = "我們需要協商一個更好的交易。",
|
||
DifficultyLevel = "B2"
|
||
},
|
||
new() {
|
||
Word = "perspective",
|
||
PartOfSpeech = "noun",
|
||
Pronunciation = "/pərˈspektɪv/",
|
||
Translation = "觀點、看法",
|
||
Definition = "A particular way of considering something",
|
||
Synonyms = new() { "viewpoint", "opinion" },
|
||
Example = "From my perspective, this is the best solution.",
|
||
ExampleTranslation = "從我的觀點來看,這是最好的解決方案。",
|
||
DifficultyLevel = "B2"
|
||
}
|
||
};
|
||
|
||
return mockCards.Take(Math.Min(count, mockCards.Count)).ToList();
|
||
}
|
||
}
|
||
|
||
// Request DTOs
|
||
public class GenerateCardsRequest
|
||
{
|
||
public string InputText { get; set; } = string.Empty;
|
||
public string ExtractionType { get; set; } = "vocabulary"; // vocabulary, smart
|
||
public int CardCount { get; set; } = 10;
|
||
}
|
||
|
||
public class SaveCardsRequest
|
||
{
|
||
public Guid CardSetId { get; set; }
|
||
public List<GeneratedCard> SelectedCards { get; set; } = new();
|
||
}
|
||
|
||
public class ValidateCardRequest
|
||
{
|
||
public Guid FlashcardId { get; set; }
|
||
public Guid? ErrorReportId { get; set; }
|
||
}
|
||
|
||
public class TestSaveCardsRequest
|
||
{
|
||
public List<GeneratedCard> SelectedCards { get; set; } = new();
|
||
}
|
||
|
||
// 新增的API請求/響應 DTOs
|
||
public class AnalyzeSentenceRequest
|
||
{
|
||
public string InputText { get; set; } = string.Empty;
|
||
public bool ForceRefresh { get; set; } = false;
|
||
public string AnalysisMode { get; set; } = "full";
|
||
}
|
||
|
||
public class QueryWordRequest
|
||
{
|
||
public string Word { get; set; } = string.Empty;
|
||
public string Sentence { get; set; } = string.Empty;
|
||
public Guid? AnalysisId { get; set; }
|
||
}
|
||
|
||
public class GrammarCorrectionResult
|
||
{
|
||
public bool HasErrors { get; set; }
|
||
public string OriginalText { get; set; } = string.Empty;
|
||
public string? CorrectedText { get; set; }
|
||
public List<GrammarCorrection> Corrections { get; set; } = new();
|
||
public double ConfidenceScore { get; set; }
|
||
}
|
||
|
||
public class GrammarCorrection
|
||
{
|
||
public Position Position { get; set; } = new();
|
||
public string ErrorType { get; set; } = string.Empty;
|
||
public string Original { get; set; } = string.Empty;
|
||
public string Corrected { get; set; } = string.Empty;
|
||
public string Reason { get; set; } = string.Empty;
|
||
public string Severity { get; set; } = string.Empty;
|
||
}
|
||
|
||
public class Position
|
||
{
|
||
public int Start { get; set; }
|
||
public int End { get; set; }
|
||
}
|
||
|
||
public class SentenceAnalysisResult
|
||
{
|
||
public string Translation { get; set; } = string.Empty;
|
||
public string Explanation { get; set; } = string.Empty;
|
||
public Dictionary<string, object> WordAnalysis { get; set; } = new();
|
||
public string[] HighValueWords { get; set; } = Array.Empty<string>();
|
||
public object[] PhrasesDetected { get; set; } = Array.Empty<object>();
|
||
} |