diff --git a/backend/DramaLing.Api/Controllers/AIController.cs b/backend/DramaLing.Api/Controllers/AIController.cs
index 11959be..ac02770 100644
--- a/backend/DramaLing.Api/Controllers/AIController.cs
+++ b/backend/DramaLing.Api/Controllers/AIController.cs
@@ -29,6 +29,88 @@ public class AIController : ControllerBase
_logger = logger;
}
+ ///
+ /// AI 生成詞卡測試端點 (開發用,無需認證)
+ ///
+ [HttpPost("test/generate")]
+ [AllowAnonymous]
+ public async Task 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
+ });
+ }
+ }
+
///
/// AI 生成詞卡 (支援 /frontend/app/generate/page.tsx)
///
@@ -155,6 +237,100 @@ public class AIController : ControllerBase
}
}
+ ///
+ /// 測試版保存生成的詞卡 (無需認證)
+ ///
+ [HttpPost("test/save")]
+ [AllowAnonymous]
+ public async Task 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
+ });
+ }
+ }
+
///
/// 保存生成的詞卡
///
@@ -374,4 +550,9 @@ public class ValidateCardRequest
{
public Guid FlashcardId { get; set; }
public Guid? ErrorReportId { get; set; }
+}
+
+public class TestSaveCardsRequest
+{
+ public List SelectedCards { get; set; } = new();
}
\ No newline at end of file
diff --git a/backend/DramaLing.Api/DramaLing.Api.csproj b/backend/DramaLing.Api/DramaLing.Api.csproj
index 2111157..c49ef9a 100644
--- a/backend/DramaLing.Api/DramaLing.Api.csproj
+++ b/backend/DramaLing.Api/DramaLing.Api.csproj
@@ -1,23 +1,24 @@
-
-
-
- net8.0
- enable
- enable
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+ net8.0
+ enable
+ enable
+ d32b5470-3ba2-442c-8352-dd968fd406d8
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/backend/DramaLing.Api/Services/GeminiService.cs b/backend/DramaLing.Api/Services/GeminiService.cs
index 0c11ff5..cd528e1 100644
--- a/backend/DramaLing.Api/Services/GeminiService.cs
+++ b/backend/DramaLing.Api/Services/GeminiService.cs
@@ -30,7 +30,7 @@ public class GeminiService : IGeminiService
{
try
{
- if (string.IsNullOrEmpty(_apiKey))
+ if (string.IsNullOrEmpty(_apiKey) || _apiKey == "your-gemini-api-key-here")
{
throw new InvalidOperationException("Gemini API key not configured");
}
@@ -51,7 +51,7 @@ public class GeminiService : IGeminiService
{
try
{
- if (string.IsNullOrEmpty(_apiKey))
+ if (string.IsNullOrEmpty(_apiKey) || _apiKey == "your-gemini-api-key-here")
{
throw new InvalidOperationException("Gemini API key not configured");
}
diff --git a/frontend/app/generate/page.tsx b/frontend/app/generate/page.tsx
index d659fdc..6836b1b 100644
--- a/frontend/app/generate/page.tsx
+++ b/frontend/app/generate/page.tsx
@@ -86,19 +86,116 @@ function GenerateContent() {
}
]
- const handleGenerate = () => {
+ const handleGenerate = async () => {
+ if (!textInput.trim()) return
+
setIsGenerating(true)
- // Simulate AI generation
- setTimeout(() => {
+
+ try {
+ const response = await fetch('http://localhost:5000/api/ai/test/generate', {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ body: JSON.stringify({
+ inputText: textInput,
+ extractionType: extractionType,
+ cardCount: cardCount
+ })
+ })
+
+ if (!response.ok) {
+ throw new Error(`HTTP error! status: ${response.status}`)
+ }
+
+ const result = await response.json()
+
+ if (result.success) {
+ // 將後端格式轉換為前端格式
+ const convertedCards = result.data.generatedCards.map((card: any, index: number) => ({
+ id: index + 1,
+ word: card.word,
+ partOfSpeech: card.partOfSpeech,
+ pronunciation: {
+ us: card.pronunciation || '/unknown/',
+ uk: card.pronunciation || '/unknown/'
+ },
+ translation: card.translation,
+ definition: card.definition,
+ synonyms: card.synonyms || [],
+ antonyms: [], // 後端暫不提供,使用空陣列
+ originalExample: card.example || '',
+ originalExampleTranslation: card.exampleTranslation || '',
+ generatedExample: {
+ sentence: card.example || '',
+ translation: card.exampleTranslation || '',
+ imageUrl: '/images/examples/placeholder.png', // 佔位圖
+ audioUrl: '#'
+ },
+ difficulty: card.difficultyLevel || 'B1'
+ }))
+
+ setGeneratedCards(convertedCards)
+ setShowPreview(true)
+ } else {
+ throw new Error(result.error || 'Generation failed')
+ }
+ } catch (error) {
+ console.error('Error generating cards:', error)
+ alert(`生成詞卡時發生錯誤: ${error instanceof Error ? error.message : '未知錯誤'}`)
+ // 錯誤時使用模擬資料作為備用
setGeneratedCards(mockGeneratedCards)
setShowPreview(true)
+ } finally {
setIsGenerating(false)
- }, 2000)
+ }
}
- const handleSaveCards = () => {
- // Mock save action
- alert('詞卡已保存到您的卡組!')
+ const handleSaveCards = async () => {
+ if (generatedCards.length === 0) return
+
+ try {
+ // 將前端格式轉換為後端格式
+ const cardsToSave = generatedCards.map(card => ({
+ word: card.word,
+ partOfSpeech: card.partOfSpeech,
+ pronunciation: card.pronunciation.us, // 使用美式發音
+ translation: card.translation,
+ definition: card.definition,
+ synonyms: card.synonyms,
+ example: card.originalExample || card.generatedExample.sentence,
+ exampleTranslation: card.originalExampleTranslation || card.generatedExample.translation,
+ difficultyLevel: card.difficulty
+ }))
+
+ const response = await fetch('http://localhost:5000/api/ai/test/save', {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ body: JSON.stringify({
+ selectedCards: cardsToSave
+ })
+ })
+
+ if (!response.ok) {
+ throw new Error(`HTTP error! status: ${response.status}`)
+ }
+
+ const result = await response.json()
+
+ if (result.success) {
+ alert(`🎉 成功保存 ${result.data.savedCount} 張詞卡到「${result.data.cardSetName}」!`)
+ // 可選:清空生成的詞卡或跳轉到詞卡列表
+ // setGeneratedCards([])
+ // setShowPreview(false)
+ } else {
+ throw new Error(result.error || 'Save failed')
+ }
+ } catch (error) {
+ console.error('Error saving cards:', error)
+ alert(`❌ 保存詞卡時發生錯誤: ${error instanceof Error ? error.message : '未知錯誤'}`)
+ }
}
const toggleImageForCard = (cardId: number) => {