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 ILogger _logger; public AIController( DramaLingDbContext context, IAuthService authService, IGeminiService geminiService, ILogger logger) { _context = context; _authService = authService; _geminiService = geminiService; _logger = logger; } /// /// AI 生成詞卡 (支援 /frontend/app/generate/page.tsx) /// [HttpPost("generate")] public async Task 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 }); } } /// /// 保存生成的詞卡 /// [HttpPost("generate/{taskId}/save")] public async Task 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 }); } } /// /// 智能檢測詞卡內容 /// [HttpPost("validate-card")] public async Task 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(), Suggestions = new List { "詞卡內容看起來正確", "建議添加更多例句" }, 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 }); } } /// /// 生成模擬資料 (開發階段使用) /// private List GenerateMockCards(int count) { var mockCards = new List { 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 SelectedCards { get; set; } = new(); } public class ValidateCardRequest { public Guid FlashcardId { get; set; } public Guid? ErrorReportId { get; set; } }