diff --git a/AI生成頁面重新設計計劃.md b/AI生成頁面重新設計計劃.md new file mode 100644 index 0000000..19d1eb5 --- /dev/null +++ b/AI生成頁面重新設計計劃.md @@ -0,0 +1,109 @@ +# AI 生成頁面重新設計計劃 + +## 設計目標 +將當前的兩階段界面 (輸入 → 按鈕 → 結果頁面) 重新設計為統一的單頁面界面 + +## 新的布局設計 + +### 桌面版布局 (左右分欄) +``` +┌─────────────────────────────────────────────────────────────┐ +│ AI 智能生成詞卡 [你的程度 A2 ⚙️] │ +├─────────────────────┬───────────────────────────────────────┤ +│ 左側:輸入區 │ 右側:結果顯示區 │ +│ • 文字輸入框 │ • 句子分析結果 (有結果時顯示) │ +│ • 分析按鈕 │ • 詞彙統計 │ +│ • 歷史記錄 │ • 互動詞彙 │ +│ │ • 保存提醒 │ +└─────────────────────┴───────────────────────────────────────┘ +``` + +### 手機版布局 (上下分區) +``` +┌─────────────────────────────────────┐ +│ AI 智能生成詞卡 [你的程度 A2 ⚙️] │ +├─────────────────────────────────────┤ +│ 輸入區 │ +│ • 文字輸入框 │ +│ • 分析按鈕 │ +├─────────────────────────────────────┤ +│ 結果顯示區 (展開/摺疊) │ +│ • 句子分析結果 │ +│ • 詞彙統計 │ +│ • 保存提醒 │ +└─────────────────────────────────────┘ +``` + +## 功能增強 + +### 1. 統一界面設計 +- **移除視圖切換**:不再使用 `showAnalysisView` 狀態 +- **固定雙欄布局**:輸入區和結果區同時可見 +- **即時結果顯示**:分析完成後立即在右側顯示 + +### 2. 歷史記錄系統 +- **localStorage 多記錄**:保存最近 5-10 次分析記錄 +- **歷史查詢列表**:在左側輸入區下方顯示 +- **快速切換**:點擊歷史記錄可立即載入該分析結果 +- **記錄格式**: + ```javascript + { + id: timestamp, + textInput: "原始輸入文字...", + sentenceAnalysis: {...}, + sentenceMeaning: "翻譯", + createdAt: Date, + saved: boolean // 是否已保存詞卡 + } + ``` + +### 3. 保存提醒系統 +- **警告訊息**:「⚠️ 請及時保存詞卡,避免查詢紀錄消失」 +- **未保存計數**:顯示當前分析中有多少詞彙未保存 +- **批量保存**:「保存所有重點詞彙」按鈕 +- **視覺提醒**:未保存的詞彙有特殊標記 + +## 技術實施 + +### 1. 布局重構 +- **移除條件渲染**:`{!showAnalysisView ? ... : ...}` +- **使用 Grid/Flexbox**:實現響應式左右分欄 +- **固定結構**:輸入區和結果區始終存在 + +### 2. 狀態管理優化 +- **移除 showAnalysisView 狀態** +- **新增 analysisHistory 狀態**:管理歷史記錄 +- **新增 savedWords 狀態**:追踪已保存的詞彙 + +### 3. localStorage 擴展 +- **升級快取結構**:從單一記錄改為記錄陣列 +- **自動清理**:超過最大數量時移除最舊記錄 +- **資料完整性**:確保向後兼容性 + +### 4. 用戶體驗改進 +- **空狀態設計**:結果區域在無分析時的友好提示 +- **載入狀態**:分析中的視覺反饋 +- **成功狀態**:分析完成的視覺確認 + +## 視覺設計原則 + +### 1. 一致性 +- 保持與詞卡管理頁面的設計語言一致 +- 使用相同的顏色系統和組件樣式 + +### 2. 易用性 +- 清楚的操作流程指引 +- 重要功能突出顯示 +- 減少用戶的操作步驟 + +### 3. 響應式 +- 桌面版左右分欄 +- 平板版適當調整比例 +- 手機版改為上下堆疊 + +## 實施優先級 + +1. **Phase 1**:重構基本布局 (左右分欄) +2. **Phase 2**:實現歷史記錄系統 +3. **Phase 3**:添加保存提醒功能 +4. **Phase 4**:優化響應式設計和動畫 \ No newline at end of file diff --git a/frontend/app/generate/page.tsx b/frontend/app/generate/page.tsx index 9dd8ea1..1888241 100644 --- a/frontend/app/generate/page.tsx +++ b/frontend/app/generate/page.tsx @@ -1,596 +1,476 @@ -'use client' - -import { useState, useMemo, useCallback, useEffect } from 'react' -import { ProtectedRoute } from '@/components/shared/ProtectedRoute' -import { Navigation } from '@/components/shared/Navigation' -import { WordPopup } from '@/components/word/WordPopup' -import { useToast } from '@/components/shared/Toast' -import { flashcardsService } from '@/lib/services/flashcards' -import { getLevelIndex } from '@/lib/utils/cefrUtils' -import { useWordAnalysis } from '@/hooks/word/useWordAnalysis' -import { API_CONFIG } from '@/lib/config/api' -import Link from 'next/link' - -// 常數定義 -const MAX_MANUAL_INPUT_LENGTH = 300 - - -interface GrammarCorrection { - hasErrors: boolean; - originalText: string; - correctedText: string | null; - corrections: Array<{ - position: { start: number; end: number }; - error: string; - correction: string; - type: string; - explanation: string; - severity: 'high' | 'medium' | 'low'; - }>; - confidenceScore: number; -} - -// 移除 IdiomPopup - 使用統一的 WordPopup 組件 - -function GenerateContent() { - const toast = useToast() - const { findWordAnalysis, getWordClass } = useWordAnalysis() - const [textInput, setTextInput] = useState('') - - // 獲取用戶等級 - const userLevel = typeof window !== 'undefined' - ? localStorage.getItem('userEnglishLevel') || 'A2' - : 'A2' - const [isAnalyzing, setIsAnalyzing] = useState(false) - const [showAnalysisView, setShowAnalysisView] = useState(false) - const [sentenceAnalysis, setSentenceAnalysis] = useState | null>(null) - const [sentenceMeaning, setSentenceMeaning] = useState('') - const [grammarCorrection, setGrammarCorrection] = useState(null) - const [selectedIdiom, setSelectedIdiom] = useState(null) - const [selectedWord, setSelectedWord] = useState(null) - - // localStorage 快取函數 - const saveAnalysisToCache = useCallback((cacheData: any) => { - try { - localStorage.setItem('generate_analysis_cache', JSON.stringify(cacheData)) - } catch (error) { - console.warn('無法保存分析快取:', error) - } - }, []) - - const loadAnalysisFromCache = useCallback(() => { - try { - const cached = localStorage.getItem('generate_analysis_cache') - return cached ? JSON.parse(cached) : null - } catch (error) { - console.warn('無法載入分析快取:', error) - return null - } - }, []) - - const clearAnalysisCache = useCallback(() => { - try { - localStorage.removeItem('generate_analysis_cache') - } catch (error) { - console.warn('無法清除分析快取:', error) - } - }, []) - - // 組件載入時恢復快取的分析結果 - useEffect(() => { - const cached = loadAnalysisFromCache() - if (cached) { - setTextInput(cached.textInput || '') - setSentenceAnalysis(cached.sentenceAnalysis || null) - setSentenceMeaning(cached.sentenceMeaning || '') - setGrammarCorrection(cached.grammarCorrection || null) - setShowAnalysisView(cached.showAnalysisView || false) - console.log('✅ 已恢復快取的分析結果') - } - }, [loadAnalysisFromCache]) - - // 處理句子分析 - 使用真實API - const handleAnalyzeSentence = async () => { - // 清除舊的分析快取 - clearAnalysisCache() - - setIsAnalyzing(true) - - try { - const response = await fetch(`${API_CONFIG.BASE_URL}/api/ai/analyze-sentence`, { - method: 'POST', - headers: { - 'Content-Type': 'application/json' - }, - body: JSON.stringify({ - inputText: textInput, - analysisMode: 'full', - options: { - includeGrammarCheck: true, - includeVocabularyAnalysis: true, - includeTranslation: true, - includeIdiomDetection: true, - includeExamples: true - } - }) - }) - - if (!response.ok) { - let errorMessage = `API請求失敗: ${response.status}` - try { - const errorData = await response.json() - errorMessage = errorData.error?.message || errorData.message || errorMessage - } catch (e) { - console.warn('無法解析錯誤回應:', e) - } - throw new Error(errorMessage) - } - - const result = await response.json() - - if (!result.success || !result.data) { - throw new Error('API回應格式錯誤') - } - - // 處理API回應 - 適配新的後端格式 - const apiData = result.data.data // 需要深入兩層:result.data.data - - // 設定完整的分析結果(包含vocabularyAnalysis和其他數據) - const analysisData = { - originalText: apiData.originalText, - sentenceMeaning: apiData.sentenceMeaning, - grammarCorrection: apiData.grammarCorrection, - vocabularyAnalysis: apiData.vocabularyAnalysis, - idioms: apiData.idioms || [], - processingTime: result.processingTime - } - - setSentenceAnalysis(analysisData) - setSentenceMeaning(apiData.sentenceMeaning || '') - - // 處理語法修正 - if (apiData.grammarCorrection) { - setGrammarCorrection({ - hasErrors: apiData.grammarCorrection.hasErrors, - originalText: textInput, - correctedText: apiData.grammarCorrection.correctedText || textInput, - corrections: apiData.grammarCorrection.corrections || [], - confidenceScore: apiData.grammarCorrection.confidenceScore || 0.9 - }) - } else { - setGrammarCorrection({ - hasErrors: false, - originalText: textInput, - correctedText: textInput, - corrections: [], - confidenceScore: 1.0 - }) - } - - setShowAnalysisView(true) - - // 保存分析結果到快取 - const cacheData = { - textInput, - sentenceAnalysis: analysisData, - sentenceMeaning: apiData.sentenceMeaning || '', - grammarCorrection: apiData.grammarCorrection ? { - hasErrors: apiData.grammarCorrection.hasErrors, - originalText: textInput, - correctedText: apiData.grammarCorrection.correctedText || textInput, - corrections: apiData.grammarCorrection.corrections || [], - confidenceScore: apiData.grammarCorrection.confidenceScore || 1.0 - } : { - hasErrors: false, - originalText: textInput, - correctedText: textInput, - corrections: [], - confidenceScore: 1.0 - }, - showAnalysisView: true - } - saveAnalysisToCache(cacheData) - - } catch (error) { - console.error('Error in sentence analysis:', error) - setGrammarCorrection({ - hasErrors: true, - originalText: textInput, - correctedText: textInput, - corrections: [], - confidenceScore: 0.0 - }) - setSentenceMeaning('分析過程中發生錯誤,請稍後再試。') - // 錯誤時也不設置finalText,使用原始輸入 - setShowAnalysisView(true) - } finally { - setIsAnalyzing(false) - } - } - - - - - - - const handleAcceptCorrection = useCallback(() => { - if (grammarCorrection?.correctedText) { - // 更新用戶輸入為修正後的版本 - setTextInput(grammarCorrection.correctedText) - console.log('✅ 已採用修正版本,文本已更新為正確版本!') - } - }, [grammarCorrection?.correctedText]) - - const handleRejectCorrection = useCallback(() => { - // 保持原始輸入不變,只是隱藏語法修正面板 - setGrammarCorrection(null) - console.log('📝 已保持原始版本,繼續使用您的原始輸入。') - }, []) - - // 詞彙統計計算 - 適配新的後端API格式 - const vocabularyStats = useMemo(() => { - if (!sentenceAnalysis?.vocabularyAnalysis) { - return { simpleCount: 0, moderateCount: 0, difficultCount: 0, idiomCount: 0 } - } - - const userLevel = localStorage.getItem('userEnglishLevel') || 'A2' - let simpleCount = 0 - let moderateCount = 0 - let difficultCount = 0 - - // 處理vocabularyAnalysis物件 - Object.values(sentenceAnalysis.vocabularyAnalysis).forEach((wordData: any) => { - const cefr = wordData?.cefr || 'A1' - const userIndex = getLevelIndex(userLevel) - const wordIndex = getLevelIndex(cefr) - - if (userIndex > wordIndex) { - simpleCount++ - } else if (userIndex === wordIndex) { - moderateCount++ - } else { - difficultCount++ - } - }) - - // 處理慣用語統計 - const idiomCount = sentenceAnalysis.idioms?.length || 0 - - return { simpleCount, moderateCount, difficultCount, idiomCount } - }, [sentenceAnalysis]) - - // 保存單個詞彙 - const handleSaveWord = useCallback(async (word: string, analysis: any) => { - try { - const cefrValue = analysis.cefr || analysis.cefrLevel || analysis.CEFR || 'A0' - - const cardData = { - word: word, - translation: analysis.translation || analysis.Translation || '', - definition: analysis.definition || analysis.Definition || '', - pronunciation: analysis.pronunciation || analysis.Pronunciation || `/${word}/`, - partOfSpeech: analysis.partOfSpeech || analysis.PartOfSpeech || 'noun', - example: analysis.example || `Example sentence with ${word}.`, // 使用分析結果的例句 - exampleTranslation: analysis.exampleTranslation, - synonyms: analysis.synonyms ? JSON.stringify(analysis.synonyms) : undefined, // 轉換為 JSON 字串 - cefr: cefrValue - } - - const response = await flashcardsService.createFlashcard(cardData) - - if (response.success) { - // 顯示成功提示 - const successMessage = `已成功將「${word}」保存到詞卡庫!` - toast.success(successMessage) - console.log('✅', successMessage) - return { success: true } - } else if (response.error && response.error.includes('已存在')) { - // 顯示重複提示 - const duplicateMessage = `詞卡「${word}」已經存在於詞卡庫中` - toast.warning(duplicateMessage) - console.log('⚠️', duplicateMessage) - return { success: false, error: 'duplicate', message: duplicateMessage } - } else { - throw new Error(response.error || '保存失敗') - } - } catch (error) { - console.error('Save word error:', error) - const errorMessage = error instanceof Error ? error.message : '保存失敗' - toast.error(`保存詞卡失敗: ${errorMessage}`) - return { success: false, error: errorMessage } - } - }, []) - - return ( -
- - -
- {!showAnalysisView ? ( - <> -

AI 智能生成詞卡

- - {/* Content Input */} -
-

輸入英文文本

-