diff --git a/frontend/app/flashcards/[id]/page.tsx b/frontend/app/flashcards/[id]/page.tsx index 3fd50e4..f94fe15 100644 --- a/frontend/app/flashcards/[id]/page.tsx +++ b/frontend/app/flashcards/[id]/page.tsx @@ -1,7 +1,7 @@ 'use client' import { useState, useEffect, use } from 'react' -import { useRouter } from 'next/navigation' +import { useRouter, useSearchParams } from 'next/navigation' import { Navigation } from '@/components/Navigation' import { ProtectedRoute } from '@/components/ProtectedRoute' import { useToast } from '@/components/Toast' @@ -25,6 +25,7 @@ export default function FlashcardDetailPage({ params }: FlashcardDetailPageProps function FlashcardDetailContent({ cardId }: { cardId: string }) { const router = useRouter() + const searchParams = useSearchParams() const toast = useToast() const [flashcard, setFlashcard] = useState(null) const [loading, setLoading] = useState(true) @@ -32,6 +33,10 @@ function FlashcardDetailContent({ cardId }: { cardId: string }) { const [isEditing, setIsEditing] = useState(false) const [editedCard, setEditedCard] = useState(null) + // 圖片生成狀態 + const [isGeneratingImage, setIsGeneratingImage] = useState(false) + const [generationProgress, setGenerationProgress] = useState('') + // 假資料 - 用於展示效果 const mockCards: {[key: string]: any} = { 'mock1': { @@ -94,18 +99,13 @@ function FlashcardDetailContent({ cardId }: { cardId: string }) { return } - // 載入真實詞卡 - 使用列表 API 然後找到對應詞卡 (因為列表 API 有圖片資訊) - const result = await flashcardsService.getFlashcards() + // 載入真實詞卡 - 使用直接 API 調用 + const result = await flashcardsService.getFlashcard(cardId) if (result.success && result.data) { - const targetCard = result.data.flashcards.find(card => card.id === cardId) - if (targetCard) { - setFlashcard(targetCard) - setEditedCard(targetCard) - } else { - throw new Error('詞卡不存在') - } + setFlashcard(result.data) + setEditedCard(result.data) } else { - throw new Error(result.error || '載入詞卡失敗') + throw new Error(result.error || '詞卡不存在') } } catch (err) { setError('載入詞卡時發生錯誤') @@ -117,6 +117,15 @@ function FlashcardDetailContent({ cardId }: { cardId: string }) { loadFlashcard() }, [cardId]) + // 檢查 URL 參數,自動開啟編輯模式 + useEffect(() => { + if (searchParams.get('edit') === 'true' && flashcard) { + setIsEditing(true) + // 清理 URL 參數,保持 URL 乾淨 + router.replace(`/flashcards/${cardId}`) + } + }, [flashcard, searchParams, cardId, router]) + // 獲取CEFR等級顏色 const getCEFRColor = (level: string) => { switch (level) { @@ -251,6 +260,53 @@ function FlashcardDetailContent({ cardId }: { cardId: string }) { } } + // 處理圖片生成 + const handleGenerateImage = async () => { + if (!flashcard || isGeneratingImage) return + + try { + setIsGeneratingImage(true) + setGenerationProgress('啟動生成中...') + toast.info(`開始為「${flashcard.word}」生成例句圖片...`) + + const generateResult = await imageGenerationService.generateImage(flashcard.id) + if (!generateResult.success || !generateResult.data) { + throw new Error(generateResult.error || '啟動生成失敗') + } + + const requestId = generateResult.data.requestId + setGenerationProgress('Gemini 生成描述中...') + + const finalStatus = await imageGenerationService.pollUntilComplete( + requestId, + (status) => { + const stage = status.stages.gemini.status === 'completed' + ? 'Replicate 生成圖片中...' : 'Gemini 生成描述中...' + setGenerationProgress(stage) + }, + 5 + ) + + if (finalStatus.overallStatus === 'completed') { + setGenerationProgress('生成完成,載入中...') + // 重新載入詞卡資料 + const result = await flashcardsService.getFlashcard(cardId) + if (result.success && result.data) { + setFlashcard(result.data) + setEditedCard(result.data) + } + toast.success(`「${flashcard.word}」的例句圖片生成完成!`) + } else { + throw new Error('圖片生成未完成') + } + } catch (error: any) { + toast.error(`圖片生成失敗: ${error.message || '未知錯誤'}`) + } finally { + setIsGeneratingImage(false) + setGenerationProgress('') + } + } + if (loading) { return (
@@ -382,12 +438,50 @@ function FlashcardDetailContent({ cardId }: { cardId: string }) {

例句

{/* 例句圖片 */} -
- {`${flashcard.word} +
+ {getExampleImage(flashcard) ? ( + {`${flashcard.word} + ) : ( +
+
+ + + +

尚無例句圖片

+ +
+
+ )} + + {/* 圖片上的生成按鈕 */} + {getExampleImage(flashcard) && !isGeneratingImage && ( + + )} + + {/* 生成進度覆蓋 */} + {isGeneratingImage && getExampleImage(flashcard) && ( +
+
+
+

{generationProgress}

+
+
+ )}
diff --git a/frontend/app/flashcards/page.tsx b/frontend/app/flashcards/page.tsx index a1e52b3..9e4f5a6 100644 --- a/frontend/app/flashcards/page.tsx +++ b/frontend/app/flashcards/page.tsx @@ -36,8 +36,6 @@ function FlashcardsContent() { const router = useRouter() const toast = useToast() const [activeTab, setActiveTab] = useState<'all-cards' | 'favorites'>('all-cards') - const [showForm, setShowForm] = useState(false) - const [editingCard, setEditingCard] = useState(null) const [showAdvancedSearch, setShowAdvancedSearch] = useState(false) const [totalCounts, setTotalCounts] = useState({ all: 0, favorites: 0 }) @@ -150,17 +148,9 @@ function FlashcardsContent() { } } - // 處理表單操作 - const handleFormSuccess = async () => { - setShowForm(false) - setEditingCard(null) - await searchActions.refresh() - await loadTotalCounts() - } const handleEdit = (card: Flashcard) => { - setEditingCard(card) - setShowForm(true) + router.push(`/flashcards/${card.id}?edit=true`) } const handleDelete = async (card: Flashcard) => { @@ -345,27 +335,6 @@ function FlashcardsContent() { />
- {/* Form Modal */} - {showForm && ( - { - setShowForm(false) - setEditingCard(null) - }} - /> - )} {/* Toast */}