diff --git a/backend/DramaLing.Api/Controllers/AIController.cs b/backend/DramaLing.Api/Controllers/AIController.cs
index 61c09ec..4fc5b2b 100644
--- a/backend/DramaLing.Api/Controllers/AIController.cs
+++ b/backend/DramaLing.Api/Controllers/AIController.cs
@@ -37,282 +37,13 @@ public class AIController : ControllerBase
}
- ///
- /// 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.LogError("Gemini API key not configured");
- return StatusCode(500, new
- {
- Success = false,
- Error = "AI service not configured",
- Timestamp = DateTime.UtcNow
- });
- }
- }
- 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
- });
- }
- }
-
- ///
- /// 句子分析API - 支援語法修正和高價值標記
+ /// ✅ 句子分析API - 支援語法修正和高價值標記
+ /// 🎯 前端使用:/app/generate/page.tsx (主要功能)
///
[HttpPost("analyze-sentence")]
[AllowAnonymous] // 暫時無需認證,開發階段
@@ -444,146 +175,9 @@ public class AIController : ControllerBase
}
}
- ///
- /// 單字點擊查詢API
- ///
- [HttpPost("query-word")]
- [AllowAnonymous] // 暫時無需認證,開發階段
- public async Task QueryWord([FromBody] QueryWordRequest request)
- {
- try
- {
- // 基本驗證
- if (string.IsNullOrWhiteSpace(request.Word))
- {
- return BadRequest(new { Success = false, Error = "Word is required" });
- }
- // 簡化邏輯:直接調用 GeminiService 進行詞彙分析
- var wordAnalysis = await _geminiService.AnalyzeWordAsync(request.Word, request.Sentence);
- return Ok(new
- {
- Success = true,
- Data = new
- {
- Word = request.Word,
- Analysis = wordAnalysis
- },
- Message = "詞彙分析完成"
- });
- }
- catch (Exception ex)
- {
- _logger.LogError(ex, "Error analyzing word: {Word}", request.Word);
- return StatusCode(500, new
- {
- Success = false,
- Error = "詞彙查詢失敗",
- Details = ex.Message,
- Timestamp = DateTime.UtcNow
- });
- }
- }
- ///
- /// 獲取快取統計資料
- ///
- [HttpGet("cache-stats")]
- [AllowAnonymous]
- public async Task 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
- });
- }
- }
-
- ///
- /// 清理過期快取
- ///
- [HttpPost("cache-cleanup")]
- [AllowAnonymous]
- public async Task 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
- });
- }
- }
-
- ///
- /// 獲取使用統計
- ///
- [HttpGet("usage-stats")]
- [AllowAnonymous]
- public async Task 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 私有輔助方法
@@ -1251,11 +845,6 @@ public class SaveCardsRequest
public List SelectedCards { get; set; } = new();
}
-public class ValidateCardRequest
-{
- public Guid FlashcardId { get; set; }
- public Guid? ErrorReportId { get; set; }
-}
// 新增的API請求/響應 DTOs
@@ -1267,12 +856,6 @@ public class AnalyzeSentenceRequest
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
{
diff --git a/backend/DramaLing.Api/Services/GeminiService.cs b/backend/DramaLing.Api/Services/GeminiService.cs
index a0e8b60..e912ac2 100644
--- a/backend/DramaLing.Api/Services/GeminiService.cs
+++ b/backend/DramaLing.Api/Services/GeminiService.cs
@@ -7,9 +7,7 @@ namespace DramaLing.Api.Services;
public interface IGeminiService
{
Task> GenerateCardsAsync(string inputText, string extractionType, int cardCount);
- Task ValidateCardAsync(Flashcard card);
Task AnalyzeSentenceAsync(string inputText, string userLevel = "A2");
- Task AnalyzeWordAsync(string word, string sentence);
}
public class GeminiService : IGeminiService
@@ -50,7 +48,7 @@ public class GeminiService : IGeminiService
}
///
- /// 真正的句子分析和翻譯 - 調用 Gemini AI
+ /// 句子分析和翻譯 - 調用 Gemini AI
///
public async Task AnalyzeSentenceAsync(string inputText, string userLevel = "A2")
{
@@ -126,81 +124,7 @@ public class GeminiService : IGeminiService
}
}
- public async Task AnalyzeWordAsync(string word, string sentence)
- {
- try
- {
- if (string.IsNullOrEmpty(_apiKey) || _apiKey == "your-gemini-api-key-here")
- {
- throw new InvalidOperationException("Gemini API key not configured");
- }
- var prompt = $@"
-請分析單字 ""{word}"" 在句子 ""{sentence}"" 中的詳細資訊:
-
-單字: {word}
-語境: {sentence}
-
-請以JSON格式回應,不要包含任何其他文字:
-{{
- ""word"": ""{word}"",
- ""translation"": ""繁體中文翻譯"",
- ""definition"": ""英文定義"",
- ""partOfSpeech"": ""詞性(n./v./adj./adv.等)"",
- ""pronunciation"": ""IPA音標"",
- ""difficultyLevel"": ""CEFR等級(A1/A2/B1/B2/C1/C2)"",
- ""isHighValue"": false,
- ""contextMeaning"": ""在此句子中的具體含義""
-}}
-
-要求:
-1. 翻譯要準確自然
-2. 定義要簡潔易懂
-3. 音標使用標準IPA格式
-4. 根據語境判斷詞性和含義
-";
-
- var response = await CallGeminiApiAsync(prompt);
- return ParseWordAnalysisResponse(response, word);
- }
- catch (Exception ex)
- {
- _logger.LogError(ex, "Error analyzing word with Gemini API");
-
- // 回退到基本資料
- return new WordAnalysisResult
- {
- Word = word,
- Translation = $"{word} (AI 暫時不可用)",
- Definition = $"Definition of {word} (service temporarily unavailable)",
- PartOfSpeech = "unknown",
- Pronunciation = $"/{word}/",
- DifficultyLevel = "unknown",
- IsHighValue = false
- };
- }
- }
-
- public async Task ValidateCardAsync(Flashcard card)
- {
- try
- {
- if (string.IsNullOrEmpty(_apiKey) || _apiKey == "your-gemini-api-key-here")
- {
- throw new InvalidOperationException("Gemini API key not configured");
- }
-
- var prompt = BuildValidationPrompt(card);
- var response = await CallGeminiApiAsync(prompt);
-
- return ParseValidationResult(response);
- }
- catch (Exception ex)
- {
- _logger.LogError(ex, "Error validating card with Gemini API");
- throw;
- }
- }
private string BuildPrompt(string inputText, string extractionType, int cardCount)
{
@@ -211,16 +135,6 @@ public class GeminiService : IGeminiService
.Replace("{inputText}", inputText);
}
- private string BuildValidationPrompt(Flashcard card)
- {
- return CardValidationPrompt
- .Replace("{word}", card.Word)
- .Replace("{translation}", card.Translation)
- .Replace("{definition}", card.Definition)
- .Replace("{partOfSpeech}", card.PartOfSpeech ?? "")
- .Replace("{pronunciation}", card.Pronunciation ?? "")
- .Replace("{example}", card.Example ?? "");
- }
private async Task CallGeminiApiAsync(string prompt)
{
@@ -324,92 +238,7 @@ public class GeminiService : IGeminiService
}
}
- private ValidationResult ParseValidationResult(string response)
- {
- try
- {
- var cleanText = response.Trim().Replace("```json", "").Replace("```", "").Trim();
- var jsonResponse = JsonSerializer.Deserialize(cleanText);
- var issues = new List();
- if (jsonResponse.TryGetProperty("issues", out var issuesElement))
- {
- foreach (var issueElement in issuesElement.EnumerateArray())
- {
- issues.Add(new ValidationIssue
- {
- Field = GetStringProperty(issueElement, "field"),
- Original = GetStringProperty(issueElement, "original"),
- Corrected = GetStringProperty(issueElement, "corrected"),
- Reason = GetStringProperty(issueElement, "reason"),
- Severity = GetStringProperty(issueElement, "severity")
- });
- }
- }
-
- var suggestions = new List();
- if (jsonResponse.TryGetProperty("suggestions", out var suggestionsElement))
- {
- foreach (var suggestion in suggestionsElement.EnumerateArray())
- {
- suggestions.Add(suggestion.GetString() ?? "");
- }
- }
-
- return new ValidationResult
- {
- Issues = issues,
- Suggestions = suggestions,
- OverallScore = jsonResponse.TryGetProperty("overall_score", out var scoreElement)
- ? scoreElement.GetInt32() : 85,
- Confidence = jsonResponse.TryGetProperty("confidence", out var confidenceElement)
- ? confidenceElement.GetDouble() : 0.9
- };
- }
- catch (Exception ex)
- {
- _logger.LogError(ex, "Error parsing validation result: {Response}", response);
- throw new InvalidOperationException($"Failed to parse validation response: {ex.Message}");
- }
- }
-
- ///
- /// 解析 Gemini AI 詞彙分析響應
- ///
- private WordAnalysisResult ParseWordAnalysisResponse(string response, string word)
- {
- try
- {
- var cleanText = response.Trim().Replace("```json", "").Replace("```", "").Trim();
- var jsonResponse = JsonSerializer.Deserialize(cleanText);
-
- return new WordAnalysisResult
- {
- Word = GetStringProperty(jsonResponse, "word") ?? word,
- Translation = GetStringProperty(jsonResponse, "translation") ?? $"{word} 的翻譯",
- Definition = GetStringProperty(jsonResponse, "definition") ?? $"Definition of {word}",
- PartOfSpeech = GetStringProperty(jsonResponse, "partOfSpeech") ?? "unknown",
- Pronunciation = GetStringProperty(jsonResponse, "pronunciation") ?? $"/{word}/",
- DifficultyLevel = GetStringProperty(jsonResponse, "difficultyLevel") ?? "A1",
- IsHighValue = jsonResponse.TryGetProperty("isHighValue", out var isHighValueElement) && isHighValueElement.GetBoolean()
- };
- }
- catch (Exception ex)
- {
- _logger.LogError(ex, "Failed to parse word analysis response");
-
- return new WordAnalysisResult
- {
- Word = word,
- Translation = $"{word} (解析失敗)",
- Definition = $"Definition of {word} (parsing failed)",
- PartOfSpeech = "unknown",
- Pronunciation = $"/{word}/",
- DifficultyLevel = "unknown",
- IsHighValue = false
- };
- }
- }
///
/// 解析 Gemini AI 句子分析響應
@@ -562,23 +391,6 @@ public class GeminiService : IGeminiService
請按照相同的 JSON 格式回應...";
- private const string CardValidationPrompt = @"
-請檢查以下詞卡內容的準確性:
-
-單字: {word}
-翻譯: {translation}
-定義: {definition}
-詞性: {partOfSpeech}
-發音: {pronunciation}
-例句: {example}
-
-請按照以下 JSON 格式回應:
-{
- ""issues"": [],
- ""suggestions"": [],
- ""overall_score"": 85,
- ""confidence"": 0.9
-}";
}
// 支援類型
@@ -595,22 +407,7 @@ public class GeneratedCard
public string DifficultyLevel { get; set; } = string.Empty;
}
-public class ValidationResult
-{
- public List Issues { get; set; } = new();
- public List Suggestions { get; set; } = new();
- public int OverallScore { get; set; }
- public double Confidence { get; set; }
-}
-public class ValidationIssue
-{
- public string Field { 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 SentenceAnalysisResponse