diff --git a/AI功能使用說明.md b/AI功能使用說明.md index a329117..ee60062 100644 --- a/AI功能使用說明.md +++ b/AI功能使用說明.md @@ -8,26 +8,12 @@ DramaLing的AI生成功能現已完全實現,支持智能英文句子分析、 ### **1. 設置Gemini API Key** -#### **方法一:使用.NET User Secrets(推薦)** +#### **方法一:使用.NET User Secrets** ```bash cd backend/DramaLing.Api dotnet user-secrets set "Gemini:ApiKey" "你的真實Gemini API Key" ``` -#### **方法二:使用環境變數** -```bash -export GEMINI_API_KEY="你的真實Gemini API Key" -``` - -#### **方法三:appsettings.json(不推薦用於生產)** -```json -{ - "Gemini": { - "ApiKey": "你的真實Gemini API Key" - } -} -``` - ### **2. 啟動服務** #### **後端服務(port 5000)** @@ -116,13 +102,6 @@ npm run dev ## 🧪 **測試模式** -### **Mock模式(預設)** -當沒有設置真實Gemini API Key時,系統自動使用Mock數據: -- 模擬1秒API延遲 -- 提供完整的測試數據 -- 包含語法錯誤修正範例 -- 包含16個詞彙分析和1個慣用語 - ### **真實API模式** 設置真實Gemini API Key後: - 調用Google Gemini Pro模型 diff --git a/AI生成網頁前端需求規格.md b/AI生成網頁前端需求規格.md index b715888..d388856 100644 --- a/AI生成網頁前端需求規格.md +++ b/AI生成網頁前端需求規格.md @@ -49,7 +49,7 @@ DramaLing AI生成網頁是個人化英語學習平台的核心功能,專注 驗收標準: - 簡單詞彙顯示灰色虛線(已掌握) - 適中詞彙顯示綠色邊框(重點學習) -- 艱難詞彙顯示橙色邊框(挑戰詞彙) +- 困難詞彙顯示橙色邊框(挑戰詞彙) - 能調整個人CEFR等級設定 ``` @@ -104,7 +104,7 @@ DramaLing AI生成網頁是個人化英語學習平台的核心功能,專注 系統會根據我的CEFR程度與詞彙CEFR程度進行比較分類: - 簡單啦:學習者CEFR > 詞彙CEFR(簡單詞彙) - 重點學習:學習者CEFR = 詞彙CEFR(適中難度詞彙) -- 具挑戰:學習者CEFR < 詞彙CEFR(艱難詞彙) +- 具挑戰:學習者CEFR < 詞彙CEFR(困難詞彙) - 慣用語:獨立分類,不參與等級比較 範例(用戶A2等級): @@ -213,7 +213,7 @@ DramaLing AI生成網頁是個人化英語學習平台的核心功能,專注 用戶等級 vs 詞彙等級: - 用戶 > 詞彙 → 簡單詞彙 (灰色虛線) - 用戶 = 詞彙 → 適中難度詞彙 (綠色邊框) -- 用戶 < 詞彙 → 艱難詞彙 (橙色邊框) +- 用戶 < 詞彙 → 困難詞彙 (橙色邊框) - 慣用語標記 → 藍色邊框,在慣用語區域顯示 ``` @@ -239,7 +239,7 @@ DramaLing AI生成網頁是個人化英語學習平台的核心功能,專注 - 樣式:`bg-green-50 border border-green-200` - 顏色:`text-green-700 font-medium` - 含義:重點學習目標 -3. **艱難詞彙**: +3. **困難詞彙**: - 樣式:`bg-orange-50 border border-orange-200` - 顏色:`text-orange-700 font-medium` - 含義:挑戰性詞彙 @@ -263,7 +263,7 @@ DramaLing AI生成網頁是個人化英語學習平台的核心功能,專注 - 背景:綠色邊框 - 數字:綠色大字體 - 標籤:「重點學習」 -3. **艱難詞彙卡片**: +3. **困難詞彙卡片**: - 背景:橙色邊框 - 數字:橙色大字體 - 標籤:「有點挑戰」 @@ -397,7 +397,7 @@ DramaLing AI生成網頁是個人化英語學習平台的核心功能,專注 3. **個人化訊息**: - 簡單詞彙:對你來說太簡單 - 適中難度詞彙:對你來說剛剛好 - - 艱難詞彙:對你來說較難 + - 困難詞彙:對你來說較難 --- @@ -573,7 +573,7 @@ interface WordAnalysisOptional { - 語法修正:顯示2個錯誤修正 - 簡單詞彙:8個 - 適中詞彙:4個 -- 艱難詞彙:3個 +- 困難詞彙:3個 - 慣用語:1個 #### **TEST1.2 邊界值測試** diff --git a/frontend/app/generate/page.tsx b/frontend/app/generate/page.tsx index 86a8fd6..d3c2639 100644 --- a/frontend/app/generate/page.tsx +++ b/frontend/app/generate/page.tsx @@ -36,8 +36,8 @@ interface GrammarCorrection { }>; } -interface PhrasePopup { - phrase: string; +interface IdiomPopup { + idiom: string; analysis: any; position: { x: number; y: number }; } @@ -49,8 +49,7 @@ function GenerateContent() { const [sentenceAnalysis, setSentenceAnalysis] = useState | null>(null) const [sentenceMeaning, setSentenceMeaning] = useState('') const [grammarCorrection, setGrammarCorrection] = useState(null) - const [finalText, setFinalText] = useState('') - const [phrasePopup, setPhrasePopup] = useState(null) + const [idiomPopup, setIdiomPopup] = useState(null) // 處理句子分析 - 使用真實API @@ -75,15 +74,21 @@ function GenerateContent() { includeGrammarCheck: true, includeVocabularyAnalysis: true, includeTranslation: true, - includePhraseDetection: true, + includeIdiomDetection: true, includeExamples: true } }) }) if (!response.ok) { - const errorData = await response.json() - throw new Error(errorData.error?.message || `API請求失敗: ${response.status}`) + 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() @@ -108,10 +113,10 @@ function GenerateContent() { corrections: apiData.grammarCorrection.corrections || [] }) - // 使用修正後的文本作為最終文本 - setFinalText(apiData.grammarCorrection.correctedText || textInput) + // 不需要單獨設置finalText,直接從API數據計算 + // setFinalText() - 移除這個狀態設置 } else { - setFinalText(textInput) + // 如果沒有語法修正,也不需要設置finalText } setShowAnalysisView(true) @@ -125,7 +130,7 @@ function GenerateContent() { corrections: [] }) setSentenceMeaning('分析過程中發生錯誤,請稍後再試。') - setFinalText(textInput) + // 錯誤時也不設置finalText,使用原始輸入 setShowAnalysisView(true) } finally { setIsAnalyzing(false) @@ -139,15 +144,17 @@ function GenerateContent() { const handleAcceptCorrection = useCallback(() => { if (grammarCorrection?.correctedText) { - setFinalText(grammarCorrection.correctedText) - console.log('✅ 已採用修正版本,後續學習將基於正確的句子進行!') + // 更新用戶輸入為修正後的版本 + setTextInput(grammarCorrection.correctedText) + console.log('✅ 已採用修正版本,文本已更新為正確版本!') } }, [grammarCorrection?.correctedText]) const handleRejectCorrection = useCallback(() => { - setFinalText(grammarCorrection?.originalText || textInput) - console.log('📝 已保持原始版本,將基於您的原始輸入進行學習。') - }, [grammarCorrection?.originalText, textInput]) + // 保持原始輸入不變,只是隱藏語法修正面板 + setGrammarCorrection(null) + console.log('📝 已保持原始版本,繼續使用您的原始輸入。') + }, []) // 詞彙統計計算 - 移到組件頂層避免Hooks順序問題 const vocabularyStats = useMemo(() => { @@ -157,14 +164,14 @@ function GenerateContent() { let simpleCount = 0 let moderateCount = 0 let difficultCount = 0 - let phraseCount = 0 + let idiomCount = 0 Object.entries(sentenceAnalysis).forEach(([, wordData]: [string, any]) => { - const isPhrase = wordData?.isPhrase || wordData?.IsPhrase + const isIdiom = wordData?.isIdiom || wordData?.IsIdiom const difficultyLevel = wordData?.difficultyLevel || 'A1' - if (isPhrase) { - phraseCount++ + if (isIdiom) { + idiomCount++ } else { const userIndex = getLevelIndex(userLevel) const wordIndex = getLevelIndex(difficultyLevel) @@ -179,7 +186,7 @@ function GenerateContent() { } }) - return { simpleCount, moderateCount, difficultCount, phraseCount } + return { simpleCount, moderateCount, difficultCount, idiomCount } }, [sentenceAnalysis]) // 保存單個詞彙 @@ -340,7 +347,7 @@ function GenerateContent() {
建議修正:
- {grammarCorrection.correctedText || finalText} + {grammarCorrection.correctedText || textInput}
@@ -389,7 +396,7 @@ function GenerateContent() { {/* 片語與俚語卡片 */}
-
{vocabularyStats.phraseCount}
+
{vocabularyStats.idiomCount}
慣用語
@@ -399,9 +406,9 @@ function GenerateContent() {
{ console.log('Clicked word:', word, analysis) }} @@ -420,43 +427,43 @@ function GenerateContent() { if (!sentenceAnalysis) return null // 提取片語 - const phrases: Array<{ - phrase: string + const idioms: Array<{ + idiom: string meaning: string difficultyLevel: string }> = [] Object.entries(sentenceAnalysis).forEach(([word, wordData]: [string, any]) => { - const isPhrase = wordData?.isPhrase || wordData?.IsPhrase - if (isPhrase) { - phrases.push({ - phrase: wordData?.word || word, + const isIdiom = wordData?.isIdiom || wordData?.IsIdiom + if (isIdiom) { + idioms.push({ + idiom: wordData?.word || word, meaning: wordData?.translation || '', difficultyLevel: wordData?.difficultyLevel || 'A1' }) } }) - if (phrases.length === 0) return null + if (idioms.length === 0) return null return (

慣用語

- {phrases.map((phrase, index) => ( + {idioms.map((idiom, index) => ( { // 找到片語的完整分析資料 - const phraseAnalysis = sentenceAnalysis?.["cut someone some slack"] + const idiomAnalysis = sentenceAnalysis?.["cut someone some slack"] - if (phraseAnalysis) { - // 設定片語彈窗狀態 - setPhrasePopup({ - phrase: phrase.phrase, - analysis: phraseAnalysis, + if (idiomAnalysis) { + // 設定慣用語彈窗狀態 + setIdiomPopup({ + idiom: idiom.idiom, + analysis: idiomAnalysis, position: { x: e.currentTarget.getBoundingClientRect().left + e.currentTarget.getBoundingClientRect().width / 2, y: e.currentTarget.getBoundingClientRect().bottom + 10 @@ -464,9 +471,9 @@ function GenerateContent() { }) } }} - title={`${phrase.phrase}: ${phrase.meaning}`} + title={`${idiom.idiom}: ${idiom.meaning}`} > - {phrase.phrase} + {idiom.idiom} ))}
@@ -490,17 +497,17 @@ function GenerateContent() { )} {/* 片語彈窗 */} - {phrasePopup && ( + {idiomPopup && ( <>
setPhrasePopup(null)} + onClick={() => setIdiomPopup(null)} />
-

{phrasePopup.analysis.word}

+

{idiomPopup.analysis.word}

- {phrasePopup.analysis.partOfSpeech} + {idiomPopup.analysis.partOfSpeech} - {phrasePopup.analysis.pronunciation} + {idiomPopup.analysis.pronunciation}
- {phrasePopup.analysis.difficultyLevel} + {idiomPopup.analysis.difficultyLevel}
@@ -537,23 +544,23 @@ function GenerateContent() {

中文翻譯

-

{phrasePopup.analysis.translation}

+

{idiomPopup.analysis.translation}

英文定義

-

{phrasePopup.analysis.definition}

+

{idiomPopup.analysis.definition}

- {phrasePopup.analysis.example && ( + {idiomPopup.analysis.example && (

例句

- "{phrasePopup.analysis.example}" + "{idiomPopup.analysis.example}"

- {phrasePopup.analysis.exampleTranslation} + {idiomPopup.analysis.exampleTranslation}

@@ -563,11 +570,11 @@ function GenerateContent() {